From aeb30b135c524713ad352fd25ba6e86151c23b8c Mon Sep 17 00:00:00 2001 From: Nisheeth Barthwal Date: Mon, 22 Jul 2024 15:45:10 +0200 Subject: [PATCH] chore: foundry update 4af6cfa (#445) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: DaniPopes <57450786+DaniPopes@users.noreply.github.com> Co-authored-by: Alisina Bahadori Co-authored-by: Matthias Seitz Co-authored-by: sealer3 <125761775+sealer3@users.noreply.github.com> Co-authored-by: evalir Co-authored-by: Yash Atreya <44857776+yash-atreya@users.noreply.github.com> Co-authored-by: Arsenii Kulikov Co-authored-by: jxom Co-authored-by: grandizzy <38490174+grandizzy@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Meet Mangukiya Co-authored-by: mattsse Co-authored-by: galois Co-authored-by: Rafael Quintero Co-authored-by: bixia <343224563@qq.com> Co-authored-by: Azleal Co-authored-by: aureliusbtc <82057759+aureliusbtc@users.noreply.github.com> Co-authored-by: Federico Gimenez Co-authored-by: NIC Lin Co-authored-by: Colin Kennedy Co-authored-by: Colin Kennedy Co-authored-by: sodamntired Co-authored-by: Andre Miras Co-authored-by: sam bacha Co-authored-by: teddav Co-authored-by: grandizzy Co-authored-by: Dhairya Sethi <55102840+DhairyaSethi@users.noreply.github.com> Co-authored-by: m4rio <92288535+mario-eth@users.noreply.github.com> Co-authored-by: Oliver Nordbjerg Co-authored-by: Oliver Nordbjerg Co-authored-by: morito Co-authored-by: tesseract <146037313+DoTheBestToGetTheBest@users.noreply.github.com> Co-authored-by: mattsse <19890894+mattsse@users.noreply.github.com> Co-authored-by: poma Co-authored-by: poma Co-authored-by: Swanny Co-authored-by: Zhuo Zhang <14835483+ZhangZhuoSJTU@users.noreply.github.com> Co-authored-by: Ayene <2958807+ayenesimo1i@users.noreply.github.com> Co-authored-by: Samuel Laferriere Co-authored-by: Paul Peregud Co-authored-by: Pawel Peregud Co-authored-by: funnybird Co-authored-by: fenghaojiang Co-authored-by: Karrq Co-authored-by: Federico Rodríguez Co-authored-by: Juan Rigada <62958725+Jrigada@users.noreply.github.com> Co-authored-by: Francesco Dainese --- .github/CODEOWNERS | 10 +- .github/scripts/matrices.py | 2 +- .github/workflows/deny.yml | 38 +- .github/workflows/dependencies.yml | 80 +- .github/workflows/infrastructure.yml | 2 +- .github/workflows/release.yml | 158 +- .github/workflows/secret_scanner.yaml | 4 +- .github/workflows/test.yml | 22 +- .gitignore | 3 +- CONTRIBUTING.md | 2 +- Cargo.lock | 5282 +++++++++++------ Cargo.toml | 208 +- Dockerfile | 5 +- README.md | 17 +- clippy.toml | 5 +- crates/anvil/Cargo.toml | 86 +- crates/anvil/README.md | 11 +- crates/anvil/core/Cargo.toml | 29 +- crates/anvil/core/src/eth/block.rs | 65 +- crates/anvil/core/src/eth/mod.rs | 108 +- crates/anvil/core/src/eth/proof.rs | 2 +- crates/anvil/core/src/eth/serde_helpers.rs | 29 - crates/anvil/core/src/eth/subscription.rs | 14 +- crates/anvil/core/src/eth/transaction/mod.rs | 1083 +++- .../core/src/eth/transaction/optimism.rs | 136 +- crates/anvil/core/src/eth/trie.rs | 50 +- crates/anvil/core/src/eth/utils.rs | 33 +- crates/anvil/core/src/lib.rs | 7 + crates/anvil/core/src/types.rs | 38 +- crates/anvil/rpc/Cargo.toml | 6 +- crates/anvil/rpc/src/error.rs | 78 +- crates/anvil/rpc/src/lib.rs | 7 + crates/anvil/rpc/src/request.rs | 8 +- crates/anvil/rpc/src/response.rs | 14 +- crates/anvil/server/Cargo.toml | 16 +- crates/anvil/server/src/config.rs | 13 +- crates/anvil/server/src/ipc.rs | 73 +- crates/anvil/server/src/lib.rs | 96 +- crates/anvil/server/src/pubsub.rs | 4 - crates/anvil/src/anvil.rs | 22 +- crates/anvil/src/cmd.rs | 171 +- crates/anvil/src/config.rs | 262 +- crates/anvil/src/eth/api.rs | 664 ++- crates/anvil/src/eth/backend/cheats.rs | 5 +- crates/anvil/src/eth/backend/db.rs | 112 +- crates/anvil/src/eth/backend/executor.rs | 263 +- crates/anvil/src/eth/backend/fork.rs | 204 +- crates/anvil/src/eth/backend/genesis.rs | 10 +- crates/anvil/src/eth/backend/info.rs | 2 - crates/anvil/src/eth/backend/mem/cache.rs | 2 +- crates/anvil/src/eth/backend/mem/fork_db.rs | 27 +- .../anvil/src/eth/backend/mem/in_memory_db.rs | 55 +- crates/anvil/src/eth/backend/mem/inspector.rs | 111 +- crates/anvil/src/eth/backend/mem/mod.rs | 1013 ++-- crates/anvil/src/eth/backend/mem/state.rs | 97 +- crates/anvil/src/eth/backend/mem/storage.rs | 101 +- crates/anvil/src/eth/backend/time.rs | 13 +- crates/anvil/src/eth/backend/validate.rs | 6 +- crates/anvil/src/eth/error.rs | 122 +- crates/anvil/src/eth/fees.rs | 270 +- crates/anvil/src/eth/miner.rs | 20 +- crates/anvil/src/eth/otterscan/api.rs | 91 +- crates/anvil/src/eth/otterscan/types.rs | 184 +- crates/anvil/src/eth/pool/mod.rs | 64 +- crates/anvil/src/eth/pool/transactions.rs | 42 +- crates/anvil/src/eth/sign.rs | 27 +- crates/anvil/src/eth/util.rs | 33 +- crates/anvil/src/evm.rs | 81 + crates/anvil/src/filter.rs | 18 +- crates/anvil/src/hardfork.rs | 240 +- crates/anvil/src/lib.rs | 99 +- crates/anvil/src/logging.rs | 4 - crates/anvil/src/pubsub.rs | 77 +- crates/anvil/src/server/handler.rs | 2 - crates/anvil/src/server/mod.rs | 57 +- crates/anvil/src/service.rs | 56 +- crates/anvil/src/tasks/mod.rs | 71 +- crates/anvil/test-data/SimpleStorage.json | 118 +- crates/anvil/test-data/emit_logs.json | 68 +- crates/anvil/test-data/greeter.json | 45 +- crates/anvil/test-data/multicall.json | 145 +- crates/anvil/test-data/storage_sample.json | 1 + crates/anvil/tests/it/abi.rs | 105 +- crates/anvil/tests/it/anvil.rs | 44 +- crates/anvil/tests/it/anvil_api.rs | 449 +- crates/anvil/tests/it/api.rs | 348 +- crates/anvil/tests/it/eip4844.rs | 206 + crates/anvil/tests/it/fork.rs | 756 +-- crates/anvil/tests/it/ganache.rs | 185 - crates/anvil/tests/it/gas.rs | 203 +- crates/anvil/tests/it/genesis.rs | 11 +- crates/anvil/tests/it/geth.rs | 91 - crates/anvil/tests/it/ipc.rs | 50 +- crates/anvil/tests/it/logs.rs | 266 +- crates/anvil/tests/it/main.rs | 9 +- crates/anvil/tests/it/optimism.rs | 218 +- crates/anvil/tests/it/otterscan.rs | 736 +-- crates/anvil/tests/it/proof.rs | 133 + crates/anvil/tests/it/proof/eip1186.rs | 297 - crates/anvil/tests/it/proof/mod.rs | 76 - crates/anvil/tests/it/pubsub.rs | 254 +- crates/anvil/tests/it/revert.rs | 277 +- crates/anvil/tests/it/sign.rs | 64 +- crates/anvil/tests/it/traces.rs | 250 +- crates/anvil/tests/it/transaction.rs | 1044 ++-- crates/anvil/tests/it/txpool.rs | 37 +- crates/anvil/tests/it/utils.rs | 80 +- crates/anvil/tests/it/wsapi.rs | 9 +- crates/cast/Cargo.toml | 60 +- crates/cast/bin/cmd/access_list.rs | 98 +- crates/cast/bin/cmd/bind.rs | 69 +- crates/cast/bin/cmd/call.rs | 226 +- crates/cast/bin/cmd/create2.rs | 56 +- crates/cast/bin/cmd/estimate.rs | 109 +- crates/cast/bin/cmd/find_block.rs | 38 +- crates/cast/bin/cmd/interface.rs | 73 +- crates/cast/bin/cmd/logs.rs | 313 +- crates/cast/bin/cmd/mktx.rs | 100 + crates/cast/bin/cmd/mod.rs | 1 + crates/cast/bin/cmd/rpc.rs | 6 +- crates/cast/bin/cmd/run.rs | 88 +- crates/cast/bin/cmd/send.rs | 198 +- crates/cast/bin/cmd/storage.rs | 89 +- crates/cast/bin/cmd/wallet/list.rs | 28 +- crates/cast/bin/cmd/wallet/mod.rs | 275 +- crates/cast/bin/cmd/wallet/vanity.rs | 27 +- crates/cast/bin/main.rs | 127 +- crates/cast/bin/opts.rs | 393 +- crates/cast/bin/tx.rs | 296 + crates/cast/src/base.rs | 4 +- crates/cast/src/errors.rs | 8 +- crates/cast/src/lib.rs | 845 ++- crates/cast/src/rlp_converter.rs | 37 +- crates/cast/src/tx.rs | 402 -- crates/cast/tests/cli/main.rs | 321 +- crates/cast/tests/fixtures/ERC20Artifact.json | 386 +- crates/cast/tests/fixtures/interface.json | 1 + .../cast/tests/fixtures/sign_typed_data.json | 39 +- crates/cheatcodes/Cargo.toml | 28 +- crates/cheatcodes/assets/cheatcodes.json | 1003 +++- .../cheatcodes/assets/cheatcodes.schema.json | 7 + crates/cheatcodes/common/src/expect.rs | 4 +- crates/cheatcodes/spec/Cargo.toml | 3 + crates/cheatcodes/spec/src/cheatcode.rs | 10 +- crates/cheatcodes/spec/src/lib.rs | 11 +- crates/cheatcodes/spec/src/vm.rs | 319 +- crates/cheatcodes/src/config.rs | 51 +- crates/cheatcodes/src/env.rs | 30 +- crates/cheatcodes/src/error.rs | 13 +- crates/cheatcodes/src/evm.rs | 249 +- crates/cheatcodes/src/evm/fork.rs | 166 +- crates/cheatcodes/src/evm/mapping.rs | 5 +- crates/cheatcodes/src/evm/mock.rs | 23 +- crates/cheatcodes/src/evm/prank.rs | 18 +- crates/cheatcodes/src/fs.rs | 234 +- crates/cheatcodes/src/inspector.rs | 1103 ++-- crates/cheatcodes/src/json.rs | 77 +- crates/cheatcodes/src/lib.rs | 76 +- crates/cheatcodes/src/script.rs | 60 +- crates/cheatcodes/src/string.rs | 11 +- crates/cheatcodes/src/test.rs | 14 +- crates/cheatcodes/src/test/assert.rs | 44 +- crates/cheatcodes/src/test/expect.rs | 75 +- crates/cheatcodes/src/toml.rs | 243 + crates/cheatcodes/src/utils.rs | 113 +- crates/chisel/Cargo.toml | 36 +- crates/chisel/benches/session_source.rs | 5 +- crates/chisel/bin/main.rs | 48 +- crates/chisel/src/cmd.rs | 50 +- crates/chisel/src/dispatcher.rs | 72 +- crates/chisel/src/executor.rs | 144 +- crates/chisel/src/lib.rs | 13 +- crates/chisel/src/runner.rs | 6 +- crates/chisel/src/session.rs | 8 +- crates/chisel/src/session_source.rs | 77 +- crates/chisel/src/solidity_helper.rs | 36 +- crates/chisel/tests/cache.rs | 19 +- crates/cli/Cargo.toml | 23 +- crates/cli/src/handler.rs | 2 +- crates/cli/src/lib.rs | 7 +- crates/cli/src/opts/build/core.rs | 86 +- crates/cli/src/opts/build/mod.rs | 18 +- crates/cli/src/opts/build/paths.rs | 30 +- crates/cli/src/opts/dependency.rs | 2 +- crates/cli/src/opts/ethereum.rs | 24 +- crates/cli/src/opts/transaction.rs | 26 +- crates/cli/src/utils/abi.rs | 61 + crates/cli/src/utils/cmd.rs | 181 +- crates/cli/src/utils/mod.rs | 89 +- crates/common/Cargo.toml | 84 +- crates/common/src/abi.rs | 37 +- crates/common/src/compile.rs | 471 +- crates/common/src/console/HardhatConsole.json | 2 +- crates/common/src/constants.rs | 2 +- crates/common/src/contracts.rs | 286 +- crates/common/src/ens.rs | 237 + crates/common/src/errors/fs.rs | 53 +- crates/common/src/evm.rs | 77 +- crates/common/src/fmt/console.rs | 6 +- crates/common/src/fmt/dynamic.rs | 4 +- crates/common/src/fmt/mod.rs | 8 +- crates/common/src/fmt/ui.rs | 339 +- crates/common/src/fs.rs | 15 +- crates/common/src/glob.rs | 94 - crates/common/src/lib.rs | 13 +- crates/common/src/provider/alloy.rs | 318 - crates/common/src/provider/ethers.rs | 323 - crates/common/src/provider/mod.rs | 339 +- crates/common/src/provider/retry.rs | 54 +- .../common/src/provider/runtime_transport.rs | 38 +- crates/common/src/provider/tower.rs | 18 +- crates/common/src/runtime_client.rs | 337 -- crates/common/src/selectors.rs | 74 +- crates/common/src/serde_helpers.rs | 18 +- crates/common/src/shell.rs | 34 +- crates/common/src/term.rs | 51 +- crates/common/src/traits.rs | 30 + crates/common/src/transactions.rs | 87 +- crates/common/src/types.rs | 262 - crates/common/src/utils.rs | 40 + crates/config/Cargo.toml | 31 +- crates/config/README.md | 7 +- crates/config/src/cache.rs | 12 +- crates/config/src/endpoints.rs | 10 +- crates/config/src/error.rs | 42 +- crates/config/src/etherscan.rs | 48 +- crates/config/src/filter.rs | 204 + crates/config/src/fix.rs | 20 +- crates/config/src/fs_permissions.rs | 14 +- crates/config/src/fuzz.rs | 34 +- crates/config/src/inline/conf_parser.rs | 4 +- crates/config/src/inline/error.rs | 5 +- crates/config/src/inline/mod.rs | 38 +- crates/config/src/inline/natspec.rs | 357 +- crates/config/src/invariant.rs | 56 +- crates/config/src/lib.rs | 786 ++- crates/config/src/macros.rs | 34 +- crates/config/src/providers/mod.rs | 60 +- crates/config/src/providers/remappings.rs | 3 +- crates/config/src/resolve.rs | 2 - crates/config/src/soldeer.rs | 25 + crates/config/src/utils.rs | 8 +- crates/config/src/vyper.rs | 15 + crates/config/src/zksync.rs | 20 +- crates/debugger/Cargo.toml | 5 +- crates/debugger/src/lib.rs | 3 +- crates/debugger/src/tui/context.rs | 290 +- crates/debugger/src/tui/draw.rs | 268 +- crates/debugger/src/tui/mod.rs | 14 +- crates/doc/Cargo.toml | 12 +- crates/doc/src/builder.rs | 13 +- crates/doc/src/document.rs | 34 +- crates/doc/src/lib.rs | 12 +- crates/doc/src/parser/comment.rs | 35 +- crates/doc/src/parser/item.rs | 18 +- crates/doc/src/parser/mod.rs | 2 +- .../doc/src/preprocessor/infer_hyperlinks.rs | 2 +- crates/doc/src/preprocessor/inheritdoc.rs | 2 +- crates/doc/src/writer/as_doc.rs | 2 +- crates/evm/core/Cargo.toml | 46 +- crates/evm/core/src/abi/HardhatConsole.json | 1 + crates/evm/core/src/abi/console.rs | 90 + crates/evm/core/src/abi/hardhat_console.rs | 566 ++ crates/evm/core/src/abi/mod.rs | 12 + .../evm/core/src/backend/{fuzz.rs => cow.rs} | 86 +- crates/evm/core/src/backend/diagnostic.rs | 4 +- crates/evm/core/src/backend/error.rs | 20 +- crates/evm/core/src/backend/fork_type.rs | 10 +- crates/evm/core/src/backend/in_memory_db.rs | 14 +- crates/evm/core/src/backend/mod.rs | 356 +- crates/evm/core/src/backend/snapshot.rs | 14 +- crates/evm/core/src/constants.rs | 12 +- crates/evm/core/src/debug.rs | 87 +- crates/evm/core/src/decode.rs | 7 +- crates/evm/core/src/fork/backend.rs | 100 +- crates/evm/core/src/fork/cache.rs | 60 +- crates/evm/core/src/fork/database.rs | 6 +- crates/evm/core/src/fork/init.rs | 25 +- crates/evm/core/src/fork/mod.rs | 5 +- crates/evm/core/src/fork/multi.rs | 36 +- crates/evm/core/src/ic.rs | 91 +- crates/evm/core/src/lib.rs | 29 +- crates/evm/core/src/opcodes.rs | 25 + crates/evm/core/src/opts.rs | 36 +- crates/evm/core/src/utils.rs | 273 +- crates/evm/core/test-data/storage.json | 2 +- crates/evm/coverage/Cargo.toml | 11 +- crates/evm/coverage/src/analysis.rs | 415 +- crates/evm/coverage/src/anchors.rs | 99 +- crates/evm/coverage/src/inspector.rs | 21 +- crates/evm/coverage/src/lib.rs | 135 +- crates/evm/evm/Cargo.toml | 22 +- crates/evm/evm/src/executors/builder.rs | 24 +- crates/evm/evm/src/executors/fuzz/mod.rs | 122 +- .../evm/evm/src/executors/invariant/error.rs | 408 +- .../evm/evm/src/executors/invariant/funcs.rs | 130 - crates/evm/evm/src/executors/invariant/mod.rs | 751 ++- .../evm/evm/src/executors/invariant/replay.rs | 151 + .../evm/evm/src/executors/invariant/result.rs | 177 + .../evm/evm/src/executors/invariant/shrink.rs | 178 + crates/evm/evm/src/executors/mod.rs | 866 ++- crates/evm/evm/src/executors/tracing.rs | 15 +- crates/evm/evm/src/inspectors/access_list.rs | 79 - crates/evm/evm/src/inspectors/chisel_state.rs | 13 +- crates/evm/evm/src/inspectors/debugger.rs | 156 +- crates/evm/evm/src/inspectors/logs.rs | 47 +- crates/evm/evm/src/inspectors/mod.rs | 6 +- crates/evm/evm/src/inspectors/printer.rs | 65 - crates/evm/evm/src/inspectors/stack.rs | 557 +- crates/evm/evm/src/lib.rs | 13 +- crates/evm/fuzz/Cargo.toml | 26 +- crates/evm/fuzz/src/error.rs | 2 - crates/evm/fuzz/src/inspector.rs | 59 +- .../evm/fuzz/src/invariant/call_override.rs | 20 +- crates/evm/fuzz/src/invariant/filters.rs | 15 +- crates/evm/fuzz/src/invariant/mod.rs | 213 +- crates/evm/fuzz/src/lib.rs | 93 +- crates/evm/fuzz/src/strategies/calldata.rs | 88 +- crates/evm/fuzz/src/strategies/int.rs | 68 +- crates/evm/fuzz/src/strategies/invariants.rs | 201 +- crates/evm/fuzz/src/strategies/mod.rs | 9 +- crates/evm/fuzz/src/strategies/param.rs | 269 +- crates/evm/fuzz/src/strategies/state.rs | 541 +- crates/evm/fuzz/src/strategies/uint.rs | 64 +- crates/evm/traces/Cargo.toml | 29 +- crates/evm/traces/src/decoder/mod.rs | 90 +- crates/evm/traces/src/identifier/etherscan.rs | 140 +- crates/evm/traces/src/identifier/local.rs | 44 +- crates/evm/traces/src/identifier/mod.rs | 58 +- .../evm/traces/src/identifier/signatures.rs | 13 +- crates/evm/traces/src/lib.rs | 96 +- crates/fmt/Cargo.toml | 9 +- crates/fmt/src/buffer.rs | 8 +- crates/fmt/src/chunk.rs | 6 +- crates/fmt/src/comments.rs | 14 +- crates/fmt/src/formatter.rs | 23 +- crates/fmt/src/helpers.rs | 55 +- crates/fmt/src/inline_config.rs | 16 +- crates/fmt/src/lib.rs | 3 +- crates/fmt/src/solang_ext/ast_eq.rs | 24 +- crates/fmt/src/string.rs | 18 +- crates/fmt/src/visit.rs | 120 +- crates/fmt/testdata/Repros/fmt.sol | 12 + crates/fmt/testdata/Repros/original.sol | 12 + crates/fmt/tests/formatter.rs | 8 +- crates/forge/Cargo.toml | 91 +- crates/forge/README.md | 4 +- crates/forge/benches/test.rs | 2 +- crates/forge/bin/cmd/bind.rs | 319 +- crates/forge/bin/cmd/build.rs | 69 +- crates/forge/bin/cmd/cache.rs | 23 +- crates/forge/bin/cmd/clone.rs | 801 +++ crates/forge/bin/cmd/config.rs | 15 +- crates/forge/bin/cmd/coverage.rs | 384 +- crates/forge/bin/cmd/create.rs | 1058 ++-- crates/forge/bin/cmd/debug.rs | 17 +- crates/forge/bin/cmd/doc/mod.rs | 26 +- crates/forge/bin/cmd/doc/server.rs | 13 +- crates/forge/bin/cmd/flatten.rs | 38 +- crates/forge/bin/cmd/fmt.rs | 51 +- crates/forge/bin/cmd/geiger/error.rs | 7 +- crates/forge/bin/cmd/geiger/find.rs | 31 +- crates/forge/bin/cmd/geiger/mod.rs | 22 +- crates/forge/bin/cmd/generate/mod.rs | 8 +- crates/forge/bin/cmd/init.rs | 41 +- crates/forge/bin/cmd/inspect.rs | 8 +- crates/forge/bin/cmd/install.rs | 24 +- crates/forge/bin/cmd/mod.rs | 9 +- crates/forge/bin/cmd/remappings.rs | 13 +- crates/forge/bin/cmd/remove.rs | 5 +- crates/forge/bin/cmd/script/artifacts.rs | 9 - crates/forge/bin/cmd/script/multi.rs | 240 - crates/forge/bin/cmd/script/receipts.rs | 155 - crates/forge/bin/cmd/script/transaction.rs | 513 -- crates/forge/bin/cmd/selectors.rs | 41 +- crates/forge/bin/cmd/snapshot.rs | 45 +- crates/forge/bin/cmd/soldeer.rs | 37 + crates/forge/bin/cmd/test/filter.rs | 42 +- crates/forge/bin/cmd/test/mod.rs | 392 +- crates/forge/bin/cmd/tree.rs | 10 +- crates/forge/bin/cmd/update.rs | 10 +- .../bin/cmd/verify/etherscan/standard_json.rs | 80 - crates/forge/bin/cmd/verify/provider.rs | 84 - crates/forge/bin/cmd/watch.rs | 552 +- crates/forge/bin/main.rs | 43 +- crates/forge/bin/opts.rs | 85 +- crates/forge/src/coverage.rs | 82 +- crates/forge/src/gas_report.rs | 25 +- crates/forge/src/lib.rs | 96 +- crates/forge/src/multi_runner.rs | 447 +- crates/forge/src/progress.rs | 116 + crates/forge/src/result.rs | 109 +- crates/forge/src/runner.rs | 776 ++- crates/forge/tests/cli/build.rs | 76 +- crates/forge/tests/cli/cmd.rs | 215 +- crates/forge/tests/cli/config.rs | 83 +- crates/forge/tests/cli/context.rs | 81 + crates/forge/tests/cli/coverage.rs | 12 +- crates/forge/tests/cli/create.rs | 8 +- crates/forge/tests/cli/ext_integration.rs | 2 +- crates/forge/tests/cli/main.rs | 2 + crates/forge/tests/cli/script.rs | 339 +- crates/forge/tests/cli/soldeer.rs | 176 + crates/forge/tests/cli/test_cmd.rs | 116 +- crates/forge/tests/cli/utils.rs | 40 +- crates/forge/tests/cli/verify.rs | 125 +- .../can_build_path_with_one_file.stdout | 3 + .../can_build_path_with_three_files.stdout | 3 + .../can_build_path_with_two_files.stdout | 3 + .../can_create_template_contract.stdout | 2 +- .../fixtures/can_create_using_unlocked.stdout | 2 +- .../can_create_with_constructor_args.stdout | 2 +- .../tests/fixtures/can_test_repeatedly.stdout | 2 +- .../forge/tests/fixtures/compile_json.stdout | 9 +- .../include_custom_types_in_traces.stdout | 4 +- crates/forge/tests/fixtures/repro_6531.stdout | 16 +- .../suggest_when_no_tests_match.stdout | 4 - .../forge/tests/fixtures/warn_no_tests.stdout | 4 - .../tests/fixtures/warn_no_tests_match.stdout | 4 - crates/forge/tests/it/cheats.rs | 62 +- crates/forge/tests/it/config.rs | 181 +- crates/forge/tests/it/core.rs | 105 +- crates/forge/tests/it/fork.rs | 85 +- crates/forge/tests/it/fs.rs | 14 +- crates/forge/tests/it/fuzz.rs | 143 +- crates/forge/tests/it/inline.rs | 85 +- crates/forge/tests/it/invariant.rs | 735 ++- crates/forge/tests/it/repros.rs | 111 +- crates/forge/tests/it/spec.rs | 7 +- crates/forge/tests/it/test_helpers.rs | 421 +- crates/forge/tests/it/zk.rs | 29 +- crates/linking/Cargo.toml | 20 + .../{forge/src/link.rs => linking/src/lib.rs} | 392 +- crates/macros/Cargo.toml | 3 + crates/macros/src/lib.rs | 7 +- crates/script/Cargo.toml | 64 + crates/script/src/broadcast.rs | 482 ++ crates/script/src/build.rs | 388 ++ crates/script/src/execute.rs | 505 ++ .../cmd/script/mod.rs => script/src/lib.rs} | 687 +-- crates/script/src/multi_sequence.rs | 154 + crates/script/src/progress.rs | 238 + .../cmd/script => script/src}/providers.rs | 42 +- crates/script/src/receipts.rs | 85 + .../bin/cmd/script => script/src}/runner.rs | 201 +- .../bin/cmd/script => script/src}/sequence.rs | 263 +- crates/script/src/simulate.rs | 436 ++ crates/script/src/transaction.rs | 235 + .../bin/cmd/script => script/src}/verify.rs | 55 +- crates/sol-macro-gen/Cargo.toml | 26 + crates/sol-macro-gen/src/lib.rs | 5 + crates/sol-macro-gen/src/sol_macro_gen.rs | 337 ++ crates/test-utils/Cargo.toml | 18 +- crates/test-utils/src/filter.rs | 21 +- crates/test-utils/src/lib.rs | 9 +- crates/test-utils/src/macros.rs | 17 + crates/{common => test-utils}/src/rpc.rs | 22 + crates/test-utils/src/script.rs | 152 +- crates/test-utils/src/util.rs | 91 +- crates/verify/Cargo.toml | 50 + crates/verify/src/bytecode.rs | 658 ++ .../src}/etherscan/flatten.rs | 104 +- .../verify => verify/src}/etherscan/mod.rs | 365 +- crates/verify/src/etherscan/standard_json.rs | 76 + .../cmd/verify/mod.rs => verify/src/lib.rs} | 190 +- crates/verify/src/provider.rs | 175 + crates/{forge/bin/cmd => verify/src}/retry.rs | 8 +- .../bin/cmd/verify => verify/src}/sourcify.rs | 73 +- crates/wallets/Cargo.toml | 47 +- crates/wallets/src/error.rs | 33 +- crates/wallets/src/lib.rs | 7 + crates/wallets/src/multi_wallet.rs | 50 +- crates/wallets/src/raw_wallet.rs | 18 +- crates/wallets/src/utils.rs | 46 +- crates/wallets/src/wallet.rs | 46 +- crates/wallets/src/wallet_signer.rs | 222 +- crates/zksync/compiler/Cargo.toml | 2 + crates/zksync/compiler/src/lib.rs | 133 + crates/zksync/compiler/src/zksolc/mod.rs | 5 +- crates/zksync/core/Cargo.toml | 11 +- crates/zksync/core/src/cheatcodes.rs | 160 +- crates/zksync/core/src/convert.rs | 67 + crates/zksync/core/src/convert/eip712.rs | 194 + crates/zksync/core/src/lib.rs | 118 +- crates/zksync/core/src/vm/db.rs | 87 +- crates/zksync/core/src/vm/env.rs | 3 +- crates/zksync/core/src/vm/inspect.rs | 76 +- crates/zksync/core/src/vm/runner.rs | 190 +- crates/zksync/core/src/vm/tracer.rs | 13 +- deny.toml | 49 +- docs/dev/debugging.md | 3 +- flake.lock | 24 +- flake.nix | 4 +- foundryup-zksync/foundryup-zksync | 36 +- foundryup-zksync/install | 8 +- rust-toolchain | 2 +- testdata/cancun/cheats/BlobBaseFee.t.sol | 14 + testdata/cancun/cheats/Blobhashes.t.sol | 20 + testdata/cheats/Vm.sol | 69 + testdata/{ => default}/cheats/Addr.t.sol | 2 +- testdata/{ => default}/cheats/Assert.t.sol | 4 +- testdata/{ => default}/cheats/Assume.t.sol | 2 +- testdata/{ => default}/cheats/Bank.t.sol | 2 +- testdata/{ => default}/cheats/Base64.t.sol | 2 +- testdata/{ => default}/cheats/Broadcast.t.sol | 44 +- testdata/{ => default}/cheats/ChainId.t.sol | 2 +- testdata/{ => default}/cheats/Cool.t.sol | 4 +- testdata/{ => default}/cheats/Deal.t.sol | 2 +- testdata/{ => default}/cheats/Derive.t.sol | 2 +- testdata/default/cheats/EnsNamehash.t.sol | 15 + testdata/{ => default}/cheats/Env.t.sol | 10 +- testdata/{ => default}/cheats/Etch.t.sol | 2 +- .../{ => default}/cheats/ExpectCall.t.sol | 20 +- .../{ => default}/cheats/ExpectEmit.t.sol | 2 +- .../{ => default}/cheats/ExpectRevert.t.sol | 2 +- testdata/{ => default}/cheats/Fee.t.sol | 2 +- testdata/{ => default}/cheats/Ffi.t.sol | 2 +- testdata/{ => default}/cheats/Fork.t.sol | 13 +- testdata/{ => default}/cheats/Fork2.t.sol | 2 +- testdata/{ => default}/cheats/Fs.t.sol | 2 +- .../{ => default}/cheats/GasMetering.t.sol | 2 +- .../cheats/GetBlockTimestamp.t.sol | 2 +- testdata/{ => default}/cheats/GetCode.t.sol | 24 +- .../cheats/GetDeployedCode.t.sol | 14 +- testdata/{ => default}/cheats/GetLabel.t.sol | 2 +- testdata/{ => default}/cheats/GetNonce.t.sol | 2 +- testdata/{ => default}/cheats/Json.t.sol | 141 +- testdata/{ => default}/cheats/Label.t.sol | 2 +- testdata/default/cheats/LastCallGas.t.sol | 109 + testdata/{ => default}/cheats/Load.t.sol | 2 +- testdata/{ => default}/cheats/Mapping.t.sol | 2 +- testdata/{ => default}/cheats/MemSafety.t.sol | 47 +- testdata/{ => default}/cheats/MockCall.t.sol | 28 +- testdata/{ => default}/cheats/Parse.t.sol | 2 +- testdata/{ => default}/cheats/Prank.t.sol | 2 +- .../{ => default}/cheats/Prevrandao.t.sol | 8 +- .../{ => default}/cheats/ProjectRoot.t.sol | 2 +- testdata/default/cheats/Prompt.t.sol | 32 + testdata/default/cheats/RandomUint.t.sol | 15 + .../{ => default}/cheats/ReadCallers.t.sol | 2 +- testdata/{ => default}/cheats/Record.t.sol | 2 +- .../cheats/RecordAccountAccesses.t.sol | 4 +- .../{ => default}/cheats/RecordLogs.t.sol | 2 +- testdata/{ => default}/cheats/Remember.t.sol | 2 +- .../{ => default}/cheats/ResetNonce.t.sol | 2 +- testdata/{ => default}/cheats/Roll.t.sol | 2 +- testdata/{ => default}/cheats/RpcUrls.t.sol | 9 +- testdata/{ => default}/cheats/SetNonce.t.sol | 2 +- .../{ => default}/cheats/SetNonceUnsafe.t.sol | 2 +- testdata/{ => default}/cheats/Setup.t.sol | 4 +- testdata/{ => default}/cheats/Sign.t.sol | 2 +- testdata/{ => default}/cheats/SignP256.t.sol | 2 +- testdata/{ => default}/cheats/Skip.t.sol | 2 +- testdata/{ => default}/cheats/Sleep.t.sol | 2 +- testdata/{ => default}/cheats/Snapshots.t.sol | 4 +- testdata/{ => default}/cheats/Store.t.sol | 2 +- .../{ => default}/cheats/StringUtils.t.sol | 14 +- testdata/{ => default}/cheats/ToString.t.sol | 2 +- testdata/default/cheats/Toml.t.sol | 318 + testdata/{ => default}/cheats/Travel.t.sol | 2 +- testdata/{ => default}/cheats/TryFfi.sol | 2 +- testdata/{ => default}/cheats/UnixTime.t.sol | 7 +- testdata/{ => default}/cheats/Wallet.t.sol | 2 +- testdata/{ => default}/cheats/Warp.t.sol | 2 +- testdata/{ => default}/cheats/dumpState.t.sol | 2 +- .../{ => default}/cheats/getBlockNumber.t.sol | 2 +- .../{ => default}/cheats/loadAllocs.t.sol | 2 +- testdata/{ => default}/core/Abstract.t.sol | 0 .../default/core/BadSigAfterInvariant.t.sol | 12 + .../core/ContractEnvironment.t.sol | 0 testdata/{ => default}/core/DSStyle.t.sol | 0 .../{ => default}/core/FailingSetup.t.sol | 0 .../core/FailingTestAfterFailedSetup.t.sol | 0 .../default/core/MultipleAfterInvariant.t.sol | 14 + .../{ => default}/core/MultipleSetup.t.sol | 0 .../{ => default}/core/PaymentFailure.t.sol | 2 +- testdata/{ => default}/core/Reverting.t.sol | 0 .../{ => default}/core/SetupConsistency.t.sol | 0 testdata/{ => default}/fork/DssExecLib.sol | 0 testdata/{ => default}/fork/ForkSame_1.t.sol | 2 +- testdata/{ => default}/fork/ForkSame_2.t.sol | 2 +- testdata/{ => default}/fork/LaunchFork.t.sol | 0 testdata/{ => default}/fork/Transact.t.sol | 2 +- testdata/{ => default}/fs/Default.t.sol | 2 +- testdata/{ => default}/fs/Disabled.t.sol | 2 +- testdata/{ => default}/fuzz/Fuzz.t.sol | 0 .../{ => default}/fuzz/FuzzCollection.t.sol | 0 .../default/fuzz/FuzzFailurePersist.t.sol | 29 + testdata/{ => default}/fuzz/FuzzInt.t.sol | 17 +- .../{ => default}/fuzz/FuzzPositive.t.sol | 0 .../default/fuzz/FuzzScrapeBytecode.t.sol | 35 + testdata/{ => default}/fuzz/FuzzUint.t.sol | 11 +- .../common/InvariantAfterInvariant.t.sol | 55 + .../invariant/common/InvariantAssume.t.sol | 23 + .../common/InvariantCalldataDictionary.t.sol | 95 + .../common/InvariantCustomError.t.sol | 35 + .../common/InvariantExcludedSenders.t.sol | 22 + .../invariant/common/InvariantFixtures.t.sol | 77 + .../common/InvariantHandlerFailure.t.sol | 0 .../common/InvariantInnerContract.t.sol | 0 .../common/InvariantPreserveState.t.sol | 49 + .../common/InvariantReentrancy.t.sol | 12 +- .../invariant/common/InvariantRollFork.t.sol | 50 + .../common/InvariantScrapeValues.t.sol | 69 + .../common/InvariantSelectorsWeight.t.sol | 101 + .../common/InvariantShrinkBigSequence.t.sol | 31 + .../common/InvariantShrinkFailOnRevert.t.sol | 26 + .../common/InvariantShrinkWithAssert.t.sol | 115 + .../invariant/common/InvariantTest1.t.sol | 0 .../storage/InvariantStorageTest.t.sol | 0 .../invariant/target/ExcludeContracts.t.sol | 0 .../invariant/target/ExcludeSelectors.t.sol | 41 + .../invariant/target/ExcludeSenders.t.sol | 0 .../target/FuzzedTargetContracts.t.sol | 66 + .../invariant/target/TargetContracts.t.sol | 0 .../invariant/target/TargetInterfaces.t.sol | 0 .../invariant/target/TargetSelectors.t.sol | 0 .../fuzz/invariant/target/TargetSenders.t.sol | 0 .../targetAbi/ExcludeArtifacts.t.sol | 2 +- .../targetAbi/TargetArtifactSelectors.t.sol | 11 +- .../targetAbi/TargetArtifactSelectors2.t.sol | 16 +- .../invariant/targetAbi/TargetArtifacts.t.sol | 2 +- .../{ => default}/inline/FuzzInlineConf.t.sol | 0 .../inline/InvariantInlineConf.t.sol | 0 .../{ => default}/linking/cycle/Cycle.t.sol | 0 .../linking/duplicate/Duplicate.t.sol | 0 .../{ => default}/linking/nested/Nested.t.sol | 0 .../{ => default}/linking/simple/Simple.t.sol | 0 testdata/{ => default}/logs/DebugLogs.t.sol | 0 testdata/{ => default}/logs/HardhatLogs.t.sol | 0 testdata/{ => default}/logs/console.sol | 0 testdata/{ => default}/repros/Issue2623.t.sol | 2 +- testdata/{ => default}/repros/Issue2629.t.sol | 2 +- testdata/{ => default}/repros/Issue2723.t.sol | 2 +- testdata/default/repros/Issue2851.t.sol | 28 + testdata/{ => default}/repros/Issue2898.t.sol | 2 +- testdata/{ => default}/repros/Issue2956.t.sol | 6 +- testdata/{ => default}/repros/Issue2984.t.sol | 2 +- testdata/{ => default}/repros/Issue3055.t.sol | 2 +- testdata/{ => default}/repros/Issue3077.t.sol | 2 +- testdata/{ => default}/repros/Issue3110.t.sol | 2 +- testdata/{ => default}/repros/Issue3119.t.sol | 2 +- testdata/{ => default}/repros/Issue3189.t.sol | 2 +- testdata/{ => default}/repros/Issue3190.t.sol | 2 +- testdata/{ => default}/repros/Issue3192.t.sol | 2 +- testdata/{ => default}/repros/Issue3220.t.sol | 2 +- testdata/{ => default}/repros/Issue3221.t.sol | 6 +- testdata/{ => default}/repros/Issue3223.t.sol | 8 +- testdata/{ => default}/repros/Issue3347.t.sol | 2 +- testdata/{ => default}/repros/Issue3437.t.sol | 2 +- testdata/{ => default}/repros/Issue3596.t.sol | 2 +- testdata/{ => default}/repros/Issue3653.t.sol | 2 +- testdata/{ => default}/repros/Issue3661.t.sol | 0 testdata/{ => default}/repros/Issue3674.t.sol | 4 +- testdata/{ => default}/repros/Issue3685.t.sol | 2 +- testdata/{ => default}/repros/Issue3703.t.sol | 2 +- testdata/{ => default}/repros/Issue3708.t.sol | 2 +- testdata/{ => default}/repros/Issue3723.t.sol | 2 +- testdata/{ => default}/repros/Issue3753.t.sol | 2 +- testdata/{ => default}/repros/Issue3792.t.sol | 2 +- testdata/default/repros/Issue4402.t.sol | 64 + testdata/{ => default}/repros/Issue4586.t.sol | 2 +- testdata/{ => default}/repros/Issue4630.t.sol | 2 +- testdata/{ => default}/repros/Issue4640.t.sol | 2 +- testdata/{ => default}/repros/Issue4832.t.sol | 2 +- testdata/{ => default}/repros/Issue5038.t.sol | 2 +- testdata/{ => default}/repros/Issue5529.t.sol | 2 +- testdata/default/repros/Issue5739.t.sol | 35 + testdata/{ => default}/repros/Issue5808.t.sol | 2 +- testdata/{ => default}/repros/Issue5929.t.sol | 2 +- testdata/{ => default}/repros/Issue5935.t.sol | 2 +- testdata/{ => default}/repros/Issue5948.t.sol | 2 +- testdata/{ => default}/repros/Issue6006.t.sol | 2 +- testdata/{ => default}/repros/Issue6032.t.sol | 2 +- testdata/{ => default}/repros/Issue6070.t.sol | 2 +- testdata/{ => default}/repros/Issue6115.t.sol | 0 testdata/{ => default}/repros/Issue6170.t.sol | 2 +- testdata/{ => default}/repros/Issue6180.t.sol | 2 +- testdata/{ => default}/repros/Issue6293.t.sol | 2 +- testdata/{ => default}/repros/Issue6355.t.sol | 2 +- testdata/{ => default}/repros/Issue6437.t.sol | 2 +- testdata/{ => default}/repros/Issue6501.t.sol | 0 testdata/{ => default}/repros/Issue6538.t.sol | 2 +- testdata/{ => default}/repros/Issue6554.t.sol | 6 +- testdata/{ => default}/repros/Issue6616.t.sol | 2 +- testdata/{ => default}/repros/Issue6634.t.sol | 2 +- testdata/{ => default}/repros/Issue6759.t.sol | 2 +- testdata/{ => default}/repros/Issue6966.t.sol | 0 testdata/default/repros/Issue7481.t.sol | 22 + testdata/default/repros/Issue8004.t.sol | 94 + testdata/default/repros/Issue8006.t.sol | 44 + .../deploy.sol/31337/run-latest.json | 1 + testdata/{ => default}/script/deploy.sol | 4 +- .../{ => default}/spec/ShanghaiCompat.t.sol | 2 +- .../trace/ConflictingSignatures.t.sol | 2 +- testdata/{ => default}/trace/Trace.t.sol | 2 +- .../creation_data.json | 1 + .../metadata.json | 1 + .../creation_data.json | 1 + .../metadata.json | 1 + .../creation_data.json | 1 + .../metadata.json | 1 + .../creation_data.json | 1 + .../metadata.json | 1 + .../creation_data.json | 1 + .../metadata.json | 1 + .../creation_data.json | 1 + .../metadata.json | 1 + .../creation_data.json | 5 + .../metadata.json | 96 + .../creation_data.json | 1 + .../metadata.json | 1 + testdata/fixtures/Json/Issue4402.json | 4 + testdata/fixtures/Json/test.json | 33 +- .../Json/{wholeJson.json => whole_json.json} | 0 testdata/fixtures/Toml/Issue4402.toml | 2 + testdata/fixtures/Toml/test.toml | 50 + testdata/fixtures/Toml/whole_toml.toml | 3 + .../fixtures/Toml/write_complex_test.toml | 6 + testdata/fixtures/Toml/write_test.toml | 2 + testdata/foundry.toml | 2 +- testdata/multi-version/Counter.sol | 11 + testdata/multi-version/Importer.sol | 7 + testdata/multi-version/cheats/GetCode.t.sol | 25 + testdata/multi-version/cheats/GetCode17.t.sol | 26 + .../deploy.sol/31337/run-latest.json | 63 - zk-tests/foundry.toml | 8 +- zk-tests/src/Basic.t.sol | 22 +- zk-tests/src/Cheatcodes.t.sol | 22 + zk-tests/src/Contracts.t.sol | 26 +- zk-tests/src/Deposit.sol | 2 +- zk-tests/src/Maths.sol | 8 - zk-tests/src/MissingLibraries.sol | 25 - zk-tests/src/NestedMaths.sol | 10 - zk-tests/src/NestedMissingLibraries.sol | 25 - zk-tests/test.sh | 13 +- 736 files changed, 40105 insertions(+), 25740 deletions(-) create mode 100644 crates/anvil/src/evm.rs create mode 100644 crates/anvil/test-data/storage_sample.json create mode 100644 crates/anvil/tests/it/eip4844.rs delete mode 100644 crates/anvil/tests/it/ganache.rs delete mode 100644 crates/anvil/tests/it/geth.rs create mode 100644 crates/anvil/tests/it/proof.rs delete mode 100644 crates/anvil/tests/it/proof/eip1186.rs delete mode 100644 crates/anvil/tests/it/proof/mod.rs create mode 100644 crates/cast/bin/cmd/mktx.rs create mode 100644 crates/cast/bin/tx.rs delete mode 100644 crates/cast/src/tx.rs create mode 100644 crates/cast/tests/fixtures/interface.json create mode 100644 crates/cheatcodes/src/toml.rs create mode 100644 crates/cli/src/utils/abi.rs create mode 100644 crates/common/src/ens.rs delete mode 100644 crates/common/src/glob.rs delete mode 100644 crates/common/src/provider/alloy.rs delete mode 100644 crates/common/src/provider/ethers.rs delete mode 100644 crates/common/src/runtime_client.rs delete mode 100644 crates/common/src/types.rs create mode 100644 crates/common/src/utils.rs create mode 100644 crates/config/src/filter.rs create mode 100644 crates/config/src/soldeer.rs create mode 100644 crates/config/src/vyper.rs create mode 100644 crates/evm/core/src/abi/HardhatConsole.json create mode 100644 crates/evm/core/src/abi/console.rs create mode 100644 crates/evm/core/src/abi/hardhat_console.rs create mode 100644 crates/evm/core/src/abi/mod.rs rename crates/evm/core/src/backend/{fuzz.rs => cow.rs} (82%) create mode 100644 crates/evm/core/src/opcodes.rs delete mode 100644 crates/evm/evm/src/executors/invariant/funcs.rs create mode 100644 crates/evm/evm/src/executors/invariant/replay.rs create mode 100644 crates/evm/evm/src/executors/invariant/result.rs create mode 100644 crates/evm/evm/src/executors/invariant/shrink.rs delete mode 100644 crates/evm/evm/src/inspectors/access_list.rs delete mode 100644 crates/evm/evm/src/inspectors/printer.rs create mode 100644 crates/forge/bin/cmd/clone.rs delete mode 100644 crates/forge/bin/cmd/script/artifacts.rs delete mode 100644 crates/forge/bin/cmd/script/multi.rs delete mode 100644 crates/forge/bin/cmd/script/receipts.rs delete mode 100644 crates/forge/bin/cmd/script/transaction.rs create mode 100644 crates/forge/bin/cmd/soldeer.rs delete mode 100644 crates/forge/bin/cmd/verify/etherscan/standard_json.rs delete mode 100644 crates/forge/bin/cmd/verify/provider.rs create mode 100644 crates/forge/src/progress.rs create mode 100644 crates/forge/tests/cli/context.rs create mode 100644 crates/forge/tests/cli/soldeer.rs create mode 100644 crates/forge/tests/fixtures/can_build_path_with_one_file.stdout create mode 100644 crates/forge/tests/fixtures/can_build_path_with_three_files.stdout create mode 100644 crates/forge/tests/fixtures/can_build_path_with_two_files.stdout create mode 100644 crates/linking/Cargo.toml rename crates/{forge/src/link.rs => linking/src/lib.rs} (51%) create mode 100644 crates/script/Cargo.toml create mode 100644 crates/script/src/broadcast.rs create mode 100644 crates/script/src/build.rs create mode 100644 crates/script/src/execute.rs rename crates/{forge/bin/cmd/script/mod.rs => script/src/lib.rs} (53%) create mode 100644 crates/script/src/multi_sequence.rs create mode 100644 crates/script/src/progress.rs rename crates/{forge/bin/cmd/script => script/src}/providers.rs (61%) create mode 100644 crates/script/src/receipts.rs rename crates/{forge/bin/cmd/script => script/src}/runner.rs (62%) rename crates/{forge/bin/cmd/script => script/src}/sequence.rs (65%) create mode 100644 crates/script/src/simulate.rs create mode 100644 crates/script/src/transaction.rs rename crates/{forge/bin/cmd/script => script/src}/verify.rs (72%) create mode 100644 crates/sol-macro-gen/Cargo.toml create mode 100644 crates/sol-macro-gen/src/lib.rs create mode 100644 crates/sol-macro-gen/src/sol_macro_gen.rs rename crates/{common => test-utils}/src/rpc.rs (85%) create mode 100644 crates/verify/Cargo.toml create mode 100644 crates/verify/src/bytecode.rs rename crates/{forge/bin/cmd/verify => verify/src}/etherscan/flatten.rs (64%) rename crates/{forge/bin/cmd/verify => verify/src}/etherscan/mod.rs (65%) create mode 100644 crates/verify/src/etherscan/standard_json.rs rename crates/{forge/bin/cmd/verify/mod.rs => verify/src/lib.rs} (54%) create mode 100644 crates/verify/src/provider.rs rename crates/{forge/bin/cmd => verify/src}/retry.rs (89%) rename crates/{forge/bin/cmd/verify => verify/src}/sourcify.rs (70%) create mode 100644 crates/zksync/core/src/convert/eip712.rs create mode 100644 testdata/cancun/cheats/BlobBaseFee.t.sol create mode 100644 testdata/cancun/cheats/Blobhashes.t.sol rename testdata/{ => default}/cheats/Addr.t.sol (95%) rename testdata/{ => default}/cheats/Assert.t.sol (99%) rename testdata/{ => default}/cheats/Assume.t.sol (92%) rename testdata/{ => default}/cheats/Bank.t.sol (95%) rename testdata/{ => default}/cheats/Base64.t.sol (96%) rename testdata/{ => default}/cheats/Broadcast.t.sol (91%) rename testdata/{ => default}/cheats/ChainId.t.sol (93%) rename testdata/{ => default}/cheats/Cool.t.sol (95%) rename testdata/{ => default}/cheats/Deal.t.sol (96%) rename testdata/{ => default}/cheats/Derive.t.sol (96%) create mode 100644 testdata/default/cheats/EnsNamehash.t.sol rename testdata/{ => default}/cheats/Env.t.sol (99%) rename testdata/{ => default}/cheats/Etch.t.sol (96%) rename testdata/{ => default}/cheats/ExpectCall.t.sol (96%) rename testdata/{ => default}/cheats/ExpectEmit.t.sol (99%) rename testdata/{ => default}/cheats/ExpectRevert.t.sol (99%) rename testdata/{ => default}/cheats/Fee.t.sol (94%) rename testdata/{ => default}/cheats/Ffi.t.sol (97%) rename testdata/{ => default}/cheats/Fork.t.sol (88%) rename testdata/{ => default}/cheats/Fork2.t.sol (99%) rename testdata/{ => default}/cheats/Fs.t.sol (99%) rename testdata/{ => default}/cheats/GasMetering.t.sol (98%) rename testdata/{ => default}/cheats/GetBlockTimestamp.t.sol (96%) rename testdata/{ => default}/cheats/GetCode.t.sol (80%) rename testdata/{ => default}/cheats/GetDeployedCode.t.sol (87%) rename testdata/{ => default}/cheats/GetLabel.t.sol (94%) rename testdata/{ => default}/cheats/GetNonce.t.sol (94%) rename testdata/{ => default}/cheats/Json.t.sol (80%) rename testdata/{ => default}/cheats/Label.t.sol (91%) create mode 100644 testdata/default/cheats/LastCallGas.t.sol rename testdata/{ => default}/cheats/Load.t.sol (97%) rename testdata/{ => default}/cheats/Mapping.t.sol (99%) rename testdata/{ => default}/cheats/MemSafety.t.sol (95%) rename testdata/{ => default}/cheats/MockCall.t.sol (90%) rename testdata/{ => default}/cheats/Parse.t.sol (99%) rename testdata/{ => default}/cheats/Prank.t.sol (99%) rename testdata/{ => default}/cheats/Prevrandao.t.sol (85%) rename testdata/{ => default}/cheats/ProjectRoot.t.sol (97%) create mode 100644 testdata/default/cheats/Prompt.t.sol create mode 100644 testdata/default/cheats/RandomUint.t.sol rename testdata/{ => default}/cheats/ReadCallers.t.sol (99%) rename testdata/{ => default}/cheats/Record.t.sol (98%) rename testdata/{ => default}/cheats/RecordAccountAccesses.t.sol (99%) rename testdata/{ => default}/cheats/RecordLogs.t.sol (99%) rename testdata/{ => default}/cheats/Remember.t.sol (96%) rename testdata/{ => default}/cheats/ResetNonce.t.sol (97%) rename testdata/{ => default}/cheats/Roll.t.sol (97%) rename testdata/{ => default}/cheats/RpcUrls.t.sol (87%) rename testdata/{ => default}/cheats/SetNonce.t.sol (96%) rename testdata/{ => default}/cheats/SetNonceUnsafe.t.sol (97%) rename testdata/{ => default}/cheats/Setup.t.sol (93%) rename testdata/{ => default}/cheats/Sign.t.sol (96%) rename testdata/{ => default}/cheats/SignP256.t.sol (96%) rename testdata/{ => default}/cheats/Skip.t.sol (96%) rename testdata/{ => default}/cheats/Sleep.t.sol (98%) rename testdata/{ => default}/cheats/Snapshots.t.sol (97%) rename testdata/{ => default}/cheats/Store.t.sol (98%) rename testdata/{ => default}/cheats/StringUtils.t.sol (73%) rename testdata/{ => default}/cheats/ToString.t.sol (98%) create mode 100644 testdata/default/cheats/Toml.t.sol rename testdata/{ => default}/cheats/Travel.t.sol (95%) rename testdata/{ => default}/cheats/TryFfi.sol (97%) rename testdata/{ => default}/cheats/UnixTime.t.sol (80%) rename testdata/{ => default}/cheats/Wallet.t.sol (99%) rename testdata/{ => default}/cheats/Warp.t.sol (96%) rename testdata/{ => default}/cheats/dumpState.t.sol (99%) rename testdata/{ => default}/cheats/getBlockNumber.t.sol (97%) rename testdata/{ => default}/cheats/loadAllocs.t.sol (99%) rename testdata/{ => default}/core/Abstract.t.sol (100%) create mode 100644 testdata/default/core/BadSigAfterInvariant.t.sol rename testdata/{ => default}/core/ContractEnvironment.t.sol (100%) rename testdata/{ => default}/core/DSStyle.t.sol (100%) rename testdata/{ => default}/core/FailingSetup.t.sol (100%) rename testdata/{ => default}/core/FailingTestAfterFailedSetup.t.sol (100%) create mode 100644 testdata/default/core/MultipleAfterInvariant.t.sol rename testdata/{ => default}/core/MultipleSetup.t.sol (100%) rename testdata/{ => default}/core/PaymentFailure.t.sol (93%) rename testdata/{ => default}/core/Reverting.t.sol (100%) rename testdata/{ => default}/core/SetupConsistency.t.sol (100%) rename testdata/{ => default}/fork/DssExecLib.sol (100%) rename testdata/{ => default}/fork/ForkSame_1.t.sol (95%) rename testdata/{ => default}/fork/ForkSame_2.t.sol (95%) rename testdata/{ => default}/fork/LaunchFork.t.sol (100%) rename testdata/{ => default}/fork/Transact.t.sol (99%) rename testdata/{ => default}/fs/Default.t.sol (97%) rename testdata/{ => default}/fs/Disabled.t.sol (97%) rename testdata/{ => default}/fuzz/Fuzz.t.sol (100%) rename testdata/{ => default}/fuzz/FuzzCollection.t.sol (100%) create mode 100644 testdata/default/fuzz/FuzzFailurePersist.t.sol rename testdata/{ => default}/fuzz/FuzzInt.t.sol (67%) rename testdata/{ => default}/fuzz/FuzzPositive.t.sol (100%) create mode 100644 testdata/default/fuzz/FuzzScrapeBytecode.t.sol rename testdata/{ => default}/fuzz/FuzzUint.t.sol (69%) create mode 100644 testdata/default/fuzz/invariant/common/InvariantAfterInvariant.t.sol create mode 100644 testdata/default/fuzz/invariant/common/InvariantAssume.t.sol create mode 100644 testdata/default/fuzz/invariant/common/InvariantCalldataDictionary.t.sol create mode 100644 testdata/default/fuzz/invariant/common/InvariantCustomError.t.sol create mode 100644 testdata/default/fuzz/invariant/common/InvariantExcludedSenders.t.sol create mode 100644 testdata/default/fuzz/invariant/common/InvariantFixtures.t.sol rename testdata/{ => default}/fuzz/invariant/common/InvariantHandlerFailure.t.sol (100%) rename testdata/{ => default}/fuzz/invariant/common/InvariantInnerContract.t.sol (100%) create mode 100644 testdata/default/fuzz/invariant/common/InvariantPreserveState.t.sol rename testdata/{ => default}/fuzz/invariant/common/InvariantReentrancy.t.sol (64%) create mode 100644 testdata/default/fuzz/invariant/common/InvariantRollFork.t.sol create mode 100644 testdata/default/fuzz/invariant/common/InvariantScrapeValues.t.sol create mode 100644 testdata/default/fuzz/invariant/common/InvariantSelectorsWeight.t.sol create mode 100644 testdata/default/fuzz/invariant/common/InvariantShrinkBigSequence.t.sol create mode 100644 testdata/default/fuzz/invariant/common/InvariantShrinkFailOnRevert.t.sol create mode 100644 testdata/default/fuzz/invariant/common/InvariantShrinkWithAssert.t.sol rename testdata/{ => default}/fuzz/invariant/common/InvariantTest1.t.sol (100%) rename testdata/{ => default}/fuzz/invariant/storage/InvariantStorageTest.t.sol (100%) rename testdata/{ => default}/fuzz/invariant/target/ExcludeContracts.t.sol (100%) create mode 100644 testdata/default/fuzz/invariant/target/ExcludeSelectors.t.sol rename testdata/{ => default}/fuzz/invariant/target/ExcludeSenders.t.sol (100%) create mode 100644 testdata/default/fuzz/invariant/target/FuzzedTargetContracts.t.sol rename testdata/{ => default}/fuzz/invariant/target/TargetContracts.t.sol (100%) rename testdata/{ => default}/fuzz/invariant/target/TargetInterfaces.t.sol (100%) rename testdata/{ => default}/fuzz/invariant/target/TargetSelectors.t.sol (100%) rename testdata/{ => default}/fuzz/invariant/target/TargetSenders.t.sol (100%) rename testdata/{ => default}/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol (91%) rename testdata/{ => default}/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol (65%) rename testdata/{ => default}/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol (71%) rename testdata/{ => default}/fuzz/invariant/targetAbi/TargetArtifacts.t.sol (91%) rename testdata/{ => default}/inline/FuzzInlineConf.t.sol (100%) rename testdata/{ => default}/inline/InvariantInlineConf.t.sol (100%) rename testdata/{ => default}/linking/cycle/Cycle.t.sol (100%) rename testdata/{ => default}/linking/duplicate/Duplicate.t.sol (100%) rename testdata/{ => default}/linking/nested/Nested.t.sol (100%) rename testdata/{ => default}/linking/simple/Simple.t.sol (100%) rename testdata/{ => default}/logs/DebugLogs.t.sol (100%) rename testdata/{ => default}/logs/HardhatLogs.t.sol (100%) rename testdata/{ => default}/logs/console.sol (100%) rename testdata/{ => default}/repros/Issue2623.t.sol (95%) rename testdata/{ => default}/repros/Issue2629.t.sol (96%) rename testdata/{ => default}/repros/Issue2723.t.sol (95%) create mode 100644 testdata/default/repros/Issue2851.t.sol rename testdata/{ => default}/repros/Issue2898.t.sol (96%) rename testdata/{ => default}/repros/Issue2956.t.sol (82%) rename testdata/{ => default}/repros/Issue2984.t.sol (96%) rename testdata/{ => default}/repros/Issue3055.t.sol (97%) rename testdata/{ => default}/repros/Issue3077.t.sol (97%) rename testdata/{ => default}/repros/Issue3110.t.sol (97%) rename testdata/{ => default}/repros/Issue3119.t.sol (96%) rename testdata/{ => default}/repros/Issue3189.t.sol (96%) rename testdata/{ => default}/repros/Issue3190.t.sol (94%) rename testdata/{ => default}/repros/Issue3192.t.sol (95%) rename testdata/{ => default}/repros/Issue3220.t.sol (97%) rename testdata/{ => default}/repros/Issue3221.t.sol (82%) rename testdata/{ => default}/repros/Issue3223.t.sol (76%) rename testdata/{ => default}/repros/Issue3347.t.sol (91%) rename testdata/{ => default}/repros/Issue3437.t.sol (93%) rename testdata/{ => default}/repros/Issue3596.t.sol (96%) rename testdata/{ => default}/repros/Issue3653.t.sol (95%) rename testdata/{ => default}/repros/Issue3661.t.sol (100%) rename testdata/{ => default}/repros/Issue3674.t.sol (77%) rename testdata/{ => default}/repros/Issue3685.t.sol (97%) rename testdata/{ => default}/repros/Issue3703.t.sol (98%) rename testdata/{ => default}/repros/Issue3708.t.sol (97%) rename testdata/{ => default}/repros/Issue3723.t.sol (93%) rename testdata/{ => default}/repros/Issue3753.t.sol (94%) rename testdata/{ => default}/repros/Issue3792.t.sol (96%) create mode 100644 testdata/default/repros/Issue4402.t.sol rename testdata/{ => default}/repros/Issue4586.t.sol (97%) rename testdata/{ => default}/repros/Issue4630.t.sol (96%) rename testdata/{ => default}/repros/Issue4640.t.sol (94%) rename testdata/{ => default}/repros/Issue4832.t.sol (92%) rename testdata/{ => default}/repros/Issue5038.t.sol (99%) rename testdata/{ => default}/repros/Issue5529.t.sol (97%) create mode 100644 testdata/default/repros/Issue5739.t.sol rename testdata/{ => default}/repros/Issue5808.t.sol (95%) rename testdata/{ => default}/repros/Issue5929.t.sol (95%) rename testdata/{ => default}/repros/Issue5935.t.sol (97%) rename testdata/{ => default}/repros/Issue5948.t.sol (97%) rename testdata/{ => default}/repros/Issue6006.t.sol (97%) rename testdata/{ => default}/repros/Issue6032.t.sol (98%) rename testdata/{ => default}/repros/Issue6070.t.sol (95%) rename testdata/{ => default}/repros/Issue6115.t.sol (100%) rename testdata/{ => default}/repros/Issue6170.t.sol (95%) rename testdata/{ => default}/repros/Issue6180.t.sol (95%) rename testdata/{ => default}/repros/Issue6293.t.sol (93%) rename testdata/{ => default}/repros/Issue6355.t.sol (96%) rename testdata/{ => default}/repros/Issue6437.t.sol (97%) rename testdata/{ => default}/repros/Issue6501.t.sol (100%) rename testdata/{ => default}/repros/Issue6538.t.sol (95%) rename testdata/{ => default}/repros/Issue6554.t.sol (61%) rename testdata/{ => default}/repros/Issue6616.t.sol (96%) rename testdata/{ => default}/repros/Issue6634.t.sol (99%) rename testdata/{ => default}/repros/Issue6759.t.sol (95%) rename testdata/{ => default}/repros/Issue6966.t.sol (100%) create mode 100644 testdata/default/repros/Issue7481.t.sol create mode 100644 testdata/default/repros/Issue8004.t.sol create mode 100644 testdata/default/repros/Issue8006.t.sol create mode 100644 testdata/default/script/broadcast/deploy.sol/31337/run-latest.json rename testdata/{ => default}/script/deploy.sol (89%) rename testdata/{ => default}/spec/ShanghaiCompat.t.sol (96%) rename testdata/{ => default}/trace/ConflictingSignatures.t.sol (97%) rename testdata/{ => default}/trace/Trace.t.sol (98%) create mode 100644 testdata/etherscan/0x044b75f554b886A065b9567891e45c79542d7357/creation_data.json create mode 100644 testdata/etherscan/0x044b75f554b886A065b9567891e45c79542d7357/metadata.json create mode 100644 testdata/etherscan/0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193/creation_data.json create mode 100644 testdata/etherscan/0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193/metadata.json create mode 100644 testdata/etherscan/0x3a23F943181408EAC424116Af7b7790c94Cb97a5/creation_data.json create mode 100644 testdata/etherscan/0x3a23F943181408EAC424116Af7b7790c94Cb97a5/metadata.json create mode 100644 testdata/etherscan/0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05/creation_data.json create mode 100644 testdata/etherscan/0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05/metadata.json create mode 100644 testdata/etherscan/0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545/creation_data.json create mode 100644 testdata/etherscan/0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545/metadata.json create mode 100644 testdata/etherscan/0x9AB6b21cDF116f611110b048987E58894786C244/creation_data.json create mode 100644 testdata/etherscan/0x9AB6b21cDF116f611110b048987E58894786C244/metadata.json create mode 100644 testdata/etherscan/0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F/creation_data.json create mode 100644 testdata/etherscan/0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F/metadata.json create mode 100644 testdata/etherscan/0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec/creation_data.json create mode 100644 testdata/etherscan/0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec/metadata.json create mode 100644 testdata/fixtures/Json/Issue4402.json rename testdata/fixtures/Json/{wholeJson.json => whole_json.json} (100%) create mode 100644 testdata/fixtures/Toml/Issue4402.toml create mode 100644 testdata/fixtures/Toml/test.toml create mode 100644 testdata/fixtures/Toml/whole_toml.toml create mode 100644 testdata/fixtures/Toml/write_complex_test.toml create mode 100644 testdata/fixtures/Toml/write_test.toml create mode 100644 testdata/multi-version/Counter.sol create mode 100644 testdata/multi-version/Importer.sol create mode 100644 testdata/multi-version/cheats/GetCode.t.sol create mode 100644 testdata/multi-version/cheats/GetCode17.t.sol delete mode 100644 testdata/script/broadcast/deploy.sol/31337/run-latest.json delete mode 100644 zk-tests/src/Maths.sol delete mode 100644 zk-tests/src/MissingLibraries.sol delete mode 100644 zk-tests/src/NestedMaths.sol delete mode 100644 zk-tests/src/NestedMissingLibraries.sol diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 99659760a..4eea49b38 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,6 +1,10 @@ * @danipopes @evalir @mattsse -crates/anvil/ @evalir @mattsse -crates/evm/coverage/ @evalir @onbjerg +crates/anvil/ @danipopes @mattsse @evalir +crates/cheatcodes/ @danipopes @mattsse @klkvr @evalir +crates/evm/coverage/ @onbjerg crates/fmt/ @rkrasiuk -crates/macros/impls/ @danipopes +crates/linking/ @klkvr +crates/macros/ @danipopes +crates/script/ @danipopes @mattsse @klkvr +crates/wallets/ @klkvr diff --git a/.github/scripts/matrices.py b/.github/scripts/matrices.py index 3fdcea115..a71ff3683 100755 --- a/.github/scripts/matrices.py +++ b/.github/scripts/matrices.py @@ -69,7 +69,7 @@ def __init__( t_linux_x86 = Target("ubuntu-latest", "x86_64-unknown-linux-gnu", "linux-amd64") # TODO: Figure out how to make this work # t_linux_arm = Target("ubuntu-latest", "aarch64-unknown-linux-gnu", "linux-aarch64") -t_macos = Target("macos-latest", "x86_64-apple-darwin", "macosx-amd64") +t_macos = Target("macos-latest", "aarch64-apple-darwin", "macosx-aarch64") t_windows = Target("windows-latest", "x86_64-pc-windows-msvc", "windows-amd64") targets = [t_linux_x86, t_windows] if is_pr else [t_linux_x86, t_macos, t_windows] diff --git a/.github/workflows/deny.yml b/.github/workflows/deny.yml index 79a421338..72de0dd7a 100644 --- a/.github/workflows/deny.yml +++ b/.github/workflows/deny.yml @@ -1,26 +1,26 @@ name: deny on: - push: - branches: [main] - paths: [Cargo.lock, deny.toml] - pull_request: - branches: [main] - paths: [Cargo.lock, deny.toml] + push: + branches: [dev] + paths: [Cargo.lock, deny.toml] + pull_request: + branches: [dev] + paths: [Cargo.lock, deny.toml] env: - CARGO_TERM_COLOR: always + CARGO_TERM_COLOR: always jobs: - cargo-deny: - name: cargo deny check - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - uses: actions/checkout@v4 - - uses: EmbarkStudios/cargo-deny-action@v1 - with: - command: check all - # Clear out arguments to not pass `--all-features` to `cargo deny`. - # many crates have an `openssl` feature which enables banned dependencies - arguments: "" + cargo-deny: + name: cargo deny check + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + - uses: EmbarkStudios/cargo-deny-action@v1 + with: + command: check all + # Clear out arguments to not pass `--all-features` to `cargo deny`. + # many crates have an `openssl` feature which enables banned dependencies + arguments: "" diff --git a/.github/workflows/dependencies.yml b/.github/workflows/dependencies.yml index 725b6aeb4..d8e38b8ff 100644 --- a/.github/workflows/dependencies.yml +++ b/.github/workflows/dependencies.yml @@ -1,30 +1,30 @@ -# Automatically run `cargo update` periodically +# Runs `cargo update` periodically. name: dependencies on: - schedule: - # Run weekly - - cron: "0 0 * * SUN" - workflow_dispatch: - # Needed so we can run it manually + schedule: + # Run weekly + - cron: "0 0 * * SUN" + workflow_dispatch: + # Needed so we can run it manually env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BRANCH: cargo-update - TITLE: "chore(deps): weekly `cargo update`" - BODY: | - Automation to keep dependencies in `Cargo.lock` current. + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BRANCH: cargo-update + TITLE: "chore(deps): weekly `cargo update`" + BODY: | + Automation to keep dependencies in `Cargo.lock` current. -
cargo update log -

+

cargo update log +

- ```log - $cargo_update_log - ``` + ```log + $cargo_update_log + ``` -

-
+

+
jobs: update: @@ -37,30 +37,28 @@ jobs: with: toolchain: nightly-2023-09-30 - - name: cargo update - # Remove first line that always just says "Updating crates.io index" - run: - cargo update --color never 2>&1 | sed '/crates.io index/d' | tee -a - cargo_update.log + - name: cargo update + # Remove first line that always just says "Updating crates.io index" + run: cargo update --color never 2>&1 | sed '/crates.io index/d' | tee -a cargo_update.log - - name: craft commit message and PR body - id: msg - run: | - export cargo_update_log="$(cat cargo_update.log)" + - name: craft commit message and PR body + id: msg + run: | + export cargo_update_log="$(cat cargo_update.log)" - echo "commit_message<> $GITHUB_OUTPUT - printf "$TITLE\n\n$cargo_update_log\n" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT + echo "commit_message<> $GITHUB_OUTPUT + printf "$TITLE\n\n$cargo_update_log\n" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT - echo "body<> $GITHUB_OUTPUT - echo "$BODY" | envsubst >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT + echo "body<> $GITHUB_OUTPUT + echo "$BODY" | envsubst >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT - - name: Create Pull Request - uses: peter-evans/create-pull-request@v5 - with: - add-paths: ./Cargo.lock - commit-message: ${{ steps.msg.outputs.commit_message }} - title: ${{ env.TITLE }} - body: ${{ steps.msg.outputs.body }} - branch: ${{ env.BRANCH }} + - name: Create Pull Request + uses: peter-evans/create-pull-request@v6 + with: + add-paths: ./Cargo.lock + commit-message: ${{ steps.msg.outputs.commit_message }} + title: ${{ env.TITLE }} + body: ${{ steps.msg.outputs.body }} + branch: ${{ env.BRANCH }} diff --git a/.github/workflows/infrastructure.yml b/.github/workflows/infrastructure.yml index dd583f35d..04c3f0c88 100644 --- a/.github/workflows/infrastructure.yml +++ b/.github/workflows/infrastructure.yml @@ -24,7 +24,7 @@ jobs: - name: Install Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: - toolchain: nightly-2024-02-06 + toolchain: nightly-2024-04-28 - name: Build forge binary run: cargo build --release --bin forge diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index db9fc48d5..72773249a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,65 +1,65 @@ name: release on: - push: - tags: - - "v*.*.*" - schedule: - - cron: "0 0 * * *" # Daily at midnight - workflow_dispatch: + push: + tags: + - "v*.*.*" + schedule: + - cron: "0 0 * * *" + workflow_dispatch: env: - CARGO_TERM_COLOR: always - IS_NIGHTLY: ${{ github.event_name == 'workflow_dispatch' }} + CARGO_TERM_COLOR: always + IS_NIGHTLY: ${{ github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' }} jobs: - prepare: - name: Prepare release - runs-on: ubuntu-latest - timeout-minutes: 30 - 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@v4 - with: - fetch-depth: 0 - - - name: Compute release name and tag - id: release_info - run: | - if [[ $IS_NIGHTLY ]]; then - echo "tag_name=nightly-${GITHUB_SHA}" >> $GITHUB_OUTPUT - echo "release_name=foundry-zksync Nightly ($(date '+%Y-%m-%d'))" >> $GITHUB_OUTPUT - else - echo "tag_name=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT - echo "release_name=foundry-zksync@${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 }} - uses: actions/github-script@v7 - 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@v4 - with: - configuration: "./.github/changelog.json" - fromTag: ${{ env.IS_NIGHTLY && 'nightly' || '' }} - toTag: ${{ steps.release_info.outputs.tag_name }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + prepare: + name: Prepare release + runs-on: ubuntu-latest + timeout-minutes: 30 + 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@v4 + with: + fetch-depth: 0 + + - name: Compute release name and tag + id: release_info + run: | + if [[ $IS_NIGHTLY ]]; then + echo "tag_name=nightly-${GITHUB_SHA}" >> $GITHUB_OUTPUT + echo "release_name=foundry-zksync Nightly ($(date '+%Y-%m-%d'))" >> $GITHUB_OUTPUT + else + echo "tag_name=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT + echo "release_name=foundry-zksync@${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 }} + uses: actions/github-script@v7 + 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@v4 + with: + configuration: "./.github/changelog.json" + fromTag: ${{ env.IS_NIGHTLY && 'nightly' || '' }} + toTag: ${{ steps.release_info.outputs.tag_name }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} release: name: ${{ matrix.target }} (${{ matrix.runner }}) @@ -220,27 +220,27 @@ jobs: ${{ steps.artifacts.outputs.file_name }} ${{ steps.man.outputs.foundry_man }} - cleanup: - name: Release cleanup - runs-on: ubuntu-latest - timeout-minutes: 30 - needs: release - if: always() - steps: - - uses: actions/checkout@v4 - - # Moves the `nightly` tag to `HEAD` - - name: Move nightly tag - if: ${{ env.IS_NIGHTLY }} - uses: actions/github-script@v7 - with: - script: | - const moveTag = require('./.github/scripts/move-tag.js') - await moveTag({ github, context }, 'nightly') - - - name: Delete old nightlies - uses: actions/github-script@v7 - with: - script: | - const prunePrereleases = require('./.github/scripts/prune-prereleases.js') - await prunePrereleases({github, context}) \ No newline at end of file + cleanup: + name: Release cleanup + runs-on: ubuntu-latest + timeout-minutes: 30 + needs: release + if: always() + steps: + - uses: actions/checkout@v4 + + # Moves the `nightly` tag to `HEAD` + - name: Move nightly tag + if: ${{ env.IS_NIGHTLY }} + uses: actions/github-script@v7 + with: + script: | + const moveTag = require('./.github/scripts/move-tag.js') + await moveTag({ github, context }, 'nightly') + + - name: Delete old nightlies + uses: actions/github-script@v7 + with: + script: | + const prunePrereleases = require('./.github/scripts/prune-prereleases.js') + await prunePrereleases({github, context}) diff --git a/.github/workflows/secret_scanner.yaml b/.github/workflows/secret_scanner.yaml index 1d237c6f5..7d745ae80 100644 --- a/.github/workflows/secret_scanner.yaml +++ b/.github/workflows/secret_scanner.yaml @@ -5,7 +5,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: TruffleHog OSS @@ -14,4 +14,4 @@ jobs: path: ./ base: ${{ github.event.repository.default_branch }} head: HEAD - extra_args: --debug --only-verified \ No newline at end of file + extra_args: --debug --only-verified --exclude-globs=zk-tests/src/Cheatcodes.t.sol # TODO: Remove this file once merged \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f08d0d0c0..dc1e2ad8c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,10 +21,10 @@ jobs: runs-on: ubuntu-22.04-github-hosted-16core timeout-minutes: 60 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly with: - toolchain: nightly-2024-02-06 + toolchain: nightly-2024-04-28 - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true @@ -38,7 +38,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@clippy - uses: Swatinem/rust-cache@v2 with: @@ -52,10 +52,10 @@ jobs: runs-on: ubuntu-22.04-github-hosted-16core timeout-minutes: 60 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly with: - toolchain: nightly-2024-02-06 + toolchain: nightly-2024-04-28 components: rustfmt - run: cargo fmt --all --check @@ -64,10 +64,10 @@ jobs: runs-on: ubuntu-22.04-github-hosted-16core timeout-minutes: 60 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly with: - toolchain: nightly-2024-02-06 + toolchain: nightly-2024-04-28 - uses: Swatinem/rust-cache@v2 with: cache-on-failure: true @@ -79,10 +79,10 @@ jobs: runs-on: ubuntu-22.04-github-hosted-16core timeout-minutes: 60 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly with: - toolchain: nightly-2024-02-06 + toolchain: nightly-2024-04-28 - uses: taiki-e/install-action@cargo-hack - uses: Swatinem/rust-cache@v2 with: @@ -103,7 +103,7 @@ jobs: # - name: Install Rust # uses: actions-rust-lang/setup-rust-toolchain@v1 # with: - # toolchain: nightly-2024-02-06 + # toolchain: nightly-2024-04-28 # - name: Get solc @@ -130,7 +130,7 @@ jobs: - name: Install Rust uses: actions-rust-lang/setup-rust-toolchain@v1 with: - toolchain: nightly-2024-02-06 + toolchain: nightly-2024-04-28 - name: Run smoke-test env: diff --git a/.gitignore b/.gitignore index 43356be97..846a3d171 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ out/ out.json .idea .vscode -install.log \ No newline at end of file +bloat* +install.log diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 859aaa6d6..98b36e853 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -86,7 +86,7 @@ Please also make sure that the following commands pass if you have changed the c cargo check --all cargo test --all --all-features cargo +nightly fmt -- --check -cargo +nightly clippy --all --all-features -- -D warnings +cargo +nightly clippy --all --all-targets --all-features -- -D warnings ``` 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: diff --git a/Cargo.lock b/Cargo.lock index 47c715bf3..39e3a6033 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,19 +44,19 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "getrandom 0.2.12", + "getrandom 0.2.15", "once_cell", "version_check", ] [[package]] name = "ahash" -version = "0.8.9" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d713b3834d76b85304d4d525563c1276e2e30dc97cc67bfb4585a4a29fc2c89f" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if 1.0.0", - "getrandom 0.2.12", + "getrandom 0.2.15", "once_cell", "version_check", "zerocopy", @@ -64,46 +64,69 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "allocator-api2" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "alloy-chains" -version = "0.1.14" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "973deb9e9d5db1f28c2a478073aeb435f1c07f72cf5935caa0c421e6b68f2db1" +checksum = "1752d7d62e2665da650a36d84abbf239f812534475d51f072a49a533513b7cdd" dependencies = [ "num_enum 0.7.2", "serde", - "strum 0.26.1", + "strum 0.26.3", ] [[package]] name = "alloy-consensus" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f63a6c9eb45684a5468536bc55379a2af0f45ffa5d756e4e4964532737e1836" dependencies = [ "alloy-eips", - "alloy-network", "alloy-primitives", "alloy-rlp", + "alloy-serde", + "c-kzg", + "serde", +] + +[[package]] +name = "alloy-contract" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c26b7d34cb76f826558e9409a010e25257f7bfb5aa5e3dd0042c564664ae159" +dependencies = [ + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-pubsub", + "alloy-rpc-types-eth", + "alloy-sol-types", + "alloy-transport", + "futures 0.3.30", + "futures-util", + "thiserror", ] [[package]] name = "alloy-dyn-abi" -version = "0.6.3" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b1a44ed6b4126e4818d20c9e48176ae9d6d4fcbe6c909f8cd0bf050eb56fd8" +checksum = "cb6e6436a9530f25010d13653e206fab4c9feddacf21a54de8d7311b275bc56b" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -112,40 +135,46 @@ dependencies = [ "arbitrary", "const-hex", "derive_arbitrary", - "derive_more 0.99.17", + "derive_more 0.99.18", "itoa", "proptest", "serde", "serde_json", - "winnow 0.5.40", + "winnow 0.6.13", ] [[package]] name = "alloy-eips" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4b0fc6a572ef2eebda0a31a5e393d451abda703fec917c75d9615d8c978cf2" dependencies = [ "alloy-primitives", "alloy-rlp", + "alloy-serde", + "c-kzg", + "derive_more 0.99.18", + "once_cell", "serde", - "thiserror", + "sha2 0.10.8", ] [[package]] name = "alloy-genesis" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48450f9c6f0821c1eee00ed912942492ed4f11dd69532825833de23ecc7a2256" dependencies = [ "alloy-primitives", - "alloy-rpc-types", + "alloy-serde", "serde", ] [[package]] name = "alloy-json-abi" -version = "0.6.3" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30c6a6c5140fc762edfe55349f9ddefa821f4b7f2339cef582de911a3f1fb6d3" +checksum = "aaeaccd50238126e3a0ff9387c7c568837726ad4f4e399b528ca88104d6c25ef" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -155,32 +184,42 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d484c2a934d0a4d86f8ad4db8113cb1d607707a6c54f6e78f4f1b4451b47aa70" dependencies = [ "alloy-primitives", "serde", "serde_json", "thiserror", + "tracing", ] [[package]] name = "alloy-network" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a20eba9bc551037f0626d6d29e191888638d979943fa4e842e9e6fc72bf0565" dependencies = [ + "alloy-consensus", "alloy-eips", "alloy-json-rpc", "alloy-primitives", - "alloy-rlp", - "serde", + "alloy-rpc-types-eth", + "alloy-serde", + "alloy-signer", + "alloy-sol-types", + "async-trait", + "auto_impl", + "futures-utils-wasm", + "thiserror", ] [[package]] name = "alloy-primitives" -version = "0.6.3" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef197eb250c64962003cb08b90b17f0882c192f4a6f2f544809d424fd7cb0e7d" +checksum = "f783611babedbbe90db3478c120fb5f5daacceffc210b39adc0af4fe0da70bad" dependencies = [ "alloy-rlp", "arbitrary", @@ -188,12 +227,12 @@ dependencies = [ "cfg-if 1.0.0", "const-hex", "derive_arbitrary", - "derive_more 0.99.17", + "derive_more 0.99.18", "ethereum_ssz", - "getrandom 0.2.12", + "getrandom 0.2.15", "hex-literal", "itoa", - "k256 0.13.1", + "k256 0.13.3", "keccak-asm", "proptest", "proptest-derive", @@ -204,28 +243,47 @@ dependencies = [ ] [[package]] -name = "alloy-providers" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" +name = "alloy-provider" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad5d89acb7339fad13bc69e7b925232f242835bfd91c82fcb9326b36481bd0f0" dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-eips", + "alloy-json-rpc", "alloy-network", "alloy-primitives", + "alloy-pubsub", "alloy-rpc-client", - "alloy-rpc-trace-types", - "alloy-rpc-types", + "alloy-rpc-types-eth", + "alloy-rpc-types-trace", + "alloy-rpc-types-txpool", "alloy-transport", "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", + "async-stream", "async-trait", "auto_impl", - "reqwest", + "dashmap", + "futures 0.3.30", + "futures-utils-wasm", + "lru", + "pin-project 1.1.5", + "reqwest 0.12.5", "serde", - "thiserror", + "serde_json", + "tokio", + "tracing", + "url", ] [[package]] name = "alloy-pubsub" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "034258dfaa51c278e1f7fcc46e587d10079ec9372866fa48c5df9d908fc1f6b1" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -235,15 +293,16 @@ dependencies = [ "serde", "serde_json", "tokio", + "tokio-stream", "tower", "tracing", ] [[package]] name = "alloy-rlp" -version = "0.3.4" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d58d9f5da7b40e9bfff0b7e7816700be4019db97d4b6359fe7f94a9e22e42ac" +checksum = "a43b18702501396fa9bcdeecd533bc85fac75150d308fc0f6800a01e6234a003" dependencies = [ "alloy-rlp-derive", "arrayvec 0.7.4", @@ -252,109 +311,298 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.4" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a047897373be4bbb0224c1afdabca92648dc57a9c9ef6e7b0be3aff7a859c83" +checksum = "d83524c1f6162fcb5b0decf775498a125066c86dda6066ed609531b0e912f85a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] name = "alloy-rpc-client" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ce003e8c74bbbc7d4235131c1d6b7eaf14a533ae850295b90d240340989cb" dependencies = [ "alloy-json-rpc", + "alloy-primitives", + "alloy-pubsub", "alloy-transport", "alloy-transport-http", + "alloy-transport-ipc", + "alloy-transport-ws", "futures 0.3.30", - "pin-project", - "reqwest", + "pin-project 1.1.5", + "reqwest 0.12.5", + "serde", "serde_json", + "tokio", + "tokio-stream", "tower", "tracing", "url", ] [[package]] -name = "alloy-rpc-trace-types" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" +name = "alloy-rpc-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dfa1dd3e0bc3a3d89744fba8d1511216e83257160da2cd028a18b7d9c026030" dependencies = [ + "alloy-rpc-types-eth", + "alloy-rpc-types-trace", + "alloy-rpc-types-txpool", + "alloy-serde", +] + +[[package]] +name = "alloy-rpc-types-engine" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc40df2dda7561d1406d0bee1d19c8787483a2cf2ee8011c05909475e7bc102d" +dependencies = [ + "alloy-consensus", + "alloy-eips", "alloy-primitives", - "alloy-rpc-types", + "alloy-rlp", + "alloy-rpc-types-eth", + "alloy-serde", + "jsonwebtoken 9.3.0", + "rand 0.8.5", "serde", - "serde_json", + "thiserror", ] [[package]] -name = "alloy-rpc-types" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" +name = "alloy-rpc-types-eth" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13bd7aa9ff9e67f1ba7ee0dd8cebfc95831d1649b0e4eeefae940dc3681079fa" dependencies = [ + "alloy-consensus", + "alloy-eips", "alloy-primitives", "alloy-rlp", - "itertools 0.12.1", + "alloy-serde", + "alloy-sol-types", + "itertools 0.13.0", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "alloy-rpc-types-trace" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "535d26db98ac320a0d1637faf3e210328c3df3b1998abd7e72343d3857058efe" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", "serde", "serde_json", "thiserror", ] +[[package]] +name = "alloy-rpc-types-txpool" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971c92989c6a5588d3f6d1e99e5328fba6e68694efbe969d6ec96ae5b9d1037" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-serde" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8913f9e825068d77c516188c221c44f78fd814fce8effe550a783295a2757d19" +dependencies = [ + "alloy-primitives", + "serde", + "serde_json", +] + [[package]] name = "alloy-signer" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f740e13eb4c6a0e4d0e49738f1e86f31ad2d7ef93be499539f492805000f7237" dependencies = [ - "alloy-network", + "alloy-dyn-abi", "alloy-primitives", "alloy-sol-types", "async-trait", "auto_impl", - "coins-bip32", - "coins-bip39", + "elliptic-curve 0.13.8", + "k256 0.13.3", + "thiserror", +] + +[[package]] +name = "alloy-signer-aws" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e9573e8a5339fefc515b3e336fae177e2080225a4ea49cd5ab24de4b0bdc81d" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "aws-sdk-kms", + "k256 0.13.3", + "spki 0.7.3", + "thiserror", + "tracing", +] + +[[package]] +name = "alloy-signer-gcp" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4911b3b4e104af7ed40bf51031a6f0f2400788759f6073a5d90003db6bb88fe6" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "gcloud-sdk", + "k256 0.13.3", + "spki 0.7.3", + "thiserror", + "tracing", +] + +[[package]] +name = "alloy-signer-ledger" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eb31f033976724d10f90633477436f5e3757b04283c475a750a77e82422aa36" +dependencies = [ + "alloy-consensus", + "alloy-dyn-abi", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "alloy-sol-types", + "async-trait", + "coins-ledger", + "futures-util", + "semver 1.0.23", + "thiserror", + "tracing", +] + +[[package]] +name = "alloy-signer-local" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87db68d926887393a1d0f9c43833b44446ea29d603291e7b20e5d115f31aa4e3" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "coins-bip32 0.11.1", + "coins-bip39 0.11.1", "elliptic-curve 0.13.8", "eth-keystore", - "k256 0.13.1", + "k256 0.13.3", "rand 0.8.5", "thiserror", ] +[[package]] +name = "alloy-signer-trezor" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39c0c55911ca291f842f7d18a06a993679fe672d5d02049c665fa01aafa2b31a" +dependencies = [ + "alloy-consensus", + "alloy-network", + "alloy-primitives", + "alloy-signer", + "async-trait", + "semver 1.0.23", + "thiserror", + "tracing", + "trezor-client", +] + [[package]] name = "alloy-sol-macro" -version = "0.6.3" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bad41a7c19498e3f6079f7744656328699f8ea3e783bdd10d85788cd439f572" +dependencies = [ + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e92100dee7fd1e44abbe0ef6607f18758cf0ad4e483f4c65ff5c8d85428a6d" +checksum = "fd9899da7d011b4fe4c406a524ed3e3f963797dbc93b45479d60341d3a27b252" dependencies = [ "alloy-json-abi", + "alloy-sol-macro-input", "const-hex", - "dunce", - "heck 0.4.1", - "indexmap 2.2.3", + "heck 0.5.0", + "indexmap 2.2.6", "proc-macro-error", "proc-macro2", "quote", - "serde_json", - "syn 2.0.50", + "syn 2.0.68", "syn-solidity", "tiny-keccak 2.0.2", ] +[[package]] +name = "alloy-sol-macro-input" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32d595768fdc61331a132b6f65db41afae41b9b97d36c21eb1b955c422a7e60" +dependencies = [ + "alloy-json-abi", + "const-hex", + "dunce", + "heck 0.5.0", + "proc-macro2", + "quote", + "serde_json", + "syn 2.0.68", + "syn-solidity", +] + [[package]] name = "alloy-sol-type-parser" -version = "0.6.3" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d146adca22a853b5aaaa98a6c78bd9d8f1d627ca7b01d170edccf45430e9b2cb" +checksum = "baa2fbd22d353d8685bd9fee11ba2d8b5c3b1d11e56adb3265fcf1f32bfdf404" dependencies = [ - "winnow 0.5.40", + "winnow 0.6.13", ] [[package]] name = "alloy-sol-types" -version = "0.6.3" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e7c6a8c492b1d6a4f92a8fc6a13cf39473978dd7d459d7221969ce5a73d97cd" +checksum = "a49042c6d3b66a9fe6b2b5a8bf0d39fc2ae1ee0310a2a26ffedd79fb097878dd" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -365,38 +613,42 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd9773e4ec6832346171605c776315544bd06e40f803e7b5b7824b325d5442ca" dependencies = [ "alloy-json-rpc", - "base64 0.21.7", + "base64 0.22.1", "futures-util", + "futures-utils-wasm", "serde", "serde_json", "thiserror", "tokio", "tower", "url", - "wasm-bindgen-futures", ] [[package]] name = "alloy-transport-http" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff8ef947b901c0d4e97370f9fa25844cf8b63b1a58fd4011ee82342dc8a9fc6b" dependencies = [ "alloy-json-rpc", "alloy-transport", - "reqwest", + "reqwest 0.12.5", "serde_json", "tower", + "tracing", "url", ] [[package]] name = "alloy-transport-ipc" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb40ee66887a66d875a5bb5e01cee4c9a467c263ef28c865cd4b0ebf15f705af" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -404,34 +656,54 @@ dependencies = [ "bytes", "futures 0.3.30", "interprocess", - "pin-project", + "pin-project 1.1.5", + "serde", "serde_json", + "tempfile", "tokio", - "tokio-util 0.7.10", + "tokio-util 0.7.11", "tracing", ] [[package]] name = "alloy-transport-ws" -version = "0.1.0" -source = "git+https://github.com/alloy-rs/alloy#29a7886e1311062c589a6853a0f21f32901835d9" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d92049d6642a18c9849ce7659430151e7c92b51552a0cabdc038c1af4cd7308" dependencies = [ "alloy-pubsub", "alloy-transport", "futures 0.3.30", - "http", + "http 1.1.0", + "rustls 0.23.10", "serde_json", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.23.1", "tracing", "ws_stream_wasm", ] +[[package]] +name = "alloy-trie" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03704f265cbbb943b117ecb5055fd46e8f41e7dc8a58b1aed20bcd40ace38c15" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "derive_more 0.99.18", + "hashbrown 0.14.5", + "nybbles", + "serde", + "smallvec", + "tracing", +] + [[package]] name = "ammonia" -version = "3.3.0" +version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e6d1c7838db705c9b756557ee27c384ce695a1c51a6fe528784cb1c6840170" +checksum = "1ab99eae5ee58501ab236beb6f20f6ca39be615267b014899c89b2f0bc18a459" dependencies = [ "html5ever", "maplit", @@ -472,47 +744,48 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.12" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -524,61 +797,68 @@ version = "0.0.2" dependencies = [ "alloy-chains", "alloy-consensus", + "alloy-contract", "alloy-dyn-abi", + "alloy-eips", "alloy-genesis", + "alloy-json-abi", + "alloy-json-rpc", "alloy-network", "alloy-primitives", - "alloy-providers", + "alloy-provider", + "alloy-pubsub", "alloy-rlp", - "alloy-rpc-trace-types", + "alloy-rpc-client", "alloy-rpc-types", + "alloy-rpc-types-trace", + "alloy-serde", "alloy-signer", + "alloy-signer-local", "alloy-sol-types", "alloy-transport", + "alloy-transport-ipc", + "alloy-transport-ws", + "alloy-trie", "anvil-core", "anvil-rpc", "anvil-server", "async-trait", "auto_impl", - "axum", + "axum 0.7.5", "bytes", "chrono", "clap", "clap_complete", "clap_complete_fig", - "crc 3.0.1", "ctrlc", - "ethereum-forkid", - "ethers", - "ethers-core", - "ethers-solc", "eyre", "fdlimit", "flate2", + "foundry-cli", "foundry-common", "foundry-config", "foundry-evm", + "foundry-test-utils", "futures 0.3.30", - "hash-db", - "hyper", - "itertools 0.11.0", - "k256 0.13.1", - "memory-db", - "parking_lot 0.12.1", - "pretty_assertions", + "hyper 1.4.0", + "itertools 0.13.0", + "k256 0.13.3", + "parking_lot 0.12.3", "rand 0.8.5", + "revm", "serde", "serde_json", "serde_repr", + "similar-asserts", "tempfile", "thiserror", + "tikv-jemallocator", "tokio", "tower", "tracing", "tracing-subscriber", - "trie-db", "vergen", - "yansi 0.5.1", + "yansi 1.0.1", ] [[package]] @@ -588,31 +868,25 @@ dependencies = [ "alloy-consensus", "alloy-dyn-abi", "alloy-eips", - "alloy-network", "alloy-primitives", "alloy-rlp", - "alloy-rpc-trace-types", "alloy-rpc-types", - "anvil-core", + "alloy-rpc-types-trace", + "alloy-serde", + "alloy-trie", "bytes", "foundry-common", "foundry-evm", - "hash-db", - "hash256-std-hasher", - "keccak-hasher", "rand 0.8.5", - "reference-trie", "revm", "serde", "serde_json", - "triehash", ] [[package]] name = "anvil-rpc" version = "0.0.2" dependencies = [ - "rand 0.8.5", "serde", "serde_json", ] @@ -623,42 +897,44 @@ version = "0.0.2" dependencies = [ "anvil-rpc", "async-trait", - "axum", + "axum 0.7.5", "bytes", "clap", "futures 0.3.30", - "hyper", - "parity-tokio-ipc", - "parking_lot 0.12.1", - "pin-project", + "interprocess", + "parking_lot 0.12.3", + "pin-project 1.1.5", "serde", "serde_json", "thiserror", - "tokio-util 0.7.10", + "tokio-util 0.7.11", "tower-http", "tracing", ] [[package]] name = "anyhow" -version = "1.0.80" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "arbitrary" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] [[package]] name = "ariadne" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72fe02fc62033df9ba41cba57ee19acf5e742511a140c7dbc3a873e19a19a1bd" +checksum = "44055e597c674aef7cb903b2b9f6e4cba1277ed0d2d61dae7cd52d7ffa81f8e2" dependencies = [ "unicode-width", - "yansi 0.5.1", + "yansi 1.0.1", ] [[package]] @@ -813,64 +1089,80 @@ dependencies = [ ] [[package]] -name = "async-channel" -version = "2.2.0" +name = "async-compression" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" +checksum = "cd066d0b4ef8ecb03a55319dc13aa6910616d0f44008a045bb1835af830abff5" dependencies = [ - "concurrent-queue", - "event-listener 5.1.0", - "event-listener-strategy 0.5.0", + "flate2", "futures-core", + "memchr", "pin-project-lite", + "tokio", ] [[package]] name = "async-lock" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ - "event-listener 4.0.3", - "event-listener-strategy 0.4.0", + "event-listener 5.3.1", + "event-listener-strategy", "pin-project-lite", ] [[package]] name = "async-priority-channel" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c21678992e1b21bebfe2bc53ab5f5f68c106eddab31b24e0bb06e9b715a86640" +checksum = "acde96f444d31031f760c5c43dc786b97d3e1cb2ee49dd06898383fe9a999758" dependencies = [ - "event-listener 2.5.3", + "event-listener 4.0.3", ] [[package]] name = "async-recursion" -version = "1.0.5" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] -name = "async-task" -version = "4.7.0" +name = "async-stream" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] [[package]] name = "async-trait" -version = "0.1.77" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] @@ -885,61 +1177,373 @@ dependencies = [ ] [[package]] -name = "atoi" -version = "2.0.0" +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atomic" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "atomic-take" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8ab6b55fe97976e46f91ddbed8d147d966475dc29b2032757ba47e02376fbc3" + +[[package]] +name = "aurora-engine-modexp" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aef7712851e524f35fbbb74fa6599c5cd8692056a1c36f9ca0d2001b670e7e5" +dependencies = [ + "hex", + "num", +] + +[[package]] +name = "auto_impl" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "aws-config" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf6cfe2881cb1fcbba9ae946fb9a6480d3b7a714ca84c74925014a89ef3387a" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sso", + "aws-sdk-ssooidc", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "hex", + "http 0.2.12", + "hyper 0.14.29", + "ring 0.17.8", + "time", + "tokio", + "tracing", + "url", + "zeroize", +] + +[[package]] +name = "aws-credential-types" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16838e6c9e12125face1c1eff1343c75e3ff540de98ff7ebd61874a89bcfeb9" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + +[[package]] +name = "aws-runtime" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c5f920ffd1e0526ec9e70e50bf444db50b204395a0fa7016bbf9e31ea1698f" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http-body 0.4.6", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid 1.9.1", +] + +[[package]] +name = "aws-sdk-kms" +version = "1.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1e9940bfbfded74ea7172fe75815ce2b2aed4766d2375620df812b2aeab8eaa" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sso" +version = "1.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdcfae7bf8b8f14cade7579ffa8956fcee91dc23633671096b4b5de7d16f682a" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-ssooidc" +version = "1.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b30def8f02ba81276d5dbc22e7bf3bed20d62d1b175eef82680d6bdc7a6f4c" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sts" +version = "1.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0804f840ad31537d5d1a4ec48d59de5e674ad05f1db7d3def2c9acadaf1f7e60" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5df1b0fa6be58efe9d4ccc257df0a53b89cd8909e86591a13ca54817c87517be" +dependencies = [ + "aws-credential-types", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "form_urlencoded", + "hex", + "hmac", + "http 0.2.12", + "http 1.1.0", + "once_cell", + "percent-encoding", + "sha2 0.10.8", + "time", + "tracing", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62220bc6e97f946ddd51b5f1361f78996e704677afc518a4ff66b7a72ea1378c" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-http" +version = "0.60.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9cd0ae3d97daa0a2bf377a4d8e8e1362cae590c4a1aad0d40058ebca18eb91e" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http-body 0.4.6", + "once_cell", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.60.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +checksum = "4683df9469ef09468dad3473d129960119a0d3593617542b7d52086c8486f2d6" dependencies = [ - "num-traits", + "aws-smithy-types", ] [[package]] -name = "atomic" -version = "0.6.0" +name = "aws-smithy-query" +version = "0.60.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +checksum = "f2fbd61ceb3fe8a1cb7352e42689cec5335833cd9f94103a61e98f9bb61c64bb" dependencies = [ - "bytemuck", + "aws-smithy-types", + "urlencoding", ] [[package]] -name = "atomic-take" -version = "1.1.0" +name = "aws-smithy-runtime" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8ab6b55fe97976e46f91ddbed8d147d966475dc29b2032757ba47e02376fbc3" +checksum = "3df4217d39fe940066174e6238310167bf466bfbebf3be0661e53cacccde6313" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "http-body 1.0.0", + "httparse", + "hyper 0.14.29", + "hyper-rustls 0.24.2", + "once_cell", + "pin-project-lite", + "pin-utils", + "rustls 0.21.12", + "tokio", + "tracing", +] [[package]] -name = "atomic-waker" -version = "1.1.2" +name = "aws-smithy-runtime-api" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +checksum = "30819352ed0a04ecf6a2f3477e344d2d1ba33d43e0f09ad9047c12e0d923616f" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.1.0", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] [[package]] -name = "aurora-engine-modexp" -version = "1.0.0" +name = "aws-smithy-types" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfacad86e9e138fca0670949eb8ed4ffdf73a55bded8887efe0863cd1a3a6f70" +checksum = "cfe321a6b21f5d8eabd0ade9c55d3d0335f3c3157fc2b3e87f05f34b539e4df5" dependencies = [ - "hex", - "num", + "base64-simd", + "bytes", + "bytes-utils", + "http 0.2.12", + "http 1.1.0", + "http-body 0.4.6", + "http-body 1.0.0", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", ] [[package]] -name = "auto_impl" -version = "1.1.2" +name = "aws-smithy-xml" +version = "0.60.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "823b8bb275161044e2ac7a25879cb3e2480cb403e3943022c7c769c599b756aa" +checksum = "d123fbc2a4adc3c301652ba8e149bf4bc1d1725affb9784eb20c953ace06bf55" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.50", + "xmlparser", ] [[package]] -name = "autocfg" -version = "1.1.0" +name = "aws-types" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "5221b91b3e441e6675310829fd8984801b772cb1546ef6c0e54dec9f1ac13fef" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "rustc_version 0.4.0", + "tracing", +] [[package]] name = "axum" @@ -948,14 +1552,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ "async-trait", - "axum-core", - "base64 0.21.7", + "axum-core 0.3.4", "bitflags 1.3.2", "bytes", "futures-util", - "http", - "http-body", - "hyper", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.29", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper 0.1.2", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +dependencies = [ + "async-trait", + "axum-core 0.4.3", + "base64 0.21.7", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.4.0", + "hyper-util", "itoa", "matchit", "memchr", @@ -968,12 +1601,13 @@ dependencies = [ "serde_path_to_error", "serde_urlencoded", "sha1", - "sync_wrapper", + "sync_wrapper 1.0.1", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.21.0", "tower", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -985,19 +1619,40 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http", - "http-body", + "http 0.2.12", + "http-body 0.4.6", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", "mime", + "pin-project-lite", "rustversion", + "sync_wrapper 0.1.2", "tower-layer", "tower-service", + "tracing", ] [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -1034,9 +1689,19 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" -version = "0.22.0" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] [[package]] name = "base64ct" @@ -1075,7 +1740,7 @@ dependencies = [ "hex", "lazy_static", "num_cpus", - "pairing_ce 0.28.5 (registry+https://github.com/rust-lang/crates.io-index)", + "pairing_ce 0.28.6", "rand 0.4.6", "serde", "smallvec", @@ -1127,7 +1792,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] @@ -1156,9 +1821,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" dependencies = [ "arbitrary", "serde", @@ -1252,27 +1917,11 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" -[[package]] -name = "blocking" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" -dependencies = [ - "async-channel", - "async-lock", - "async-task", - "fastrand", - "futures-io", - "futures-lite", - "piper", - "tracing", -] - [[package]] name = "blst" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c94087b935a822949d3291a9989ad2b2051ea141eda0fd4e478a75f6aa3e604b" +checksum = "62dc83a094a71d43eeadd254b1ec2d24cb6a0bb6cadce00df51f0db594711a32" dependencies = [ "cc", "glob", @@ -1283,13 +1932,13 @@ dependencies = [ [[package]] name = "boojum" version = "0.2.0" -source = "git+https://github.com/nbaztec/era-boojum?branch=foundry-fix#3bc6e87455a504ebc7377c0b9735ebda613e10e0" +source = "git+https://github.com/matter-labs/era-boojum.git?branch=main#4bcb11f0610302110ae8109af01d5b652191b2f6" dependencies = [ "arrayvec 0.7.4", "bincode", "blake2 0.10.6 (registry+https://github.com/rust-lang/crates.io-index)", "const_format", - "convert_case", + "convert_case 0.6.0", "crossbeam 0.8.4", "crypto-bigint 0.5.5", "cs_derive", @@ -1300,7 +1949,6 @@ dependencies = [ "lazy_static", "num-modular", "num_cpus", - "packed_simd", "pairing_ce 0.28.5 (git+https://github.com/matter-labs/pairing.git)", "rand 0.8.5", "rayon", @@ -1313,33 +1961,33 @@ dependencies = [ [[package]] name = "borsh" -version = "1.4.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0901fc8eb0aca4c83be0106d6f2db17d86a08dfc2c25f0e84464bf381158add6" +checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" dependencies = [ "borsh-derive", - "cfg_aliases", + "cfg_aliases 0.2.1", ] [[package]] name = "borsh-derive" -version = "1.4.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51670c3aa053938b0ee3bd67c3817e471e626151131b934038e83c5bf8de48f5" +checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" dependencies = [ "once_cell", "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", "syn_derive", ] [[package]] name = "bs58" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5353f36341f7451062466f0b755b96ac3a9547e4d7f6b70d603fc721a7d7896" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" dependencies = [ "sha2 0.10.8", "tinyvec", @@ -1347,22 +1995,24 @@ dependencies = [ [[package]] name = "bstr" -version = "1.9.1" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" dependencies = [ + "lazy_static", "memchr", - "regex-automata 0.4.5", - "serde", + "regex-automata 0.1.10", ] [[package]] -name = "btoi" -version = "0.4.3" +name = "bstr" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd6407f73a9b8b6162d8a2ef999fe6afd7cc15902ebf42c5cd296addf17e0ad" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" dependencies = [ - "num-traits", + "memchr", + "regex-automata 0.4.7", + "serde", ] [[package]] @@ -1373,9 +2023,9 @@ checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" [[package]] name = "bumpalo" -version = "3.15.3" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byte-slice-cast" @@ -1407,15 +2057,15 @@ dependencies = [ [[package]] name = "bytecount" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" +checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" [[package]] name = "bytemuck" -version = "1.14.3" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ef034f05691a48569bd920a96c81b9d91bbad1ab5ac7c4616c1f6ef36cb79f" +checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" [[package]] name = "byteorder" @@ -1425,13 +2075,23 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" dependencies = [ "serde", ] +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] + [[package]] name = "bzip2" version = "0.4.4" @@ -1455,9 +2115,9 @@ dependencies = [ [[package]] name = "c-kzg" -version = "0.4.2" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94a4bc5367b6284358d2a6a6a1dc2d92ec4b86034561c3b9d3341909752fd848" +checksum = "cdf100c4cea8f207e883ff91ca886d621d8a166cb04971dfaa9bb8fd99ed95df" dependencies = [ "blst", "cc", @@ -1469,18 +2129,18 @@ dependencies = [ [[package]] name = "camino" -version = "1.1.6" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" dependencies = [ "serde", ] [[package]] name = "cargo-platform" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "694c8807f2ae16faecc43dc17d74b3eb042482789fd0eb64b39a2e04e087053f" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" dependencies = [ "serde", ] @@ -1493,7 +2153,7 @@ checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" dependencies = [ "camino", "cargo-platform", - "semver 1.0.22", + "semver 1.0.23", "serde", "serde_json", ] @@ -1506,7 +2166,7 @@ checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" dependencies = [ "camino", "cargo-platform", - "semver 1.0.22", + "semver 1.0.23", "serde", "serde_json", "thiserror", @@ -1522,14 +2182,24 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" name = "cast" version = "0.0.2" dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-contract", "alloy-dyn-abi", "alloy-json-abi", + "alloy-json-rpc", + "alloy-network", "alloy-primitives", - "alloy-providers", + "alloy-provider", "alloy-rlp", "alloy-rpc-types", + "alloy-serde", "alloy-signer", + "alloy-signer-local", + "alloy-sol-types", + "alloy-transport", "async-trait", + "aws-sdk-kms", "chrono", "clap", "clap_complete", @@ -1538,12 +2208,6 @@ dependencies = [ "const-hex", "criterion", "dunce", - "eth-keystore", - "ethers-contract", - "ethers-core", - "ethers-middleware", - "ethers-providers", - "ethers-signers", "evm-disassembler", "evmole", "eyre", @@ -1557,21 +2221,20 @@ dependencies = [ "foundry-wallets", "futures 0.3.30", "indicatif", - "itertools 0.11.0", + "itertools 0.13.0", "rand 0.8.5", "rayon", "regex", "rpassword", - "rusoto_core", - "rusoto_kms", - "semver 1.0.22", + "semver 1.0.23", "serde", "serde_json", "tempfile", + "tikv-jemallocator", "tokio", "tracing", "vergen", - "yansi 0.5.1", + "yansi 1.0.1", ] [[package]] @@ -1580,13 +2243,24 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + [[package]] name = "cc" -version = "1.0.87" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3286b845d0fccbdd15af433f61c5970e711987036cb468f437ff6badd70f4e24" +checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490" dependencies = [ + "jobserver", "libc", + "once_cell", ] [[package]] @@ -1616,6 +2290,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chisel" version = "0.0.2" @@ -1637,28 +2317,29 @@ dependencies = [ "foundry-evm", "once_cell", "regex", - "reqwest", + "reqwest 0.12.5", "revm", "rustyline", - "semver 1.0.22", + "semver 1.0.23", "serde", "serde_json", "serial_test", "solang-parser", - "strum 0.26.1", + "strum 0.26.3", + "tikv-jemallocator", "time", "tokio", "tracing", "tracing-subscriber", "vergen", - "yansi 0.5.1", + "yansi 1.0.1", ] [[package]] name = "chrono" -version = "0.4.34" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", @@ -1666,7 +2347,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.3", + "windows-targets 0.52.6", ] [[package]] @@ -1815,9 +2496,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", @@ -1826,9 +2507,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.1" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" +checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" dependencies = [ "clap_builder", "clap_derive", @@ -1836,14 +2517,14 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.1" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" +checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim 0.11.0", + "strsim 0.11.1", "terminal_size", "unicase", "unicode-width", @@ -1851,18 +2532,18 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.1" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "885e4d7d5af40bfb99ae6f9433e292feac98d452dcb3ec3d25dfe7552b77da8c" +checksum = "1d598e88f6874d4b888ed40c71efbcbf4076f1dfbae128a08a8c9e45f710605d" dependencies = [ "clap", ] [[package]] name = "clap_complete_fig" -version = "4.5.0" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54b3e65f91fabdd23cac3d57d39d5d938b4daabd070c335c006dccb866a61110" +checksum = "fb4bc503cddc1cd320736fb555d6598309ad07c2ddeaa23891a10ffb759ee612" dependencies = [ "clap", "clap_complete", @@ -1870,29 +2551,29 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.0" +version = "4.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" [[package]] name = "clearscreen" -version = "2.0.1" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f3f22f1a586604e62efd23f78218f3ccdecf7a33c4500db2d37d85a24fe994" +checksum = "2f8c93eb5f77c9050c7750e14f13ef1033a40a0aac70c6371535b6763a01438c" dependencies = [ - "nix 0.26.4", + "nix 0.28.0", "terminfo", "thiserror", "which", @@ -1917,10 +2598,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b6be4a5df2098cd811f3194f64ddb96c267606bffd9689ac7b0160097b01ad3" dependencies = [ "bs58", - "coins-core", + "coins-core 0.8.7", + "digest 0.10.7", + "hmac", + "k256 0.13.3", + "serde", + "sha2 0.10.8", + "thiserror", +] + +[[package]] +name = "coins-bip32" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c43ff7fd9ff522219058808a259e61423335767b1071d5b346de60d9219657" +dependencies = [ + "bs58", + "coins-core 0.11.1", "digest 0.10.7", - "hmac 0.12.1", - "k256 0.13.1", + "hmac", + "k256 0.13.3", "serde", "sha2 0.10.8", "thiserror", @@ -1933,8 +2630,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3db8fba409ce3dc04f7d804074039eb68b960b0829161f8e06c95fea3f122528" dependencies = [ "bitvec 1.0.1", - "coins-bip32", - "hmac 0.12.1", + "coins-bip32 0.8.7", + "hmac", + "once_cell", + "pbkdf2 0.12.2", + "rand 0.8.5", + "sha2 0.10.8", + "thiserror", +] + +[[package]] +name = "coins-bip39" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4587c0b4064da887ed39a6522f577267d57e58bdd583178cd877d721b56a2e" +dependencies = [ + "bitvec 1.0.1", + "coins-bip32 0.11.1", + "hmac", "once_cell", "pbkdf2 0.12.2", "rand 0.8.5", @@ -1962,17 +2675,36 @@ dependencies = [ "thiserror", ] +[[package]] +name = "coins-core" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3aeeec621f4daec552e9d28befd58020a78cfc364827d06a753e8bc13c6c4b" +dependencies = [ + "base64 0.21.7", + "bech32", + "bs58", + "const-hex", + "digest 0.10.7", + "generic-array", + "ripemd", + "serde", + "sha2 0.10.8", + "sha3 0.10.8", + "thiserror", +] + [[package]] name = "coins-ledger" -version = "0.10.1" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e076e6e5d9708f0b90afe2dbe5a8ba406b5c794347661e6e44618388c7e3a31" +checksum = "166ef757aa936b45f3e5d39c344047f65ef7d25a50067246a498021a816d074b" dependencies = [ "async-trait", "byteorder", "cfg-if 1.0.0", - "getrandom 0.2.12", - "hex", + "const-hex", + "getrandom 0.2.15", "hidapi-rusb", "js-sys", "log", @@ -1987,9 +2719,9 @@ dependencies = [ [[package]] name = "color-eyre" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" dependencies = [ "backtrace", "color-spantrace", @@ -2014,9 +2746,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "colored" @@ -2030,26 +2762,27 @@ dependencies = [ [[package]] name = "comfy-table" -version = "7.1.0" +version = "7.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c64043d6c7b7a4c58e39e7efccfdea7b93d885a795d0c054a69dbbf4dd52686" +checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7" dependencies = [ "crossterm", - "strum 0.25.0", - "strum_macros 0.25.3", + "strum 0.26.3", + "strum_macros 0.26.4", "unicode-width", ] [[package]] -name = "command-group" -version = "2.1.0" +name = "compact_str" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5080df6b0f0ecb76cab30808f00d937ba725cebe266a3da8cd89dff92f2a9916" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" dependencies = [ - "async-trait", - "nix 0.26.4", - "tokio", - "winapi", + "castaway", + "cfg-if 1.0.0", + "itoa", + "ryu", + "static_assertions", ] [[package]] @@ -2060,11 +2793,11 @@ checksum = "bed69047ed42e52c7e38d6421eeb8ceefb4f2a2b52eed59137f7bad7908f6800" [[package]] name = "concurrent-queue" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ - "crossbeam-utils 0.8.19", + "crossbeam-utils 0.8.20", ] [[package]] @@ -2080,11 +2813,21 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen", +] + [[package]] name = "const-hex" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbd12d49ab0eaf8193ba9175e45f56bbc2e4b27d57b8cfe62aa47942a46b9a9" +checksum = "94fb8a24a26d37e1ffd45343323dc9fe6654ceea44c12f2fcb3d7ac29e610bc6" dependencies = [ "cfg-if 1.0.0", "cpufeatures", @@ -2131,6 +2874,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -2158,18 +2910,9 @@ dependencies = [ [[package]] name = "crc" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" -dependencies = [ - "build_const", -] - -[[package]] -name = "crc" -version = "3.0.1" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" dependencies = [ "crc-catalog", ] @@ -2182,9 +2925,9 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if 1.0.0", ] @@ -2247,11 +2990,11 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" dependencies = [ - "crossbeam-channel 0.5.11", + "crossbeam-channel 0.5.13", "crossbeam-deque 0.8.5", "crossbeam-epoch 0.9.18", "crossbeam-queue 0.3.11", - "crossbeam-utils 0.8.19", + "crossbeam-utils 0.8.20", ] [[package]] @@ -2266,11 +3009,11 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.11" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176dc175b78f56c0f321911d9c8eb2b77a78a4860b9c19db83835fea1a46649b" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" dependencies = [ - "crossbeam-utils 0.8.19", + "crossbeam-utils 0.8.20", ] [[package]] @@ -2291,7 +3034,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ "crossbeam-epoch 0.9.18", - "crossbeam-utils 0.8.19", + "crossbeam-utils 0.8.20", ] [[package]] @@ -2315,7 +3058,7 @@ version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "crossbeam-utils 0.8.19", + "crossbeam-utils 0.8.20", ] [[package]] @@ -2335,7 +3078,7 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" dependencies = [ - "crossbeam-utils 0.8.19", + "crossbeam-utils 0.8.20", ] [[package]] @@ -2351,9 +3094,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crossterm" @@ -2361,11 +3104,11 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "crossterm_winapi", "libc", "mio", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "signal-hook", "signal-hook-mio", "winapi", @@ -2420,20 +3163,10 @@ dependencies = [ "typenum", ] -[[package]] -name = "crypto-mac" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" -dependencies = [ - "generic-array", - "subtle", -] - [[package]] name = "cs_derive" version = "0.1.0" -source = "git+https://github.com/nbaztec/era-boojum?branch=foundry-fix#3bc6e87455a504ebc7377c0b9735ebda613e10e0" +source = "git+https://github.com/matter-labs/era-boojum.git?branch=main#4bcb11f0610302110ae8109af01d5b652191b2f6" dependencies = [ "proc-macro-error", "proc-macro2", @@ -2452,26 +3185,25 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.4.2" +version = "3.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b467862cc8610ca6fc9a1532d7777cee0804e678ab45410897b9396495994a0b" +checksum = "672465ae37dc1bc6380a6547a8883d5dd397b0f1faaad4f265726cc7042a5345" dependencies = [ - "nix 0.27.1", + "nix 0.28.0", "windows-sys 0.52.0", ] [[package]] name = "curve25519-dalek" -version = "4.1.2" +version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if 1.0.0", "cpufeatures", "curve25519-dalek-derive", "digest 0.10.7", "fiat-crypto", - "platforms", "rustc_version 0.4.0", "subtle", "zeroize", @@ -2485,14 +3217,14 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] name = "darling" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" dependencies = [ "darling_core", "darling_macro", @@ -2500,27 +3232,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim 0.10.0", - "syn 2.0.50", + "strsim 0.11.1", + "syn 2.0.68", ] [[package]] name = "darling_macro" -version = "0.20.8" +version = "0.20.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" dependencies = [ "darling_core", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] @@ -2530,17 +3262,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if 1.0.0", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "lock_api", "once_cell", - "parking_lot_core 0.9.9", + "parking_lot_core 0.9.10", ] [[package]] name = "data-encoding" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + +[[package]] +name = "dbus" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bb21987b9fb1613058ba3843121dd18b163b254d8a6e797e144cbac14d96d1b" +dependencies = [ + "libc", + "libdbus-sys", + "winapi", +] [[package]] name = "debugid" @@ -2549,7 +3292,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" dependencies = [ "serde", - "uuid 1.7.0", + "uuid 1.9.1", ] [[package]] @@ -2564,9 +3307,9 @@ dependencies = [ [[package]] name = "der" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "pem-rfc7468", @@ -2601,7 +3344,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] @@ -2622,7 +3365,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] @@ -2632,20 +3375,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" dependencies = [ "derive_builder_core", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] name = "derive_more" -version = "0.99.17" +version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ - "convert_case", + "convert_case 0.4.0", "proc-macro2", "quote", "rustc_version 0.4.0", - "syn 1.0.109", + "syn 2.0.68", ] [[package]] @@ -2665,7 +3408,7 @@ checksum = "2bba3e9872d7c58ce7ef0fcf1844fcc3e23ef2a58377b50df35dd98e42a5726e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", "unicode-xid", ] @@ -2677,15 +3420,11 @@ checksum = "658bce805d770f407bc62102fca7c2c64ceef2fbcb2b8bd19d2765ce093980de" dependencies = [ "console", "shell-words", + "tempfile", "thiserror", + "zeroize", ] -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - [[package]] name = "digest" version = "0.9.0" @@ -2769,12 +3508,35 @@ dependencies = [ "winapi", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "doctest-file" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562" + [[package]] name = "dotenvy" version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + [[package]] name = "dtoa" version = "1.0.9" @@ -2811,7 +3573,7 @@ version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ - "der 0.7.8", + "der 0.7.9", "digest 0.10.7", "elliptic-curve 0.13.8", "rfc6979 0.4.0", @@ -2846,9 +3608,9 @@ dependencies = [ [[package]] name = "either" -version = "1.10.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" dependencies = [ "serde", ] @@ -2914,11 +3676,24 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "email-address-parser" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe19a4967eca30062be4abaf813d929ba48b3bfb21830367f7e1baae37f213a" +dependencies = [ + "console_error_panic_hook", + "pest", + "pest_derive", + "quick-xml", + "wasm-bindgen", +] + [[package]] name = "ena" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1" +checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" dependencies = [ "log", ] @@ -2931,9 +3706,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if 1.0.0", ] @@ -2953,7 +3728,7 @@ dependencies = [ "base64 0.21.7", "bytes", "hex", - "k256 0.13.1", + "k256 0.13.3", "log", "rand 0.8.5", "rlp", @@ -2970,7 +3745,7 @@ checksum = "6fd000fd6988e73bbe993ea3db9b1aa64906ab88766d654973924340c8cddb42" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] @@ -2998,9 +3773,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c012a26a7f605efc424dd53697843a72be7dc86ad2d01f7814337794a12231d" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" dependencies = [ "anstream", "anstyle", @@ -3038,7 +3813,7 @@ dependencies = [ "eyre", "futures 0.3.30", "hex", - "indexmap 2.2.3", + "indexmap 2.2.6", "itertools 0.10.5", "jsonrpc-core", "jsonrpc-core-client", @@ -3048,7 +3823,7 @@ dependencies = [ "multivm", "once_cell", "openssl-sys", - "reqwest", + "reqwest 0.11.27", "rustc-hash", "serde", "serde_json", @@ -3068,9 +3843,9 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -3116,7 +3891,7 @@ dependencies = [ "ctr", "digest 0.10.7", "hex", - "hmac 0.12.1", + "hmac", "pbkdf2 0.11.0", "rand 0.8.5", "scrypt", @@ -3188,19 +3963,6 @@ dependencies = [ "tiny-keccak 2.0.2", ] -[[package]] -name = "ethereum-forkid" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcace6f36a8fd79d06c535444b42c8966e10733165fca6dec3542abfc3e00318" -dependencies = [ - "crc 1.8.1", - "fastrlp", - "maplit", - "primitive-types 0.12.2", - "thiserror", -] - [[package]] name = "ethereum-types" version = "0.12.1" @@ -3233,9 +3995,9 @@ dependencies = [ [[package]] name = "ethereum_ssz" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e61ffea29f26e8249d35128a82ec8d3bd4fbc80179ea5f5e5e3daafef6a80fcb" +checksum = "7d3627f83d8b87b432a5fad9934b4565260722a141a2c40f371f8080adec9425" dependencies = [ "ethereum-types 0.14.1", "itertools 0.10.5", @@ -3244,8 +4006,9 @@ dependencies = [ [[package]] name = "ethers" -version = "2.0.13" -source = "git+https://github.com/gakonst/ethers-rs?rev=73e5de211c32a1f5777eb5194205bdb31f6a3502#73e5de211c32a1f5777eb5194205bdb31f6a3502" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "816841ea989f0c69e459af1cf23a6b0033b19a55424a1ea3a30099becdb8dec0" dependencies = [ "ethers-addressbook", "ethers-contract", @@ -3259,8 +4022,9 @@ dependencies = [ [[package]] name = "ethers-addressbook" -version = "2.0.13" -source = "git+https://github.com/gakonst/ethers-rs?rev=73e5de211c32a1f5777eb5194205bdb31f6a3502#73e5de211c32a1f5777eb5194205bdb31f6a3502" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5495afd16b4faa556c3bba1f21b98b4983e53c1755022377051a975c3b021759" dependencies = [ "ethers-core", "once_cell", @@ -3270,8 +4034,9 @@ dependencies = [ [[package]] name = "ethers-contract" -version = "2.0.13" -source = "git+https://github.com/gakonst/ethers-rs?rev=73e5de211c32a1f5777eb5194205bdb31f6a3502#73e5de211c32a1f5777eb5194205bdb31f6a3502" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fceafa3578c836eeb874af87abacfb041f92b4da0a78a5edd042564b8ecdaaa" dependencies = [ "const-hex", "ethers-contract-abigen", @@ -3280,7 +4045,7 @@ dependencies = [ "ethers-providers", "futures-util", "once_cell", - "pin-project", + "pin-project 1.1.5", "serde", "serde_json", "thiserror", @@ -3288,8 +4053,9 @@ dependencies = [ [[package]] name = "ethers-contract-abigen" -version = "2.0.13" -source = "git+https://github.com/gakonst/ethers-rs?rev=73e5de211c32a1f5777eb5194205bdb31f6a3502#73e5de211c32a1f5777eb5194205bdb31f6a3502" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04ba01fbc2331a38c429eb95d4a570166781f14290ef9fdb144278a90b5a739b" dependencies = [ "Inflector", "const-hex", @@ -3301,18 +4067,19 @@ dependencies = [ "proc-macro2", "quote", "regex", - "reqwest", + "reqwest 0.11.27", "serde", "serde_json", - "syn 2.0.50", - "toml 0.8.10", + "syn 2.0.68", + "toml 0.8.14", "walkdir", ] [[package]] name = "ethers-contract-derive" -version = "2.0.13" -source = "git+https://github.com/gakonst/ethers-rs?rev=73e5de211c32a1f5777eb5194205bdb31f6a3502#73e5de211c32a1f5777eb5194205bdb31f6a3502" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87689dcabc0051cde10caaade298f9e9093d65f6125c14575db3fd8c669a168f" dependencies = [ "Inflector", "const-hex", @@ -3321,13 +4088,14 @@ dependencies = [ "proc-macro2", "quote", "serde_json", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] name = "ethers-core" -version = "2.0.13" -source = "git+https://github.com/gakonst/ethers-rs?rev=73e5de211c32a1f5777eb5194205bdb31f6a3502#73e5de211c32a1f5777eb5194205bdb31f6a3502" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d80cc6ad30b14a48ab786523af33b37f28a8623fc06afd55324816ef18fb1f" dependencies = [ "arrayvec 0.7.4", "bytes", @@ -3337,7 +4105,7 @@ dependencies = [ "elliptic-curve 0.13.8", "ethabi 18.0.0", "generic-array", - "k256 0.13.1", + "k256 0.13.3", "num_enum 0.7.2", "once_cell", "open-fastrlp", @@ -3345,8 +4113,8 @@ dependencies = [ "rlp", "serde", "serde_json", - "strum 0.26.1", - "syn 2.0.50", + "strum 0.26.3", + "syn 2.0.68", "tempfile", "thiserror", "tiny-keccak 2.0.2", @@ -3355,13 +4123,14 @@ dependencies = [ [[package]] name = "ethers-etherscan" -version = "2.0.13" -source = "git+https://github.com/gakonst/ethers-rs?rev=73e5de211c32a1f5777eb5194205bdb31f6a3502#73e5de211c32a1f5777eb5194205bdb31f6a3502" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79e5973c26d4baf0ce55520bd732314328cabe53193286671b47144145b9649" dependencies = [ "chrono", "ethers-core", - "reqwest", - "semver 1.0.22", + "reqwest 0.11.27", + "semver 1.0.23", "serde", "serde_json", "thiserror", @@ -3370,8 +4139,9 @@ dependencies = [ [[package]] name = "ethers-middleware" -version = "2.0.13" -source = "git+https://github.com/gakonst/ethers-rs?rev=73e5de211c32a1f5777eb5194205bdb31f6a3502#73e5de211c32a1f5777eb5194205bdb31f6a3502" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f9fdf09aec667c099909d91908d5eaf9be1bd0e2500ba4172c1d28bfaa43de" dependencies = [ "async-trait", "auto_impl", @@ -3384,7 +4154,7 @@ dependencies = [ "futures-locks", "futures-util", "instant", - "reqwest", + "reqwest 0.11.27", "serde", "serde_json", "thiserror", @@ -3396,8 +4166,9 @@ dependencies = [ [[package]] name = "ethers-providers" -version = "2.0.13" -source = "git+https://github.com/gakonst/ethers-rs?rev=73e5de211c32a1f5777eb5194205bdb31f6a3502#73e5de211c32a1f5777eb5194205bdb31f6a3502" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6434c9a33891f1effc9c75472e12666db2fa5a0fec4b29af6221680a6fe83ab2" dependencies = [ "async-trait", "auto_impl", @@ -3406,89 +4177,73 @@ dependencies = [ "const-hex", "enr", "ethers-core", - "futures-channel", "futures-core", "futures-timer", "futures-util", "hashers", - "http", + "http 0.2.12", "instant", - "jsonwebtoken", + "jsonwebtoken 8.3.0", "once_cell", - "pin-project", - "reqwest", + "pin-project 1.1.5", + "reqwest 0.11.27", "serde", "serde_json", "thiserror", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.20.1", "tracing", "tracing-futures", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "winapi", "ws_stream_wasm", ] [[package]] name = "ethers-signers" -version = "2.0.13" -source = "git+https://github.com/gakonst/ethers-rs?rev=73e5de211c32a1f5777eb5194205bdb31f6a3502#73e5de211c32a1f5777eb5194205bdb31f6a3502" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "228875491c782ad851773b652dd8ecac62cda8571d3bc32a5853644dd26766c2" dependencies = [ "async-trait", - "coins-bip32", - "coins-bip39", - "coins-ledger", + "coins-bip32 0.8.7", + "coins-bip39 0.8.7", "const-hex", "elliptic-curve 0.13.8", "eth-keystore", "ethers-core", - "futures-executor", - "futures-util", - "home", - "protobuf", "rand 0.8.5", - "rusoto_core", - "rusoto_kms", - "semver 1.0.22", "sha2 0.10.8", - "spki 0.7.3", "thiserror", "tracing", - "trezor-client", ] [[package]] name = "ethers-solc" -version = "2.0.13" -source = "git+https://github.com/gakonst/ethers-rs?rev=73e5de211c32a1f5777eb5194205bdb31f6a3502#73e5de211c32a1f5777eb5194205bdb31f6a3502" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66244a771d9163282646dbeffe0e6eca4dda4146b6498644e678ac6089b11edd" dependencies = [ "cfg-if 1.0.0", "const-hex", "dirs 5.0.1", "dunce", "ethers-core", - "fs_extra", - "futures-util", "glob", "home", - "md-5 0.10.6", + "md-5", "num_cpus", "once_cell", "path-slash", - "rand 0.8.5", "rayon", "regex", - "semver 1.0.22", + "semver 1.0.23", "serde", "serde_json", - "sha2 0.10.8", "solang-parser", - "svm-rs", - "svm-rs-builds 0.2.3", - "tempfile", + "svm-rs 0.3.5", "thiserror", "tiny-keccak 2.0.2", "tokio", @@ -3516,9 +4271,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.1.0" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7ad6fd685ce13acd6d9541a30f6db6567a7a24c9ffd4ba2955d29e3f22c8b27" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" dependencies = [ "concurrent-queue", "parking", @@ -3527,29 +4282,19 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" -dependencies = [ - "event-listener 4.0.3", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ - "event-listener 5.1.0", + "event-listener 5.3.1", "pin-project-lite", ] [[package]] name = "evm-disassembler" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f6fc9c732a3210153e6aa26746f0abd8773dbf204c64ae3e824309b32b384c5" +checksum = "ded685d9f07315ff689ba56e7d84e6f1e782db19b531a46c34061a733bba7258" dependencies = [ "eyre", "hex", @@ -3557,9 +4302,9 @@ dependencies = [ [[package]] name = "evmole" -version = "0.3.2" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ef57dfcf13fc3486c3a760427d88ab0d97cb911f7104fe5a132f2b934d0fe29" +checksum = "ce047d502545e3a726948bb8a532b8ea1446238f829e01448c802b2f10edbe70" dependencies = [ "ruint", ] @@ -3574,11 +4319,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "faster-hex" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183" + [[package]] name = "fastrand" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fastrlp" @@ -3589,19 +4340,6 @@ dependencies = [ "arrayvec 0.7.4", "auto_impl", "bytes", - "fastrlp-derive", -] - -[[package]] -name = "fastrlp-derive" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4caf31122bfc780557fdcd80897e95f12cc4d7217f8ac6b9d150df828a38ee8" -dependencies = [ - "bytes", - "proc-macro2", - "quote", - "syn 2.0.50", ] [[package]] @@ -3686,22 +4424,22 @@ dependencies = [ [[package]] name = "fiat-crypto" -version = "0.2.6" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1676f435fc1dadde4d03e43f5d62b259e1ce5f40bd4ffb21db2b42ebe59c1382" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "figment" -version = "0.10.14" +version = "0.10.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b6e5bc7bd59d60d0d45a6ccab6cf0f4ce28698fb4e81e750ddf229c9b824026" +checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" dependencies = [ "atomic", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "pear", "serde", "tempfile", - "toml 0.8.10", + "toml 0.8.14", "uncased", "version_check", ] @@ -3730,12 +4468,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "finl_unicode" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" - [[package]] name = "firestorm" version = "0.5.1" @@ -3775,9 +4507,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", @@ -3819,116 +4551,233 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" name = "forge" version = "0.0.2" dependencies = [ + "alloy-chains", + "alloy-consensus", "alloy-dyn-abi", "alloy-json-abi", + "alloy-network", "alloy-primitives", + "alloy-provider", "alloy-rpc-types", + "alloy-serde", + "alloy-signer", + "alloy-signer-local", + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "alloy-transport", "anvil", "async-trait", - "axum", + "axum 0.7.5", "clap", "clap_complete", "clap_complete_fig", + "clearscreen", "comfy-table", "const-hex", - "criterion", + "criterion", + "dialoguer", + "dunce", + "ethers-contract-abigen", + "evm-disassembler", + "eyre", + "forge-doc", + "forge-fmt", + "forge-script", + "forge-sol-macro-gen", + "forge-verify", + "foundry-block-explorers", + "foundry-cli", + "foundry-common", + "foundry-compilers", + "foundry-config", + "foundry-debugger", + "foundry-evm", + "foundry-linking", + "foundry-test-utils", + "foundry-wallets", + "foundry-zksync-compiler", + "foundry-zksync-core", + "futures 0.3.30", + "globset", + "humantime-serde", + "hyper 1.4.0", + "indicatif", + "itertools 0.13.0", + "mockall", + "once_cell", + "opener", + "parking_lot 0.12.3", + "paste", + "path-slash", + "proptest", + "rayon", + "regex", + "reqwest 0.12.5", + "revm-inspectors", + "rustc-hash", + "semver 1.0.23", + "serde", + "serde_json", + "similar", + "similar-asserts", + "solang-parser", + "soldeer", + "strum 0.26.3", + "svm-rs 0.5.4", + "tempfile", + "thiserror", + "tikv-jemallocator", + "tokio", + "toml 0.8.14", + "toml_edit 0.22.14", + "tower-http", + "tracing", + "tracing-subscriber", + "vergen", + "watchexec", + "watchexec-events", + "watchexec-signals", + "yansi 1.0.1", + "zksync-web3-rs", + "zksync_types", +] + +[[package]] +name = "forge-doc" +version = "0.0.2" +dependencies = [ + "alloy-primitives", + "auto_impl", + "derive_more 0.99.18", + "eyre", + "forge-fmt", + "foundry-compilers", + "foundry-config", + "itertools 0.13.0", + "mdbook", + "once_cell", + "rayon", + "regex", + "serde", + "serde_json", + "solang-parser", + "thiserror", + "toml 0.8.14", + "tracing", +] + +[[package]] +name = "forge-fmt" +version = "0.0.2" +dependencies = [ + "alloy-primitives", + "ariadne", + "foundry-config", + "itertools 0.13.0", + "similar-asserts", + "solang-parser", + "thiserror", + "toml 0.8.14", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "forge-script" +version = "0.0.2" +dependencies = [ + "alloy-chains", + "alloy-dyn-abi", + "alloy-eips", + "alloy-json-abi", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types", + "alloy-serde", + "alloy-signer", + "alloy-transport", + "async-recursion", + "clap", + "const-hex", "dialoguer", "dunce", - "ethers-contract", - "ethers-core", - "ethers-middleware", - "ethers-providers", - "ethers-signers", - "evm-disassembler", "eyre", - "forge-doc", - "forge-fmt", - "foundry-block-explorers", + "forge-verify", + "foundry-cheatcodes", "foundry-cli", "foundry-common", "foundry-compilers", "foundry-config", "foundry-debugger", "foundry-evm", - "foundry-test-utils", + "foundry-linking", "foundry-wallets", "foundry-zksync-compiler", "foundry-zksync-core", "futures 0.3.30", - "globset", - "hyper", "indicatif", - "itertools 0.11.0", - "once_cell", - "opener", - "parking_lot 0.12.1", - "paste", - "path-slash", - "pretty_assertions", - "proptest", - "rayon", - "regex", - "reqwest", + "itertools 0.13.0", + "parking_lot 0.12.3", "revm-inspectors", - "semver 1.0.22", + "semver 1.0.23", "serde", "serde_json", - "similar", - "solang-parser", - "strum 0.26.1", - "svm-rs", "tempfile", - "thiserror", - "tokio", - "tower-http", "tracing", - "tracing-subscriber", - "vergen", - "watchexec", - "yansi 0.5.1", + "yansi 1.0.1", "zksync-web3-rs", - "zksync_types", ] [[package]] -name = "forge-doc" +name = "forge-sol-macro-gen" version = "0.0.2" dependencies = [ - "alloy-primitives", - "auto_impl", - "derive_more 0.99.17", + "alloy-json-abi", + "alloy-sol-macro-expander", + "alloy-sol-macro-input", "eyre", - "forge-fmt", "foundry-common", - "foundry-compilers", - "foundry-config", - "itertools 0.11.0", - "mdbook", - "once_cell", - "rayon", - "regex", - "serde", + "prettyplease", + "proc-macro2", + "quote", "serde_json", - "solang-parser", - "thiserror", - "toml 0.8.10", - "tracing", + "syn 2.0.68", ] [[package]] -name = "forge-fmt" +name = "forge-verify" version = "0.0.2" dependencies = [ + "alloy-json-abi", "alloy-primitives", - "ariadne", + "alloy-provider", + "alloy-rpc-types", + "async-trait", + "clap", + "const-hex", + "eyre", + "foundry-block-explorers", + "foundry-cli", + "foundry-common", + "foundry-compilers", "foundry-config", - "itertools 0.11.0", - "pretty_assertions", - "solang-parser", - "thiserror", - "toml 0.8.10", + "foundry-evm", + "foundry-test-utils", + "foundry-zksync-compiler", + "futures 0.3.30", + "itertools 0.13.0", + "once_cell", + "regex", + "reqwest 0.12.5", + "revm-primitives", + "semver 1.0.23", + "serde", + "serde_json", + "tempfile", + "tokio", "tracing", - "tracing-subscriber", + "yansi 1.0.1", ] [[package]] @@ -3942,15 +4791,15 @@ dependencies = [ [[package]] name = "foundry-block-explorers" -version = "0.2.3" -source = "git+https://github.com/Moonsong-Labs/block-explorers?branch=zksync-v0.2.3#b442f11077f30584cda7621311ee36a322135b86" +version = "0.4.1" +source = "git+https://github.com/Moonsong-Labs/block-explorers?branch=zksync-v0.4.1#9dd59fc58a1a200355c2554d88c06386a9ce2db1" dependencies = [ "alloy-chains", "alloy-json-abi", "alloy-primitives", "foundry-compilers", - "reqwest", - "semver 1.0.22", + "reqwest 0.12.5", + "semver 1.0.23", "serde", "serde_json", "thiserror", @@ -3965,12 +4814,14 @@ dependencies = [ "alloy-genesis", "alloy-json-abi", "alloy-primitives", - "alloy-providers", + "alloy-provider", "alloy-rpc-types", "alloy-signer", + "alloy-signer-local", "alloy-sol-types", - "base64 0.21.7", + "base64 0.22.1", "const-hex", + "dialoguer", "eyre", "foundry-cheatcodes-common", "foundry-cheatcodes-spec", @@ -3981,14 +4832,18 @@ dependencies = [ "foundry-wallets", "foundry-zksync-compiler", "foundry-zksync-core", - "itertools 0.11.0", + "itertools 0.13.0", "jsonpath_lib", - "k256 0.13.1", + "k256 0.13.3", "p256", - "parking_lot 0.12.1", + "parking_lot 0.12.3", + "rand 0.8.5", "revm", + "rustc-hash", + "semver 1.0.23", "serde_json", "thiserror", + "toml 0.8.14", "tracing", "walkdir", "zksync_types", @@ -4017,14 +4872,16 @@ dependencies = [ name = "foundry-cli" version = "0.0.2" dependencies = [ + "alloy-chains", "alloy-dyn-abi", "alloy-json-abi", "alloy-primitives", + "alloy-provider", + "alloy-transport", "clap", "color-eyre", + "const-hex", "dotenvy", - "ethers-core", - "ethers-providers", "eyre", "forge-fmt", "foundry-common", @@ -4033,111 +4890,199 @@ dependencies = [ "foundry-debugger", "foundry-evm", "foundry-wallets", + "futures 0.3.30", "indicatif", "once_cell", "regex", "serde", "strsim 0.10.0", - "strum 0.26.1", + "strum 0.26.3", "tempfile", "tokio", "tracing", "tracing-error", "tracing-subscriber", - "yansi 0.5.1", + "yansi 1.0.1", ] [[package]] name = "foundry-common" version = "0.0.2" dependencies = [ + "alloy-consensus", + "alloy-contract", "alloy-dyn-abi", "alloy-json-abi", "alloy-json-rpc", "alloy-primitives", - "alloy-providers", + "alloy-provider", "alloy-pubsub", "alloy-rpc-client", "alloy-rpc-types", - "alloy-signer", + "alloy-rpc-types-engine", + "alloy-serde", "alloy-sol-types", "alloy-transport", "alloy-transport-http", "alloy-transport-ipc", "alloy-transport-ws", "async-trait", + "chrono", "clap", "comfy-table", - "const-hex", - "derive_more 0.99.17", + "derive_more 0.99.18", "dunce", - "ethers-core", - "ethers-middleware", - "ethers-providers", - "ethers-signers", "eyre", "foundry-block-explorers", "foundry-compilers", "foundry-config", + "foundry-linking", "foundry-macros", "foundry-zksync-compiler", - "glob", "globset", - "itertools 0.11.0", + "itertools 0.13.0", + "num-format", "once_cell", - "pretty_assertions", - "rand 0.8.5", - "reqwest", + "reqwest 0.12.5", "revm", - "semver 1.0.22", + "rustc-hash", + "semver 1.0.23", "serde", "serde_json", - "tempfile", + "similar-asserts", "thiserror", "tokio", "tower", "tracing", "url", "walkdir", - "yansi 0.5.1", + "yansi 1.0.1", ] [[package]] name = "foundry-compilers" -version = "0.3.9" -source = "git+https://github.com/Moonsong-Labs/compilers?branch=zksync-v0.3.9#0123f4007edd6fbfc74199e9fe85fd885035478f" +version = "0.8.0" +source = "git+https://github.com/Moonsong-Labs/compilers?branch=zksync-v0.8.0#2aa7b80c17e7aad4e1b1f0d74bf80e5bd1d61a3e" dependencies = [ "alloy-json-abi", "alloy-primitives", - "cfg-if 1.0.0", + "auto_impl", + "derivative", "dirs 5.0.1", - "dunce", + "dyn-clone", + "foundry-compilers-artifacts", + "foundry-compilers-core", "fs_extra", "futures-util", - "globset", "home", - "itertools 0.12.1", - "md-5 0.10.6", - "memmap2 0.9.4", + "itertools 0.13.0", + "md-5", "once_cell", "path-slash", "rand 0.8.5", "rayon", - "regex", - "reqwest", - "semver 1.0.22", + "reqwest 0.12.5", + "semver 1.0.23", "serde", "serde_json", "sha2 0.10.8", "solang-parser", - "svm-rs", - "svm-rs-builds 0.3.5", + "svm-rs 0.5.4", + "svm-rs-builds", "tempfile", "thiserror", "tokio", "tracing", + "winnow 0.6.13", + "yansi 1.0.1", +] + +[[package]] +name = "foundry-compilers-artifacts" +version = "0.8.0" +source = "git+https://github.com/Moonsong-Labs/compilers?branch=zksync-v0.8.0#2aa7b80c17e7aad4e1b1f0d74bf80e5bd1d61a3e" +dependencies = [ + "foundry-compilers-artifacts-solc", + "foundry-compilers-artifacts-vyper", + "foundry-compilers-artifacts-zksolc", +] + +[[package]] +name = "foundry-compilers-artifacts-solc" +version = "0.8.0" +source = "git+https://github.com/Moonsong-Labs/compilers?branch=zksync-v0.8.0#2aa7b80c17e7aad4e1b1f0d74bf80e5bd1d61a3e" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "foundry-compilers-core", + "futures-util", + "md-5", + "path-slash", + "rayon", + "semver 1.0.23", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", + "walkdir", + "yansi 1.0.1", +] + +[[package]] +name = "foundry-compilers-artifacts-vyper" +version = "0.8.0" +source = "git+https://github.com/Moonsong-Labs/compilers?branch=zksync-v0.8.0#2aa7b80c17e7aad4e1b1f0d74bf80e5bd1d61a3e" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "foundry-compilers-artifacts-solc", + "path-slash", + "serde", +] + +[[package]] +name = "foundry-compilers-artifacts-zksolc" +version = "0.8.0" +source = "git+https://github.com/Moonsong-Labs/compilers?branch=zksync-v0.8.0#2aa7b80c17e7aad4e1b1f0d74bf80e5bd1d61a3e" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "foundry-compilers-artifacts-solc", + "foundry-compilers-core", + "md-5", + "path-slash", + "rayon", + "semver 1.0.23", + "serde", + "serde_json", + "thiserror", + "tracing", + "walkdir", + "yansi 1.0.1", +] + +[[package]] +name = "foundry-compilers-core" +version = "0.8.0" +source = "git+https://github.com/Moonsong-Labs/compilers?branch=zksync-v0.8.0#2aa7b80c17e7aad4e1b1f0d74bf80e5bd1d61a3e" +dependencies = [ + "alloy-primitives", + "cfg-if 1.0.0", + "dunce", + "fs_extra", + "memmap2", + "once_cell", + "path-slash", + "regex", + "semver 1.0.23", + "serde", + "serde_json", + "svm-rs 0.5.4", + "tempfile", + "thiserror", + "tokio", "walkdir", - "yansi 0.5.1", ] [[package]] @@ -4153,22 +5098,24 @@ dependencies = [ "figment", "foundry-block-explorers", "foundry-compilers", + "glob", "globset", "number_prefix", "once_cell", "path-slash", - "pretty_assertions", "regex", - "reqwest", + "reqwest 0.12.5", "revm-primitives", - "semver 1.0.22", + "semver 1.0.23", "serde", "serde_json", "serde_regex", + "similar-asserts", + "solang-parser", "tempfile", "thiserror", - "toml 0.8.10", - "toml_edit 0.21.1", + "toml 0.8.14", + "toml_edit 0.22.14", "tracing", "walkdir", ] @@ -4197,9 +5144,8 @@ dependencies = [ "alloy-dyn-abi", "alloy-json-abi", "alloy-primitives", - "alloy-rpc-types", "alloy-sol-types", - "const-hex", + "arrayvec 0.7.4", "eyre", "foundry-cheatcodes", "foundry-common", @@ -4210,12 +5156,10 @@ dependencies = [ "foundry-evm-fuzz", "foundry-evm-traces", "foundry-zksync-core", - "hashbrown 0.14.3", - "itertools 0.11.0", - "parking_lot 0.12.1", + "indicatif", + "itertools 0.13.0", + "parking_lot 0.12.3", "proptest", - "rand 0.8.5", - "rayon", "revm", "revm-inspectors", "thiserror", @@ -4230,22 +5174,29 @@ dependencies = [ "alloy-genesis", "alloy-json-abi", "alloy-primitives", - "alloy-providers", + "alloy-provider", "alloy-rpc-types", + "alloy-serde", "alloy-sol-types", "alloy-transport", + "arrayvec 0.7.4", + "auto_impl", "const-hex", + "derive_more 0.99.18", "eyre", "foundry-cheatcodes-spec", "foundry-common", - "foundry-compilers", "foundry-config", + "foundry-macros", + "foundry-test-utils", "foundry-zksync-core", "futures 0.3.30", - "itertools 0.11.0", - "parking_lot 0.12.1", + "itertools 0.13.0", + "once_cell", + "parking_lot 0.12.3", "revm", "revm-inspectors", + "rustc-hash", "serde", "serde_json", "thiserror", @@ -4263,8 +5214,10 @@ dependencies = [ "foundry-common", "foundry-compilers", "foundry-evm-core", + "rayon", "revm", - "semver 1.0.22", + "rustc-hash", + "semver 1.0.23", "tracing", ] @@ -4272,10 +5225,10 @@ dependencies = [ name = "foundry-evm-fuzz" version = "0.0.2" dependencies = [ + "ahash 0.8.11", "alloy-dyn-abi", "alloy-json-abi", "alloy-primitives", - "arbitrary", "eyre", "foundry-common", "foundry-compilers", @@ -4284,9 +5237,9 @@ dependencies = [ "foundry-evm-coverage", "foundry-evm-traces", "foundry-zksync-core", - "hashbrown 0.14.3", - "itertools 0.11.0", - "parking_lot 0.12.1", + "indexmap 2.2.6", + "itertools 0.13.0", + "parking_lot 0.12.3", "proptest", "rand 0.8.5", "revm", @@ -4312,15 +5265,25 @@ dependencies = [ "foundry-config", "foundry-evm-core", "futures 0.3.30", - "hashbrown 0.14.3", - "itertools 0.11.0", + "itertools 0.13.0", "once_cell", "revm-inspectors", + "rustc-hash", "serde", "tempfile", "tokio", "tracing", - "yansi 0.5.1", + "yansi 1.0.1", +] + +[[package]] +name = "foundry-linking" +version = "0.0.2" +dependencies = [ + "alloy-primitives", + "foundry-compilers", + "semver 1.0.23", + "thiserror", ] [[package]] @@ -4330,7 +5293,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] @@ -4338,18 +5301,18 @@ name = "foundry-test-utils" version = "0.0.2" dependencies = [ "alloy-primitives", - "ethers-core", - "ethers-providers", + "alloy-provider", "eyre", "fd-lock 4.0.2", "foundry-common", "foundry-compilers", "foundry-config", "once_cell", - "parking_lot 0.12.1", - "pretty_assertions", + "parking_lot 0.12.3", + "rand 0.8.5", "regex", "serde_json", + "similar-asserts", "tracing", "tracing-subscriber", "walkdir", @@ -4359,21 +5322,27 @@ dependencies = [ name = "foundry-wallets" version = "0.0.2" dependencies = [ + "alloy-consensus", + "alloy-dyn-abi", + "alloy-network", "alloy-primitives", + "alloy-signer", + "alloy-signer-aws", + "alloy-signer-gcp", + "alloy-signer-ledger", + "alloy-signer-local", + "alloy-signer-trezor", + "alloy-sol-types", "async-trait", + "aws-config", + "aws-sdk-kms", "clap", "const-hex", "derive_builder", - "ethers-core", - "ethers-providers", - "ethers-signers", "eyre", - "foundry-common", "foundry-config", - "itertools 0.11.0", + "gcloud-sdk", "rpassword", - "rusoto_core", - "rusoto_kms", "serde", "thiserror", "tokio", @@ -4387,6 +5356,8 @@ dependencies = [ "alloy-primitives", "eyre", "foundry-compilers", + "foundry-config", + "semver 1.0.23", "serde", "serde_json", "tracing", @@ -4397,8 +5368,16 @@ dependencies = [ name = "foundry-zksync-core" version = "0.0.2" dependencies = [ + "alloy-consensus", + "alloy-dyn-abi", + "alloy-network", "alloy-primitives", + "alloy-provider", + "alloy-rpc-types", + "alloy-serde", + "alloy-signer", "alloy-sol-types", + "alloy-transport", "ansi_term", "const-hex", "era_test_node", @@ -4406,11 +5385,12 @@ dependencies = [ "foundry-cheatcodes-common", "foundry-common", "foundry-zksync-compiler", - "itertools 0.11.0", + "itertools 0.13.0", "lazy_static", "multivm", "once_cell", "revm", + "serde_json", "tracing", "url", "zksync-web3-rs", @@ -4421,6 +5401,12 @@ dependencies = [ "zksync_utils", ] +[[package]] +name = "fragile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" + [[package]] name = "fs2" version = "0.4.3" @@ -4431,6 +5417,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "fs4" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7e180ac76c23b45e767bd7ae9579bc0bb458618c4bc71835926e098e61d15f8" +dependencies = [ + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "fs_extra" version = "1.3.0" @@ -4531,7 +5527,7 @@ checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" dependencies = [ "futures-core", "lock_api", - "parking_lot 0.12.1", + "parking_lot 0.12.3", ] [[package]] @@ -4540,16 +5536,6 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" -[[package]] -name = "futures-lite" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba" -dependencies = [ - "futures-core", - "pin-project-lite", -] - [[package]] name = "futures-locks" version = "0.7.1" @@ -4568,7 +5554,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] @@ -4612,6 +5598,12 @@ dependencies = [ "slab", ] +[[package]] +name = "futures-utils-wasm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" + [[package]] name = "fxhash" version = "0.2.1" @@ -4621,6 +5613,49 @@ dependencies = [ "byteorder", ] +[[package]] +name = "gcemeta" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d460327b24cc34c86d53d60a90e9e6044817f7906ebd9baa5c3d0ee13e1ecf" +dependencies = [ + "bytes", + "hyper 0.14.29", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "gcloud-sdk" +version = "0.24.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1afe2a62202f8f8eb624638f7e5b8f0988a540dd8dbb69e098daeb277273b2ab" +dependencies = [ + "async-trait", + "chrono", + "futures 0.3.30", + "gcemeta", + "hyper 0.14.29", + "jsonwebtoken 9.3.0", + "once_cell", + "prost 0.12.6", + "prost-types", + "reqwest 0.11.27", + "secret-vault-value", + "serde", + "serde_json", + "tokio", + "tonic 0.11.0", + "tower", + "tower-layer", + "tower-util", + "tracing", + "url", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -4645,9 +5680,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -4664,48 +5699,47 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "gix-actor" -version = "0.20.0" +version = "0.31.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "848efa0f1210cea8638f95691c82a46f98a74b9e3524f01d4955ebc25a8f84f3" +checksum = "d9b8ee65074b2bbb91d9d97c15d172ea75043aefebf9869b5b329149dc76501c" dependencies = [ - "bstr", - "btoi", + "bstr 1.9.1", "gix-date", + "gix-utils", "itoa", - "nom", "thiserror", + "winnow 0.6.13", ] [[package]] name = "gix-config" -version = "0.22.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d252a0eddb6df74600d3d8872dc9fe98835a7da43110411d705b682f49d4ac1" +checksum = "7580e05996e893347ad04e1eaceb92e1c0e6a3ffe517171af99bf6b6df0ca6e5" dependencies = [ - "bstr", + "bstr 1.9.1", "gix-config-value", "gix-features", "gix-glob", "gix-path", "gix-ref", "gix-sec", - "log", "memchr", - "nom", "once_cell", "smallvec", "thiserror", "unicode-bom", + "winnow 0.6.13", ] [[package]] name = "gix-config-value" -version = "0.12.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e874f41437441c02991dcea76990b9058fadfc54b02ab4dd06ab2218af43897" +checksum = "fbd06203b1a9b33a78c88252a625031b094d9e1b647260070c25b09910c0a804" dependencies = [ - "bitflags 2.4.2", - "bstr", + "bitflags 2.6.0", + "bstr 1.9.1", "gix-path", "libc", "thiserror", @@ -4713,11 +5747,11 @@ dependencies = [ [[package]] name = "gix-date" -version = "0.5.1" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc164145670e9130a60a21670d9b6f0f4f8de04e5dd256c51fa5a0340c625902" +checksum = "9eed6931f21491ee0aeb922751bd7ec97b4b2fe8fbfedcb678e2a2dce5f3b8c0" dependencies = [ - "bstr", + "bstr 1.9.1", "itoa", "thiserror", "time", @@ -4725,52 +5759,56 @@ dependencies = [ [[package]] name = "gix-features" -version = "0.29.0" +version = "0.38.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf69b0f5c701cc3ae22d3204b671907668f6437ca88862d355eaf9bc47a4f897" +checksum = "ac7045ac9fe5f9c727f38799d002a7ed3583cd777e3322a7c4b43e3cf437dc69" dependencies = [ "gix-hash", + "gix-trace", + "gix-utils", "libc", + "prodash", "sha1_smol", "walkdir", ] [[package]] name = "gix-fs" -version = "0.1.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b37a1832f691fdc09910bd267f9a2e413737c1f9ec68c6e31f9e802616278a9" +checksum = "e2184c40e7910529677831c8b481acf788ffd92427ed21fad65b6aa637e631b8" dependencies = [ "gix-features", + "gix-utils", ] [[package]] name = "gix-glob" -version = "0.7.0" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c07c98204529ac3f24b34754540a852593d2a4c7349008df389240266627a72a" +checksum = "c2a29ad0990cf02c48a7aac76ed0dbddeb5a0d070034b83675cc3bbf937eace4" dependencies = [ - "bitflags 2.4.2", - "bstr", + "bitflags 2.6.0", + "bstr 1.9.1", "gix-features", "gix-path", ] [[package]] name = "gix-hash" -version = "0.11.4" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b422ff2ad9a0628baaad6da468cf05385bf3f5ab495ad5a33cce99b9f41092f" +checksum = "f93d7df7366121b5018f947a04d37f034717e113dcf9ccd85c34b58e57a74d5e" dependencies = [ - "hex", + "faster-hex", "thiserror", ] [[package]] name = "gix-lock" -version = "5.0.1" +version = "13.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c693d7f05730fa74a7c467150adc7cea393518410c65f0672f80226b8111555" +checksum = "e7c359f81f01b8352063319bcb39789b7ea0887b406406381106e38c4a34d049" dependencies = [ "gix-tempfile", "gix-utils", @@ -4779,30 +5817,30 @@ dependencies = [ [[package]] name = "gix-object" -version = "0.29.2" +version = "0.42.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d96bd620fd08accdd37f70b2183cfa0b001b4f1c6ade8b7f6e15cb3d9e261ce" +checksum = "25da2f46b4e7c2fa7b413ce4dffb87f69eaf89c2057e386491f4c55cadbfe386" dependencies = [ - "bstr", - "btoi", + "bstr 1.9.1", "gix-actor", + "gix-date", "gix-features", "gix-hash", + "gix-utils", "gix-validate", - "hex", "itoa", - "nom", "smallvec", "thiserror", + "winnow 0.6.13", ] [[package]] name = "gix-path" -version = "0.8.4" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18609c8cbec8508ea97c64938c33cd305b75dfc04a78d0c3b78b8b3fd618a77c" +checksum = "8d23d5bbda31344d8abc8de7c075b3cf26e5873feba7c4a15d916bce67382bd9" dependencies = [ - "bstr", + "bstr 1.9.1", "gix-trace", "home", "once_cell", @@ -4811,11 +5849,12 @@ dependencies = [ [[package]] name = "gix-ref" -version = "0.29.1" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e03989e9d49954368e1b526578230fc7189d1634acdfbe79e9ba1de717e15d5" +checksum = "fd4aba68b925101cb45d6df328979af0681364579db889098a0de75b36c77b65" dependencies = [ "gix-actor", + "gix-date", "gix-features", "gix-fs", "gix-hash", @@ -4823,48 +5862,49 @@ dependencies = [ "gix-object", "gix-path", "gix-tempfile", + "gix-utils", "gix-validate", - "memmap2 0.5.10", - "nom", + "memmap2", "thiserror", + "winnow 0.6.13", ] [[package]] name = "gix-sec" -version = "0.8.4" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9615cbd6b456898aeb942cd75e5810c382fbfc48dbbff2fa23ebd2d33dcbe9c7" +checksum = "fddc27984a643b20dd03e97790555804f98cf07404e0e552c0ad8133266a79a1" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "gix-path", "libc", - "windows", + "windows-sys 0.52.0", ] [[package]] name = "gix-tempfile" -version = "5.0.3" +version = "13.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71a0d32f34e71e86586124225caefd78dabc605d0486de580d717653addf182" +checksum = "a761d76594f4443b675e85928e4902dec333273836bd386906f01e7e346a0d11" dependencies = [ "gix-fs", "libc", "once_cell", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "tempfile", ] [[package]] name = "gix-trace" -version = "0.1.7" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b202d766a7fefc596e2cc6a89cda8ad8ad733aed82da635ac120691112a9b1" +checksum = "f924267408915fddcd558e3f37295cc7d6a3e50f8bd8b606cee0808c3915157e" [[package]] name = "gix-utils" -version = "0.1.9" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e839f3d0798b296411263da6bee780a176ef8008a5dfc31287f7eda9266ab8" +checksum = "35192df7fd0fa112263bad8021e2df7167df4cc2a6e6d15892e1e55621d3d4dc" dependencies = [ "fastrand", "unicode-normalization", @@ -4872,11 +5912,11 @@ dependencies = [ [[package]] name = "gix-validate" -version = "0.7.7" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba9b3737b2cef3dcd014633485f0034b0f1a931ee54aeb7d8f87f177f3c89040" +checksum = "82c27dd34a49b1addf193c92070bcbf3beaf6e10f16a78544de6372e146a0acf" dependencies = [ - "bstr", + "bstr 1.9.1", "thiserror", ] @@ -4893,10 +5933,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" dependencies = [ "aho-corasick", - "bstr", + "bstr 1.9.1", "log", - "regex-automata 0.4.5", - "regex-syntax 0.8.2", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", ] [[package]] @@ -4909,9 +5949,9 @@ dependencies = [ "futures-core", "futures-sink", "gloo-utils", - "http", + "http 0.2.12", "js-sys", - "pin-project", + "pin-project 1.1.5", "serde", "serde_json", "thiserror", @@ -4969,28 +6009,28 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.24" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http", - "indexmap 2.2.3", + "http 0.2.12", + "indexmap 2.2.6", "slab", "tokio", - "tokio-util 0.7.10", + "tokio-util 0.7.11", "tracing", ] [[package]] name = "half" -version = "2.3.1" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc52e53916c08643f1b56ec082790d1e86a32e58dc5268f897f313fbae7b4872" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" dependencies = [ "cfg-if 1.0.0", "crunchy", @@ -4998,9 +6038,9 @@ dependencies = [ [[package]] name = "handlebars" -version = "5.1.0" +version = "5.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab283476b99e66691dee3f1640fea91487a8d81f50fb5ecc75538f8f8879a1e4" +checksum = "d08485b96a0e6393e9e4d1b8d48cf74ad6c063cd905eb33f42c1ce3f0377539b" dependencies = [ "log", "pest", @@ -5010,21 +6050,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "hash-db" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d23bd4e7b5eda0d0f3a307e8b381fdc8ba9000f26fbe912250c0a4cc3956364a" - -[[package]] -name = "hash256-std-hasher" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92c171d55b98633f4ed3860808f004099b36c1cc29c42cfc53aa8591b21efcf2" -dependencies = [ - "crunchy", -] - [[package]] name = "hashbrown" version = "0.12.3" @@ -5036,11 +6061,11 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "ahash 0.8.9", + "ahash 0.8.11", "allocator-api2", "serde", ] @@ -5060,7 +6085,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -5080,9 +6105,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "379dada1584ad501b383485dd706b8afb7a70fcbc7f4da7d780638a5a6124a60" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -5117,17 +6142,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ - "hmac 0.12.1", -] - -[[package]] -name = "hmac" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" -dependencies = [ - "crypto-mac", - "digest 0.9.0", + "hmac", ] [[package]] @@ -5161,23 +6176,34 @@ dependencies = [ [[package]] name = "html5ever" -version = "0.26.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" +checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4" dependencies = [ "log", "mac", "markup5ever", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.68", ] [[package]] name = "http" -version = "0.2.11" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -5191,21 +6217,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", "pin-project-lite", ] [[package]] name = "http-range-header" -version = "0.3.1" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" +checksum = "08a397c49fec283e3d6211adbe480be95aae5f304cfb923e9970e08956d5168a" [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "httpdate" @@ -5219,19 +6268,29 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +[[package]] +name = "humantime-serde" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" +dependencies = [ + "humantime", + "serde", +] + [[package]] name = "hyper" -version = "0.14.28" +version = "0.14.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", "h2", - "http", - "http-body", + "http 0.2.12", + "http-body 0.4.6", "httparse", "httpdate", "itoa", @@ -5244,18 +6303,23 @@ dependencies = [ ] [[package]] -name = "hyper-rustls" -version = "0.23.2" +name = "hyper" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" +checksum = "c4fe55fb7a772d59a5ff1dfbff4fe0258d19b89fec4b233e75d35d5d2316badc" dependencies = [ - "http", - "hyper", - "log", - "rustls 0.20.9", - "rustls-native-certs 0.6.3", + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", "tokio", - "tokio-rustls 0.23.4", + "want", ] [[package]] @@ -5265,22 +6329,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http", - "hyper", + "http 0.2.12", + "hyper 0.14.29", "log", - "rustls 0.21.10", + "rustls 0.21.12", "rustls-native-certs 0.6.3", "tokio", "tokio-rustls 0.24.1", ] +[[package]] +name = "hyper-rustls" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.4.0", + "hyper-util", + "rustls 0.23.10", + "rustls-native-certs 0.7.1", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", + "webpki-roots 0.26.3", +] + [[package]] name = "hyper-timeout" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper", + "hyper 0.14.29", "pin-project-lite", "tokio", "tokio-io-timeout", @@ -5293,10 +6376,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper", + "hyper 0.14.29", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.4.0", + "hyper-util", "native-tls", "tokio", "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "hyper 1.4.0", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", ] [[package]] @@ -5310,7 +6429,7 @@ dependencies = [ "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core", + "windows-core 0.52.0", ] [[package]] @@ -5348,7 +6467,7 @@ dependencies = [ "globset", "log", "memchr", - "regex-automata 0.4.5", + "regex-automata 0.4.7", "same-file", "walkdir", "winapi-util", @@ -5356,15 +6475,16 @@ dependencies = [ [[package]] name = "ignore-files" -version = "1.3.1" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a4d73056a8d335492938cabeef794f38968ef43e6db9bc946638cfd6281003b" +checksum = "99f84e7f847462c582abc4c2aef6ede285ad6e8f66aeec83b47f5481706ddeba" dependencies = [ "dunce", "futures 0.3.30", "gix-config", "ignore", - "miette", + "miette 7.2.0", + "normalize-path", "project-origins", "radix_trie", "thiserror", @@ -5387,7 +6507,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" dependencies = [ - "parity-scale-codec 3.6.9", + "parity-scale-codec 3.6.12", ] [[package]] @@ -5446,12 +6566,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.3" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -5467,12 +6587,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "indoc" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" - [[package]] name = "inlinable_string" version = "0.1.15" @@ -5510,40 +6624,28 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "interprocess" -version = "1.2.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81f2533f3be42fffe3b5e63b71aeca416c1c3bc33e4e27be018521e76b1f38fb" +checksum = "67bafc2f5dbdad79a6d925649758d5472647b416028099f0b829d1b67fdd47d3" dependencies = [ - "blocking", - "cfg-if 1.0.0", + "doctest-file", "futures-core", - "futures-io", - "intmap", "libc", - "once_cell", - "rustc_version 0.4.0", - "spinning", - "thiserror", - "to_method", + "recvmsg", "tokio", - "winapi", + "widestring", + "windows-sys 0.52.0", ] -[[package]] -name = "intmap" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae52f28f45ac2bc96edb7714de995cffc174a395fb0abf5bff453587c980d7b9" - [[package]] name = "ipnet" version = "2.9.0" @@ -5570,6 +6672,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itertools" version = "0.10.5" @@ -5597,17 +6705,35 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "jobserver" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] [[package]] name = "js-sys" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -5628,7 +6754,7 @@ name = "jsonrpc-client-transports" version = "18.0.0" source = "git+https://github.com/matter-labs/jsonrpc.git?branch=master#12c53e3e20c09c2fb9966a4ef1b0ea63de172540" dependencies = [ - "derive_more 0.99.17", + "derive_more 0.99.18", "futures 0.3.30", "jsonrpc-core", "jsonrpc-pubsub", @@ -5677,7 +6803,7 @@ version = "18.0.0" source = "git+https://github.com/matter-labs/jsonrpc.git?branch=master#12c53e3e20c09c2fb9966a4ef1b0ea63de172540" dependencies = [ "futures 0.3.30", - "hyper", + "hyper 0.14.29", "jsonrpc-core", "jsonrpc-server-utils", "log", @@ -5744,19 +6870,19 @@ dependencies = [ "futures-channel", "futures-util", "gloo-net", - "http", + "http 0.2.12", "jsonrpsee-core", - "pin-project", - "rustls-native-certs 0.7.0", + "pin-project 1.1.5", + "rustls-native-certs 0.7.1", "rustls-pki-types", "soketto", "thiserror", "tokio", "tokio-rustls 0.25.0", - "tokio-util 0.7.10", + "tokio-util 0.7.11", "tracing", "url", - "webpki-roots 0.26.1", + "webpki-roots 0.26.3", ] [[package]] @@ -5771,10 +6897,10 @@ dependencies = [ "beef", "futures-timer", "futures-util", - "hyper", + "hyper 0.14.29", "jsonrpsee-types", - "parking_lot 0.12.1", - "pin-project", + "parking_lot 0.12.3", + "pin-project 1.1.5", "rand 0.8.5", "rustc-hash", "serde", @@ -5793,7 +6919,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b7de9f3219d95985eb77fd03194d7c1b56c19bce1abfcc9d07462574b15572" dependencies = [ "async-trait", - "hyper", + "hyper 0.14.29", "hyper-rustls 0.24.2", "jsonrpsee-core", "jsonrpsee-types", @@ -5826,11 +6952,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cc7c6d1a2c58f6135810284a390d9f823d0f508db74cd914d8237802de80f98" dependencies = [ "futures-util", - "http", - "hyper", + "http 0.2.12", + "hyper 0.14.29", "jsonrpsee-core", "jsonrpsee-types", - "pin-project", + "pin-project 1.1.5", "route-recognizer", "serde", "serde_json", @@ -5838,7 +6964,7 @@ dependencies = [ "thiserror", "tokio", "tokio-stream", - "tokio-util 0.7.10", + "tokio-util 0.7.11", "tower", "tracing", ] @@ -5873,7 +6999,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "073c077471e89c4b511fa88b3df9a0f0abdf4a0a2e6683dd2ab36893af87bb2d" dependencies = [ - "http", + "http 0.2.12", "jsonrpsee-client-transport", "jsonrpsee-core", "jsonrpsee-types", @@ -5887,13 +7013,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" dependencies = [ "base64 0.21.7", - "pem", + "pem 1.1.1", "ring 0.16.20", "serde", "serde_json", "simple_asn1", ] +[[package]] +name = "jsonwebtoken" +version = "9.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" +dependencies = [ + "base64 0.21.7", + "js-sys", + "pem 3.0.4", + "ring 0.17.8", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "k256" version = "0.11.6" @@ -5908,9 +7049,9 @@ dependencies = [ [[package]] name = "k256" -version = "0.13.1" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" dependencies = [ "cfg-if 1.0.0", "ecdsa 0.16.9", @@ -5931,25 +7072,14 @@ dependencies = [ [[package]] name = "keccak-asm" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb8515fff80ed850aea4a1595f2e519c003e2a00a82fe168ebf5269196caf444" +checksum = "47a3633291834c4fbebf8673acbc1b04ec9d151418ff9b8e26dcd79129928758" dependencies = [ "digest 0.10.7", "sha3-asm", ] -[[package]] -name = "keccak-hasher" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711adba9940a039f4374fc5724c0a5eaca84a2d558cce62256bfe26f0dbef05e" -dependencies = [ - "hash-db", - "hash256-std-hasher", - "tiny-keccak 2.0.2", -] - [[package]] name = "kqueue" version = "1.0.8" @@ -5972,39 +7102,41 @@ dependencies = [ [[package]] name = "lalrpop" -version = "0.20.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da4081d44f4611b66c6dd725e6de3169f9f63905421e8626fcb86b6a898998b8" +checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" dependencies = [ "ascii-canvas", "bit-set", - "diff", "ena", - "is-terminal", - "itertools 0.10.5", + "itertools 0.11.0", "lalrpop-util", "petgraph", "regex", - "regex-syntax 0.7.5", + "regex-syntax 0.8.4", "string_cache", "term", "tiny-keccak 2.0.2", "unicode-xid", + "walkdir", ] [[package]] name = "lalrpop-util" -version = "0.20.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f35c735096c0293d313e8f2a641627472b83d01b937177fe76e5e2708d31e0d" +checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" +dependencies = [ + "regex-automata 0.4.7", +] [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin 0.5.2", + "spin 0.9.8", ] [[package]] @@ -6015,18 +7147,28 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libdbus-sys" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72" +dependencies = [ + "cc", + "pkg-config", +] [[package]] name = "libloading" -version = "0.8.1" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c571b676ddfc9a8c12f1f3d3085a7b163966a8fd8098a90640953ce5f6170161" +checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d" dependencies = [ "cfg-if 1.0.0", - "windows-sys 0.48.0", + "windows-targets 0.52.6", ] [[package]] @@ -6037,13 +7179,12 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "libredox" -version = "0.0.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "libc", - "redox_syscall 0.4.1", ] [[package]] @@ -6075,9 +7216,9 @@ dependencies = [ [[package]] name = "libusb1-sys" -version = "0.6.4" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d0e2afce4245f2c9a418511e5af8718bcaf2fa408aefb259504d1a9cb25f27" +checksum = "da050ade7ac4ff1ba5379af847a10a10a8e284181e060105bf8d86960ce9ce0f" dependencies = [ "cc", "libc", @@ -6087,9 +7228,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.15" +version = "1.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037731f5d3aaa87a5675e895b63ddff1a87624bc29f77004ea829809654e48f6" +checksum = "c15da26e5af7e25c90b37a2d75cdbf940cf4a55316de9d84c679c9b8bfabf82e" dependencies = [ "cc", "pkg-config", @@ -6098,45 +7239,51 @@ dependencies = [ [[package]] name = "linkme" -version = "0.3.23" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a78816ac097580aa7fd9d2e9cc7395dda34367c07267a8657516d4ad5e2e3d3" +checksum = "ccb76662d78edc9f9bf56360d6919bdacc8b7761227727e5082f128eeb90bbf5" dependencies = [ "linkme-impl", ] [[package]] name = "linkme-impl" -version = "0.3.23" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee9023a564f8bf7fe3da285a50c3e70de0df3e2bf277ff7c4e76d66008ef93b0" +checksum = "f8dccda732e04fa3baf2e17cf835bfe2601c7c2edafd64417c627dabae3a8cda" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", ] +[[package]] +name = "lockfree-object-pool" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" + [[package]] name = "log" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "logos" @@ -6158,7 +7305,7 @@ dependencies = [ "proc-macro2", "quote", "regex-syntax 0.6.29", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] @@ -6176,7 +7323,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" dependencies = [ - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -6203,13 +7350,13 @@ checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" [[package]] name = "markup5ever" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" dependencies = [ "log", - "phf 0.10.1", - "phf_codegen 0.10.0", + "phf", + "phf_codegen", "string_cache", "string_cache_codegen", "tendril", @@ -6242,17 +7389,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" -[[package]] -name = "md-5" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" -dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", - "opaque-debug", -] - [[package]] name = "md-5" version = "0.10.6" @@ -6265,9 +7401,9 @@ dependencies = [ [[package]] name = "mdbook" -version = "0.4.37" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c33564061c3c640bed5ace7d6a2a1b65f2c64257d1ac930c15e94ed0fb561d3" +checksum = "b45a38e19bd200220ef07c892b0157ad3d2365e5b5a267ca01ad12182491eea5" dependencies = [ "ammonia", "anyhow", @@ -6275,13 +7411,13 @@ dependencies = [ "clap", "clap_complete", "elasticlunr-rs", - "env_logger 0.11.2", + "env_logger 0.11.3", "handlebars", "log", "memchr", "once_cell", "opener", - "pulldown-cmark 0.10.0", + "pulldown-cmark 0.10.3", "regex", "serde", "serde_json", @@ -6293,18 +7429,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" - -[[package]] -name = "memmap2" -version = "0.5.10" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" -dependencies = [ - "libc", -] +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memmap2" @@ -6334,24 +7461,25 @@ dependencies = [ ] [[package]] -name = "memory-db" -version = "0.29.0" +name = "miette" +version = "5.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6566c70c1016f525ced45d7b7f97730a2bafb037c788211d0c186ef5b2189f0a" +checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e" dependencies = [ - "hash-db", - "hashbrown 0.12.3", - "parity-util-mem", + "miette-derive 5.10.0", + "once_cell", + "thiserror", + "unicode-width", ] [[package]] name = "miette" -version = "5.10.0" +version = "7.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e" +checksum = "4edc8853320c2a0dab800fbda86253c8938f6ea88510dc92c5f1ed20e794afc1" dependencies = [ - "miette-derive", - "once_cell", + "cfg-if 1.0.0", + "miette-derive 7.2.0", "thiserror", "unicode-width", ] @@ -6364,7 +7492,18 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", +] + +[[package]] +name = "miette-derive" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", ] [[package]] @@ -6375,9 +7514,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", @@ -6389,8 +7528,8 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c325dfab65f261f386debee8b0969da215b3fa0037e74c8a1234db7ba986d803" dependencies = [ - "crossbeam-channel 0.5.11", - "crossbeam-utils 0.8.19", + "crossbeam-channel 0.5.13", + "crossbeam-utils 0.8.20", "dashmap", "skeptic", "smallvec", @@ -6406,18 +7545,18 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", @@ -6425,11 +7564,38 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mockall" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43766c2b5203b10de348ffe19f7e54564b64f3d6018ff7648d1e2d6d3a0f0a48" +dependencies = [ + "cfg-if 1.0.0", + "downcast", + "fragile", + "lazy_static", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af7cbce79ec385a1d4f54baa90a76401eb15d9cab93685f62e7e9f942aa00ae2" +dependencies = [ + "cfg-if 1.0.0", + "proc-macro2", + "quote", + "syn 2.0.68", +] + [[package]] name = "multimap" -version = "0.8.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" [[package]] name = "multivm" @@ -6463,11 +7629,10 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" dependencies = [ - "lazy_static", "libc", "log", "openssl", @@ -6492,9 +7657,9 @@ dependencies = [ [[package]] name = "new_debug_unreachable" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nibble_vec" @@ -6520,12 +7685,13 @@ dependencies = [ [[package]] name = "nix" -version = "0.27.1" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "cfg-if 1.0.0", + "cfg_aliases 0.1.1", "libc", ] @@ -6556,20 +7722,21 @@ dependencies = [ [[package]] name = "notify" -version = "5.2.0" +version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "729f63e1ca555a43fe3efa4f3efdf4801c479da85b432242a7b726f353c88486" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" dependencies = [ - "bitflags 1.3.2", - "crossbeam-channel 0.5.11", + "bitflags 2.6.0", + "crossbeam-channel 0.5.13", "filetime", "fsevent-sys", "inotify", "kqueue", "libc", + "log", "mio", "walkdir", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -6584,9 +7751,9 @@ dependencies = [ [[package]] name = "num" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ "num-bigint", "num-complex", @@ -6598,11 +7765,10 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", "serde", @@ -6627,9 +7793,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", "serde", @@ -6641,6 +7807,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-format" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +dependencies = [ + "arrayvec 0.7.4", + "itoa", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -6652,9 +7828,9 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", @@ -6673,11 +7849,10 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "autocfg", "num-bigint", "num-integer", "num-traits", @@ -6686,9 +7861,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", @@ -6731,7 +7906,7 @@ dependencies = [ "proc-macro-crate 1.3.1", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] @@ -6743,7 +7918,7 @@ dependencies = [ "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] @@ -6761,6 +7936,19 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" +[[package]] +name = "nybbles" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95f06be0417d97f81fe4e5c86d7d01b392655a9cac9c19a848aa033e18937b23" +dependencies = [ + "alloy-rlp", + "const-hex", + "proptest", + "serde", + "smallvec", +] + [[package]] name = "object" version = "0.32.2" @@ -6784,9 +7972,9 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "opaque-debug" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "open-fastrlp" @@ -6815,22 +8003,23 @@ dependencies = [ [[package]] name = "opener" -version = "0.6.1" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c62dcb6174f9cb326eac248f07e955d5d559c272730b6c03e396b443b562788" +checksum = "f8df34be653210fbe9ffaff41d3b92721c56ce82dfee58ee684f9afb5e3a90c0" dependencies = [ - "bstr", + "bstr 1.9.1", + "dbus", "normpath", - "winapi", + "windows-sys 0.52.0", ] [[package]] name = "openssl" -version = "0.10.64" +version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "cfg-if 1.0.0", "foreign-types", "libc", @@ -6847,7 +8036,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] @@ -6858,18 +8047,18 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.2.3+3.2.1" +version = "300.3.1+3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cff92b6f71555b61bb9315f7c64da3ca43d87531622120fea0195fc761b4843" +checksum = "7259953d42a81bf137fbbd73bd30a8e1914d6dce43c2b90ed575783a22608b91" dependencies = [ "cc", ] [[package]] name = "openssl-sys" -version = "0.9.101" +version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", @@ -6896,9 +8085,9 @@ checksum = "c7594ec0e11d8e33faf03530a4c49af7064ebba81c1480e01be67d90b356508b" dependencies = [ "async-trait", "bytes", - "http", + "http 0.2.12", "opentelemetry_api", - "reqwest", + "reqwest 0.11.27", ] [[package]] @@ -6909,17 +8098,17 @@ checksum = "7e5e5a5c4135864099f3faafbe939eb4d7f9b80ebf68a8448da961b32a7c1275" dependencies = [ "async-trait", "futures-core", - "http", + "http 0.2.12", "opentelemetry-http", "opentelemetry-proto", "opentelemetry-semantic-conventions", "opentelemetry_api", "opentelemetry_sdk", "prost 0.11.9", - "reqwest", + "reqwest 0.11.27", "thiserror", "tokio", - "tonic", + "tonic 0.9.2", ] [[package]] @@ -6931,7 +8120,7 @@ dependencies = [ "opentelemetry_api", "opentelemetry_sdk", "prost 0.11.9", - "tonic", + "tonic 0.9.2", ] [[package]] @@ -6966,7 +8155,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa8e705a0612d48139799fcbaba0d4a90f06277153e43dd2bdc16c6f0edd8026" dependencies = [ "async-trait", - "crossbeam-channel 0.5.11", + "crossbeam-channel 0.5.13", "futures-channel", "futures-executor", "futures-util", @@ -7008,15 +8197,21 @@ dependencies = [ [[package]] name = "os_info" -version = "3.7.0" +version = "3.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e" +checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" dependencies = [ "log", "serde", - "winapi", + "windows-sys 0.52.0", ] +[[package]] +name = "outref" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" + [[package]] name = "overload" version = "0.1.1" @@ -7041,20 +8236,10 @@ dependencies = [ "sha2 0.10.8", ] -[[package]] -name = "packed_simd" -version = "0.3.9" -source = "git+https://github.com/nbaztec/packed_simd?branch=foundry-fix#0d4aca41e2a7e5001017fd5eccbbea7821800d06" -dependencies = [ - "cfg-if 1.0.0", - "num-traits", -] - [[package]] name = "pairing_ce" version = "0.28.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db007b21259660d025918e653508f03050bf23fb96a88601f9936329faadc597" +source = "git+https://github.com/matter-labs/pairing.git?rev=d24f2c5871089c4cd4f54c0ca266bb9fef6115eb#d24f2c5871089c4cd4f54c0ca266bb9fef6115eb" dependencies = [ "byteorder", "cfg-if 1.0.0", @@ -7066,7 +8251,7 @@ dependencies = [ [[package]] name = "pairing_ce" version = "0.28.5" -source = "git+https://github.com/matter-labs/pairing.git?rev=d24f2c5871089c4cd4f54c0ca266bb9fef6115eb#d24f2c5871089c4cd4f54c0ca266bb9fef6115eb" +source = "git+https://github.com/matter-labs/pairing.git#d24f2c5871089c4cd4f54c0ca266bb9fef6115eb" dependencies = [ "byteorder", "cfg-if 1.0.0", @@ -7077,8 +8262,9 @@ dependencies = [ [[package]] name = "pairing_ce" -version = "0.28.5" -source = "git+https://github.com/matter-labs/pairing.git#f55393fd366596eac792d78525d26e9c4d6ed1ca" +version = "0.28.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843b5b6fb63f00460f611dbc87a50bbbb745f0dfe5cbf67ca89299c79098640e" dependencies = [ "byteorder", "cfg-if 1.0.0", @@ -7103,15 +8289,15 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "3.6.9" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "881331e34fa842a2fb61cc2db9643a8fedc615e47cfcc52597d1af0db9a7e8fe" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" dependencies = [ "arrayvec 0.7.4", "bitvec 1.0.1", "byte-slice-cast", "impl-trait-for-tuples", - "parity-scale-codec-derive 3.6.9", + "parity-scale-codec-derive 3.6.12", "serde", ] @@ -7129,55 +8315,16 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.6.9" +version = "3.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be30eaf4b0a9fba5336683b38de57bb86d179a35862ba6bfcf57625d006bde5b" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" dependencies = [ - "proc-macro-crate 2.0.0", + "proc-macro-crate 3.1.0", "proc-macro2", "quote", "syn 1.0.109", ] -[[package]] -name = "parity-tokio-ipc" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9981e32fb75e004cc148f5fb70342f393830e0a4aa62e3cc93b50976218d42b6" -dependencies = [ - "futures 0.3.30", - "libc", - "log", - "rand 0.7.3", - "tokio", - "winapi", -] - -[[package]] -name = "parity-util-mem" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c32561d248d352148124f036cac253a644685a21dc9fea383eb4907d7bd35a8f" -dependencies = [ - "cfg-if 1.0.0", - "hashbrown 0.12.3", - "impl-trait-for-tuples", - "parity-util-mem-derive", - "parking_lot 0.12.1", - "winapi", -] - -[[package]] -name = "parity-util-mem-derive" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f557c32c6d268a07c921471619c0295f5efad3a0e76d4f97a05c091a51d110b2" -dependencies = [ - "proc-macro2", - "syn 1.0.109", - "synstructure", -] - [[package]] name = "parking" version = "2.2.0" @@ -7197,12 +8344,12 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", - "parking_lot_core 0.9.9", + "parking_lot_core 0.9.10", ] [[package]] @@ -7221,15 +8368,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.4.1", + "redox_syscall 0.5.2", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -7245,9 +8392,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "path-slash" @@ -7262,7 +8409,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ "digest 0.10.7", - "hmac 0.12.1", + "hmac", "password-hash", "sha2 0.10.8", ] @@ -7274,30 +8421,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", - "hmac 0.12.1", + "hmac", ] [[package]] name = "pear" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ccca0f6c17acc81df8e242ed473ec144cbf5c98037e69aa6d144780aad103c8" +checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467" dependencies = [ "inlinable_string", "pear_codegen", - "yansi 1.0.0-rc.1", + "yansi 1.0.1", ] [[package]] name = "pear_codegen" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e22670e8eb757cff11d6c199ca7b987f352f0346e0be4dd23869ec72cb53c77" +checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147" dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] @@ -7315,6 +8462,16 @@ dependencies = [ "base64 0.13.1", ] +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.1", + "serde", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -7332,9 +8489,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.7" +version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219c0dcc30b6a27553f9cc242972b67f75b60eb0db71f0b5462f38b058c41546" +checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" dependencies = [ "memchr", "thiserror", @@ -7343,9 +8500,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.7" +version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e1288dbd7786462961e69bfd4df7848c1e37e8b74303dbdab82c3a9cdd2809" +checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a" dependencies = [ "pest", "pest_generator", @@ -7353,22 +8510,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.7" +version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1381c29a877c6d34b8c176e734f35d7f7f5b3adaefe940cb4d1bb7af94678e2e" +checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] name = "pest_meta" -version = "2.7.7" +version = "2.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0934d6907f148c22a3acbda520c7eed243ad7487a30f51f6ce52b58b7077a8a" +checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f" dependencies = [ "once_cell", "pest", @@ -7377,12 +8534,12 @@ dependencies = [ [[package]] name = "petgraph" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.2.3", + "indexmap 2.2.6", ] [[package]] @@ -7395,15 +8552,6 @@ dependencies = [ "rustc_version 0.4.0", ] -[[package]] -name = "phf" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" -dependencies = [ - "phf_shared 0.10.0", -] - [[package]] name = "phf" version = "0.11.2" @@ -7414,16 +8562,6 @@ dependencies = [ "phf_shared 0.11.2", ] -[[package]] -name = "phf_codegen" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", -] - [[package]] name = "phf_codegen" version = "0.11.2" @@ -7464,7 +8602,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] @@ -7487,29 +8625,49 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.4" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ef0f924a5ee7ea9cbcea77529dba45f8a9ba9f622419fe3386ca581a3ae9d5a" +dependencies = [ + "pin-project-internal 0.4.30", +] + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal 1.1.5", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" +checksum = "851c8d0ce9bebe43790dedfc86614c23494ac9f423dd618d3a61fc693eafe61e" dependencies = [ - "pin-project-internal", + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] name = "pin-project-internal" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -7517,24 +8675,13 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "piper" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - [[package]] name = "pkcs1" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ - "der 0.7.8", + "der 0.7.9", "pkcs8 0.10.2", "spki 0.7.3", ] @@ -7555,7 +8702,7 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der 0.7.8", + "der 0.7.9", "spki 0.7.3", ] @@ -7565,17 +8712,11 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" -[[package]] -name = "platforms" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626dec3cac7cc0e1577a2ec3fc496277ec2baa084bebad95bb6fdbfae235f84c" - [[package]] name = "plotters" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" dependencies = [ "num-traits", "plotters-backend", @@ -7586,15 +8727,15 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" +checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" [[package]] name = "plotters-svg" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" dependencies = [ "plotters-backend", ] @@ -7621,26 +8762,42 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" name = "precomputed-hash" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "predicates" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b87bfd4605926cdfefc1c3b5f8fe560e3feca9d5552cf68c466d3d8236c7e8" +dependencies = [ + "anstyle", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" [[package]] -name = "pretty_assertions" -version = "1.4.0" +name = "predicates-tree" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" dependencies = [ - "diff", - "yansi 0.5.1", + "predicates-core", + "termtree", ] [[package]] name = "prettyplease" -version = "0.2.16" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" +checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" dependencies = [ "proc-macro2", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] @@ -7742,9 +8899,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -7757,16 +8914,36 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", "version_check", - "yansi 1.0.0-rc.1", + "yansi 1.0.1", +] + +[[package]] +name = "process-wrap" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38ee68ae331824036479c84060534b18254c864fa73366c58d86db3b7b811619" +dependencies = [ + "futures 0.3.30", + "indexmap 2.2.6", + "nix 0.28.0", + "tokio", + "tracing", + "windows", ] +[[package]] +name = "prodash" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79" + [[package]] name = "project-origins" -version = "1.2.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "629e0d57f265ca8238345cb616eea8847b8ecb86b5d97d155be2c8963a314379" +checksum = "735c6b4b1c67863c2211cac24badb0dca9fabfe1098209834fc5e0f92eda6c2c" dependencies = [ "futures 0.3.30", "tokio", @@ -7781,7 +8958,7 @@ checksum = "c1ca959da22a332509f2a73ae9e5f23f9dcfc31fd3a54d71f159495bd5909baa" dependencies = [ "dtoa", "itoa", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "prometheus-client-derive-encode", ] @@ -7793,24 +8970,24 @@ checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] name = "proptest" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" +checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.4.2", + "bitflags 2.6.0", "lazy_static", "num-traits", "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift", - "regex-syntax 0.8.2", + "regex-syntax 0.8.4", "rusty-fork", "tempfile", "unarray", @@ -7839,34 +9016,33 @@ dependencies = [ [[package]] name = "prost" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ "bytes", - "prost-derive 0.12.3", + "prost-derive 0.12.6", ] [[package]] name = "prost-build" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2" +checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes", - "heck 0.4.1", - "itertools 0.11.0", + "heck 0.5.0", + "itertools 0.12.1", "log", "multimap", "once_cell", "petgraph", "prettyplease", - "prost 0.12.3", + "prost 0.12.6", "prost-types", "regex", - "syn 2.0.50", + "syn 2.0.68", "tempfile", - "which", ] [[package]] @@ -7884,15 +9060,15 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.11.0", + "itertools 0.12.1", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] @@ -7903,9 +9079,9 @@ checksum = "057237efdb71cf4b3f9396302a3d6599a92fa94063ba537b66130980ea9909f3" dependencies = [ "base64 0.21.7", "logos", - "miette", + "miette 5.10.0", "once_cell", - "prost 0.12.3", + "prost 0.12.6", "prost-types", "serde", "serde-value", @@ -7913,18 +9089,18 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" dependencies = [ - "prost 0.12.3", + "prost 0.12.6", ] [[package]] name = "protobuf" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b55bad9126f378a853655831eb7363b7b01b81d19f8cb1218861086ca4a1a61e" +checksum = "b65f4a8ec18723a734e5dc09c173e0abf9690432da5340285d536edcb4dac190" dependencies = [ "once_cell", "protobuf-support", @@ -7933,9 +9109,9 @@ dependencies = [ [[package]] name = "protobuf-support" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5d4d7b8601c814cfb36bcebb79f0e61e45e1e93640cf778837833bbed05c372" +checksum = "6872f4d4f4b98303239a2b5838f5bbbb77b01ffc892d627957f37a22d7cfe69c" dependencies = [ "thiserror", ] @@ -7947,8 +9123,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00bb76c5f6221de491fe2c8f39b106330bbd9762c6511119c07940e10eb9ff11" dependencies = [ "bytes", - "miette", - "prost 0.12.3", + "miette 5.10.0", + "prost 0.12.6", "prost-reflect", "prost-types", "protox-parse", @@ -7962,7 +9138,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b4581f441c58863525a3e6bec7b8de98188cf75239a56c725a3e7288450a33f" dependencies = [ "logos", - "miette", + "miette 5.10.0", "prost-types", "thiserror", ] @@ -7993,18 +9169,18 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "memchr", "unicase", ] [[package]] name = "pulldown-cmark" -version = "0.10.0" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce76ce678ffc8e5675b22aa1405de0b7037e2fdf8913fea40d1926c6fe1e6e7" +checksum = "76979bea66e7875e7509c4ec5300112b316af87fa7a252ca91c448b32dfe3993" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "memchr", "pulldown-cmark-escape", "unicase", @@ -8012,9 +9188,9 @@ dependencies = [ [[package]] name = "pulldown-cmark-escape" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d8f9aa0e3cbcfaf8bf00300004ee3b72f74770f9cbac93f6928771f613276b" +checksum = "bd348ff538bc9caeda7ee8cad2d1d48236a1f443c1fa3913c6a02fe0043b1dd3" [[package]] name = "quick-error" @@ -8031,11 +9207,67 @@ dependencies = [ "byteorder", ] +[[package]] +name = "quick-xml" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cc440ee4802a86e357165021e3e255a9143724da31db1e2ea540214c96a0f82" +dependencies = [ + "memchr", +] + +[[package]] +name = "quinn" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ceeeeabace7857413798eb1ffa1e9c905a9946a57d81fb69b4b71c4d8eb3ad" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.23.10", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddf517c03a109db8100448a4be38d498df8a210a99fe0e1b9eaf39e78c640efe" +dependencies = [ + "bytes", + "rand 0.8.5", + "ring 0.17.8", + "rustc-hash", + "rustls 0.23.10", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9096629c45860fc7fb143e125eb826b5e721e10be3263160c7d60ca832cf8c46" +dependencies = [ + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.52.0", +] + [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -8149,7 +9381,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.12", + "getrandom 0.2.15", ] [[package]] @@ -8172,27 +9404,29 @@ dependencies = [ [[package]] name = "ratatui" -version = "0.24.0" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ebc917cfb527a566c37ecb94c7e3fd098353516fb4eb6bea17015ade0182425" +checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "cassowary", + "compact_str", "crossterm", - "indoc", - "itertools 0.11.0", + "itertools 0.12.1", "lru", "paste", - "strum 0.25.0", + "stability", + "strum 0.26.3", "unicode-segmentation", + "unicode-truncate", "unicode-width", ] [[package]] name = "rayon" -version = "1.8.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -8205,7 +9439,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque 0.8.5", - "crossbeam-utils 0.8.19", + "crossbeam-utils 0.8.20", ] [[package]] @@ -8217,6 +9451,12 @@ dependencies = [ "rand_core 0.3.1", ] +[[package]] +name = "recvmsg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" + [[package]] name = "redox_syscall" version = "0.2.16" @@ -8236,40 +9476,35 @@ dependencies = [ ] [[package]] -name = "redox_users" -version = "0.4.4" +name = "redox_syscall" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" dependencies = [ - "getrandom 0.2.12", - "libredox", - "thiserror", + "bitflags 2.6.0", ] [[package]] -name = "reference-trie" -version = "0.25.0" +name = "redox_users" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f63dfce83d1e0e80cf2dc5222df5c2bc30992b30c44d1e66e7c9793d3a418e4" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" dependencies = [ - "hash-db", - "hash256-std-hasher", - "keccak-hasher", - "parity-scale-codec 3.6.9", - "trie-db", - "trie-root", + "getrandom 0.2.15", + "libredox", + "thiserror", ] [[package]] name = "regex" -version = "1.10.3" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.5", - "regex-syntax 0.8.2", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", ] [[package]] @@ -8283,32 +9518,32 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax 0.8.4", ] [[package]] -name = "regex-syntax" -version = "0.6.29" +name = "regex-lite" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "rend" @@ -8321,55 +9556,111 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.24" +version = "0.11.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" dependencies = [ + "async-compression", "base64 0.21.7", "bytes", "encoding_rs", "futures-core", "futures-util", "h2", - "http", - "http-body", - "hyper", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.29", "hyper-rustls 0.24.2", - "hyper-tls", + "hyper-tls 0.5.0", "ipnet", "js-sys", "log", "mime", + "mime_guess", "native-tls", "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.10", + "rustls 0.21.12", "rustls-native-certs 0.6.3", "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 0.1.2", "system-configuration", "tokio", "tokio-native-tls", "tokio-rustls 0.24.1", + "tokio-util 0.7.11", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", "webpki-roots 0.25.4", - "winreg", + "winreg 0.50.0", +] + +[[package]] +name = "reqwest" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.4.0", + "hyper-rustls 0.27.2", + "hyper-tls 0.6.0", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.10", + "rustls-native-certs 0.7.1", + "rustls-pemfile 2.1.2", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "tokio", + "tokio-native-tls", + "tokio-rustls 0.26.0", + "tokio-util 0.7.11", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "webpki-roots 0.26.3", + "winreg 0.52.0", ] [[package]] name = "revm" -version = "3.5.0" -source = "git+https://github.com/bluealloy/revm?branch=reth_freeze#ba28a42393604beeb2da5a339ac47d3d5d3f2271" +version = "9.0.0" +source = "git+https://github.com/bluealloy/revm.git?rev=41e2f7f#41e2f7f9740c0fb70c5ba888a36453712b6de39c" dependencies = [ "auto_impl", + "cfg-if 1.0.0", + "dyn-clone", "revm-interpreter", "revm-precompile", "serde", @@ -8379,20 +9670,23 @@ dependencies = [ [[package]] name = "revm-inspectors" version = "0.1.0" -source = "git+https://github.com/paradigmxyz/evm-inspectors?rev=e90052361276aebcdc67cb24d8e2c4d907b6d299#e90052361276aebcdc67cb24d8e2c4d907b6d299" +source = "git+https://github.com/paradigmxyz/revm-inspectors?rev=4fe17f0#4fe17f08797450d9d5df315e724d14c9f3749b3f" dependencies = [ "alloy-primitives", - "alloy-rpc-trace-types", "alloy-rpc-types", "alloy-sol-types", + "anstyle", + "colorchoice", "revm", "serde", + "serde_json", + "thiserror", ] [[package]] name = "revm-interpreter" -version = "1.3.0" -source = "git+https://github.com/bluealloy/revm?branch=reth_freeze#ba28a42393604beeb2da5a339ac47d3d5d3f2271" +version = "5.0.0" +source = "git+https://github.com/bluealloy/revm.git?rev=41e2f7f#41e2f7f9740c0fb70c5ba888a36453712b6de39c" dependencies = [ "revm-primitives", "serde", @@ -8400,35 +9694,38 @@ dependencies = [ [[package]] name = "revm-precompile" -version = "2.2.0" -source = "git+https://github.com/bluealloy/revm?branch=reth_freeze#ba28a42393604beeb2da5a339ac47d3d5d3f2271" +version = "7.0.0" +source = "git+https://github.com/bluealloy/revm.git?rev=41e2f7f#41e2f7f9740c0fb70c5ba888a36453712b6de39c" dependencies = [ "aurora-engine-modexp", "c-kzg", - "k256 0.13.1", + "k256 0.13.3", "once_cell", + "p256", "revm-primitives", "ripemd", - "secp256k1 0.28.2", + "secp256k1 0.29.0", "sha2 0.10.8", "substrate-bn", ] [[package]] name = "revm-primitives" -version = "1.3.0" -source = "git+https://github.com/bluealloy/revm?branch=reth_freeze#ba28a42393604beeb2da5a339ac47d3d5d3f2271" +version = "4.0.0" +source = "git+https://github.com/bluealloy/revm.git?rev=41e2f7f#41e2f7f9740c0fb70c5ba888a36453712b6de39c" dependencies = [ "alloy-primitives", - "alloy-rlp", "auto_impl", - "bitflags 2.4.2", + "bitflags 2.6.0", "bitvec 1.0.1", "c-kzg", "cfg-if 1.0.0", + "derive_more 0.99.18", + "dyn-clone", "enumn", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "hex", + "once_cell", "serde", ] @@ -8439,7 +9736,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ "crypto-bigint 0.4.9", - "hmac 0.12.1", + "hmac", "zeroize", ] @@ -8449,7 +9746,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "hmac 0.12.1", + "hmac", "subtle", ] @@ -8476,7 +9773,7 @@ checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if 1.0.0", - "getrandom 0.2.12", + "getrandom 0.2.15", "libc", "spin 0.9.8", "untrusted 0.9.0", @@ -8507,7 +9804,7 @@ dependencies = [ "rkyv_derive", "seahash", "tinyvec", - "uuid 1.7.0", + "uuid 1.9.1", ] [[package]] @@ -8602,9 +9899,9 @@ dependencies = [ [[package]] name = "ruint" -version = "1.11.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608a5726529f2f0ef81b8fde9873c4bb829d6b5b5ca6be4d97345ddf0749c825" +checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286" dependencies = [ "alloy-rlp", "arbitrary", @@ -8614,7 +9911,7 @@ dependencies = [ "fastrlp", "num-bigint", "num-traits", - "parity-scale-codec 3.6.9", + "parity-scale-codec 3.6.12", "primitive-types 0.12.2", "proptest", "rand 0.8.5", @@ -8627,103 +9924,20 @@ dependencies = [ [[package]] name = "ruint-macro" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e666a5496a0b2186dbcd0ff6106e29e093c15591bde62c20d3842007c6978a09" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" [[package]] name = "rusb" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45fff149b6033f25e825cbb7b2c625a11ee8e6dac09264d49beb125e39aa97bf" +checksum = "ab9f9ff05b63a786553a4c02943b74b34a988448671001e9a27e2f0565cc05a4" dependencies = [ "libc", "libusb1-sys", ] -[[package]] -name = "rusoto_core" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1db30db44ea73551326269adcf7a2169428a054f14faf9e1768f2163494f2fa2" -dependencies = [ - "async-trait", - "base64 0.13.1", - "bytes", - "crc32fast", - "futures 0.3.30", - "http", - "hyper", - "hyper-rustls 0.23.2", - "lazy_static", - "log", - "rusoto_credential", - "rusoto_signature", - "rustc_version 0.4.0", - "serde", - "serde_json", - "tokio", - "xml-rs", -] - -[[package]] -name = "rusoto_credential" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee0a6c13db5aad6047b6a44ef023dbbc21a056b6dab5be3b79ce4283d5c02d05" -dependencies = [ - "async-trait", - "chrono", - "dirs-next", - "futures 0.3.30", - "hyper", - "serde", - "serde_json", - "shlex", - "tokio", - "zeroize", -] - -[[package]] -name = "rusoto_kms" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e1fc19cfcfd9f6b2f96e36d5b0dddda9004d2cbfc2d17543e3b9f10cc38fce8" -dependencies = [ - "async-trait", - "bytes", - "futures 0.3.30", - "rusoto_core", - "serde", - "serde_json", -] - -[[package]] -name = "rusoto_signature" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ae95491c8b4847931e291b151127eccd6ff8ca13f33603eb3d0035ecb05272" -dependencies = [ - "base64 0.13.1", - "bytes", - "chrono", - "digest 0.9.0", - "futures 0.3.30", - "hex", - "hmac 0.11.0", - "http", - "hyper", - "log", - "md-5 0.9.1", - "percent-encoding", - "pin-project-lite", - "rusoto_credential", - "rustc_version 0.4.0", - "serde", - "sha2 0.9.9", - "tokio", -] - [[package]] name = "rust_decimal" version = "1.35.0" @@ -8742,9 +9956,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" @@ -8773,16 +9987,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.22", + "semver 1.0.23", ] [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", @@ -8791,38 +10005,40 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.9" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring 0.16.20", + "ring 0.17.8", + "rustls-webpki 0.101.7", "sct", - "webpki", ] [[package]] name = "rustls" -version = "0.21.10" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", "ring 0.17.8", - "rustls-webpki 0.101.7", - "sct", + "rustls-pki-types", + "rustls-webpki 0.102.5", + "subtle", + "zeroize", ] [[package]] name = "rustls" -version = "0.22.3" +version = "0.23.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99008d7ad0bbbea527ec27bddbc0e432c5b87d8175178cee68d2eec9c4a1813c" +checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" dependencies = [ - "log", + "once_cell", "ring 0.17.8", "rustls-pki-types", - "rustls-webpki 0.102.2", + "rustls-webpki 0.102.5", "subtle", "zeroize", ] @@ -8841,9 +10057,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +checksum = "a88d6d420651b496bdd98684116959239430022a115c1240e6c3993be0b15fba" dependencies = [ "openssl-probe", "rustls-pemfile 2.1.2", @@ -8867,15 +10083,15 @@ version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64 0.22.0", + "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.4.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" [[package]] name = "rustls-webpki" @@ -8889,9 +10105,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.2" +version = "0.102.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +checksum = "f9a6fccd794a42c2c105b513a2f62bc3fd8f3ba57a4593677ceb0bd035164d78" dependencies = [ "ring 0.17.8", "rustls-pki-types", @@ -8900,9 +10116,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.14" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "rusty-fork" @@ -8922,7 +10138,7 @@ version = "12.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "994eca4bca05c87e86e15d90fc7a91d1be64b4482b38cb2d27474568fe7c9db9" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "cfg-if 1.0.0", "clipboard-win", "fd-lock 3.0.13", @@ -8941,9 +10157,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "salsa20" @@ -8965,26 +10181,35 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.10.0" +version = "2.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7d66a1128282b7ef025a8ead62a4a9fcf017382ec53b8ffbf4d7bf77bd3c60" +checksum = "eca070c12893629e2cc820a9761bedf6ce1dcddc9852984d1dc734b8bd9bd024" dependencies = [ "cfg-if 1.0.0", - "derive_more 0.99.17", - "parity-scale-codec 3.6.9", + "derive_more 0.99.18", + "parity-scale-codec 3.6.12", "scale-info-derive", ] [[package]] -name = "scale-info-derive" -version = "2.10.0" +name = "scale-info-derive" +version = "2.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d35494501194174bda522a32605929eefc9ecf7e0a326c26db1fdd85881eb62" +dependencies = [ + "proc-macro-crate 3.1.0", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "scc" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf2c68b89cafb3b8d918dd07b42be0da66ff202cf1155c5739a4e0c1ea0dc19" +checksum = "af947d0ca10a2f3e00c7ec1b515b7c83e5cb3fa62d4c11a64301d9eec54440e9" dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "syn 1.0.109", + "sdd", ] [[package]] @@ -8998,9 +10223,9 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.16" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a28f4c49489add4ce10783f7911893516f15afe45d015608d41faca6bc4d29" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" dependencies = [ "dyn-clone", "schemars_derive", @@ -9010,14 +10235,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.16" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c767fd6fa65d9ccf9cf026122c1b555f2ef9a4f0cea69da4d7dbc3e258d30967" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 1.0.109", + "syn 2.0.68", ] [[package]] @@ -9032,7 +10257,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d" dependencies = [ - "hmac 0.12.1", + "hmac", "pbkdf2 0.11.0", "salsa20", "sha2 0.10.8", @@ -9048,6 +10273,12 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "sdd" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84345e4c9bd703274a082fb80caaa99b7612be48dfaa1dd9266577ec412309d" + [[package]] name = "seahash" version = "4.1.0" @@ -9075,7 +10306,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct 0.2.0", - "der 0.7.8", + "der 0.7.9", "generic-array", "pkcs8 0.10.2", "subtle", @@ -9093,11 +10324,12 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.28.2" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" +checksum = "0e0cc0f1cf93f4969faf3ea1c7d8a9faed25918d96affa959720823dfe86d4f3" dependencies = [ - "secp256k1-sys 0.9.2", + "rand 0.8.5", + "secp256k1-sys 0.10.0", ] [[package]] @@ -9111,9 +10343,9 @@ dependencies = [ [[package]] name = "secp256k1-sys" -version = "0.9.2" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb" +checksum = "1433bd67156263443f14d603720b082dd3121779323fce20cba2aa07b874bc1b" dependencies = [ "cc", ] @@ -9127,13 +10359,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "secret-vault-value" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5f8cfb86d2019f64a4cfb49e499f401f406fbec946c1ffeea9d0504284347de" +dependencies = [ + "prost 0.12.6", + "prost-types", + "serde", + "serde_json", + "zeroize", +] + [[package]] name = "security-framework" -version = "2.9.2" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", @@ -9142,9 +10387,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", @@ -9161,9 +10406,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" dependencies = [ "serde", ] @@ -9197,7 +10442,7 @@ checksum = "6ce4b57f1b521f674df7a1d200be8ff5d74e3712020ee25b553146657b5377d5" dependencies = [ "httpdate", "native-tls", - "reqwest", + "reqwest 0.11.27", "sentry-backtrace", "sentry-contexts", "sentry-core", @@ -9294,7 +10539,7 @@ dependencies = [ "thiserror", "time", "url", - "uuid 1.7.0", + "uuid 1.9.1", ] [[package]] @@ -9305,9 +10550,9 @@ checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] @@ -9324,33 +10569,33 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] name = "serde_derive_internals" -version = "0.26.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85bf8229e7920a9f636479437026331ce11aa132b4dde37d121944a44d6e5f3c" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.68", ] [[package]] name = "serde_json" -version = "1.0.114" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.2.6", "itoa", "ryu", "serde", @@ -9358,9 +10603,9 @@ dependencies = [ [[package]] name = "serde_path_to_error" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd154a240de39fdebcf5775d2675c204d7c13cf39a4c697be6493c8e734337c" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" dependencies = [ "itoa", "serde", @@ -9378,20 +10623,20 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] name = "serde_spanned" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" dependencies = [ "serde", ] @@ -9414,7 +10659,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.2.6", "itoa", "ryu", "serde", @@ -9423,27 +10668,27 @@ dependencies = [ [[package]] name = "serial_test" -version = "3.0.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ad9342b3aaca7cb43c45c097dd008d4907070394bd0751a0aa8817e5a018d" +checksum = "4b4b487fe2acf240a021cf57c6b2b4903b1e78ca0ecd862a71b71d2a51fed77d" dependencies = [ - "dashmap", "futures 0.3.30", - "lazy_static", "log", - "parking_lot 0.12.1", + "once_cell", + "parking_lot 0.12.3", + "scc", "serial_test_derive", ] [[package]] name = "serial_test_derive" -version = "3.0.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b93fb4adc70021ac1b47f7d45e8cc4169baaa7ea58483bc5b721d19a26202212" +checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] @@ -9510,6 +10755,19 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha256" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18278f6a914fa3070aa316493f7d2ddfb9ac86ebc06fa3b83bffda487e9065b0" +dependencies = [ + "async-trait", + "bytes", + "hex", + "sha2 0.10.8", + "tokio", +] + [[package]] name = "sha3" version = "0.9.1" @@ -9543,9 +10801,9 @@ dependencies = [ [[package]] name = "sha3-asm" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bac61da6b35ad76b195eb4771210f947734321a8d81d7738e1580d953bc7a15e" +checksum = "a9b57fd861253bff08bb1919e995f90ba8f4889de2726091c8876f3a4e823b40" dependencies = [ "cc", "cfg-if 1.0.0", @@ -9595,9 +10853,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -9622,6 +10880,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "simdutf8" version = "0.1.4" @@ -9630,9 +10894,32 @@ checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" [[package]] name = "similar" -version = "2.4.0" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa42c91313f1d05da9b26f267f931cf178d4aba455b4c4622dd7355eb80c6640" +dependencies = [ + "bstr 0.2.17", + "unicode-segmentation", +] + +[[package]] +name = "similar-asserts" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e041bb827d1bfca18f213411d51b665309f1afb37a04a5d1464530e13779fc0f" +dependencies = [ + "console", + "similar", +] + +[[package]] +name = "simple-home-dir" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21" +checksum = "c221cbc8c1ff6bdf949b12cc011456c510ec6840654b444c7374c78e928ce344" +dependencies = [ + "windows-sys 0.52.0", +] [[package]] name = "simple_asn1" @@ -9678,18 +10965,18 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" dependencies = [ "serde", ] [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -9704,7 +10991,7 @@ dependencies = [ "base64 0.13.1", "bytes", "futures 0.3.30", - "http", + "http 0.2.12", "httparse", "log", "rand 0.8.5", @@ -9720,11 +11007,41 @@ dependencies = [ "itertools 0.11.0", "lalrpop", "lalrpop-util", - "phf 0.11.2", + "phf", "thiserror", "unicode-xid", ] +[[package]] +name = "soldeer" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef46372c17d5650cb18b7f374c45732334fa0867de6c7f14c1fc6973559cd3ff" +dependencies = [ + "chrono", + "clap", + "email-address-parser", + "futures 0.3.30", + "once_cell", + "regex", + "reqwest 0.12.5", + "rpassword", + "serde", + "serde_derive", + "serde_json", + "sha256", + "simple-home-dir", + "tokio", + "toml 0.8.14", + "toml_edit 0.22.14", + "uuid 1.9.1", + "walkdir", + "yansi 1.0.1", + "yash-fnmatch", + "zip 2.1.3", + "zip-extract", +] + [[package]] name = "spin" version = "0.5.2" @@ -9740,15 +11057,6 @@ dependencies = [ "lock_api", ] -[[package]] -name = "spinning" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d4f0e86297cad2658d92a707320d87bf4e6ae1050287f51d19b67ef3f153a7b" -dependencies = [ - "lock_api", -] - [[package]] name = "spki" version = "0.6.0" @@ -9766,16 +11074,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", - "der 0.7.8", + "der 0.7.9", ] [[package]] name = "sqlformat" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c" +checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f" dependencies = [ - "itertools 0.12.1", "nom", "unicode_categories", ] @@ -9799,13 +11106,13 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" dependencies = [ - "ahash 0.8.9", + "ahash 0.8.11", "atoi", "bigdecimal", "byteorder", "bytes", "chrono", - "crc 3.0.1", + "crc", "crossbeam-queue 0.3.11", "either", "event-listener 2.5.3", @@ -9816,7 +11123,7 @@ dependencies = [ "futures-util", "hashlink", "hex", - "indexmap 2.2.3", + "indexmap 2.2.6", "ipnetwork", "log", "memchr", @@ -9885,11 +11192,11 @@ dependencies = [ "atoi", "base64 0.21.7", "bigdecimal", - "bitflags 2.4.2", + "bitflags 2.6.0", "byteorder", "bytes", "chrono", - "crc 3.0.1", + "crc", "digest 0.10.7", "dotenvy", "either", @@ -9900,10 +11207,10 @@ dependencies = [ "generic-array", "hex", "hkdf", - "hmac 0.12.1", + "hmac", "itoa", "log", - "md-5 0.10.6", + "md-5", "memchr", "once_cell", "percent-encoding", @@ -9930,10 +11237,10 @@ dependencies = [ "atoi", "base64 0.21.7", "bigdecimal", - "bitflags 2.4.2", + "bitflags 2.6.0", "byteorder", "chrono", - "crc 3.0.1", + "crc", "dotenvy", "etcetera", "futures-channel", @@ -9942,12 +11249,12 @@ dependencies = [ "futures-util", "hex", "hkdf", - "hmac 0.12.1", + "hmac", "home", "ipnetwork", "itoa", "log", - "md-5 0.10.6", + "md-5", "memchr", "num-bigint", "once_cell", @@ -9988,6 +11295,16 @@ dependencies = [ "urlencoding", ] +[[package]] +name = "stability" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" +dependencies = [ + "quote", + "syn 2.0.68", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -10014,7 +11331,7 @@ checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" dependencies = [ "new_debug_unreachable", "once_cell", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "phf_shared 0.10.0", "precomputed-hash", "serde", @@ -10034,13 +11351,13 @@ dependencies = [ [[package]] name = "stringprep" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" dependencies = [ - "finl_unicode", "unicode-bidi", "unicode-normalization", + "unicode-properties", ] [[package]] @@ -10051,9 +11368,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strsim" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "strum" @@ -10066,20 +11383,11 @@ dependencies = [ [[package]] name = "strum" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" -dependencies = [ - "strum_macros 0.25.3", -] - -[[package]] -name = "strum" -version = "0.26.1" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ - "strum_macros 0.26.1", + "strum_macros 0.26.4", ] [[package]] @@ -10097,28 +11405,15 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.25.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.50", -] - -[[package]] -name = "strum_macros" -version = "0.26.1" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "proc-macro2", "quote", "rustversion", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] @@ -10136,9 +11431,9 @@ dependencies = [ [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "svm-rs" @@ -10150,40 +11445,47 @@ dependencies = [ "fs2", "hex", "once_cell", - "reqwest", - "semver 1.0.22", + "reqwest 0.11.27", + "semver 1.0.23", "serde", "serde_json", "sha2 0.10.8", "thiserror", "url", - "zip", + "zip 0.6.6", ] [[package]] -name = "svm-rs-builds" -version = "0.2.3" +name = "svm-rs" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa64b5e8eecd3a8af7cfc311e29db31a268a62d5953233d3e8243ec77a71c4e3" +checksum = "af5910befd515534a92e9424f250d952fe6f6dba6a92bd001dfeba1fb4a2f87c" dependencies = [ - "build_const", - "hex", - "semver 1.0.22", + "const-hex", + "dirs 5.0.1", + "fs4", + "once_cell", + "reqwest 0.12.5", + "semver 1.0.23", + "serde", "serde_json", - "svm-rs", + "sha2 0.10.8", + "thiserror", + "url", + "zip 2.1.3", ] [[package]] name = "svm-rs-builds" -version = "0.3.5" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8d3c94c4d3337336f58493471b98d712c267c66977b0fbe48efd6cbf69ffd0" +checksum = "3d5ea000fdbeab0b2739315f9093c75ea63030e5c44f92daa72401d11b48adda" dependencies = [ "build_const", - "hex", - "semver 1.0.22", + "const-hex", + "semver 1.0.23", "serde_json", - "svm-rs", + "svm-rs 0.5.4", ] [[package]] @@ -10199,9 +11501,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.50" +version = "2.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" +checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" dependencies = [ "proc-macro2", "quote", @@ -10210,14 +11512,14 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "0.6.3" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e656cbcef8a77543b5accbd76f60f9e0bc4be364b0aba4263a6f313f8a355511" +checksum = "8d71e19bca02c807c9faa67b5a47673ff231b6e7449b251695188522f1dc44b2" dependencies = [ "paste", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] @@ -10229,7 +11531,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] @@ -10239,16 +11541,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] -name = "synstructure" -version = "0.12.6" +name = "sync_wrapper" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "unicode-xid", -] +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" [[package]] name = "system-configuration" @@ -10285,9 +11581,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.10.0" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if 1.0.0", "fastrand", @@ -10345,28 +11641,34 @@ dependencies = [ "dirs 4.0.0", "fnv", "nom", - "phf 0.11.2", - "phf_codegen 0.11.2", + "phf", + "phf_codegen", ] +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] @@ -10388,11 +11690,31 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "tikv-jemalloc-sys" +version = "0.5.4+5.3.0-patched" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9402443cb8fd499b6f327e40565234ff34dbda27460c5b47db0db77443dd85d1" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "tikv-jemallocator" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965fe0c26be5c56c94e38ba547249074803efd52adfb66de62107d95aab3eaca" +dependencies = [ + "libc", + "tikv-jemalloc-sys", +] + [[package]] name = "time" -version = "0.3.34" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", @@ -10413,9 +11735,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", @@ -10451,9 +11773,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "ce6b6a2fb3a985e99cebfaefa9faa3024743da73304ca1c683a36429613d3d22" dependencies = [ "tinyvec_macros", ] @@ -10464,24 +11786,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" -[[package]] -name = "to_method" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c4ceeeca15c8384bbc3e011dbd8fccb7f068a440b752b7d9b32ceb0ca0e2e8" - [[package]] name = "tokio" -version = "1.36.0" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ "backtrace", "bytes", "libc", "mio", "num_cpus", - "parking_lot 0.12.1", + "parking_lot 0.12.3", "pin-project-lite", "signal-hook-registry", "socket2", @@ -10501,13 +11817,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] @@ -10522,45 +11838,46 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.23.4" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.20.9", + "rustls 0.21.12", "tokio", - "webpki", ] [[package]] name = "tokio-rustls" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ - "rustls 0.21.10", + "rustls 0.22.4", + "rustls-pki-types", "tokio", ] [[package]] name = "tokio-rustls" -version = "0.25.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.22.3", + "rustls 0.23.10", "rustls-pki-types", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", "tokio", + "tokio-util 0.7.11", ] [[package]] @@ -10571,15 +11888,41 @@ checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", "log", - "native-tls", - "rustls 0.21.10", + "rustls 0.21.12", "tokio", - "tokio-native-tls", "tokio-rustls 0.24.1", - "tungstenite", + "tungstenite 0.20.1", "webpki-roots 0.25.4", ] +[[package]] +name = "tokio-tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite 0.21.0", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" +dependencies = [ + "futures-util", + "log", + "rustls 0.23.10", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.0", + "tungstenite 0.23.0", + "webpki-roots 0.26.3", +] + [[package]] name = "tokio-util" version = "0.6.10" @@ -10596,9 +11939,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", @@ -10606,7 +11949,6 @@ dependencies = [ "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] @@ -10620,22 +11962,22 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.10" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" +checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.6", + "toml_edit 0.22.14", ] [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" dependencies = [ "serde", ] @@ -10646,7 +11988,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.2.6", "toml_datetime", "winnow 0.5.40", ] @@ -10657,7 +11999,7 @@ version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.2.6", "toml_datetime", "winnow 0.5.40", ] @@ -10668,22 +12010,22 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.2.6", "toml_datetime", "winnow 0.5.40", ] [[package]] name = "toml_edit" -version = "0.22.6" +version = "0.22.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1b5fd4128cc8d3e0cb74d4ed9a9cc7c7284becd4df68f5f940e1ad123606f6" +checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" dependencies = [ - "indexmap 2.2.3", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.2", + "winnow 0.6.13", ] [[package]] @@ -10693,18 +12035,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3082666a3a6433f7f511c7192923fa1fe07c69332d3c6a2e6bb040b569199d5a" dependencies = [ "async-trait", - "axum", + "axum 0.6.20", "base64 0.21.7", "bytes", "futures-core", "futures-util", "h2", - "http", - "http-body", - "hyper", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.29", "hyper-timeout", "percent-encoding", - "pin-project", + "pin-project 1.1.5", "prost 0.11.9", "tokio", "tokio-stream", @@ -10714,6 +12056,37 @@ dependencies = [ "tracing", ] +[[package]] +name = "tonic" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13" +dependencies = [ + "async-stream", + "async-trait", + "axum 0.6.20", + "base64 0.21.7", + "bytes", + "h2", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.29", + "hyper-timeout", + "percent-encoding", + "pin-project 1.1.5", + "prost 0.12.6", + "rustls-native-certs 0.7.1", + "rustls-pemfile 2.1.2", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.25.0", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "topological-sort" version = "0.2.2" @@ -10729,12 +12102,12 @@ dependencies = [ "futures-core", "futures-util", "indexmap 1.9.3", - "pin-project", + "pin-project 1.1.5", "pin-project-lite", "rand 0.8.5", "slab", "tokio", - "tokio-util 0.7.10", + "tokio-util 0.7.11", "tower-layer", "tower-service", "tracing", @@ -10742,16 +12115,16 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.4.4" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "bytes", - "futures-core", "futures-util", - "http", - "http-body", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", "http-range-header", "httpdate", "mime", @@ -10759,7 +12132,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "tokio", - "tokio-util 0.7.10", + "tokio-util 0.7.11", "tower-layer", "tower-service", "tracing", @@ -10775,7 +12148,19 @@ checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" name = "tower-service" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tower-util" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1093c19826d33807c72511e68f73b4a0469a3f22c2bd5f7d5212178b4b89674" +dependencies = [ + "futures-core", + "futures-util", + "pin-project 0.4.30", + "tower-service", +] [[package]] name = "tracing" @@ -10797,7 +12182,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] @@ -10826,7 +12211,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" dependencies = [ - "pin-project", + "pin-project 1.1.5", "tracing", ] @@ -10902,13 +12287,12 @@ dependencies = [ [[package]] name = "trezor-client" -version = "0.1.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cddb76a030b141d9639470eca2a236f3057a651bba78227cfa77830037a8286" +checksum = "f62c95b37f6c769bd65a0d0beb8b2b003e72998003b896a616a6777c645c05ed" dependencies = [ "byteorder", "hex", - "primitive-types 0.12.2", "protobuf", "rusb", "thiserror", @@ -10916,67 +12300,73 @@ dependencies = [ ] [[package]] -name = "trie-db" -version = "0.23.1" +name = "triomphe" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32d034c0d3db64b43c31de38e945f15b40cd4ca6d2dcfc26d4798ce8de4ab83" -dependencies = [ - "hash-db", - "hashbrown 0.12.3", - "log", - "rustc-hex", - "smallvec", -] +checksum = "e6631e42e10b40c0690bf92f404ebcfe6e1fdb480391d15f17cc8e96eeed5369" [[package]] -name = "trie-root" -version = "0.17.0" +name = "try-lock" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a36c5ca3911ed3c9a5416ee6c679042064b93fc637ded67e25f92e68d783891" -dependencies = [ - "hash-db", -] +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] -name = "triehash" -version = "0.8.4" +name = "tungstenite" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1631b201eb031b563d2e85ca18ec8092508e262a3196ce9bd10a67ec87b9f5c" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" dependencies = [ - "hash-db", - "rlp", + "byteorder", + "bytes", + "data-encoding", + "http 0.2.12", + "httparse", + "log", + "rand 0.8.5", + "rustls 0.21.12", + "sha1", + "thiserror", + "url", + "utf-8", ] [[package]] -name = "triomphe" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3" - -[[package]] -name = "try-lock" -version = "0.2.5" +name = "tungstenite" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.1.0", + "httparse", + "log", + "rand 0.8.5", + "sha1", + "thiserror", + "url", + "utf-8", +] [[package]] name = "tungstenite" -version = "0.20.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" +checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" dependencies = [ "byteorder", "bytes", "data-encoding", - "http", + "http 1.1.0", "httparse", "log", - "native-tls", "rand 0.8.5", - "rustls 0.21.10", + "rustls 0.23.10", + "rustls-pki-types", "sha1", "thiserror", - "url", "utf-8", ] @@ -11065,17 +12455,33 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-properties" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" + [[package]] name = "unicode-segmentation" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +[[package]] +name = "unicode-truncate" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5fbabedabe362c618c714dbefda9927b5afc8e2a8102f47f081089a9019226" +dependencies = [ + "itertools 0.12.1", + "unicode-width", +] + [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "unicode-xid" @@ -11119,11 +12525,11 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "2.9.6" +version = "2.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11f214ce18d8b2cbe84ed3aa6486ed3f5b285cf8d8fbdbce9f3f767a724adc35" +checksum = "d11a831e3c0b56e438a28308e7c810799e3c118417f342d30ecec080105395cd" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "log", "native-tls", "once_cell", @@ -11132,9 +12538,9 @@ dependencies = [ [[package]] name = "url" -version = "2.5.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", @@ -11156,9 +12562,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" @@ -11166,16 +12572,17 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.12", + "getrandom 0.2.15", "serde", ] [[package]] name = "uuid" -version = "1.7.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +checksum = "5de17fd2f7da591098415cff336e12965a28061ddace43b59cb3c430179c9439" dependencies = [ + "getrandom 0.2.15", "serde", ] @@ -11229,7 +12636,7 @@ source = "git+https://github.com/matter-labs/vise.git?rev=a5bb80c9ce7168663114ee dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] @@ -11249,6 +12656,12 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "wait-timeout" version = "0.2.0" @@ -11260,9 +12673,9 @@ dependencies = [ [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -11289,11 +12702,17 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + [[package]] name = "wasm-bindgen" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -11301,24 +12720,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -11328,9 +12747,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -11338,89 +12757,107 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.91" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "wasm-streams" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" +checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] [[package]] name = "watchexec" -version = "2.3.2" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5931215e14de2355a3039477138ae08a905abd7ad0265519fd79717ff1380f88" +checksum = "c635816bdb583dcd1cf58935899df38b5c5ffb1b9d0cc89f8d3c7b33e2c005e3" dependencies = [ "async-priority-channel", "async-recursion", "atomic-take", - "clearscreen", - "command-group", "futures 0.3.30", "ignore-files", - "miette", - "nix 0.26.4", + "miette 7.2.0", + "nix 0.28.0", "normalize-path", "notify", "once_cell", + "process-wrap", "project-origins", "thiserror", "tokio", "tracing", "watchexec-events", "watchexec-signals", + "watchexec-supervisor", ] [[package]] name = "watchexec-events" -version = "1.0.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01603bbe02fd75918f010dadad456d47eda14fb8fdcab276b0b4b8362f142ae3" +checksum = "1ce015ba32ff91a7f796cea3798e7998d3645411f03fc373ef0e7c7e564291bc" dependencies = [ - "nix 0.26.4", + "nix 0.28.0", "notify", "watchexec-signals", ] [[package]] name = "watchexec-signals" -version = "1.0.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2a5df96c388901c94ca04055fcd51d4196ca3e971c5e805bd4a4b61dd6a7e5" +checksum = "8f7ccc54db7df8cbbe3251508321e46986ce179af4c4a03b4c70bda539d72755" dependencies = [ - "miette", - "nix 0.26.4", + "miette 7.2.0", + "nix 0.28.0", "thiserror", ] [[package]] -name = "web-sys" -version = "0.3.68" +name = "watchexec-supervisor" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" +checksum = "b97efb9292bebdf72a777a0d6e400b69b32b4f3daee1ddd30214317a18ff20ab" dependencies = [ - "js-sys", - "wasm-bindgen", + "futures 0.3.30", + "nix 0.28.0", + "process-wrap", + "tokio", + "tracing", + "watchexec-events", + "watchexec-signals", ] [[package]] -name = "webpki" -version = "0.22.4" +name = "web-sys" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", + "js-sys", + "wasm-bindgen", ] [[package]] @@ -11431,30 +12868,40 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "0.26.1" +version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" dependencies = [ "rustls-pki-types", ] [[package]] name = "which" -version = "4.4.2" +version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +checksum = "8211e4f58a2b2805adfbefbc07bab82958fc91e3836339b1ab7ae32465dce0d7" dependencies = [ "either", "home", - "once_cell", "rustix", + "winsafe", ] [[package]] name = "whoami" -version = "1.4.1" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +dependencies = [ + "redox_syscall 0.4.1", + "wasite", +] + +[[package]] +name = "widestring" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" +checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" [[package]] name = "winapi" @@ -11474,11 +12921,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -11489,11 +12936,12 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.48.0" +version = "0.56.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132" dependencies = [ - "windows-targets 0.48.5", + "windows-core 0.56.0", + "windows-targets 0.52.6", ] [[package]] @@ -11502,16 +12950,50 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.3", + "windows-targets 0.52.6", ] [[package]] -name = "windows-sys" -version = "0.45.0" +name = "windows-core" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "windows-interface" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.68", +] + +[[package]] +name = "windows-result" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" dependencies = [ - "windows-targets 0.42.2", + "windows-targets 0.52.6", ] [[package]] @@ -11529,22 +13011,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.3", -] - -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets 0.52.6", ] [[package]] @@ -11564,25 +13031,20 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.3" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.3", - "windows_aarch64_msvc 0.52.3", - "windows_i686_gnu 0.52.3", - "windows_i686_msvc 0.52.3", - "windows_x86_64_gnu 0.52.3", - "windows_x86_64_gnullvm 0.52.3", - "windows_x86_64_msvc 0.52.3", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -11591,15 +13053,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -11609,15 +13065,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -11627,15 +13077,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.3" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] -name = "windows_i686_msvc" -version = "0.42.2" +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -11645,15 +13095,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -11663,15 +13107,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -11681,15 +13119,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -11699,9 +13131,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.3" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" @@ -11714,9 +13146,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.2" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a4191c47f15cc3ec71fcb4913cb83d58def65dd3787610213c649283b5ce178" +checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" dependencies = [ "memchr", ] @@ -11731,6 +13163,22 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if 1.0.0", + "windows-sys 0.48.0", +] + +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + [[package]] name = "ws_stream_wasm" version = "0.7.4" @@ -11766,10 +13214,10 @@ dependencies = [ ] [[package]] -name = "xml-rs" -version = "0.8.19" +name = "xmlparser" +version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" [[package]] name = "yansi" @@ -11779,35 +13227,49 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "yansi" -version = "1.0.0-rc.1" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +dependencies = [ + "is-terminal", +] + +[[package]] +name = "yash-fnmatch" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1367295b8f788d371ce2dbc842c7b709c73ee1364d30351dd300ec2203b12377" +checksum = "697c20b479d2e6419e9a073bfdd20e90cbd8540d6c683ee46712e13de650e54f" +dependencies = [ + "regex", + "regex-syntax 0.8.4", + "thiserror", +] [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ "zeroize_derive", ] @@ -11820,7 +13282,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] @@ -11834,15 +13296,43 @@ dependencies = [ "bzip2", "constant_time_eq", "crc32fast", - "crossbeam-utils 0.8.19", + "crossbeam-utils 0.8.20", "flate2", - "hmac 0.12.1", + "hmac", "pbkdf2 0.11.0", "sha1", "time", "zstd", ] +[[package]] +name = "zip" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775a2b471036342aa69bc5a602bc889cb0a06cda00477d0c69566757d5553d39" +dependencies = [ + "arbitrary", + "crc32fast", + "crossbeam-utils 0.8.20", + "displaydoc", + "flate2", + "indexmap 2.2.6", + "memchr", + "thiserror", + "zopfli", +] + +[[package]] +name = "zip-extract" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e109e5a291403b4c1e514d39f8a22d3f98d257e691a52bb1f16051bb1ffed63e" +dependencies = [ + "log", + "thiserror", + "zip 0.6.6", +] + [[package]] name = "zk_evm" version = "1.3.1" @@ -12047,7 +13537,7 @@ name = "zkevm_opcode_defs" version = "1.3.2" source = "git+https://github.com/matter-labs/era-zkevm_opcode_defs.git?branch=v1.3.2#dffacadeccdfdbff4bc124d44c595c4a6eae5013" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "blake2 0.10.6 (git+https://github.com/RustCrypto/hashes.git?rev=1f727ce37ff40fa0cce84eb8543a45bdd3ca4a4e)", "ethereum-types 0.14.1", "k256 0.11.6", @@ -12061,10 +13551,10 @@ name = "zkevm_opcode_defs" version = "1.4.1" source = "git+https://github.com/matter-labs/era-zkevm_opcode_defs.git?branch=v1.4.1#ba8228ff0582d21f64d6a319d50d0aec48e9e7b6" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "blake2 0.10.6 (registry+https://github.com/rust-lang/crates.io-index)", "ethereum-types 0.14.1", - "k256 0.13.1", + "k256 0.13.3", "lazy_static", "sha2 0.10.8", "sha3 0.10.8", @@ -12075,10 +13565,10 @@ name = "zkevm_opcode_defs" version = "1.5.0" source = "git+https://github.com/matter-labs/era-zkevm_opcode_defs.git?branch=v1.5.0#28d2edabf902ea9b08f6a26a4506831fd89346b9" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.6.0", "blake2 0.10.6 (registry+https://github.com/rust-lang/crates.io-index)", "ethereum-types 0.14.1", - "k256 0.13.1", + "k256 0.13.3", "lazy_static", "p256", "serde", @@ -12132,7 +13622,7 @@ source = "git+https://github.com/matter-labs/era-consensus.git?rev=92ecb2d5d65e3 dependencies = [ "anyhow", "once_cell", - "pin-project", + "pin-project 1.1.5", "rand 0.8.5", "sha3 0.10.8", "thiserror", @@ -12187,7 +13677,7 @@ dependencies = [ "bit-vec", "hex", "num-bigint", - "prost 0.12.3", + "prost 0.12.6", "rand 0.8.5", "serde", "thiserror", @@ -12206,7 +13696,7 @@ source = "git+https://github.com/matter-labs/era-consensus.git?rev=92ecb2d5d65e3 dependencies = [ "anyhow", "async-trait", - "prost 0.12.3", + "prost 0.12.6", "rand 0.8.5", "thiserror", "tracing", @@ -12282,7 +13772,7 @@ dependencies = [ "chrono", "hex", "itertools 0.10.5", - "prost 0.12.3", + "prost 0.12.6", "rand 0.8.5", "serde", "serde_json", @@ -12401,7 +13891,7 @@ dependencies = [ "anyhow", "bit-vec", "once_cell", - "prost 0.12.3", + "prost 0.12.6", "prost-reflect", "quick-protobuf", "rand 0.8.5", @@ -12426,7 +13916,7 @@ dependencies = [ "prost-reflect", "protox", "quote", - "syn 2.0.50", + "syn 2.0.68", ] [[package]] @@ -12497,7 +13987,7 @@ dependencies = [ "num", "num_enum 0.7.2", "once_cell", - "prost 0.12.3", + "prost 0.12.6", "rlp", "secp256k1 0.27.0", "serde", @@ -12526,7 +14016,7 @@ dependencies = [ "hex", "itertools 0.10.5", "num", - "reqwest", + "reqwest 0.11.27", "serde", "thiserror", "tokio", @@ -12557,6 +14047,20 @@ dependencies = [ "zksync_types", ] +[[package]] +name = "zopfli" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" +dependencies = [ + "bumpalo", + "crc32fast", + "lockfree-object-pool", + "log", + "once_cell", + "simd-adler32", +] + [[package]] name = "zstd" version = "0.11.2+zstd.1.5.2" @@ -12578,9 +14082,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" +version = "2.0.11+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +checksum = "75652c55c0b6f3e6f12eb786fe1bc960396bf05a1eb3bf1f3691c3610ac2e6d4" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 224ddaa4b..e798875d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,13 +28,28 @@ resolver = "2" [workspace.package] version = "0.0.2" edition = "2021" -rust-version = "1.75" # Remember to update clippy.toml as well +# Remember to update clippy.toml as well +rust-version = "1.76" authors = ["Foundry Contributors"] license = "MIT OR Apache-2.0" homepage = "https://github.com/foundry-rs/foundry" repository = "https://github.com/foundry-rs/foundry" exclude = ["benches/", "tests/", "test-data/", "testdata/"] +[workspace.lints.clippy] +dbg-macro = "warn" +manual-string-new = "warn" +uninlined-format-args = "warn" +use-self = "warn" + +[workspace.lints.rust] +rust-2018-idioms = "deny" +# unreachable-pub = "warn" +unused-must-use = "deny" + +[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. @@ -46,6 +61,7 @@ debug = false # Solc and artifacts foundry-compilers.opt-level = 3 solang-parser.opt-level = 3 +lalrpop-util.opt-level = 3 serde_json.opt-level = 3 # EVM @@ -64,6 +80,7 @@ ruint.opt-level = 3 sha2.opt-level = 3 sha3.opt-level = 3 tiny-keccak.opt-level = 3 +bitvec.opt-level = 3 # fuzzing proptest.opt-level = 3 @@ -79,17 +96,18 @@ scrypt.opt-level = 3 [profile.local] inherits = "dev" opt-level = 1 +debug-assertions = false +overflow-checks = false strip = "debuginfo" panic = "abort" codegen-units = 16 # Like release, but with full debug symbols and with stack unwinds. Useful for e.g. `perf`. [profile.debug-fast] -inherits = "release" +inherits = "local" debug = true strip = "none" panic = "unwind" -incremental = false # Optimized release profile. [profile.release] @@ -104,9 +122,6 @@ codegen-units = 1 [profile.release.package] mdbook.opt-level = 1 protobuf.opt-level = 1 -rusoto_core.opt-level = 1 -rusoto_credential.opt-level = 1 -rusoto_kms.opt-level = 1 toml_edit.opt-level = 1 trezor-client.opt-level = 1 @@ -118,6 +133,9 @@ forge = { path = "crates/forge" } forge-doc = { path = "crates/doc" } forge-fmt = { path = "crates/fmt" } +forge-verify = { path = "crates/verify" } +forge-script = { path = "crates/script" } +forge-sol-macro-gen = { path = "crates/sol-macro-gen" } foundry-cheatcodes = { path = "crates/cheatcodes" } foundry-cheatcodes-spec = { path = "crates/cheatcodes/spec" } foundry-cheatcodes-common = { path = "crates/cheatcodes/common" } @@ -133,53 +151,61 @@ 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-zksync-core = { path = "crates/zksync/core" } foundry-zksync-compiler = { path = "crates/zksync/compiler" } # solc & compilation utilities -foundry-block-explorers = { git = "https://github.com/Moonsong-Labs/block-explorers", branch = "zksync-v0.2.3", default-features = false } -foundry-compilers = { git = "https://github.com/Moonsong-Labs/compilers", branch = "zksync-v0.3.9" } +# foundry-block-explorers = { version = "0.4.1", default-features = false } +# foundry-compilers = { version = "0.8.0", default-features = false } +foundry-block-explorers = { git = "https://github.com/Moonsong-Labs/block-explorers", branch = "zksync-v0.4.1", default-features = false } +foundry-compilers = { git = "https://github.com/Moonsong-Labs/compilers", branch = "zksync-v0.8.0" } ## revm # no default features to avoid c-kzg -revm = { version = "3", default-features = false } -revm-primitives = { version = "1", default-features = false } -revm-inspectors = { git = "https://github.com/paradigmxyz/evm-inspectors", rev = "e90052361276aebcdc67cb24d8e2c4d907b6d299", default-features = false } +revm = { version = "9.0.0", default-features = false } +revm-primitives = { version = "4.0.0", default-features = false } +revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors", rev = "4fe17f0", features = [ + "serde", +] } ## ethers -ethers = { version = "2.0", default-features = false } -ethers-core = { version = "2.0", default-features = false } -ethers-contract = { version = "2.0", default-features = false } -ethers-contract-abigen = { version = "2.0", default-features = false } -ethers-providers = { version = "2.0", default-features = false } -ethers-signers = { version = "2.0", default-features = false } -ethers-middleware = { version = "2.0", default-features = false } -ethers-solc = { version = "2.0", default-features = false } +ethers-contract-abigen = { version = "2.0.14", default-features = false } ## alloy -alloy-consensus = { git = "https://github.com/alloy-rs/alloy" } -alloy-eips = { git = "https://github.com/alloy-rs/alloy" } -alloy-genesis = { git = "https://github.com/alloy-rs/alloy" } -alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy" } -alloy-network = { git = "https://github.com/alloy-rs/alloy" } -alloy-node-bindings = { git = "https://github.com/alloy-rs/alloy" } -alloy-providers = { git = "https://github.com/alloy-rs/alloy" } -alloy-pubsub = { git = "https://github.com/alloy-rs/alloy" } -alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy" } -alloy-rpc-trace-types = { git = "https://github.com/alloy-rs/alloy" } -alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy" } -alloy-signer = { git = "https://github.com/alloy-rs/alloy" } -alloy-transport = { git = "https://github.com/alloy-rs/alloy" } -alloy-transport-http = { git = "https://github.com/alloy-rs/alloy" } -alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy" } -alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy" } -alloy-primitives = { version = "0.6.3", features = ["getrandom"] } -alloy-dyn-abi = "0.6.3" -alloy-json-abi = "0.6.3" -alloy-sol-types = "0.6.3" -syn-solidity = "0.6.3" +alloy-consensus = { version = "0.1.1", default-features = false } +alloy-contract = { version = "0.1.1", default-features = false } +alloy-eips = { version = "0.1.1", default-features = false } +alloy-genesis = { version = "0.1.1", default-features = false } +alloy-json-rpc = { version = "0.1.1", default-features = false } +alloy-network = { version = "0.1.1", default-features = false } +alloy-node-bindings = { version = "0.1.1", default-features = false } +alloy-provider = { version = "0.1.1", default-features = false } +alloy-pubsub = { version = "0.1.1", default-features = false } +alloy-rpc-client = { version = "0.1.1", default-features = false } +alloy-rpc-types-engine = { version = "0.1.1", default-features = false } +alloy-rpc-types-trace = { version = "0.1.1", default-features = false } +alloy-rpc-types = { version = "0.1.1", default-features = false } +alloy-serde = { version = "0.1.1", default-features = false } +alloy-signer = { version = "0.1.1", default-features = false } +alloy-signer-local = { version = "0.1.1", default-features = false } +alloy-signer-aws = { version = "0.1.1", default-features = false } +alloy-signer-gcp = { version = "0.1.1", default-features = false } +alloy-signer-ledger = { version = "0.1.1", default-features = false } +alloy-signer-trezor = { version = "0.1.1", default-features = false } +alloy-transport = { version = "0.1.1", default-features = false } +alloy-transport-http = { version = "0.1.1", default-features = false } +alloy-transport-ipc = { version = "0.1.1", default-features = false } +alloy-transport-ws = { version = "0.1.1", default-features = false } +alloy-primitives = { version = "0.7.1", features = ["getrandom", "rand"] } +alloy-dyn-abi = "0.7.1" +alloy-json-abi = "0.7.1" +alloy-sol-types = "0.7.1" +alloy-sol-macro-input = "0.7.3" +alloy-sol-macro-expander = "0.7.3" +syn-solidity = "0.7.1" alloy-chains = "0.1" - +alloy-trie = "0.4.1" alloy-rlp = "0.3.3" solang-parser = "=0.3.3" @@ -195,60 +221,67 @@ zksync_utils = { git = "https://github.com/matter-labs/zksync-era.git", rev = "e zksync_contracts = { git = "https://github.com/matter-labs/zksync-era.git", rev = "e10bbdd1e863962552f37e768ae6af649353e4ea" } ## misc -chrono = { version = "0.4", default-features = false, features = ["clock", "std"] } +async-trait = "0.1" +auto_impl = "1" +walkdir = "2" +proc-macro2 = "1.0.82" +quote = "1.0" +syn = "2.0" +prettyplease = "0.2.20" +ahash = "0.8" +arrayvec = "0.7" +base64 = "0.22" +chrono = { version = "0.4", default-features = false, features = [ + "clock", + "std", +] } color-eyre = "0.6" derive_more = "0.99" +dunce = "1" +evm-disassembler = "0.5" eyre = "0.6" +figment = "0.10" +futures = "0.3" hex = { package = "const-hex", version = "1.6", features = ["hex"] } -itertools = "0.11" +itertools = "0.13" jsonpath_lib = "0.3" -pretty_assertions = "1.4" -protobuf = "=3.2.0" +k256 = "0.13" +once_cell = "1" +parking_lot = "0.12" rand = "0.8" +rustc-hash = "1.1" +semver = "1" serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", features = ["arbitrary_precision"] } -base64 = "0.21" +similar-asserts = "1.5" strum = "0.26" +thiserror = "1" toml = "0.8" tracing = "0.1" tracing-subscriber = "0.3" -evm-disassembler = "0.4" vergen = { version = "8", default-features = false } -# TODO: bumping to >=0.13.2 breaks ecrecover: https://github.com/foundry-rs/foundry/pull/6969 -# TODO: unpin on next revm release: https://github.com/bluealloy/revm/pull/870 -k256 = "=0.13.1" +indexmap = "2.2" +tikv-jemallocator = "0.5.4" +url = "2" +num-format = "0.4.4" +yansi = { version = "1.0", features = ["detect-tty", "detect-env"] } +tempfile = "3.10" +tokio = "1" +rayon = "1" -axum = "0.6" -hyper = "0.14" +axum = "0.7" +hyper = "1.0" +reqwest = { version = "0.12", default-features = false } tower = "0.4" -tower-http = "0.4" - -#[patch."https://github.com/gakonst/ethers-rs"] -#ethers = { path = "../ethers-rs/ethers" } -#ethers-addressbook = { path = "../ethers-rs/ethers-addressbook" } -#ethers-contract = { path = "../ethers-rs/ethers-contract" } -#ethers-contract-abigen = { path = "../ethers-rs/ethers-contract/ethers-contract-abigen" } -#ethers-core = { path = "../ethers-rs/ethers-core" } -#ethers-etherscan = { path = "../ethers-rs/ethers-etherscan" } -#ethers-middleware = { path = "../ethers-rs/ethers-middleware" } -#ethers-providers = { path = "../ethers-rs/ethers-providers" } -#ethers-signers = { path = "../ethers-rs/ethers-signers" } -#ethers-solc = { path = "../ethers-rs/ethers-solc" } +tower-http = "0.5" +# soldeer +soldeer = "0.2.15" [patch.crates-io] -ethers = { git = "https://github.com/gakonst/ethers-rs", rev = "73e5de211c32a1f5777eb5194205bdb31f6a3502" } -ethers-core = { git = "https://github.com/gakonst/ethers-rs", rev = "73e5de211c32a1f5777eb5194205bdb31f6a3502" } -ethers-contract = { git = "https://github.com/gakonst/ethers-rs", rev = "73e5de211c32a1f5777eb5194205bdb31f6a3502" } -ethers-contract-abigen = { git = "https://github.com/gakonst/ethers-rs", rev = "73e5de211c32a1f5777eb5194205bdb31f6a3502" } -ethers-providers = { git = "https://github.com/gakonst/ethers-rs", rev = "73e5de211c32a1f5777eb5194205bdb31f6a3502" } -ethers-signers = { git = "https://github.com/gakonst/ethers-rs", rev = "73e5de211c32a1f5777eb5194205bdb31f6a3502" } -ethers-middleware = { git = "https://github.com/gakonst/ethers-rs", rev = "73e5de211c32a1f5777eb5194205bdb31f6a3502" } -ethers-solc = { git = "https://github.com/gakonst/ethers-rs", rev = "73e5de211c32a1f5777eb5194205bdb31f6a3502" } - -revm = { git = "https://github.com/bluealloy/revm", branch = "reth_freeze" } -revm-primitives = { git = "https://github.com/bluealloy/revm", branch = "reth_freeze" } -revm-interpreter = { git = "https://github.com/bluealloy/revm", branch = "reth_freeze" } -revm-precompile = { git = "https://github.com/bluealloy/revm", branch = "reth_freeze" } +revm = { git = "https://github.com/bluealloy/revm.git", rev = "41e2f7f" } +revm-interpreter = { git = "https://github.com/bluealloy/revm.git", rev = "41e2f7f" } +revm-precompile = { git = "https://github.com/bluealloy/revm.git", rev = "41e2f7f" } +revm-primitives = { git = "https://github.com/bluealloy/revm.git", rev = "41e2f7f" } # revm-interpreter = { path = "../revm/crates/interpreter" } # revm-primitives = { path = "../revm/crates/primitives" } @@ -257,8 +290,7 @@ revm-precompile = { git = "https://github.com/bluealloy/revm", branch = "reth_fr # revm-test = { path = "../revm/bins/revm-test" } # revme = { path = "../revm/bins/revme" } -packed_simd = { git = "https://github.com/nbaztec/packed_simd", branch = "foundry-fix" } - +# NOTE: Uncomment this if overriding revm above # [patch."https://github.com/bluealloy/revm"] # revm = { path = "../revm/crates/revm" } @@ -266,8 +298,16 @@ packed_simd = { git = "https://github.com/nbaztec/packed_simd", branch = "foundr # era_test_node = { path = "../era-test-node" } # [patch."https://github.com/Moonsong-Labs/compilers"] -# foundry-compilers = { path = "../msl-compilers" } +# foundry-compilers = { path = "../compilers/crates/compilers/" } + +# [patch."https://github.com/matter-labs/era-boojum"] +# cs_derive = { git = "https://github.com/nbaztec/era-boojum", branch = "foundry-fix" } +# boojum = { git = "https://github.com/nbaztec/era-boojum", branch = "foundry-fix" } -[patch."https://github.com/matter-labs/era-boojum"] -cs_derive = { git = "https://github.com/nbaztec/era-boojum", branch = "foundry-fix" } -boojum = { git = "https://github.com/nbaztec/era-boojum", branch = "foundry-fix" } +# [patch."https://github.com/moonsong-labs/compilers"] +# foundry-compilers = { path = "../msl-compilers/crates/compilers" } +# foundry-compilers-core = { path = "../msl-compilers/crates/core" } +# foundry-compilers-artifacts-vyper = { path = "../msl-compilers/crates/artifacts/vyper" } +# foundry-compilers-artifacts-solc = { path = "../msl-compilers/crates/artifacts/solc" } +# foundry-compilers-artifacts-zksolc = { path = "../msl-compilers/crates/artifacts/zksolc" } +# foundry-compilers-artifacts = { path = "../msl-compilers/crates/artifacts/artifacts" } \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 649d5176c..48e97b7e6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,11 +15,14 @@ RUN [[ "$TARGETARCH" = "arm64" ]] && echo "export CFLAGS=-mno-outline-atomics" > WORKDIR /opt/foundry COPY . . +# see +RUN git update-index --force-write-index + # This is necessary to compile librocksdb-sys ENV RUSTFLAGS -Ctarget-feature=-crt-static RUN --mount=type=cache,target=/root/.cargo/registry --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/opt/foundry/target \ - source $HOME/.profile && cargo build --profile local \ + source $HOME/.profile && cargo build --release --features cast/aws-kms,forge/aws-kms \ && mkdir out \ && mv target/local/forge out/forge \ && mv target/local/cast out/cast \ diff --git a/README.md b/README.md index 1b3882c08..6ffc311d7 100644 --- a/README.md +++ b/README.md @@ -41,12 +41,14 @@ To use for zkSync environments, include `--zksync` when running `forge` or `vm.z - Hardhat-style: Leverage the popular console.sol contract. - **Configurable Compiler Options**: Tailor compiler settings to your needs, including LLVM optimization modes. +Forge is quite fast at both compiling (leveraging [ethers-solc]) and testing. + ### Limitations While `foundry-zksync` is **alpha stage**, there are some limitations to be aware of: - **Compile Time**: Some users may experience slower compile times. -- **Compiling Libraries**: Compiling non-inlinable libraries requires deployment and adding to configuration. For more information please refer to [official docs](https://docs.zksync.io/build/developer-reference/ethereum-differences/libraries). +- **Compiling Libraries**: Compiling non-inlinable libraries requires deployment and adding to configuration or the command line with the `--libraries` argument. For more information please refer to [official docs](https://docs.zksync.io/build/developer-reference/ethereum-differences/libraries). ``` libraries = [ @@ -191,15 +193,12 @@ Compiling smart contracts... Compiled Successfully ``` -#### Deploying missing libraries - -In case missing libraries are detected during the compilation, we can deploy them using the following command: +#### Listing missing libraries -``` -$ forge create --deploy-missing-libraries --private-key --rpc-url --chain --zksync -``` +To scan missing non-inlinable libraries, you can build the project using the `--zk-detect-missing-libraries-flag`. This will give a list of the libraries that need to be deployed and their addresses +provided via the `libraries` option for the contracts to compile. +Metadata about the libraries will be saved in `.zksolc-libraries-cache/missing_library_dependencies.json`. -After deployment is done, the configuration file will be updated and contracts will be automatically compiled again. #### Running Tests @@ -260,7 +259,7 @@ See our [contributing guidelines](./CONTRIBUTING.md). ## Acknowledgements - Foundry is a clean-room rewrite of the testing framework [DappTools](https://github.com/dapphub/dapptools). None of this would have been possible without the DappHub team's work over the years. -- [Matthias Seitz](https://twitter.com/mattsse_): Created [ethers-solc](https://github.com/gakonst/ethers-rs/tree/master/ethers-solc/) which is the backbone of our compilation pipeline, as well as countless contributions to ethers, in particular the `abigen` macros. +- [Matthias Seitz](https://twitter.com/mattsse_): Created [ethers-solc] which is the backbone of our compilation pipeline, as well as countless contributions to ethers, in particular the `abigen` macros. - [Rohit Narurkar](https://twitter.com/rohitnarurkar): Created the Rust Solidity version manager [svm-rs](https://github.com/roynalnaruto/svm-rs) which we use to auto-detect and manage multiple Solidity versions. - [Brock Elmore](https://twitter.com/brockjelmore): For extending the VM's cheatcodes and implementing [structured call tracing](https://github.com/foundry-rs/foundry/pull/192), a critical feature for debugging smart contract calls. - All the other [contributors](https://github.com/foundry-rs/foundry/graphs/contributors) to the [ethers-rs](https://github.com/gakonst/ethers-rs) & [foundry](https://github.com/foundry-rs/foundry) repositories and chatrooms. diff --git a/clippy.toml b/clippy.toml index cc4ad18b1..09acb653d 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1,4 @@ -msrv = "1.75" +msrv = "1.76" +# 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"] diff --git a/crates/anvil/Cargo.toml b/crates/anvil/Cargo.toml index ff59985f0..c374a8c8c 100644 --- a/crates/anvil/Cargo.toml +++ b/crates/anvil/Cargo.toml @@ -10,19 +10,27 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [[bin]] name = "anvil" path = "src/anvil.rs" required-features = ["cli"] [build-dependencies] -vergen = { workspace = true, default-features = false, features = ["build", "git", "gitcl"] } +vergen = { workspace = true, default-features = false, features = [ + "build", + "git", + "gitcl", +] } [dependencies] # foundry internal anvil-core = { path = "core", features = ["serde", "impersonated-tx"] } anvil-rpc = { path = "rpc" } anvil-server = { path = "server" } +foundry-cli.workspace = true foundry-common.workspace = true foundry-config.workspace = true foundry-evm.workspace = true @@ -30,23 +38,36 @@ foundry-evm.workspace = true # evm support bytes = "1.4.0" k256.workspace = true -ethers = { workspace = true, features = ["rustls", "ws", "ipc", "optimism"] } -trie-db = "0.23" -hash-db = "0.15" -memory-db = "0.29" +revm = { workspace = true, features = [ + "std", + "serde", + "memory_limit", + "c-kzg", +] } alloy-primitives = { workspace = true, features = ["serde"] } -alloy-consensus.workspace = true +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", "mnemonic"] } +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 -alloy-rpc-trace-types.workspace = true -alloy-providers.workspace = true +alloy-rpc-types = { workspace = true, features = ["txpool"] } +alloy-rpc-types-trace.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 # axum related axum.workspace = true @@ -58,43 +79,54 @@ tracing.workspace = true tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } # async -tokio = { version = "1", features = ["time"] } -parking_lot = "0.12" -futures = "0.3" -async-trait = "0.1" +tokio = { workspace = true, features = ["time"] } +parking_lot.workspace = true +futures.workspace = true +async-trait.workspace = true # misc flate2 = "1.0" serde_repr = "0.1" serde_json.workspace = true serde.workspace = true -thiserror = "1" -yansi = "0.5" -tempfile = "3" +thiserror .workspace = true +yansi.workspace = true +tempfile.workspace = true itertools.workspace = true rand = "0.8" eyre.workspace = true # cli -clap = { version = "4", features = ["derive", "env", "wrap_help"], optional = true } +clap = { version = "4", features = [ + "derive", + "env", + "wrap_help", +], optional = true } clap_complete = { version = "4", optional = true } chrono.workspace = true -auto_impl = "1" +auto_impl.workspace = true ctrlc = { version = "3", optional = true } fdlimit = { version = "0.3", optional = true } clap_complete_fig = "4" -ethereum-forkid = "0.12" + +[target.'cfg(unix)'.dependencies] +tikv-jemallocator = { workspace = true, optional = true } [dev-dependencies] -ethers = { workspace = true, features = ["abigen"] } -ethers-core = { workspace = true, features = ["optimism"] } -ethers-solc = { workspace = true, features = ["project-util", "full"] } -pretty_assertions = "1.3.0" -tokio = { version = "1", features = ["full"] } -crc = "3.0.1" +alloy-json-abi.workspace = true +alloy-rpc-client = { workspace = true, features = ["pubsub"] } +alloy-transport-ipc = { workspace = true, features = ["mock"] } +alloy-provider = { workspace = true, features = ["txpool-api"] } +alloy-transport-ws.workspace = true +alloy-json-rpc.workspace = true +alloy-pubsub.workspace = true +foundry-test-utils.workspace = true +similar-asserts.workspace = true +tokio = { workspace = true, features = ["full"] } [features] -default = ["cli"] +default = ["cli", "jemalloc"] cmd = ["clap", "clap_complete", "ctrlc", "anvil-server/clap"] cli = ["tokio/full", "cmd", "fdlimit"] asm-keccak = ["alloy-primitives/asm-keccak"] +jemalloc = ["dep:tikv-jemallocator"] diff --git a/crates/anvil/README.md b/crates/anvil/README.md index d811bf3ce..0e775441d 100644 --- a/crates/anvil/README.md +++ b/crates/anvil/README.md @@ -11,6 +11,15 @@ A local Ethereum node, akin to Ganache, designed for development with [**Forge** - mining modes: auto, interval, manual, none - ... +## Supported Versions + +- **anvil**: + - **evm**: Cancun +- **forge**: + - **solc**: Latest + - **evm**: Cancun + + ## Installation `anvil` binary is available via [`foundryup`](../../README.md#installation). @@ -20,7 +29,7 @@ A local Ethereum node, akin to Ganache, designed for development with [**Forge** ```sh git clone https://github.com/foundry-rs/foundry cd foundry -cargo install --path ./crates/anvil --profile local --locked --offline --force +cargo install --path ./crates/anvil --profile local --locked --force ``` ## Getting started diff --git a/crates/anvil/core/Cargo.toml b/crates/anvil/core/Cargo.toml index 3b164a9d8..aa7a323ed 100644 --- a/crates/anvil/core/Cargo.toml +++ b/crates/anvil/core/Cargo.toml @@ -9,37 +9,36 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [dependencies] foundry-common.workspace = true foundry-evm.workspace = true -revm = { workspace = true, default-features = false, features = ["std", "serde", "memory_limit"] } +revm = { workspace = true, default-features = false, features = [ + "std", + "serde", + "memory_limit", + "c-kzg", +] } alloy-primitives = { workspace = true, features = ["serde"] } -alloy-rpc-types = { workspace = true } -alloy-rpc-trace-types.workspace = true +alloy-rpc-types.workspace = true +alloy-rpc-types-trace.workspace = true +alloy-serde.workspace = true alloy-rlp.workspace = true alloy-eips.workspace = true -alloy-network = { workspace = true, features = ["k256"] } -alloy-consensus.workspace = true +alloy-consensus = { workspace = true, features = ["k256", "kzg"] } alloy-dyn-abi = { workspace = true, features = ["std", "eip712"] } +alloy-trie.workspace = true serde = { workspace = true, optional = true } serde_json.workspace = true bytes = "1.4" -# trie -hash-db = { version = "0.15", default-features = false } -hash256-std-hasher = { version = "0.15", default-features = false } -triehash = { version = "0.8", default-features = false } -reference-trie = "0.25" -keccak-hasher = "0.15" - # misc rand = "0.8" -[dev-dependencies] -anvil-core = { path = ".", features = ["serde"] } - [features] default = ["serde"] impersonated-tx = [] diff --git a/crates/anvil/core/src/eth/block.rs b/crates/anvil/core/src/eth/block.rs index c304eefa4..1b0895dcd 100644 --- a/crates/anvil/core/src/eth/block.rs +++ b/crates/anvil/core/src/eth/block.rs @@ -3,7 +3,8 @@ use super::{ trie, }; use alloy_consensus::Header; -use alloy_primitives::{Address, Bloom, Bytes, B256, U256}; +use alloy_eips::eip2718::Encodable2718; +use alloy_primitives::{Address, Bloom, Bytes, B256, B64, U256}; use alloy_rlp::{RlpDecodable, RlpEncodable}; // Type alias to optionally support impersonated transactions @@ -29,10 +30,10 @@ pub struct Block { } impl Block { - /// Creates a new block + /// Creates a new block. /// - /// Note: if the `impersonate-tx` feature is enabled this will also accept - /// [MaybeImpersonatedTransaction] + /// Note: if the `impersonate-tx` feature is enabled this will also accept + /// `MaybeImpersonatedTransaction`. pub fn new( partial_header: PartialHeader, transactions: impl IntoIterator, @@ -47,7 +48,7 @@ impl Block { let ommers_hash = B256::from_slice(alloy_primitives::utils::keccak256(encoded_ommers).as_slice()); let transactions_root = - trie::ordered_trie_root(transactions.iter().map(|r| Bytes::from(alloy_rlp::encode(r)))); + trie::ordered_trie_root(transactions.iter().map(|r| r.encoded_2718())); Self { header: Header { @@ -65,12 +66,13 @@ impl Block { timestamp: partial_header.timestamp, extra_data: partial_header.extra_data, mix_hash: partial_header.mix_hash, - withdrawals_root: Some(partial_header.mix_hash), - blob_gas_used: None, - excess_blob_gas: None, - parent_beacon_block_root: None, + withdrawals_root: None, + blob_gas_used: partial_header.blob_gas_used, + excess_blob_gas: partial_header.excess_blob_gas, + parent_beacon_block_root: partial_header.parent_beacon_block_root, nonce: partial_header.nonce, base_fee_per_gas: partial_header.base_fee, + requests_root: None, }, transactions, ommers, @@ -88,13 +90,16 @@ pub struct PartialHeader { pub logs_bloom: Bloom, pub difficulty: U256, pub number: u64, - pub gas_limit: u64, - pub gas_used: u64, + pub gas_limit: u128, + pub gas_used: u128, pub timestamp: u64, pub extra_data: Bytes, pub mix_hash: B256, - pub nonce: u64, - pub base_fee: Option, + pub blob_gas_used: Option, + pub excess_blob_gas: Option, + pub parent_beacon_block_root: Option, + pub nonce: B64, + pub base_fee: Option, } impl From
for PartialHeader { @@ -114,13 +119,15 @@ impl From
for PartialHeader { mix_hash: value.mix_hash, nonce: value.nonce, base_fee: value.base_fee_per_gas, + blob_gas_used: value.blob_gas_used, + excess_blob_gas: value.excess_blob_gas, + parent_beacon_block_root: value.parent_beacon_block_root, } } } #[cfg(test)] mod tests { - use alloy_network::Sealable; use alloy_primitives::{ b256, hex::{self, FromHex}, @@ -143,23 +150,24 @@ mod tests { difficulty: Default::default(), number: 124u64, gas_limit: Default::default(), - gas_used: 1337u64, + gas_used: 1337u128, timestamp: 0, extra_data: Default::default(), mix_hash: Default::default(), - nonce: 99u64, + 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_root: 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); + header.base_fee_per_gas = Some(12345u128); let encoded = alloy_rlp::encode(&header); let decoded: Header = Header::decode(&mut encoded.as_ref()).unwrap(); @@ -182,8 +190,8 @@ mod tests { logs_bloom: Bloom::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(), difficulty: U256::from(2222), number: 0xd05u64, - gas_limit: 0x115cu64, - gas_used: 0x15b3u64, + gas_limit: 0x115cu128, + gas_used: 0x15b3u128, timestamp: 0x1a0au64, extra_data: hex::decode("7788").unwrap().into(), mix_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), @@ -191,8 +199,9 @@ mod tests { blob_gas_used: None, excess_blob_gas: None, parent_beacon_block_root: None, - nonce: 0, + nonce: B64::ZERO, base_fee_per_gas: None, + requests_root: None, }; header.encode(&mut data); @@ -214,17 +223,18 @@ mod tests { logs_bloom: <[u8; 256]>::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap().into(), difficulty: U256::from(2222), number: 0xd05u64, - gas_limit: 0x115cu64, - gas_used: 0x15b3u64, + gas_limit: 0x115cu128, + gas_used: 0x15b3u128, timestamp: 0x1a0au64, extra_data: hex::decode("7788").unwrap().into(), mix_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), - nonce: 0, + 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_root: None, }; let header = Header::decode(&mut data.as_slice()).unwrap(); assert_eq!(header, expected); @@ -245,19 +255,20 @@ mod tests { 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::(), + 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: 0, + 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_root: None, }; - assert_eq!(header.hash(), expected_hash); + assert_eq!(header.hash_slow(), expected_hash); } #[test] diff --git a/crates/anvil/core/src/eth/mod.rs b/crates/anvil/core/src/eth/mod.rs index 87c02d9b8..d2fa41a7b 100644 --- a/crates/anvil/core/src/eth/mod.rs +++ b/crates/anvil/core/src/eth/mod.rs @@ -3,13 +3,14 @@ use crate::{ types::{EvmMineOptions, Forking, Index}, }; use alloy_primitives::{Address, Bytes, TxHash, B256, B64, U256}; -use alloy_rpc_trace_types::geth::{GethDebugTracingOptions, GethDefaultTracingOptions}; use alloy_rpc_types::{ pubsub::{Params as SubscriptionParams, SubscriptionKind}, request::TransactionRequest, state::StateOverride, BlockId, BlockNumberOrTag as BlockNumber, Filter, }; +use alloy_rpc_types_trace::geth::{GethDebugTracingOptions, GethDefaultTracingOptions}; +use alloy_serde::WithOtherFields; pub mod block; pub mod proof; @@ -69,6 +70,9 @@ pub enum EthRequest { )] EthMaxPriorityFeePerGas(()), + #[cfg_attr(feature = "serde", serde(rename = "eth_blobBaseFee", with = "empty_params"))] + EthBlobBaseFee(()), + #[cfg_attr( feature = "serde", serde(rename = "eth_accounts", alias = "eth_requestAccounts", with = "empty_params") @@ -139,11 +143,11 @@ pub enum EthRequest { EthGetProof(Address, Vec, Option), /// The sign method calculates an Ethereum specific signature with: - #[cfg_attr(feature = "serde", serde(rename = "eth_sign"))] + #[cfg_attr(feature = "serde", serde(rename = "eth_sign", alias = "personal_sign"))] EthSign(Address, Bytes), - #[cfg_attr(feature = "serde", serde(rename = "eth_signTransaction"))] - EthSignTransaction(Box), + #[cfg_attr(feature = "serde", serde(rename = "eth_signTransaction", with = "sequence"))] + EthSignTransaction(Box>), /// Signs data via [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md). #[cfg_attr(feature = "serde", serde(rename = "eth_signTypedData"))] @@ -158,27 +162,27 @@ pub enum EthRequest { EthSignTypedDataV4(Address, alloy_dyn_abi::TypedData), #[cfg_attr(feature = "serde", serde(rename = "eth_sendTransaction", with = "sequence"))] - EthSendTransaction(Box), + EthSendTransaction(Box>), #[cfg_attr(feature = "serde", serde(rename = "eth_sendRawTransaction", with = "sequence"))] EthSendRawTransaction(Bytes), #[cfg_attr(feature = "serde", serde(rename = "eth_call"))] EthCall( - TransactionRequest, + WithOtherFields, #[cfg_attr(feature = "serde", serde(default))] Option, #[cfg_attr(feature = "serde", serde(default))] Option, ), #[cfg_attr(feature = "serde", serde(rename = "eth_createAccessList"))] EthCreateAccessList( - TransactionRequest, + WithOtherFields, #[cfg_attr(feature = "serde", serde(default))] Option, ), #[cfg_attr(feature = "serde", serde(rename = "eth_estimateGas"))] EthEstimateGas( - TransactionRequest, + WithOtherFields, #[cfg_attr(feature = "serde", serde(default))] Option, #[cfg_attr(feature = "serde", serde(default))] Option, ), @@ -192,6 +196,18 @@ pub enum EthRequest { #[cfg_attr(feature = "serde", serde(rename = "eth_getTransactionByBlockNumberAndIndex"))] EthGetTransactionByBlockNumberAndIndex(BlockNumber, Index), + #[cfg_attr( + feature = "serde", + serde(rename = "eth_getRawTransactionByHash", with = "sequence") + )] + EthGetRawTransactionByHash(TxHash), + + #[cfg_attr(feature = "serde", serde(rename = "eth_getRawTransactionByBlockHashAndIndex"))] + EthGetRawTransactionByBlockHashAndIndex(TxHash, Index), + + #[cfg_attr(feature = "serde", serde(rename = "eth_getRawTransactionByBlockNumberAndIndex"))] + EthGetRawTransactionByBlockNumberAndIndex(BlockNumber, Index), + #[cfg_attr(feature = "serde", serde(rename = "eth_getTransactionReceipt", with = "sequence"))] EthGetTransactionReceipt(B256), @@ -262,6 +278,10 @@ pub enum EthRequest { #[cfg_attr(feature = "serde", serde(rename = "eth_syncing", with = "empty_params"))] EthSyncing(()), + /// geth's `debug_getRawTransaction` endpoint + #[cfg_attr(feature = "serde", serde(rename = "debug_getRawTransaction", with = "sequence"))] + DebugGetRawTransaction(TxHash), + /// geth's `debug_traceTransaction` endpoint #[cfg_attr(feature = "serde", serde(rename = "debug_traceTransaction"))] DebugTraceTransaction( @@ -272,7 +292,7 @@ pub enum EthRequest { /// geth's `debug_traceCall` endpoint #[cfg_attr(feature = "serde", serde(rename = "debug_traceCall"))] DebugTraceCall( - TransactionRequest, + WithOtherFields, #[cfg_attr(feature = "serde", serde(default))] Option, #[cfg_attr(feature = "serde", serde(default))] GethDefaultTracingOptions, ), @@ -371,6 +391,17 @@ pub enum EthRequest { )] DropTransaction(B256), + /// Removes transactions from the pool + #[cfg_attr( + feature = "serde", + 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 #[cfg_attr(feature = "serde", serde(rename = "anvil_reset", alias = "hardhat_reset"))] Reset(#[cfg_attr(feature = "serde", serde(default))] Option>>), @@ -597,7 +628,7 @@ pub enum EthRequest { feature = "serde", serde(rename = "eth_sendUnsignedTransaction", with = "sequence") )] - EthSendUnsignedTransaction(Box), + EthSendUnsignedTransaction(Box>), /// Turn on call traces for transactions that are returned to the user when they execute a /// transaction (instead of just txhash/receipt) @@ -606,26 +637,26 @@ pub enum EthRequest { /// 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) + /// Ref: #[cfg_attr(feature = "serde", 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: [Here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_inspect) + /// Ref: #[cfg_attr(feature = "serde", 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: [Here](https://geth.ethereum.org/docs/rpc/ns-txpool#txpool_content) + /// Ref: #[cfg_attr(feature = "serde", 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_* - /// https://github.com/otterscan/otterscan/blob/071d8c55202badf01804f6f8d53ef9311d4a9e47/src/useProvider.ts#L71 - /// Related upstream issue: https://github.com/otterscan/otterscan/issues/1081 + /// + /// Related upstream issue: #[cfg_attr(feature = "serde", serde(rename = "erigon_getHeaderByNumber"))] ErigonGetHeaderByNumber( #[cfg_attr( @@ -676,7 +707,7 @@ pub enum EthRequest { OtsGetBlockDetails( #[cfg_attr( feature = "serde", - serde(deserialize_with = "lenient_block_number::lenient_block_number", default) + serde(deserialize_with = "lenient_block_number::lenient_block_number_seq", default) )] BlockNumber, ), @@ -717,6 +748,13 @@ pub enum EthRequest { /// contract. #[cfg_attr(feature = "serde", serde(rename = "ots_getContractCreator", with = "sequence"))] OtsGetContractCreator(Address), + + /// Removes transactions from the pool by sender origin. + #[cfg_attr( + feature = "serde", + serde(rename = "anvil_removePoolTransactions", with = "sequence") + )] + RemovePoolTransactions(Address), } /// Represents ethereum JSON-RPC API @@ -1399,6 +1437,25 @@ mod tests { 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": @@ -1500,10 +1557,29 @@ true}]}"#; 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": +["0xd84de507f3fada7df80908082d3239466db55a71", "0x00"]}"#; + 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(); + } } diff --git a/crates/anvil/core/src/eth/proof.rs b/crates/anvil/core/src/eth/proof.rs index ba2357bff..450e53e47 100644 --- a/crates/anvil/core/src/eth/proof.rs +++ b/crates/anvil/core/src/eth/proof.rs @@ -14,7 +14,7 @@ pub struct BasicAccount { impl Default for BasicAccount { fn default() -> Self { - BasicAccount { + Self { balance: U256::ZERO, nonce: U256::ZERO, code_hash: KECCAK_EMPTY, diff --git a/crates/anvil/core/src/eth/serde_helpers.rs b/crates/anvil/core/src/eth/serde_helpers.rs index 311b3a9f2..f7d5bd46d 100644 --- a/crates/anvil/core/src/eth/serde_helpers.rs +++ b/crates/anvil/core/src/eth/serde_helpers.rs @@ -33,35 +33,6 @@ pub mod sequence { } } -pub mod numeric { - use alloy_primitives::U256; - use serde::{Deserialize, Deserializer}; - /// Supports parsing u64 - /// - /// See - pub fn deserialize_stringified_u64_opt<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - if let Some(num) = Option::::deserialize(deserializer)? { - num.try_into().map(Some).map_err(serde::de::Error::custom) - } else { - Ok(None) - } - } - - /// Supports parsing u64 - /// - /// See - pub fn deserialize_stringified_u64<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let num = U256::deserialize(deserializer)?; - num.try_into().map_err(serde::de::Error::custom) - } -} - /// A module that deserializes `[]` optionally pub mod empty_params { use serde::{Deserialize, Deserializer}; diff --git a/crates/anvil/core/src/eth/subscription.rs b/crates/anvil/core/src/eth/subscription.rs index cc162cc4d..9d347e9a6 100644 --- a/crates/anvil/core/src/eth/subscription.rs +++ b/crates/anvil/core/src/eth/subscription.rs @@ -14,20 +14,18 @@ pub enum SubscriptionId { String(String), } -// === impl SubscriptionId === - impl SubscriptionId { /// Generates a new random hex identifier pub fn random_hex() -> Self { - SubscriptionId::String(hex_id()) + Self::String(hex_id()) } } impl fmt::Display for SubscriptionId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - SubscriptionId::Number(num) => num.fmt(f), - SubscriptionId::String(s) => s.fmt(f), + Self::Number(num) => num.fmt(f), + Self::String(s) => s.fmt(f), } } } @@ -35,8 +33,8 @@ impl fmt::Display for SubscriptionId { impl fmt::Debug for SubscriptionId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - SubscriptionId::Number(num) => num.fmt(f), - SubscriptionId::String(s) => s.fmt(f), + Self::Number(num) => num.fmt(f), + Self::String(s) => s.fmt(f), } } } @@ -47,8 +45,6 @@ pub struct HexIdProvider { len: usize, } -// === impl HexIdProvider === - impl HexIdProvider { /// Generates a random hex encoded Id pub fn gen(&self) -> String { diff --git a/crates/anvil/core/src/eth/transaction/mod.rs b/crates/anvil/core/src/eth/transaction/mod.rs index afc16b928..83db64465 100644 --- a/crates/anvil/core/src/eth/transaction/mod.rs +++ b/crates/anvil/core/src/eth/transaction/mod.rs @@ -1,25 +1,27 @@ //! Transaction related types -use crate::eth::{ - transaction::optimism::{DepositTransaction, DepositTransactionRequest}, - utils::eip_to_revm_access_list, +use crate::eth::transaction::optimism::{DepositTransaction, DepositTransactionRequest}; +use alloy_consensus::{ + transaction::eip4844::{TxEip4844, TxEip4844Variant, TxEip4844WithSidecar}, + AnyReceiptEnvelope, Receipt, ReceiptEnvelope, ReceiptWithBloom, Signed, TxEip1559, TxEip2930, + TxEnvelope, TxLegacy, TxReceipt, }; -use alloy_consensus::{ReceiptWithBloom, TxEip1559, TxEip2930, TxLegacy}; -use alloy_network::{Signed, Transaction, TxKind}; -use alloy_primitives::{Address, Bloom, Bytes, Log, Signature, TxHash, B256, U128, U256, U64}; -use alloy_rlp::{Decodable, Encodable}; +use alloy_eips::eip2718::{Decodable2718, Eip2718Error, Encodable2718}; +use alloy_primitives::{Address, Bloom, Bytes, Log, Signature, TxHash, TxKind, B256, U256, U64}; +use alloy_rlp::{length_of_length, Decodable, Encodable, Header}; use alloy_rpc_types::{ - request::TransactionRequest, AccessList, Signature as RpcSignature, - Transaction as RpcTransaction, + request::TransactionRequest, AccessList, AnyTransactionReceipt, Signature as RpcSignature, + Transaction as RpcTransaction, TransactionReceipt, }; +use alloy_serde::{OtherFields, WithOtherFields}; +use bytes::BufMut; use foundry_evm::traces::CallTraceNode; use revm::{ interpreter::InstructionResult, - primitives::{CreateScheme, OptimismFields, TransactTo, TxEnv}, + primitives::{OptimismFields, TransactTo, TxEnv}, }; -use std::ops::Deref; - -use super::utils::from_eip_to_alloy_access_list; +use serde::{Deserialize, Serialize}; +use std::ops::{Deref, Mul}; pub mod optimism; @@ -32,30 +34,37 @@ pub fn impersonated_signature() -> Signature { /// Converts a [TransactionRequest] into a [TypedTransactionRequest]. /// Should be removed once the call builder abstraction for providers is in place. -pub fn transaction_request_to_typed(tx: TransactionRequest) -> Option { - let TransactionRequest { - from, - to, - gas_price, - max_fee_per_gas, - max_priority_fee_per_gas, - gas, - value, - input, - nonce, - mut access_list, - transaction_type, +pub fn transaction_request_to_typed( + tx: WithOtherFields, +) -> Option { + let WithOtherFields:: { + inner: + TransactionRequest { + from, + to, + gas_price, + max_fee_per_gas, + max_priority_fee_per_gas, + max_fee_per_blob_gas, + blob_versioned_hashes, + gas, + value, + input, + nonce, + access_list, + sidecar, + transaction_type, + .. + }, other, - .. } = tx; - let transaction_type = transaction_type.map(|id| id.to::()); // Special case: OP-stack deposit tx - if transaction_type == Some(126) { + if transaction_type == Some(0x7E) || has_optimism_fields(&other) { return Some(TypedTransactionRequest::Deposit(DepositTransactionRequest { from: from.unwrap_or_default(), source_hash: other.get_deserialized::("sourceHash")?.ok()?, - kind: to.into(), + kind: to.unwrap_or_default(), mint: other.get_deserialized::("mint")?.ok()?, value: value.unwrap_or_default(), gas_limit: gas.unwrap_or_default(), @@ -69,69 +78,95 @@ pub fn transaction_request_to_typed(tx: TransactionRequest) -> Option { + (Some(0), _, None, None, None, None, None, None, _) | + (None, Some(_), None, None, None, None, None, None, _) => { Some(TypedTransactionRequest::Legacy(TxLegacy { - nonce: nonce.unwrap_or_default().to::(), - gas_price: gas_price.unwrap_or_default().to::(), - gas_limit: gas.unwrap_or_default().to::(), + nonce: nonce.unwrap_or_default(), + gas_price: gas_price.unwrap_or_default(), + gas_limit: gas.unwrap_or_default(), value: value.unwrap_or(U256::ZERO), input: input.into_input().unwrap_or_default(), - to: match to { - Some(to) => TxKind::Call(to), - None => TxKind::Create, - }, + to: to.unwrap_or_default(), chain_id: None, })) } // EIP2930 - (Some(1), _, None, None, _) | (None, _, None, None, Some(_)) => { + (Some(1), _, None, None, _, None, None, None, _) | + (None, _, None, None, Some(_), None, None, None, _) => { Some(TypedTransactionRequest::EIP2930(TxEip2930 { - nonce: nonce.unwrap_or_default().to::(), - gas_price: gas_price.unwrap_or_default().to(), - gas_limit: gas.unwrap_or_default().to::(), + nonce: nonce.unwrap_or_default(), + gas_price: gas_price.unwrap_or_default(), + gas_limit: gas.unwrap_or_default(), value: value.unwrap_or(U256::ZERO), input: input.into_input().unwrap_or_default(), - to: match to { - Some(to) => TxKind::Call(to), - None => TxKind::Create, - }, + to: to.unwrap_or_default(), chain_id: 0, - access_list: to_eip_access_list(access_list.unwrap_or_default()), + access_list: access_list.unwrap_or_default(), })) } // EIP1559 - (Some(2), None, _, _, _) | - (None, None, Some(_), _, _) | - (None, None, _, Some(_), _) | - (None, None, None, None, None) => { + (Some(2), None, _, _, _, _, None, None, _) | + (None, None, Some(_), _, _, _, None, None, _) | + (None, None, _, Some(_), _, _, None, None, _) | + (None, None, None, None, None, _, None, None, _) => { // Empty fields fall back to the canonical transaction schema. Some(TypedTransactionRequest::EIP1559(TxEip1559 { - nonce: nonce.unwrap_or_default().to::(), - max_fee_per_gas: max_fee_per_gas.unwrap_or_default().to::(), - max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default().to::(), - gas_limit: gas.unwrap_or_default().to::(), + nonce: nonce.unwrap_or_default(), + max_fee_per_gas: max_fee_per_gas.unwrap_or_default(), + max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(), + gas_limit: gas.unwrap_or_default(), value: value.unwrap_or(U256::ZERO), input: input.into_input().unwrap_or_default(), - to: match to { - Some(to) => TxKind::Call(to), - None => TxKind::Create, - }, + to: to.unwrap_or_default(), chain_id: 0, - access_list: to_eip_access_list(access_list.unwrap_or_default()), + access_list: access_list.unwrap_or_default(), })) } + // EIP4844 + (Some(3), None, _, _, _, Some(_), Some(_), Some(sidecar), to) => { + let tx = TxEip4844 { + nonce: nonce.unwrap_or_default(), + max_fee_per_gas: max_fee_per_gas.unwrap_or_default(), + max_priority_fee_per_gas: max_priority_fee_per_gas.unwrap_or_default(), + max_fee_per_blob_gas: max_fee_per_blob_gas.unwrap_or_default(), + gas_limit: gas.unwrap_or_default(), + value: value.unwrap_or(U256::ZERO), + input: input.into_input().unwrap_or_default(), + to: match to.unwrap_or(TxKind::Create) { + TxKind::Call(to) => to, + TxKind::Create => Address::ZERO, + }, + chain_id: 0, + access_list: access_list.unwrap_or_default(), + blob_versioned_hashes: blob_versioned_hashes.unwrap_or_default(), + }; + Some(TypedTransactionRequest::EIP4844(TxEip4844Variant::TxEip4844WithSidecar( + TxEip4844WithSidecar::from_tx_and_sidecar(tx, sidecar), + ))) + } _ => None, } } +fn has_optimism_fields(other: &OtherFields) -> bool { + other.contains_key("sourceHash") && + other.contains_key("mint") && + other.contains_key("isSystemTx") +} + #[derive(Clone, Debug, PartialEq, Eq)] pub enum TypedTransactionRequest { Legacy(TxLegacy), EIP2930(TxEip2930), EIP1559(TxEip1559), + EIP4844(TxEip4844Variant), Deposit(DepositTransactionRequest), } @@ -197,13 +232,13 @@ impl From for TypedTransaction { impl From for MaybeImpersonatedTransaction { fn from(value: TypedTransaction) -> Self { - MaybeImpersonatedTransaction::new(value) + Self::new(value) } } impl Decodable for MaybeImpersonatedTransaction { fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { - TypedTransaction::decode(buf).map(MaybeImpersonatedTransaction::new) + TypedTransaction::decode(buf).map(Self::new) } } @@ -237,19 +272,19 @@ pub fn to_alloy_transaction_with_hash_and_sender( match transaction { TypedTransaction::Legacy(t) => RpcTransaction { hash, - nonce: U64::from(t.nonce), + nonce: t.tx().nonce, block_hash: None, block_number: None, transaction_index: None, from, to: None, - value: t.value, - gas_price: Some(U128::from(t.gas_price)), - max_fee_per_gas: Some(U128::from(t.gas_price)), - max_priority_fee_per_gas: Some(U128::from(t.gas_price)), - gas: U256::from(t.gas_limit), - input: t.input.clone(), - chain_id: t.chain_id.map(U64::from), + value: t.tx().value, + gas_price: Some(t.tx().gas_price), + max_fee_per_gas: Some(t.tx().gas_price), + max_priority_fee_per_gas: Some(t.tx().gas_price), + gas: t.tx().gas_limit, + input: t.tx().input.clone(), + chain_id: t.tx().chain_id, signature: Some(RpcSignature { r: t.signature().r(), s: t.signature().s(), @@ -259,66 +294,93 @@ pub fn to_alloy_transaction_with_hash_and_sender( access_list: None, transaction_type: None, max_fee_per_blob_gas: None, - blob_versioned_hashes: vec![], + blob_versioned_hashes: None, other: Default::default(), }, TypedTransaction::EIP2930(t) => RpcTransaction { hash, - nonce: U64::from(t.nonce), + nonce: t.tx().nonce, block_hash: None, block_number: None, transaction_index: None, from, to: None, - value: t.value, - gas_price: Some(U128::from(t.gas_price)), - max_fee_per_gas: Some(U128::from(t.gas_price)), - max_priority_fee_per_gas: Some(U128::from(t.gas_price)), - gas: U256::from(t.gas_limit), - input: t.input.clone(), - chain_id: Some(U64::from(t.chain_id)), + value: t.tx().value, + gas_price: Some(t.tx().gas_price), + max_fee_per_gas: Some(t.tx().gas_price), + max_priority_fee_per_gas: Some(t.tx().gas_price), + gas: t.tx().gas_limit, + input: t.tx().input.clone(), + chain_id: Some(t.tx().chain_id), signature: Some(RpcSignature { r: t.signature().r(), s: t.signature().s(), v: U256::from(t.signature().v().y_parity_byte()), y_parity: Some(alloy_rpc_types::Parity::from(t.signature().v().y_parity())), }), - access_list: Some(from_eip_to_alloy_access_list(t.access_list.clone()).0), - transaction_type: Some(U64::from(1)), + access_list: Some(t.tx().access_list.clone()), + transaction_type: Some(1), max_fee_per_blob_gas: None, - blob_versioned_hashes: vec![], + blob_versioned_hashes: None, other: Default::default(), }, TypedTransaction::EIP1559(t) => RpcTransaction { hash, - nonce: U64::from(t.nonce), + nonce: t.tx().nonce, block_hash: None, block_number: None, transaction_index: None, from, to: None, - value: t.value, + value: t.tx().value, gas_price: None, - max_fee_per_gas: Some(U128::from(t.max_fee_per_gas)), - max_priority_fee_per_gas: Some(U128::from(t.max_priority_fee_per_gas)), - gas: U256::from(t.gas_limit), - input: t.input.clone(), - chain_id: Some(U64::from(t.chain_id)), + max_fee_per_gas: Some(t.tx().max_fee_per_gas), + max_priority_fee_per_gas: Some(t.tx().max_priority_fee_per_gas), + gas: t.tx().gas_limit, + input: t.tx().input.clone(), + chain_id: Some(t.tx().chain_id), signature: Some(RpcSignature { r: t.signature().r(), s: t.signature().s(), v: U256::from(t.signature().v().y_parity_byte()), y_parity: Some(alloy_rpc_types::Parity::from(t.signature().v().y_parity())), }), - access_list: Some(from_eip_to_alloy_access_list(t.access_list.clone()).0), - transaction_type: Some(U64::from(2)), + access_list: Some(t.tx().access_list.clone()), + transaction_type: Some(2), max_fee_per_blob_gas: None, - blob_versioned_hashes: vec![], + blob_versioned_hashes: None, + other: Default::default(), + }, + TypedTransaction::EIP4844(t) => RpcTransaction { + hash, + nonce: t.tx().tx().nonce, + block_hash: None, + block_number: None, + transaction_index: None, + from, + to: None, + value: t.tx().tx().value, + gas_price: Some(t.tx().tx().max_fee_per_gas), + max_fee_per_gas: Some(t.tx().tx().max_fee_per_gas), + max_priority_fee_per_gas: Some(t.tx().tx().max_priority_fee_per_gas), + gas: t.tx().tx().gas_limit, + input: t.tx().tx().input.clone(), + chain_id: Some(t.tx().tx().chain_id), + signature: Some(RpcSignature { + r: t.signature().r(), + s: t.signature().s(), + v: U256::from(t.signature().v().y_parity_byte()), + y_parity: Some(alloy_rpc_types::Parity::from(t.signature().v().y_parity())), + }), + access_list: Some(t.tx().tx().access_list.clone()), + transaction_type: Some(3), + max_fee_per_blob_gas: Some(t.tx().tx().max_fee_per_blob_gas), + blob_versioned_hashes: Some(t.tx().tx().blob_versioned_hashes.clone()), other: Default::default(), }, TypedTransaction::Deposit(t) => RpcTransaction { hash, - nonce: U64::from(t.nonce), + nonce: t.nonce, block_hash: None, block_number: None, transaction_index: None, @@ -328,14 +390,14 @@ pub fn to_alloy_transaction_with_hash_and_sender( gas_price: None, max_fee_per_gas: None, max_priority_fee_per_gas: None, - gas: U256::from(t.gas_limit), + gas: t.gas_limit, input: t.input.clone().0.into(), - chain_id: t.chain_id().map(U64::from), + chain_id: t.chain_id().map(u64::from), signature: None, access_list: None, transaction_type: None, max_fee_per_blob_gas: None, - blob_versioned_hashes: vec![], + blob_versioned_hashes: None, other: Default::default(), }, } @@ -369,7 +431,7 @@ impl PendingTransaction { } } - pub fn nonce(&self) -> U256 { + pub fn nonce(&self) -> u64 { self.transaction.nonce() } @@ -387,25 +449,25 @@ impl PendingTransaction { fn transact_to(kind: &TxKind) -> TransactTo { match kind { TxKind::Call(c) => TransactTo::Call(*c), - TxKind::Create => TransactTo::Create(CreateScheme::Create), + TxKind::Create => TransactTo::Create, } } let caller = *self.sender(); match &self.transaction.transaction { TypedTransaction::Legacy(tx) => { - let chain_id = tx.chain_id(); + let chain_id = tx.tx().chain_id; let TxLegacy { nonce, gas_price, gas_limit, value, to, input, .. } = tx.tx(); TxEnv { caller, transact_to: transact_to(to), - data: alloy_primitives::Bytes(input.0.clone()), + data: input.clone(), chain_id, nonce: Some(*nonce), value: (*value), gas_price: U256::from(*gas_price), gas_priority_fee: None, - gas_limit: *gas_limit, + gas_limit: *gas_limit as u64, access_list: vec![], ..Default::default() } @@ -425,14 +487,14 @@ impl PendingTransaction { TxEnv { caller, transact_to: transact_to(to), - data: alloy_primitives::Bytes(input.0.clone()), + data: input.clone(), chain_id: Some(*chain_id), nonce: Some(*nonce), value: *value, gas_price: U256::from(*gas_price), gas_priority_fee: None, - gas_limit: *gas_limit, - access_list: eip_to_revm_access_list(access_list.0.clone()), + gas_limit: *gas_limit as u64, + access_list: access_list.flattened(), ..Default::default() } } @@ -452,14 +514,45 @@ impl PendingTransaction { TxEnv { caller, transact_to: transact_to(to), - data: alloy_primitives::Bytes(input.0.clone()), + data: input.clone(), chain_id: Some(*chain_id), nonce: Some(*nonce), value: *value, gas_price: U256::from(*max_fee_per_gas), gas_priority_fee: Some(U256::from(*max_priority_fee_per_gas)), - gas_limit: *gas_limit, - access_list: eip_to_revm_access_list(access_list.0.clone()), + gas_limit: *gas_limit as u64, + access_list: access_list.flattened(), + ..Default::default() + } + } + TypedTransaction::EIP4844(tx) => { + let TxEip4844 { + chain_id, + nonce, + max_fee_per_blob_gas, + max_fee_per_gas, + max_priority_fee_per_gas, + gas_limit, + to, + value, + input, + access_list, + blob_versioned_hashes, + .. + } = tx.tx().tx(); + TxEnv { + caller, + transact_to: TransactTo::call(*to), + data: input.clone(), + chain_id: Some(*chain_id), + nonce: Some(*nonce), + value: *value, + gas_price: U256::from(*max_fee_per_gas), + gas_priority_fee: Some(U256::from(*max_priority_fee_per_gas)), + max_fee_per_blob_gas: Some(U256::from(*max_fee_per_blob_gas)), + blob_hashes: blob_versioned_hashes.clone(), + gas_limit: *gas_limit as u64, + access_list: access_list.flattened(), ..Default::default() } } @@ -479,13 +572,13 @@ impl PendingTransaction { TxEnv { caller, transact_to: transact_to(kind), - data: alloy_primitives::Bytes(input.0.clone()), + data: input.clone(), chain_id, - nonce: Some(nonce.to::()), + nonce: Some(*nonce), value: *value, gas_price: U256::ZERO, gas_priority_fee: None, - gas_limit: gas_limit.to::(), + gas_limit: *gas_limit as u64, access_list: vec![], optimism: OptimismFields { source_hash: Some(*source_hash), @@ -501,7 +594,7 @@ impl PendingTransaction { } /// Container type for signed, typed transactions. -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum TypedTransaction { /// Legacy transaction type Legacy(Signed), @@ -509,107 +602,159 @@ pub enum TypedTransaction { EIP2930(Signed), /// EIP-1559 transaction EIP1559(Signed), + /// EIP-4844 transaction + EIP4844(Signed), /// op-stack deposit transaction Deposit(DepositTransaction), } impl TypedTransaction { - /// Returns true if the transaction uses dynamic fees: EIP1559 + /// Returns true if the transaction uses dynamic fees: EIP1559 or EIP4844 pub fn is_dynamic_fee(&self) -> bool { - matches!(self, TypedTransaction::EIP1559(_)) + matches!(self, Self::EIP1559(_)) || matches!(self, Self::EIP4844(_)) } - pub fn gas_price(&self) -> U256 { - U256::from(match self { - TypedTransaction::Legacy(tx) => tx.gas_price, - TypedTransaction::EIP2930(tx) => tx.gas_price, - TypedTransaction::EIP1559(tx) => tx.max_fee_per_gas, - TypedTransaction::Deposit(_) => 0, - }) + pub fn gas_price(&self) -> u128 { + match self { + Self::Legacy(tx) => tx.tx().gas_price, + Self::EIP2930(tx) => tx.tx().gas_price, + Self::EIP1559(tx) => tx.tx().max_fee_per_gas, + Self::EIP4844(tx) => tx.tx().tx().max_fee_per_gas, + Self::Deposit(_) => 0, + } } - pub fn gas_limit(&self) -> U256 { - U256::from(match self { - TypedTransaction::Legacy(tx) => tx.gas_limit, - TypedTransaction::EIP2930(tx) => tx.gas_limit, - TypedTransaction::EIP1559(tx) => tx.gas_limit, - TypedTransaction::Deposit(tx) => tx.gas_limit.to::(), - }) + pub fn gas_limit(&self) -> u128 { + match self { + Self::Legacy(tx) => tx.tx().gas_limit, + Self::EIP2930(tx) => tx.tx().gas_limit, + Self::EIP1559(tx) => tx.tx().gas_limit, + Self::EIP4844(tx) => tx.tx().tx().gas_limit, + Self::Deposit(tx) => tx.gas_limit, + } } pub fn value(&self) -> U256 { U256::from(match self { - TypedTransaction::Legacy(tx) => tx.value, - TypedTransaction::EIP2930(tx) => tx.value, - TypedTransaction::EIP1559(tx) => tx.value, - TypedTransaction::Deposit(tx) => tx.value, + Self::Legacy(tx) => tx.tx().value, + Self::EIP2930(tx) => tx.tx().value, + Self::EIP1559(tx) => tx.tx().value, + Self::EIP4844(tx) => tx.tx().tx().value, + Self::Deposit(tx) => tx.value, }) } pub fn data(&self) -> &Bytes { match self { - TypedTransaction::Legacy(tx) => &tx.input, - TypedTransaction::EIP2930(tx) => &tx.input, - TypedTransaction::EIP1559(tx) => &tx.input, - TypedTransaction::Deposit(tx) => &tx.input, + Self::Legacy(tx) => &tx.tx().input, + Self::EIP2930(tx) => &tx.tx().input, + Self::EIP1559(tx) => &tx.tx().input, + Self::EIP4844(tx) => &tx.tx().tx().input, + Self::Deposit(tx) => &tx.input, } } /// Returns the transaction type pub fn r#type(&self) -> Option { match self { - TypedTransaction::Legacy(_) => None, - TypedTransaction::EIP2930(_) => Some(1), - TypedTransaction::EIP1559(_) => Some(2), - TypedTransaction::Deposit(_) => Some(0x7E), + Self::Legacy(_) => None, + Self::EIP2930(_) => Some(1), + Self::EIP1559(_) => Some(2), + Self::EIP4844(_) => Some(3), + Self::Deposit(_) => Some(0x7E), } } /// Max cost of the transaction - pub fn max_cost(&self) -> U256 { - self.gas_limit().saturating_mul(self.gas_price()) + /// It is the gas limit multiplied by the gas price, + /// and if the transaction is EIP-4844, the result of (total blob gas cost * max fee per blob + /// gas) is also added + pub fn max_cost(&self) -> u128 { + let mut max_cost = self.gas_limit().saturating_mul(self.gas_price()); + + if self.is_eip4844() { + max_cost = max_cost.saturating_add( + self.blob_gas().unwrap_or(0).mul(self.max_fee_per_blob_gas().unwrap_or(0)), + ) + } + + max_cost + } + + pub fn blob_gas(&self) -> Option { + match self { + Self::EIP4844(tx) => Some(tx.tx().tx().blob_gas() as u128), + _ => None, + } + } + + pub fn max_fee_per_blob_gas(&self) -> Option { + match self { + Self::EIP4844(tx) => Some(tx.tx().tx().max_fee_per_blob_gas), + _ => None, + } } /// Returns a helper type that contains commonly used values as fields pub fn essentials(&self) -> TransactionEssentials { match self { - TypedTransaction::Legacy(t) => TransactionEssentials { + Self::Legacy(t) => TransactionEssentials { kind: t.tx().to, - input: t.input.clone(), - nonce: U256::from(t.tx().nonce), - gas_limit: U256::from(t.tx().gas_limit), + input: t.tx().input.clone(), + nonce: t.tx().nonce, + gas_limit: t.tx().gas_limit, gas_price: Some(U256::from(t.tx().gas_price)), max_fee_per_gas: None, max_priority_fee_per_gas: None, - value: t.value, + max_fee_per_blob_gas: None, + blob_versioned_hashes: None, + value: t.tx().value, chain_id: t.tx().chain_id, access_list: Default::default(), }, - TypedTransaction::EIP2930(t) => TransactionEssentials { + Self::EIP2930(t) => TransactionEssentials { kind: t.tx().to, - input: t.input.clone(), - nonce: U256::from(t.tx().nonce), - gas_limit: U256::from(t.tx().gas_limit), + input: t.tx().input.clone(), + nonce: t.tx().nonce, + gas_limit: t.tx().gas_limit, gas_price: Some(U256::from(t.tx().gas_price)), max_fee_per_gas: None, max_priority_fee_per_gas: None, - value: t.value, - chain_id: Some(t.chain_id), - access_list: to_alloy_access_list(t.access_list.clone()), + max_fee_per_blob_gas: None, + blob_versioned_hashes: None, + value: t.tx().value, + chain_id: Some(t.tx().chain_id), + access_list: t.tx().access_list.clone(), }, - TypedTransaction::EIP1559(t) => TransactionEssentials { - kind: t.to, - input: t.input.clone(), - nonce: U256::from(t.nonce), - gas_limit: U256::from(t.gas_limit), + Self::EIP1559(t) => TransactionEssentials { + kind: t.tx().to, + input: t.tx().input.clone(), + nonce: t.tx().nonce, + gas_limit: t.tx().gas_limit, gas_price: None, - max_fee_per_gas: Some(U256::from(t.max_fee_per_gas)), - max_priority_fee_per_gas: Some(U256::from(t.max_priority_fee_per_gas)), - value: t.value, - chain_id: Some(t.chain_id), - access_list: to_alloy_access_list(t.access_list.clone()), + max_fee_per_gas: Some(U256::from(t.tx().max_fee_per_gas)), + max_priority_fee_per_gas: Some(U256::from(t.tx().max_priority_fee_per_gas)), + max_fee_per_blob_gas: None, + blob_versioned_hashes: None, + value: t.tx().value, + chain_id: Some(t.tx().chain_id), + access_list: t.tx().access_list.clone(), + }, + Self::EIP4844(t) => TransactionEssentials { + kind: TxKind::Call(t.tx().tx().to), + input: t.tx().tx().input.clone(), + nonce: t.tx().tx().nonce, + gas_limit: t.tx().tx().gas_limit, + gas_price: Some(U256::from(t.tx().tx().max_fee_per_blob_gas)), + max_fee_per_gas: Some(U256::from(t.tx().tx().max_fee_per_gas)), + max_priority_fee_per_gas: Some(U256::from(t.tx().tx().max_priority_fee_per_gas)), + max_fee_per_blob_gas: Some(U256::from(t.tx().tx().max_fee_per_blob_gas)), + blob_versioned_hashes: Some(t.tx().tx().blob_versioned_hashes.clone()), + value: t.tx().tx().value, + chain_id: Some(t.tx().tx().chain_id), + access_list: t.tx().tx().access_list.clone(), }, - TypedTransaction::Deposit(t) => TransactionEssentials { + Self::Deposit(t) => TransactionEssentials { kind: t.kind, input: t.input.clone(), nonce: t.nonce, @@ -617,6 +762,8 @@ impl TypedTransaction { gas_price: Some(U256::from(0)), max_fee_per_gas: None, max_priority_fee_per_gas: None, + max_fee_per_blob_gas: None, + blob_versioned_hashes: None, value: t.value, chain_id: t.chain_id(), access_list: Default::default(), @@ -624,39 +771,51 @@ impl TypedTransaction { } } - pub fn nonce(&self) -> U256 { + pub fn nonce(&self) -> u64 { match self { - TypedTransaction::Legacy(t) => U256::from(t.nonce), - TypedTransaction::EIP2930(t) => U256::from(t.nonce), - TypedTransaction::EIP1559(t) => U256::from(t.nonce), - TypedTransaction::Deposit(t) => U256::from(t.nonce), + Self::Legacy(t) => t.tx().nonce, + Self::EIP2930(t) => t.tx().nonce, + Self::EIP1559(t) => t.tx().nonce, + Self::EIP4844(t) => t.tx().tx().nonce, + Self::Deposit(t) => t.nonce, } } pub fn chain_id(&self) -> Option { match self { - TypedTransaction::Legacy(t) => t.chain_id, - TypedTransaction::EIP2930(t) => Some(t.chain_id), - TypedTransaction::EIP1559(t) => Some(t.chain_id), - TypedTransaction::Deposit(t) => t.chain_id(), + Self::Legacy(t) => t.tx().chain_id, + Self::EIP2930(t) => Some(t.tx().chain_id), + Self::EIP1559(t) => Some(t.tx().chain_id), + Self::EIP4844(t) => Some(t.tx().tx().chain_id), + Self::Deposit(t) => t.chain_id(), } } pub fn as_legacy(&self) -> Option<&Signed> { match self { - TypedTransaction::Legacy(tx) => Some(tx), + Self::Legacy(tx) => Some(tx), _ => None, } } /// Returns true whether this tx is a legacy transaction pub fn is_legacy(&self) -> bool { - matches!(self, TypedTransaction::Legacy(_)) + matches!(self, Self::Legacy(_)) } /// Returns true whether this tx is a EIP1559 transaction pub fn is_eip1559(&self) -> bool { - matches!(self, TypedTransaction::EIP1559(_)) + matches!(self, Self::EIP1559(_)) + } + + /// Returns true whether this tx is a EIP2930 transaction + pub fn is_eip2930(&self) -> bool { + matches!(self, Self::EIP2930(_)) + } + + /// Returns true whether this tx is a EIP4844 transaction + pub fn is_eip4844(&self) -> bool { + matches!(self, Self::EIP4844(_)) } /// Returns the hash of the transaction. @@ -665,10 +824,11 @@ impl TypedTransaction { /// hash. This allows us to treat impersonated transactions as unique. pub fn hash(&self) -> B256 { match self { - TypedTransaction::Legacy(t) => *t.hash(), - TypedTransaction::EIP2930(t) => *t.hash(), - TypedTransaction::EIP1559(t) => *t.hash(), - TypedTransaction::Deposit(t) => t.hash(), + Self::Legacy(t) => *t.hash(), + Self::EIP2930(t) => *t.hash(), + Self::EIP1559(t) => *t.hash(), + Self::EIP4844(t) => *t.hash(), + Self::Deposit(t) => t.hash(), } } @@ -692,35 +852,38 @@ impl TypedTransaction { /// Recovers the Ethereum address which was used to sign the transaction. pub fn recover(&self) -> Result { match self { - TypedTransaction::Legacy(tx) => tx.recover_signer(), - TypedTransaction::EIP2930(tx) => tx.recover_signer(), - TypedTransaction::EIP1559(tx) => tx.recover_signer(), - TypedTransaction::Deposit(tx) => tx.recover(), + Self::Legacy(tx) => tx.recover_signer(), + Self::EIP2930(tx) => tx.recover_signer(), + Self::EIP1559(tx) => tx.recover_signer(), + Self::EIP4844(tx) => tx.recover_signer(), + Self::Deposit(tx) => tx.recover(), } } /// Returns what kind of transaction this is - pub fn kind(&self) -> &TxKind { + pub fn kind(&self) -> TxKind { match self { - TypedTransaction::Legacy(tx) => &tx.to, - TypedTransaction::EIP2930(tx) => &tx.to, - TypedTransaction::EIP1559(tx) => &tx.to, - TypedTransaction::Deposit(tx) => &tx.kind, + Self::Legacy(tx) => tx.tx().to, + Self::EIP2930(tx) => tx.tx().to, + Self::EIP1559(tx) => tx.tx().to, + Self::EIP4844(tx) => TxKind::Call(tx.tx().tx().to), + Self::Deposit(tx) => tx.kind, } } /// Returns the callee if this transaction is a call pub fn to(&self) -> Option
{ - self.kind().to() + self.kind().to().copied() } /// Returns the Signature of the transaction pub fn signature(&self) -> Signature { match self { - TypedTransaction::Legacy(tx) => *tx.signature(), - TypedTransaction::EIP2930(tx) => *tx.signature(), - TypedTransaction::EIP1559(tx) => *tx.signature(), - TypedTransaction::Deposit(_) => Signature::from_scalars_and_parity( + Self::Legacy(tx) => *tx.signature(), + Self::EIP2930(tx) => *tx.signature(), + Self::EIP1559(tx) => *tx.signature(), + Self::EIP4844(tx) => *tx.signature(), + Self::Deposit(_) => Signature::from_scalars_and_parity( B256::with_last_byte(1), B256::with_last_byte(1), false, @@ -733,73 +896,116 @@ impl TypedTransaction { impl Encodable for TypedTransaction { fn encode(&self, out: &mut dyn bytes::BufMut) { match self { - TypedTransaction::Legacy(tx) => tx.encode(out), - TypedTransaction::EIP2930(tx) => tx.encode(out), - TypedTransaction::EIP1559(tx) => tx.encode(out), - TypedTransaction::Deposit(tx) => tx.encode(out), + Self::Legacy(tx) => TxEnvelope::from(tx.clone()).encode(out), + Self::EIP2930(tx) => TxEnvelope::from(tx.clone()).encode(out), + Self::EIP1559(tx) => TxEnvelope::from(tx.clone()).encode(out), + Self::EIP4844(tx) => TxEnvelope::from(tx.clone()).encode(out), + Self::Deposit(tx) => { + let tx_payload_len = tx.fields_len(); + let tx_header_len = Header { list: false, payload_length: tx_payload_len }.length(); + Header { list: false, payload_length: 1 + tx_payload_len + tx_header_len } + .encode(out); + out.put_u8(0x7E); + tx.encode(out); + } } } } impl Decodable for TypedTransaction { fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { - use bytes::Buf; - use std::cmp::Ordering; + let mut h_decode_copy = *buf; + let header = alloy_rlp::Header::decode(&mut h_decode_copy)?; - let first = *buf.first().ok_or(alloy_rlp::Error::Custom("empty slice"))?; + // Legacy TX + if header.list { + return Ok(TxEnvelope::decode(buf)?.into()) + } - // a signed transaction is either encoded as a string (non legacy) or a list (legacy). - // We should not consume the buffer if we are decoding a legacy transaction, so let's - // check if the first byte is between 0x80 and 0xbf. - match first.cmp(&alloy_rlp::EMPTY_LIST_CODE) { - Ordering::Less => { - // strip out the string header - // NOTE: typed transaction encodings either contain a "rlp header" which contains - // the type of the payload and its length, or they do not contain a header and - // start with the tx type byte. - // - // This line works for both types of encodings because byte slices starting with - // 0x01 and 0x02 return a Header { list: false, payload_length: 1 } when input to - // Header::decode. - // If the encoding includes a header, the header will be properly decoded and - // consumed. - // Otherwise, header decoding will succeed but nothing is consumed. - let _header = alloy_rlp::Header::decode(buf)?; - let tx_type = *buf.first().ok_or(alloy_rlp::Error::Custom( - "typed tx cannot be decoded from an empty slice", - ))?; - if tx_type == 0x01 { - buf.advance(1); - as Decodable>::decode(buf).map(TypedTransaction::EIP2930) - } else if tx_type == 0x02 { - buf.advance(1); - as Decodable>::decode(buf).map(TypedTransaction::EIP1559) - } else if tx_type == 0x7E { - buf.advance(1); - ::decode(buf).map(TypedTransaction::Deposit) - } else { - Err(alloy_rlp::Error::Custom("invalid tx type")) - } - } - Ordering::Equal => { - Err(alloy_rlp::Error::Custom("an empty list is not a valid transaction encoding")) - } - Ordering::Greater => { - as Decodable>::decode(buf).map(TypedTransaction::Legacy) + // Check byte after header + let ty = *h_decode_copy.first().ok_or(alloy_rlp::Error::Custom("empty slice"))?; + + if ty != 0x7E { + Ok(TxEnvelope::decode(buf)?.into()) + } else { + Ok(Self::Deposit(DepositTransaction::decode(&mut h_decode_copy)?)) + } + } +} + +impl Encodable2718 for TypedTransaction { + fn type_flag(&self) -> Option { + self.r#type() + } + + fn encode_2718_len(&self) -> usize { + match self { + Self::Legacy(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(), + Self::EIP2930(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(), + Self::EIP1559(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(), + Self::EIP4844(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(), + Self::Deposit(tx) => 1 + tx.length(), + } + } + + fn encode_2718(&self, out: &mut dyn BufMut) { + match self { + Self::Legacy(tx) => TxEnvelope::from(tx.clone()).encode_2718(out), + Self::EIP2930(tx) => TxEnvelope::from(tx.clone()).encode_2718(out), + Self::EIP1559(tx) => TxEnvelope::from(tx.clone()).encode_2718(out), + Self::EIP4844(tx) => TxEnvelope::from(tx.clone()).encode_2718(out), + Self::Deposit(tx) => { + out.put_u8(0x7E); + tx.encode(out); } } } } +impl Decodable2718 for TypedTransaction { + fn typed_decode(ty: u8, buf: &mut &[u8]) -> Result { + if ty == 0x7E { + return Ok(Self::Deposit(DepositTransaction::decode(buf)?)) + } + match TxEnvelope::typed_decode(ty, buf)? { + TxEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)), + TxEnvelope::Eip1559(tx) => Ok(Self::EIP1559(tx)), + TxEnvelope::Eip4844(tx) => Ok(Self::EIP4844(tx)), + _ => unreachable!(), + } + } + + fn fallback_decode(buf: &mut &[u8]) -> Result { + match TxEnvelope::fallback_decode(buf)? { + TxEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)), + _ => unreachable!(), + } + } +} + +impl From for TypedTransaction { + fn from(value: TxEnvelope) -> Self { + match value { + TxEnvelope::Legacy(tx) => Self::Legacy(tx), + TxEnvelope::Eip2930(tx) => Self::EIP2930(tx), + TxEnvelope::Eip1559(tx) => Self::EIP1559(tx), + TxEnvelope::Eip4844(tx) => Self::EIP4844(tx), + _ => unreachable!(), + } + } +} + #[derive(Clone, Debug, PartialEq, Eq)] pub struct TransactionEssentials { pub kind: TxKind, pub input: Bytes, - pub nonce: U256, - pub gas_limit: U256, + pub nonce: u64, + pub gas_limit: u128, pub gas_price: Option, pub max_fee_per_gas: Option, pub max_priority_fee_per_gas: Option, + pub max_fee_per_blob_gas: Option, + pub blob_versioned_hashes: Option>, pub value: U256, pub chain_id: Option, pub access_list: AccessList, @@ -809,83 +1015,195 @@ pub struct TransactionEssentials { #[derive(Clone, Debug, PartialEq, Eq)] pub struct TransactionInfo { pub transaction_hash: B256, - pub transaction_index: u32, + pub transaction_index: u64, pub from: Address, pub to: Option
, pub contract_address: Option
, - pub logs: Vec, - pub logs_bloom: Bloom, pub traces: Vec, pub exit: InstructionResult, pub out: Option, pub nonce: u64, + pub gas_used: u128, } -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum TypedReceipt { - Legacy(ReceiptWithBloom), - EIP2930(ReceiptWithBloom), - EIP1559(ReceiptWithBloom), - Deposit(ReceiptWithBloom), +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct DepositReceipt { + #[serde(flatten)] + pub inner: ReceiptWithBloom, + #[serde(default, with = "alloy_serde::num::u64_opt_via_ruint")] + pub deposit_nonce: Option, + #[serde(default, with = "alloy_serde::num::u64_opt_via_ruint")] + pub deposit_receipt_version: Option, } -impl TypedReceipt { - pub fn gas_used(&self) -> U256 { - match self { - TypedReceipt::Legacy(r) | - TypedReceipt::EIP1559(r) | - TypedReceipt::EIP2930(r) | - TypedReceipt::Deposit(r) => U256::from(r.receipt.cumulative_gas_used), +impl DepositReceipt { + fn payload_len(&self) -> usize { + self.inner.receipt.status.length() + + self.inner.receipt.cumulative_gas_used.length() + + self.inner.logs_bloom.length() + + self.inner.receipt.logs.length() + + self.deposit_nonce.map_or(0, |n| n.length()) + + self.deposit_receipt_version.map_or(0, |n| n.length()) + } + + /// Returns the rlp header for the receipt payload. + fn receipt_rlp_header(&self) -> alloy_rlp::Header { + alloy_rlp::Header { list: true, payload_length: self.payload_len() } + } + + /// Encodes the receipt data. + fn encode_fields(&self, out: &mut dyn BufMut) { + self.receipt_rlp_header().encode(out); + self.inner.receipt.status.encode(out); + self.inner.receipt.cumulative_gas_used.encode(out); + self.inner.logs_bloom.encode(out); + self.inner.receipt.logs.encode(out); + if let Some(n) = self.deposit_nonce { + n.encode(out); + } + if let Some(n) = self.deposit_receipt_version { + n.encode(out); } } - pub fn logs_bloom(&self) -> &Bloom { + /// Decodes the receipt payload + fn decode_receipt(buf: &mut &[u8]) -> alloy_rlp::Result { + let b: &mut &[u8] = &mut &**buf; + let rlp_head = alloy_rlp::Header::decode(b)?; + if !rlp_head.list { + return Err(alloy_rlp::Error::UnexpectedString); + } + let started_len = b.len(); + let remaining = |b: &[u8]| rlp_head.payload_length - (started_len - b.len()) > 0; + + let status = Decodable::decode(b)?; + let cumulative_gas_used = Decodable::decode(b)?; + let logs_bloom = Decodable::decode(b)?; + let logs = Decodable::decode(b)?; + let deposit_nonce = remaining(b).then(|| alloy_rlp::Decodable::decode(b)).transpose()?; + let deposit_nonce_version = + remaining(b).then(|| alloy_rlp::Decodable::decode(b)).transpose()?; + + let this = Self { + inner: ReceiptWithBloom { + receipt: Receipt { status, cumulative_gas_used, logs }, + logs_bloom, + }, + deposit_nonce, + deposit_receipt_version: deposit_nonce_version, + }; + + let consumed = started_len - b.len(); + if consumed != rlp_head.payload_length { + return Err(alloy_rlp::Error::ListLengthMismatch { + expected: rlp_head.payload_length, + got: consumed, + }); + } + + *buf = *b; + Ok(this) + } +} + +impl alloy_rlp::Encodable for DepositReceipt { + fn encode(&self, out: &mut dyn BufMut) { + self.encode_fields(out); + } + + fn length(&self) -> usize { + let payload_length = self.payload_len(); + payload_length + length_of_length(payload_length) + } +} + +impl alloy_rlp::Decodable for DepositReceipt { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + Self::decode_receipt(buf) + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(tag = "type")] +pub enum TypedReceipt { + #[serde(rename = "0x0", alias = "0x00")] + Legacy(ReceiptWithBloom), + #[serde(rename = "0x1", alias = "0x01")] + EIP2930(ReceiptWithBloom), + #[serde(rename = "0x2", alias = "0x02")] + EIP1559(ReceiptWithBloom), + #[serde(rename = "0x3", alias = "0x03")] + EIP4844(ReceiptWithBloom), + #[serde(rename = "0x7E", alias = "0x7e")] + Deposit(DepositReceipt), +} + +impl TypedReceipt { + pub fn as_receipt_with_bloom(&self) -> &ReceiptWithBloom { match self { - TypedReceipt::Legacy(r) | - TypedReceipt::EIP1559(r) | - TypedReceipt::EIP2930(r) | - TypedReceipt::Deposit(r) => &r.bloom, + Self::Legacy(r) | Self::EIP1559(r) | Self::EIP2930(r) | Self::EIP4844(r) => r, + Self::Deposit(r) => &r.inner, } } } -impl From for ReceiptWithBloom { - fn from(val: TypedReceipt) -> Self { - match val { - TypedReceipt::Legacy(r) | - TypedReceipt::EIP1559(r) | - TypedReceipt::EIP2930(r) | - TypedReceipt::Deposit(r) => r, +impl TypedReceipt { + pub fn cumulative_gas_used(&self) -> u128 { + self.as_receipt_with_bloom().cumulative_gas_used() + } + + pub fn logs_bloom(&self) -> &Bloom { + &self.as_receipt_with_bloom().logs_bloom + } + + pub fn logs(&self) -> &[Log] { + self.as_receipt_with_bloom().logs() + } +} + +impl From> for TypedReceipt { + fn from(value: ReceiptEnvelope) -> Self { + match value { + ReceiptEnvelope::Legacy(r) => Self::Legacy(r), + ReceiptEnvelope::Eip2930(r) => Self::EIP2930(r), + ReceiptEnvelope::Eip1559(r) => Self::EIP1559(r), + ReceiptEnvelope::Eip4844(r) => Self::EIP4844(r), + _ => unreachable!(), } } } impl Encodable for TypedReceipt { fn encode(&self, out: &mut dyn bytes::BufMut) { - use alloy_rlp::Header; - match self { - TypedReceipt::Legacy(r) => r.encode(out), + Self::Legacy(r) => r.encode(out), receipt => { let payload_len = match receipt { - TypedReceipt::EIP2930(r) => r.length() + 1, - TypedReceipt::EIP1559(r) => r.length() + 1, - TypedReceipt::Deposit(r) => r.length() + 1, + Self::EIP2930(r) => r.length() + 1, + Self::EIP1559(r) => r.length() + 1, + Self::EIP4844(r) => r.length() + 1, + Self::Deposit(r) => r.length() + 1, _ => unreachable!("receipt already matched"), }; match receipt { - TypedReceipt::EIP2930(r) => { + Self::EIP2930(r) => { Header { list: true, payload_length: payload_len }.encode(out); 1u8.encode(out); r.encode(out); } - TypedReceipt::EIP1559(r) => { + Self::EIP1559(r) => { Header { list: true, payload_length: payload_len }.encode(out); 2u8.encode(out); r.encode(out); } - TypedReceipt::Deposit(r) => { + Self::EIP4844(r) => { + Header { list: true, payload_length: payload_len }.encode(out); + 3u8.encode(out); + r.encode(out); + } + Self::Deposit(r) => { Header { list: true, payload_length: payload_len }.encode(out); 0x7Eu8.encode(out); r.encode(out); @@ -899,7 +1217,6 @@ impl Encodable for TypedReceipt { impl Decodable for TypedReceipt { fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { - use alloy_rlp::Header; use bytes::Buf; use std::cmp::Ordering; @@ -923,9 +1240,12 @@ impl Decodable for TypedReceipt { } else if receipt_type == 0x02 { buf.advance(1); ::decode(buf).map(TypedReceipt::EIP1559) + } else if receipt_type == 0x03 { + buf.advance(1); + ::decode(buf).map(TypedReceipt::EIP4844) } else if receipt_type == 0x7E { buf.advance(1); - ::decode(buf).map(TypedReceipt::Deposit) + ::decode(buf).map(TypedReceipt::Deposit) } else { Err(alloy_rlp::Error::Custom("invalid receipt type")) } @@ -940,41 +1260,123 @@ impl Decodable for TypedReceipt { } } -/// Translates an EIP-2930 access list to an alloy-rpc-types access list. -pub fn to_alloy_access_list( - access_list: alloy_eips::eip2930::AccessList, -) -> alloy_rpc_types::AccessList { - alloy_rpc_types::AccessList( - access_list - .0 - .into_iter() - .map(|item| alloy_rpc_types::AccessListItem { - address: item.address, - storage_keys: item.storage_keys, - }) - .collect(), - ) +impl Encodable2718 for TypedReceipt { + fn type_flag(&self) -> Option { + match self { + Self::Legacy(_) => None, + Self::EIP2930(_) => Some(1), + Self::EIP1559(_) => Some(2), + Self::EIP4844(_) => Some(3), + Self::Deposit(_) => Some(0x7E), + } + } + + fn encode_2718_len(&self) -> usize { + match self { + Self::Legacy(r) => ReceiptEnvelope::Legacy(r.clone()).encode_2718_len(), + Self::EIP2930(r) => ReceiptEnvelope::Eip2930(r.clone()).encode_2718_len(), + Self::EIP1559(r) => ReceiptEnvelope::Eip1559(r.clone()).encode_2718_len(), + Self::EIP4844(r) => ReceiptEnvelope::Eip4844(r.clone()).encode_2718_len(), + Self::Deposit(r) => 1 + r.length(), + } + } + + fn encode_2718(&self, out: &mut dyn BufMut) { + match self { + Self::Legacy(r) => ReceiptEnvelope::Legacy(r.clone()).encode_2718(out), + Self::EIP2930(r) => ReceiptEnvelope::Eip2930(r.clone()).encode_2718(out), + Self::EIP1559(r) => ReceiptEnvelope::Eip1559(r.clone()).encode_2718(out), + Self::EIP4844(r) => ReceiptEnvelope::Eip4844(r.clone()).encode_2718(out), + Self::Deposit(r) => { + out.put_u8(0x7E); + r.encode(out); + } + } + } +} + +impl Decodable2718 for TypedReceipt { + fn typed_decode(ty: u8, buf: &mut &[u8]) -> Result { + if ty == 0x7E { + return Ok(Self::Deposit(DepositReceipt::decode(buf)?)) + } + match ReceiptEnvelope::typed_decode(ty, buf)? { + ReceiptEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)), + ReceiptEnvelope::Eip1559(tx) => Ok(Self::EIP1559(tx)), + ReceiptEnvelope::Eip4844(tx) => Ok(Self::EIP4844(tx)), + _ => unreachable!(), + } + } + + fn fallback_decode(buf: &mut &[u8]) -> Result { + match ReceiptEnvelope::fallback_decode(buf)? { + ReceiptEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)), + _ => unreachable!(), + } + } } -/// Translates an alloy-rpc-types access list to an EIP-2930 access list. -pub fn to_eip_access_list( - access_list: alloy_rpc_types::AccessList, -) -> alloy_eips::eip2930::AccessList { - alloy_eips::eip2930::AccessList( - access_list - .0 - .into_iter() - .map(|item| alloy_eips::eip2930::AccessListItem { - address: item.address, - storage_keys: item.storage_keys, - }) - .collect(), - ) +pub type ReceiptResponse = TransactionReceipt>; + +pub fn convert_to_anvil_receipt(receipt: AnyTransactionReceipt) -> Option { + let WithOtherFields { + inner: + TransactionReceipt { + transaction_hash, + transaction_index, + block_hash, + block_number, + gas_used, + contract_address, + effective_gas_price, + from, + to, + blob_gas_price, + blob_gas_used, + state_root, + inner: AnyReceiptEnvelope { inner: receipt_with_bloom, r#type }, + }, + other, + } = receipt; + + Some(TransactionReceipt { + transaction_hash, + transaction_index, + block_hash, + block_number, + gas_used, + contract_address, + effective_gas_price, + from, + to, + blob_gas_price, + blob_gas_used, + state_root, + inner: match r#type { + 0x00 => TypedReceipt::Legacy(receipt_with_bloom), + 0x01 => TypedReceipt::EIP2930(receipt_with_bloom), + 0x02 => TypedReceipt::EIP1559(receipt_with_bloom), + 0x03 => TypedReceipt::EIP4844(receipt_with_bloom), + 0x7E => TypedReceipt::Deposit(DepositReceipt { + inner: receipt_with_bloom, + deposit_nonce: other + .get_deserialized::("depositNonce") + .transpose() + .ok()? + .map(|v| v.to()), + deposit_receipt_version: other + .get_deserialized::("depositReceiptVersion") + .transpose() + .ok()? + .map(|v| v.to()), + }), + _ => return None, + }, + }) } #[cfg(test)] mod tests { - use alloy_consensus::Receipt; use alloy_primitives::{b256, hex, LogData}; use std::str::FromStr; @@ -987,8 +1389,8 @@ mod tests { let tx = TxLegacy { nonce: 2u64, - gas_price: 1000000000u64.into(), - gas_limit: 100000u64, + gas_price: 1000000000u128, + gas_limit: 100000u128, to: TxKind::Call(Address::from_slice( &hex::decode("d3e8763675e4c425df46cc3b5c0f6cbdac396046").unwrap()[..], )), @@ -1038,6 +1440,39 @@ mod tests { ); } + // Test vector from https://sepolia.etherscan.io/tx/0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0 + // Blobscan: https://sepolia.blobscan.com/tx/0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0 + #[test] + fn test_decode_live_4844_tx() { + use alloy_primitives::{address, b256}; + + // https://sepolia.etherscan.io/getRawTx?tx=0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0 + let raw_tx = alloy_primitives::hex::decode("0x03f9011d83aa36a7820fa28477359400852e90edd0008252089411e9ca82a3a762b4b5bd264d4173a242e7a770648080c08504a817c800f8a5a0012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921aa00152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4a0013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7a001148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1a0011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e654901a0c8de4cced43169f9aa3d36506363b2d2c44f6c49fc1fd91ea114c86f3757077ea01e11fdd0d1934eda0492606ee0bb80a7bf8f35cc5f86ec60fe5031ba48bfd544").unwrap(); + let res = TypedTransaction::decode(&mut raw_tx.as_slice()).unwrap(); + assert_eq!(res.r#type(), Some(3)); + + let tx = match res { + TypedTransaction::EIP4844(tx) => tx, + _ => unreachable!(), + }; + + assert_eq!(tx.tx().tx().to, address!("11E9CA82A3a762b4B5bd264d4173a242e7a77064")); + + assert_eq!( + tx.tx().tx().blob_versioned_hashes, + vec![ + b256!("012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921a"), + b256!("0152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4"), + b256!("013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7"), + b256!("01148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1"), + b256!("011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e6549") + ] + ); + + let from = tx.recover_signer().unwrap(); + assert_eq!(from, address!("A83C816D4f9b2783761a22BA6FADB0eB0606D7B2")); + } + #[test] fn can_recover_sender_not_normalized() { let bytes = hex::decode("f85f800182520894095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba048b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353a0efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804").unwrap(); @@ -1046,11 +1481,11 @@ mod tests { panic!("decoding TypedTransaction failed"); }; - assert_eq!(tx.input, Bytes::from(b"")); - assert_eq!(tx.gas_price, 1); - assert_eq!(tx.gas_limit, 21000); - assert_eq!(tx.nonce, 0); - if let TxKind::Call(to) = tx.to { + assert_eq!(tx.tx().input, Bytes::from(b"")); + assert_eq!(tx.tx().gas_price, 1); + assert_eq!(tx.tx().gas_limit, 21000); + assert_eq!(tx.tx().nonce, 0); + if let TxKind::Call(to) = tx.tx().to { assert_eq!( to, "0x095e7baea6a6c7c4c2dfeb977efac326af552d87".parse::
().unwrap() @@ -1058,7 +1493,7 @@ mod tests { } else { panic!("expected a call transaction"); } - assert_eq!(tx.value, U256::from(0x0au64)); + assert_eq!(tx.tx().value, U256::from(0x0au64)); assert_eq!( tx.recover_signer().unwrap(), "0f65fe9276bc9a24ae7083ae28e2660ef72df99e".parse::
().unwrap() @@ -1072,8 +1507,8 @@ mod tests { let mut data = vec![]; let receipt = TypedReceipt::Legacy(ReceiptWithBloom { receipt: Receipt { - success: false, - cumulative_gas_used: 0x1u64, + status: false.into(), + cumulative_gas_used: 0x1u128, logs: vec![Log { address: Address::from_str("0000000000000000000000000000000000000011").unwrap(), data: LogData::new_unchecked( @@ -1091,7 +1526,7 @@ mod tests { ), }], }, - bloom: [0; 256].into(), + logs_bloom: [0; 256].into(), }); receipt.encode(&mut data); @@ -1107,8 +1542,8 @@ mod tests { let expected = TypedReceipt::Legacy(ReceiptWithBloom { receipt: Receipt { - success: false, - cumulative_gas_used: 0x1u64, + status: false.into(), + cumulative_gas_used: 0x1u128, logs: vec![Log { address: Address::from_str("0000000000000000000000000000000000000011").unwrap(), data: LogData::new_unchecked( @@ -1126,7 +1561,7 @@ mod tests { ), }], }, - bloom: [0; 256].into(), + logs_bloom: [0; 256].into(), }); let receipt = TypedReceipt::decode(&mut &data[..]).unwrap(); diff --git a/crates/anvil/core/src/eth/transaction/optimism.rs b/crates/anvil/core/src/eth/transaction/optimism.rs index 86edc810a..4a147297b 100644 --- a/crates/anvil/core/src/eth/transaction/optimism.rs +++ b/crates/anvil/core/src/eth/transaction/optimism.rs @@ -1,9 +1,9 @@ -use alloy_consensus::TxType; -use alloy_network::{Transaction, TxKind}; -use alloy_primitives::{Address, Bytes, ChainId, Signature, B256, U256}; +use alloy_consensus::{SignableTransaction, Signed, Transaction, TxType}; +use alloy_primitives::{keccak256, Address, Bytes, ChainId, Signature, TxKind, B256, U256}; use alloy_rlp::{ length_of_length, Decodable, Encodable, Error as DecodeError, Header as RlpHeader, }; +use serde::{Deserialize, Serialize}; use std::mem; #[derive(Clone, Debug, PartialEq, Eq)] @@ -13,7 +13,7 @@ pub struct DepositTransactionRequest { pub kind: TxKind, pub mint: U256, pub value: U256, - pub gas_limit: U256, + pub gas_limit: u128, pub is_system_tx: bool, pub input: Bytes, } @@ -134,108 +134,74 @@ impl DepositTransactionRequest { 1 + length_of_length(payload_length) + payload_length } - /// Outputs the signature hash of the transaction by first encoding without a signature, then - /// hashing. - pub(crate) fn signature_hash(&self) -> B256 { - let mut buf = Vec::with_capacity(self.payload_len_for_signature()); - self.encode_for_signing(&mut buf); - alloy_primitives::utils::keccak256(&buf) + fn encoded_len_with_signature(&self, signature: &Signature) -> usize { + // this counts the tx fields and signature fields + let payload_length = self.fields_len() + signature.rlp_vrs_len(); + + // this counts: + // * tx type byte + // * inner header length + // * inner payload length + 1 + alloy_rlp::Header { list: true, payload_length }.length() + payload_length } } impl Transaction for DepositTransactionRequest { - type Signature = Signature; - - fn chain_id(&self) -> Option { - None - } - - fn gas_limit(&self) -> u64 { - self.gas_limit.to::() - } - - fn nonce(&self) -> u64 { - u64::MAX + fn input(&self) -> &[u8] { + &self.input } - fn decode_signed(buf: &mut &[u8]) -> alloy_rlp::Result> - where - Self: Sized, - { - let header = alloy_rlp::Header::decode(buf)?; - if !header.list { - return Err(alloy_rlp::Error::UnexpectedString); - } - - let tx = Self::decode_inner(buf)?; - let signature = Signature::decode_rlp_vrs(buf)?; - - Ok(tx.into_signed(signature)) + /// Get `to`. + fn to(&self) -> TxKind { + self.kind } - fn encode_signed(&self, signature: &Signature, out: &mut dyn bytes::BufMut) { - self.encode_with_signature(signature, out) + /// Get `value`. + fn value(&self) -> U256 { + self.value } - fn gas_price(&self) -> Option { + /// Get `chain_id`. + fn chain_id(&self) -> Option { None } - fn input(&self) -> &[u8] { - &self.input + /// Get `nonce`. + fn nonce(&self) -> u64 { + u64::MAX } - fn input_mut(&mut self) -> &mut Bytes { - &mut self.input + /// Get `gas_limit`. + fn gas_limit(&self) -> u128 { + self.gas_limit } - fn into_signed(self, signature: Signature) -> alloy_network::Signed - where - Self: Sized, - { - alloy_network::Signed::new_unchecked(self.clone(), signature, self.signature_hash()) + /// Get `gas_price`. + fn gas_price(&self) -> Option { + None } +} +impl SignableTransaction for DepositTransactionRequest { fn set_chain_id(&mut self, _chain_id: ChainId) {} - fn set_gas_limit(&mut self, limit: u64) { - self.gas_limit = U256::from(limit); - } - - fn set_gas_price(&mut self, _price: U256) {} - - fn set_input(&mut self, data: Bytes) { - self.input = data; - } - - fn set_nonce(&mut self, _nonce: u64) {} - - fn set_to(&mut self, to: TxKind) { - self.kind = to; - } - - fn set_value(&mut self, value: U256) { - self.value = value; - } - - fn signature_hash(&self) -> B256 { - self.signature_hash() + fn payload_len_for_signature(&self) -> usize { + self.payload_len_for_signature() } - fn to(&self) -> TxKind { - self.kind - } + fn into_signed(self, signature: Signature) -> Signed { + let mut buf = Vec::with_capacity(self.encoded_len_with_signature(&signature)); + self.encode_with_signature(&signature, &mut buf); + let hash = keccak256(&buf); - fn value(&self) -> U256 { - self.value + // Drop any v chain id value to ensure the signature format is correct at the time of + // combination for an EIP-4844 transaction. V should indicate the y-parity of the + // signature. + Signed::new_unchecked(self, signature.with_parity_bool(), hash) } fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut) { - self.encode_for_signing(out) - } - - fn payload_len_for_signature(&self) -> usize { - self.payload_len_for_signature() + self.encode_for_signing(out); } } @@ -263,21 +229,21 @@ impl Encodable for DepositTransactionRequest { /// An op-stack deposit transaction. /// See -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct DepositTransaction { - pub nonce: U256, + pub nonce: u64, pub source_hash: B256, pub from: Address, pub kind: TxKind, pub mint: U256, pub value: U256, - pub gas_limit: U256, + pub gas_limit: u128, pub is_system_tx: bool, pub input: Bytes, } impl DepositTransaction { - pub fn nonce(&self) -> &U256 { + pub fn nonce(&self) -> &u64 { &self.nonce } @@ -321,7 +287,7 @@ impl DepositTransaction { len } - /// Decodes the inner [TxDeposit] fields from RLP bytes. + /// Decodes the inner fields from RLP bytes /// /// NOTE: This assumes a RLP header has already been decoded, and _just_ decodes the following /// RLP fields in the following order: @@ -336,7 +302,7 @@ impl DepositTransaction { /// - `input` pub fn decode_inner(buf: &mut &[u8]) -> Result { Ok(Self { - nonce: U256::ZERO, + nonce: 0, source_hash: Decodable::decode(buf)?, from: Decodable::decode(buf)?, kind: Decodable::decode(buf)?, diff --git a/crates/anvil/core/src/eth/trie.rs b/crates/anvil/core/src/eth/trie.rs index 5d144a5db..59c5cd910 100644 --- a/crates/anvil/core/src/eth/trie.rs +++ b/crates/anvil/core/src/eth/trie.rs @@ -1,36 +1,12 @@ -//! Utility functions for Ethereum adapted from https://github.dev/rust-blockchain/ethereum/blob/755dffaa4903fbec1269f50cde9863cf86269a14/src/util.rs -use alloy_primitives::B256; +//! Utility functions for Ethereum adapted from -pub use keccak_hasher::KeccakHasher; - -// reexport some trie types -pub use reference_trie::*; +use alloy_primitives::{fixed_bytes, B256}; +use alloy_trie::{HashBuilder, Nibbles}; +use std::collections::BTreeMap; /// The KECCAK of the RLP encoding of empty data. -pub const KECCAK_NULL_RLP: B256 = B256::new([ - 0x56, 0xe8, 0x1f, 0x17, 0x1b, 0xcc, 0x55, 0xa6, 0xff, 0x83, 0x45, 0xe6, 0x92, 0xc0, 0xf8, 0x6e, - 0x5b, 0x48, 0xe0, 0x1b, 0x99, 0x6c, 0xad, 0xc0, 0x01, 0x62, 0x2f, 0xb5, 0xe3, 0x63, 0xb4, 0x21, -]); - -/// Generates a trie root hash for a vector of key-value tuples -pub fn trie_root(input: I) -> B256 -where - I: IntoIterator, - K: AsRef<[u8]> + Ord, - V: AsRef<[u8]>, -{ - B256::from(triehash::trie_root::(input)) -} - -/// Generates a key-hashed (secure) trie root hash for a vector of key-value tuples. -pub fn sec_trie_root(input: I) -> B256 -where - I: IntoIterator, - K: AsRef<[u8]>, - V: AsRef<[u8]>, -{ - B256::from(triehash::sec_trie_root::(input)) -} +pub const KECCAK_NULL_RLP: B256 = + fixed_bytes!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"); /// Generates a trie root hash for a vector of values pub fn ordered_trie_root(input: I) -> B256 @@ -38,5 +14,17 @@ where I: IntoIterator, V: AsRef<[u8]>, { - B256::from(triehash::ordered_trie_root::(input)) + let mut builder = HashBuilder::default(); + + let input = input + .into_iter() + .enumerate() + .map(|(i, v)| (alloy_rlp::encode(i), v)) + .collect::>(); + + for (key, value) in input { + builder.add_leaf(Nibbles::unpack(key), value.as_ref()); + } + + builder.root() } diff --git a/crates/anvil/core/src/eth/utils.rs b/crates/anvil/core/src/eth/utils.rs index 27bb46523..a60439280 100644 --- a/crates/anvil/core/src/eth/utils.rs +++ b/crates/anvil/core/src/eth/utils.rs @@ -1,35 +1,4 @@ -use alloy_eips::eip2930::{ - AccessList as AlloyEipAccessList, AccessListItem as AlloyEipAccessListItem, -}; -use alloy_primitives::{Address, Parity, U256}; -use alloy_rpc_types::{AccessList as AlloyAccessList, AccessListItem as AlloyAccessListItem}; - -pub fn alloy_to_revm_access_list(list: Vec) -> Vec<(Address, Vec)> { - list.into_iter() - .map(|item| (item.address, item.storage_keys.into_iter().map(|k| k.into()).collect())) - .collect() -} - -/// Translates a vec of [AlloyEipAccessListItem] to a [AlloyAccessList], translating from internal -/// type to rpc type. -pub fn from_eip_to_alloy_access_list(list: AlloyEipAccessList) -> AlloyAccessList { - AlloyAccessList( - list.0 - .into_iter() - .map(|item| AlloyAccessListItem { - address: item.address, - storage_keys: item.storage_keys.into_iter().collect(), - }) - .collect(), - ) -} - -/// Translates a vec of [AlloyEipAccessListItem] to a revm style Access List. -pub fn eip_to_revm_access_list(list: Vec) -> Vec<(Address, Vec)> { - list.into_iter() - .map(|item| (item.address, item.storage_keys.into_iter().map(|k| k.into()).collect())) - .collect() -} +use alloy_primitives::Parity; /// See /// > If you do, then the v of the signature MUST be set to {0,1} + CHAIN_ID * 2 + 35 where diff --git a/crates/anvil/core/src/lib.rs b/crates/anvil/core/src/lib.rs index a09e7cd68..5d51d4691 100644 --- a/crates/anvil/core/src/lib.rs +++ b/crates/anvil/core/src/lib.rs @@ -1,3 +1,10 @@ +//! # anvil-core +//! +//! Core Ethereum types for Anvil. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + /// Various Ethereum types pub mod eth; diff --git a/crates/anvil/core/src/types.rs b/crates/anvil/core/src/types.rs index db966d956..79a6c7a2c 100644 --- a/crates/anvil/core/src/types.rs +++ b/crates/anvil/core/src/types.rs @@ -24,10 +24,7 @@ impl<'de> serde::Deserialize<'de> for Forking { #[serde(rename_all = "camelCase")] struct ForkOpts { pub json_rpc_url: Option, - #[serde( - default, - deserialize_with = "crate::eth::serde_helpers::numeric::deserialize_stringified_u64_opt" - )] + #[serde(default, with = "alloy_serde::num::u64_opt_via_ruint")] pub block_number: Option, } @@ -43,12 +40,11 @@ impl<'de> serde::Deserialize<'de> for Forking { } let f = match ForkingVariants::deserialize(deserializer)? { ForkingVariants::Fork(ForkOpts { json_rpc_url, block_number }) => { - Forking { json_rpc_url, block_number } + Self { json_rpc_url, block_number } + } + ForkingVariants::Tagged(f) => { + Self { json_rpc_url: f.forking.json_rpc_url, block_number: f.forking.block_number } } - ForkingVariants::Tagged(f) => Forking { - json_rpc_url: f.forking.json_rpc_url, - block_number: f.forking.block_number, - }, }; Ok(f) } @@ -60,30 +56,20 @@ impl<'de> serde::Deserialize<'de> for Forking { #[cfg_attr(feature = "serde", serde(untagged))] pub enum EvmMineOptions { Options { - #[cfg_attr( - feature = "serde", - serde( - deserialize_with = "crate::eth::serde_helpers::numeric::deserialize_stringified_u64_opt" - ) - )] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::num::u64_opt_via_ruint"))] timestamp: Option, // If `blocks` is given, it will mine exactly blocks number of blocks, regardless of any // other blocks mined or reverted during it's operation blocks: Option, }, /// The timestamp the block should be mined with - #[cfg_attr( - feature = "serde", - serde( - deserialize_with = "crate::eth::serde_helpers::numeric::deserialize_stringified_u64_opt" - ) - )] + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::num::u64_opt_via_ruint"))] Timestamp(Option), } impl Default for EvmMineOptions { fn default() -> Self { - EvmMineOptions::Options { timestamp: None, blocks: None } + Self::Options { timestamp: None, blocks: None } } } @@ -123,7 +109,7 @@ impl From for usize { #[cfg(feature = "serde")] impl<'a> serde::Deserialize<'a> for Index { - fn deserialize(deserializer: D) -> Result + fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'a>, { @@ -190,10 +176,10 @@ pub struct NodeInfo { #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct NodeEnvironment { - pub base_fee: U256, + pub base_fee: u128, pub chain_id: u64, - pub gas_limit: U256, - pub gas_price: U256, + pub gas_limit: u128, + pub gas_price: u128, } #[derive(Clone, Debug, Default, PartialEq, Eq)] diff --git a/crates/anvil/rpc/Cargo.toml b/crates/anvil/rpc/Cargo.toml index 6d4e76540..46a148ea4 100644 --- a/crates/anvil/rpc/Cargo.toml +++ b/crates/anvil/rpc/Cargo.toml @@ -9,9 +9,9 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [dependencies] serde.workspace = true serde_json.workspace = true - -[dev-dependencies] -rand = "0.8" diff --git a/crates/anvil/rpc/src/error.rs b/crates/anvil/rpc/src/error.rs index b1929818f..4eec040ac 100644 --- a/crates/anvil/rpc/src/error.rs +++ b/crates/anvil/rpc/src/error.rs @@ -14,57 +14,53 @@ pub struct RpcError { } impl RpcError { - /// New [Error] with the given [ErrorCode] + /// New [`RpcError`] with the given [`ErrorCode`]. pub const fn new(code: ErrorCode) -> Self { - RpcError { message: Cow::Borrowed(code.message()), code, data: None } + Self { message: Cow::Borrowed(code.message()), code, data: None } } - /// Creates a new `ParseError` + /// Creates a new `ParseError` error. pub const fn parse_error() -> Self { Self::new(ErrorCode::ParseError) } - /// Creates a new `MethodNotFound` + /// Creates a new `MethodNotFound` error. pub const fn method_not_found() -> Self { Self::new(ErrorCode::MethodNotFound) } - /// Creates a new `InvalidRequest` + /// Creates a new `InvalidRequest` error. pub const fn invalid_request() -> Self { Self::new(ErrorCode::InvalidRequest) } - /// Creates a new `InternalError` + /// Creates a new `InternalError` error. pub const fn internal_error() -> Self { Self::new(ErrorCode::InternalError) } - /// Creates a new `InvalidParams` + /// Creates a new `InvalidParams` error. pub fn invalid_params(message: M) -> Self where M: Into, { - RpcError { code: ErrorCode::InvalidParams, message: message.into().into(), data: None } + Self { code: ErrorCode::InvalidParams, message: message.into().into(), data: None } } - /// Creates a new `InternalError` with a message + /// Creates a new `InternalError` error with a message. pub fn internal_error_with(message: M) -> Self where M: Into, { - RpcError { code: ErrorCode::InternalError, message: message.into().into(), data: None } + Self { code: ErrorCode::InternalError, message: message.into().into(), data: None } } - /// Creates a new rpc error for when a transaction was rejected + /// Creates a new RPC error for when a transaction was rejected. pub fn transaction_rejected(message: M) -> Self where M: Into, { - RpcError { - code: ErrorCode::TransactionRejected, - message: message.into().into(), - data: None, - } + Self { code: ErrorCode::TransactionRejected, message: message.into().into(), data: None } } } @@ -100,28 +96,28 @@ impl ErrorCode { /// Returns the error code as `i64` pub fn code(&self) -> i64 { match *self { - ErrorCode::ParseError => -32700, - ErrorCode::InvalidRequest => -32600, - ErrorCode::MethodNotFound => -32601, - ErrorCode::InvalidParams => -32602, - ErrorCode::InternalError => -32603, - ErrorCode::TransactionRejected => -32003, - ErrorCode::ExecutionError => 3, - ErrorCode::ServerError(c) => c, + 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 { - ErrorCode::ParseError => "Parse error", - ErrorCode::InvalidRequest => "Invalid request", - ErrorCode::MethodNotFound => "Method not found", - ErrorCode::InvalidParams => "Invalid params", - ErrorCode::InternalError => "Internal error", - ErrorCode::TransactionRejected => "Transaction rejected", - ErrorCode::ServerError(_) => "Server error", - ErrorCode::ExecutionError => "Execution error", + 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", } } } @@ -136,7 +132,7 @@ impl Serialize for ErrorCode { } impl<'a> Deserialize<'a> for ErrorCode { - fn deserialize(deserializer: D) -> Result + fn deserialize(deserializer: D) -> Result where D: Deserializer<'a>, { @@ -147,14 +143,14 @@ impl<'a> Deserialize<'a> for ErrorCode { impl From for ErrorCode { fn from(code: i64) -> Self { match code { - -32700 => ErrorCode::ParseError, - -32600 => ErrorCode::InvalidRequest, - -32601 => ErrorCode::MethodNotFound, - -32602 => ErrorCode::InvalidParams, - -32603 => ErrorCode::InternalError, - -32003 => ErrorCode::TransactionRejected, - 3 => ErrorCode::ExecutionError, - _ => ErrorCode::ServerError(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), } } } diff --git a/crates/anvil/rpc/src/lib.rs b/crates/anvil/rpc/src/lib.rs index 939fbff16..bd5382cee 100644 --- a/crates/anvil/rpc/src/lib.rs +++ b/crates/anvil/rpc/src/lib.rs @@ -1,3 +1,10 @@ +//! # anvil-rpc +//! +//! JSON-RPC types. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + /// JSON-RPC request bindings pub mod request; diff --git a/crates/anvil/rpc/src/request.rs b/crates/anvil/rpc/src/request.rs index 8630f4a4c..5cb8510b8 100644 --- a/crates/anvil/rpc/src/request.rs +++ b/crates/anvil/rpc/src/request.rs @@ -77,7 +77,7 @@ pub enum RequestParams { impl From for serde_json::Value { fn from(params: RequestParams) -> Self { match params { - RequestParams::None => serde_json::Value::Null, + RequestParams::None => Self::Null, RequestParams::Array(arr) => arr.into(), RequestParams::Object(obj) => obj.into(), } @@ -106,9 +106,9 @@ pub enum Id { impl fmt::Display for Id { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Id::String(s) => s.fmt(f), - Id::Number(n) => n.fmt(f), - Id::Null => f.write_str("null"), + Self::String(s) => s.fmt(f), + Self::Number(n) => n.fmt(f), + Self::Null => f.write_str("null"), } } } diff --git a/crates/anvil/rpc/src/response.rs b/crates/anvil/rpc/src/response.rs index c7649dbe3..2fd01327d 100644 --- a/crates/anvil/rpc/src/response.rs +++ b/crates/anvil/rpc/src/response.rs @@ -24,7 +24,7 @@ impl From for RpcResponse { impl RpcResponse { pub fn new(id: Id, content: impl Into) -> Self { - RpcResponse { jsonrpc: Version::V2, id: Some(id), result: content.into() } + Self { jsonrpc: Version::V2, id: Some(id), result: content.into() } } pub fn invalid_request(id: Id) -> Self { @@ -47,17 +47,17 @@ impl ResponseResult { where S: Serialize + 'static, { - ResponseResult::Success(serde_json::to_value(&content).unwrap()) + Self::Success(serde_json::to_value(&content).unwrap()) } pub fn error(error: RpcError) -> Self { - ResponseResult::Error(error) + Self::Error(error) } } impl From for ResponseResult { fn from(err: RpcError) -> Self { - ResponseResult::error(err) + Self::error(err) } } /// Synchronous response @@ -72,7 +72,7 @@ pub enum Response { } impl Response { - /// Creates new [Response] with the given [Error] + /// Creates new [`Response`] with the given [`RpcError`]. pub fn error(error: RpcError) -> Self { RpcResponse::new(Id::Null, ResponseResult::Error(error)).into() } @@ -80,12 +80,12 @@ impl Response { impl From for Response { fn from(err: RpcError) -> Self { - Response::error(err) + Self::error(err) } } impl From for Response { fn from(resp: RpcResponse) -> Self { - Response::Single(resp) + Self::Single(resp) } } diff --git a/crates/anvil/server/Cargo.toml b/crates/anvil/server/Cargo.toml index fb880a422..282e09013 100644 --- a/crates/anvil/server/Cargo.toml +++ b/crates/anvil/server/Cargo.toml @@ -10,35 +10,37 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [dependencies] anvil-rpc = { path = "../rpc" } # axum related axum = { workspace = true, features = ["ws"] } -hyper.workspace = true tower-http = { workspace = true, features = ["trace", "cors"] } # tracing tracing.workspace = true # async -parking_lot = "0.12" -futures = "0.3" +parking_lot.workspace = true +futures.workspace = true # ipc -parity-tokio-ipc = { version = "0.9", optional = true } +interprocess = { version = "2", optional = true, features = ["tokio"] } bytes = { version = "1.4", optional = true } tokio-util = { version = "0.7", features = ["codec"], optional = true } # misc serde_json.workspace = true serde.workspace = true -async-trait = "0.1" -thiserror = "1" +async-trait.workspace = true +thiserror.workspace = true clap = { version = "4", features = ["derive", "env"], optional = true } pin-project = "1" [features] default = ["ipc"] -ipc = ["parity-tokio-ipc", "bytes", "tokio-util"] +ipc = ["dep:interprocess", "dep:bytes", "dep:tokio-util"] diff --git a/crates/anvil/server/src/config.rs b/crates/anvil/server/src/config.rs index 7e2352831..ea97d24cd 100644 --- a/crates/anvil/server/src/config.rs +++ b/crates/anvil/server/src/config.rs @@ -4,16 +4,15 @@ use std::str::FromStr; /// Additional server options. #[derive(Clone, Debug, Serialize, Deserialize)] -#[cfg_attr(feature = "clap", derive(clap::Parser), clap(next_help_heading = "Server options"))] +#[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", - clap( + arg( long, help = "Set the CORS allow_origin", default_value = "*", - name = "allow-origin", value_name = "ALLOW_ORIGIN" ) )] @@ -21,13 +20,11 @@ pub struct ServerConfig { /// Whether to enable CORS #[cfg_attr( feature = "clap", - clap(long, help = "Disable CORS", conflicts_with = "allow-origin") + arg(long, help = "Disable CORS", conflicts_with = "allow_origin") )] pub no_cors: bool, } -// === impl ServerConfig === - impl ServerConfig { /// Sets the "allow origin" header for cors pub fn with_allow_origin(mut self, allow_origin: impl Into) -> Self { @@ -55,7 +52,7 @@ impl FromStr for HeaderValueWrapper { type Err = ::Err; fn from_str(s: &str) -> Result { - Ok(HeaderValueWrapper(s.parse()?)) + Ok(Self(s.parse()?)) } } @@ -94,6 +91,6 @@ impl From for HeaderValue { impl From for HeaderValueWrapper { fn from(header: HeaderValue) -> Self { - HeaderValueWrapper(header) + Self(header) } } diff --git a/crates/anvil/server/src/ipc.rs b/crates/anvil/server/src/ipc.rs index 97ec07098..8743f0642 100644 --- a/crates/anvil/server/src/ipc.rs +++ b/crates/anvil/server/src/ipc.rs @@ -4,7 +4,7 @@ use crate::{error::RequestError, pubsub::PubSubConnection, PubSubRpcHandler}; use anvil_rpc::request::Request; use bytes::BytesMut; use futures::{ready, Sink, Stream, StreamExt}; -use parity_tokio_ipc::Endpoint; +use interprocess::local_socket::{self as ls, tokio::prelude::*}; use std::{ future::Future, io, @@ -18,55 +18,58 @@ use std::{ pub struct IpcEndpoint { /// the handler for the websocket connection handler: Handler, - /// The endpoint we listen for incoming transactions - endpoint: Endpoint, + /// The path to the socket + path: String, } impl IpcEndpoint { /// Creates a new endpoint with the given handler - pub fn new(handler: Handler, endpoint: impl Into) -> Self { - Self { handler, endpoint: Endpoint::new(endpoint.into()) } + pub fn new(handler: Handler, path: String) -> Self { + Self { handler, path } } - /// Returns a stream of incoming connection handlers + /// Returns a stream of incoming connection handlers. /// - /// This establishes the ipc endpoint, converts the incoming connections into handled eth - /// connections, See [`PubSubConnection`] that should be spawned + /// This establishes the IPC endpoint, converts the incoming connections into handled + /// connections. #[instrument(target = "ipc", skip_all)] pub fn incoming(self) -> io::Result>> { - let IpcEndpoint { handler, endpoint } = self; - trace!(endpoint=?endpoint.path(), "starting IPC server" ); + let Self { handler, path } = self; + + trace!(%path, "starting IPC server"); if cfg!(unix) { // ensure the file does not exist - if std::fs::remove_file(endpoint.path()).is_ok() { - warn!(endpoint=?endpoint.path(), "removed existing file"); + if std::fs::remove_file(&path).is_ok() { + warn!(%path, "removed existing file"); } } - let connections = match endpoint.incoming() { - Ok(connections) => connections, - Err(err) => { - error!(%err, "Failed to create IPC listener"); - return Err(err) - } - }; + 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"); - let connections = connections.filter_map(move |stream| { + Ok(connections.filter_map(move |stream| { let handler = handler.clone(); - Box::pin(async move { - if let Ok(stream) = stream { - trace!("successful incoming IPC connection"); - let framed = tokio_util::codec::Decoder::framed(JsonRpcCodec, stream); - Some(PubSubConnection::new(IpcConn(framed), handler)) - } else { - None + 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 + } } - }) - }); - Ok(connections) + } + })) } } @@ -118,7 +121,7 @@ where struct JsonRpcCodec; -// Adapted from +// Adapted from impl tokio_util::codec::Decoder for JsonRpcCodec { type Item = String; type Error = io::Error; @@ -171,3 +174,11 @@ impl tokio_util::codec::Encoder for JsonRpcCodec { 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) + } +} diff --git a/crates/anvil/server/src/lib.rs b/crates/anvil/server/src/lib.rs index 5ff287c96..b60182505 100644 --- a/crates/anvil/server/src/lib.rs +++ b/crates/anvil/server/src/lib.rs @@ -1,6 +1,7 @@ -//! Bootstrap [axum] RPC servers +//! Bootstrap [axum] RPC servers. -#![warn(missing_docs, unused_crate_dependencies)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #[macro_use] extern crate tracing; @@ -12,91 +13,66 @@ use anvil_rpc::{ }; use axum::{ http::{header, HeaderValue, Method}, - routing::{post, IntoMakeService}, - Router, Server, + routing::{post, MethodRouter}, + Router, }; -use hyper::server::conn::AddrIncoming; use serde::de::DeserializeOwned; -use std::{fmt, net::SocketAddr}; +use std::fmt; use tower_http::{cors::CorsLayer, trace::TraceLayer}; mod config; +pub use config::ServerConfig; mod error; -/// handlers for axum server mod handler; -#[cfg(feature = "ipc")] -pub mod ipc; + mod pubsub; -mod ws; +pub use pubsub::{PubSubContext, PubSubRpcHandler}; -pub use crate::pubsub::{PubSubContext, PubSubRpcHandler}; -pub use config::ServerConfig; +mod ws; -/// Type alias for the configured axum server -pub type AnvilServer = Server>; +#[cfg(feature = "ipc")] +pub mod ipc; -/// Configures an [axum::Server] that handles RPC-Calls, both HTTP requests and requests via -/// websocket -pub fn serve_http_ws( - addr: SocketAddr, - config: ServerConfig, - http: Http, - ws: Ws, -) -> AnvilServer +/// 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, { - let ServerConfig { allow_origin, no_cors } = config; - - let svc = Router::new() - .route("/", post(handler::handle).get(ws::handle_ws)) - .with_state((http, ws)) - .layer(TraceLayer::new_for_http()); - - let svc = if no_cors { - svc - } else { - svc.layer( - // see https://docs.rs/tower-http/latest/tower_http/cors/index.html - // for more details - CorsLayer::new() - .allow_origin(allow_origin.0) - .allow_headers(vec![header::CONTENT_TYPE]) - .allow_methods(vec![Method::GET, Method::POST]), - ) - } - .into_make_service(); - Server::bind(&addr).serve(svc) + router_inner(config, post(handler::handle).get(ws::handle_ws), (http, ws)) } -/// Configures an [axum::Server] that handles RPC-Calls listing for POST on `/` -pub fn serve_http(addr: SocketAddr, config: ServerConfig, http: Http) -> AnvilServer +/// 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 } = config; - let svc = Router::new() - .route("/", post(handler::handle)) - .with_state((http, ())) + let mut router = Router::new() + .route("/", root_method_router) + .with_state(state) .layer(TraceLayer::new_for_http()); - let svc = if no_cors { - svc - } else { - svc.layer( - // see https://docs.rs/tower-http/latest/tower_http/cors/index.html - // for more details + 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(vec![header::CONTENT_TYPE]) - .allow_methods(vec![Method::GET, Method::POST]), - ) + .allow_headers([header::CONTENT_TYPE]) + .allow_methods([Method::GET, Method::POST]), + ); } - .into_make_service(); - - Server::bind(&addr).serve(svc) + router } /// Helper trait that is used to execute ethereum rpc calls diff --git a/crates/anvil/server/src/pubsub.rs b/crates/anvil/server/src/pubsub.rs index 3c9be85bc..2fe4358ef 100644 --- a/crates/anvil/server/src/pubsub.rs +++ b/crates/anvil/server/src/pubsub.rs @@ -40,8 +40,6 @@ pub struct PubSubContext { subscriptions: Subscriptions, } -// === impl PubSubContext === - impl PubSubContext { /// Adds new active subscription /// @@ -125,8 +123,6 @@ pub struct PubSubConnection { pending: VecDeque, } -// === impl PubSubConnection === - impl PubSubConnection { pub fn new(connection: Connection, handler: Handler) -> Self { Self { diff --git a/crates/anvil/src/anvil.rs b/crates/anvil/src/anvil.rs index 5ff939482..1aa204587 100644 --- a/crates/anvil/src/anvil.rs +++ b/crates/anvil/src/anvil.rs @@ -1,34 +1,42 @@ //! The `anvil` cli + use anvil::cmd::NodeArgs; use clap::{CommandFactory, Parser, Subcommand}; +use foundry_cli::utils; + +#[cfg(all(feature = "jemalloc", unix))] +#[global_allocator] +static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; /// A fast local Ethereum development node. #[derive(Parser)] -#[clap(name = "anvil", version = anvil::VERSION_MESSAGE, next_display_order = None)] +#[command(name = "anvil", version = anvil::VERSION_MESSAGE, next_display_order = None)] pub struct Anvil { - #[clap(flatten)] + #[command(flatten)] pub node: NodeArgs, - #[clap(subcommand)] + #[command(subcommand)] pub cmd: Option, } #[derive(Subcommand)] pub enum AnvilSubcommand { /// Generate shell completions script. - #[clap(visible_alias = "com")] + #[command(visible_alias = "com")] Completions { - #[clap(value_enum)] + #[arg(value_enum)] shell: clap_complete::Shell, }, /// Generate Fig autocompletion spec. - #[clap(visible_alias = "fig")] + #[command(visible_alias = "fig")] GenerateFigSpec, } #[tokio::main] -async fn main() -> Result<(), Box> { +async fn main() -> eyre::Result<()> { + utils::load_dotenv(); + let mut app = Anvil::parse(); app.node.evm_opts.resolve_rpc_alias(); diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index b87da3557..ca86a06e1 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -5,11 +5,11 @@ use crate::{ }; use alloy_genesis::Genesis; use alloy_primitives::{utils::Unit, U256}; -use alloy_signer::coins_bip39::{English, Mnemonic}; +use alloy_signer_local::coins_bip39::{English, Mnemonic}; use anvil_server::ServerConfig; use clap::Parser; use core::fmt; -use foundry_config::{Chain, Config}; +use foundry_config::{Chain, Config, FigmentProviders}; use futures::FutureExt; use rand::{rngs::StdRng, SeedableRng}; use std::{ @@ -30,72 +30,76 @@ use tokio::time::{Instant, Interval}; #[derive(Clone, Debug, Parser)] pub struct NodeArgs { /// Port number to listen on. - #[clap(long, short, default_value = "8545", value_name = "NUM")] + #[arg(long, short, default_value = "8545", value_name = "NUM")] pub port: u16, /// Number of dev accounts to generate and configure. - #[clap(long, short, default_value = "10", value_name = "NUM")] + #[arg(long, short, default_value = "10", value_name = "NUM")] pub accounts: u64, /// The balance of every dev account in Ether. - #[clap(long, default_value = "10000", value_name = "NUM")] + #[arg(long, default_value = "10000", value_name = "NUM")] pub balance: u64, /// The timestamp of the genesis block. - #[clap(long, value_name = "NUM")] + #[arg(long, value_name = "NUM")] pub timestamp: Option, /// BIP39 mnemonic phrase used for generating accounts. - /// Cannot be used if `mnemonic_random` or `mnemonic_seed` are used - #[clap(long, short, conflicts_with_all = &["mnemonic_seed", "mnemonic_random"])] + /// 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 + /// Cannot be used with other `mnemonic` options. /// You can specify the number of words you want in the mnemonic. /// [default: 12] - #[clap(long, conflicts_with_all = &["mnemonic", "mnemonic_seed"], default_missing_value = "12", num_args(0..=1))] + #[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 + /// Cannot be used with other `mnemonic` options. /// - /// CAREFUL: this is NOT SAFE and should only be used for testing. + /// CAREFUL: This is NOT SAFE and should only be used for testing. /// Never use the private keys generated in production. - #[clap(long = "mnemonic-seed-unsafe", conflicts_with_all = &["mnemonic", "mnemonic_random"])] + #[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/] - #[clap(long)] + #[arg(long)] pub derivation_path: Option, /// Don't print anything on startup and don't print logs - #[clap(long)] + #[arg(long)] pub silent: bool, /// The EVM hardfork to use. /// /// Choose the hardfork by name, e.g. `shanghai`, `paris`, `london`, etc... /// [default: latest] - #[clap(long, value_parser = Hardfork::from_str)] + #[arg(long, value_parser = Hardfork::from_str)] pub hardfork: Option, /// Block time in seconds for interval mining. - #[clap(short, long, visible_alias = "blockTime", name = "block-time", value_name = "SECONDS")] - pub block_time: Option, + #[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. - #[clap(long, value_name = "OUT_FILE")] + #[arg(long, value_name = "OUT_FILE")] pub config_out: Option, /// Disable auto and interval mining, and mine on demand instead. - #[clap(long, visible_alias = "no-mine", conflicts_with = "block-time")] + #[arg(long, visible_alias = "no-mine", conflicts_with = "block_time")] pub no_mining: bool, /// The hosts the server will listen on. - #[clap( + #[arg( long, value_name = "IP_ADDR", env = "ANVIL_IP_ADDR", @@ -106,18 +110,18 @@ pub struct NodeArgs { pub host: Vec, /// How transactions are sorted in the mempool. - #[clap(long, default_value = "fees")] + #[arg(long, default_value = "fees")] pub order: TransactionOrder, /// Initialize the genesis block with the given `genesis.json` file. - #[clap(long, value_name = "PATH", value_parser= read_genesis_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. - #[clap( + #[arg( long, value_name = "PATH", value_parser = StateFile::parse, @@ -132,17 +136,17 @@ pub struct NodeArgs { /// Interval in seconds at which the state and block environment is to be dumped to disk. /// /// See --state and --dump-state - #[clap(short, long, value_name = "SECONDS")] + #[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`. - #[clap(long, value_name = "PATH", conflicts_with = "init")] + #[arg(long, value_name = "PATH", conflicts_with = "init")] pub dump_state: Option, /// Initialize the chain from a previously saved state snapshot. - #[clap( + #[arg( long, value_name = "PATH", value_parser = SerializableState::parse, @@ -150,22 +154,22 @@ pub struct NodeArgs { )] pub load_state: Option, - #[clap(long, help = IPC_HELP, value_name = "PATH", visible_alias = "ipcpath")] + #[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. - #[clap(long)] + #[arg(long)] pub prune_history: Option>, /// Number of blocks with transactions to keep in memory. - #[clap(long)] + #[arg(long)] pub transaction_block_keeper: Option, - #[clap(flatten)] + #[command(flatten)] pub evm_opts: AnvilEvmArgs, - #[clap(flatten)] + #[command(flatten)] pub server_config: ServerConfig, } @@ -190,11 +194,11 @@ impl NodeArgs { }; NodeConfig::default() - .with_gas_limit(self.evm_opts.gas_limit.map(U256::from)) + .with_gas_limit(self.evm_opts.gas_limit) .disable_block_gas_limit(self.evm_opts.disable_block_gas_limit) - .with_gas_price(self.evm_opts.gas_price.map(U256::from)) + .with_gas_price(self.evm_opts.gas_price) .with_hardfork(self.hardfork) - .with_blocktime(self.block_time.map(Duration::from_secs)) + .with_blocktime(self.block_time) .with_no_mining(self.no_mining) .with_account_generator(self.account_generator()) .with_genesis_balance(genesis_balance) @@ -212,7 +216,7 @@ impl NodeArgs { .fork_retry_backoff(self.evm_opts.fork_retry_backoff.map(Duration::from_millis)) .fork_compute_units_per_second(compute_units_per_second) .with_eth_rpc_url(self.evm_opts.fork_url.map(|fork| fork.url)) - .with_base_fee(self.evm_opts.block_base_fee_per_gas.map(U256::from)) + .with_base_fee(self.evm_opts.block_base_fee_per_gas) .with_storage_caching(self.evm_opts.no_storage_caching) .with_server_config(self.server_config) .with_host(self.host) @@ -229,6 +233,9 @@ impl NodeArgs { .with_init_state(self.load_state.or_else(|| self.state.and_then(|s| s.state))) .with_transaction_block_keeper(self.transaction_block_keeper) .with_optimism(self.evm_opts.optimism) + .with_disable_default_create2_deployer(self.evm_opts.disable_default_create2_deployer) + .with_slots_in_an_epoch(self.slots_in_an_epoch) + .with_memory_limit(self.evm_opts.memory_limit) } fn account_generator(&self) -> AccountGenerator { @@ -263,12 +270,12 @@ impl NodeArgs { /// Starts the node /// /// See also [crate::spawn()] - pub async fn run(self) -> Result<(), Box> { + 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 (api, mut handle) = crate::spawn(self.into_node_config()).await; + 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(); @@ -344,12 +351,12 @@ impl NodeArgs { /// Anvil's EVM related arguments. #[derive(Clone, Debug, Parser)] -#[clap(next_help_heading = "EVM options")] +#[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. - #[clap( + #[arg( long, short, visible_alias = "rpc-url", @@ -361,7 +368,7 @@ pub struct AnvilEvmArgs { /// Headers to use for the rpc client, e.g. "User-Agent: test-agent" /// /// See --fork-url. - #[clap( + #[arg( long = "fork-header", value_name = "HEADERS", help_heading = "Fork config", @@ -372,35 +379,25 @@ pub struct AnvilEvmArgs { /// Timeout in ms for requests sent to remote JSON-RPC server in forking mode. /// /// Default value 45000 - #[clap( - long = "timeout", - name = "timeout", - help_heading = "Fork config", - requires = "fork_url" - )] + #[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 - #[clap( - long = "retries", - name = "retries", - help_heading = "Fork config", - requires = "fork_url" - )] + #[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. /// /// See --fork-url. - #[clap(long, requires = "fork_url", value_name = "BLOCK", help_heading = "Fork config")] + #[arg(long, requires = "fork_url", value_name = "BLOCK", help_heading = "Fork config")] pub fork_block_number: Option, /// Initial retry backoff on encountering errors. /// /// See --fork-url. - #[clap(long, requires = "fork_url", value_name = "BACKOFF", help_heading = "Fork config")] + #[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. @@ -408,7 +405,7 @@ pub struct AnvilEvmArgs { /// 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. - #[clap( + #[arg( long, help_heading = "Fork config", value_name = "CHAIN", @@ -420,9 +417,8 @@ pub struct AnvilEvmArgs { /// /// default value: 330 /// - /// See --fork-url. - /// See also, https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second - #[clap( + /// See also --fork-url and + #[arg( long, requires = "fork_url", alias = "cups", @@ -435,9 +431,8 @@ pub struct AnvilEvmArgs { /// /// default value: false /// - /// See --fork-url. - /// See also, https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second - #[clap( + /// See also --fork-url and + #[arg( long, requires = "fork_url", value_name = "NO_RATE_LIMITS", @@ -453,15 +448,15 @@ pub struct AnvilEvmArgs { /// This flag overrides the project's configuration file. /// /// See --fork-url. - #[clap(long, requires = "fork_url", help_heading = "Fork config")] + #[arg(long, requires = "fork_url", help_heading = "Fork config")] pub no_storage_caching: bool, /// The block gas limit. - #[clap(long, alias = "block-gas-limit", help_heading = "Environment config")] - pub gas_limit: Option, + #[arg(long, alias = "block-gas-limit", help_heading = "Environment config")] + pub gas_limit: Option, /// Disable the `call.gas_limit <= block.gas_limit` constraint. - #[clap( + #[arg( long, value_name = "DISABLE_GAS_LIMIT", help_heading = "Environment config", @@ -472,37 +467,45 @@ pub struct AnvilEvmArgs { /// EIP-170: Contract code size limit in bytes. Useful to increase this because of tests. By /// default, it is 0x6000 (~25kb). - #[clap(long, value_name = "CODE_SIZE", help_heading = "Environment config")] + #[arg(long, value_name = "CODE_SIZE", help_heading = "Environment config")] pub code_size_limit: Option, /// The gas price. - #[clap(long, help_heading = "Environment config")] - pub gas_price: Option, + #[arg(long, help_heading = "Environment config")] + pub gas_price: Option, /// The base fee in a block. - #[clap( + #[arg( long, visible_alias = "base-fee", value_name = "FEE", help_heading = "Environment config" )] - pub block_base_fee_per_gas: Option, + pub block_base_fee_per_gas: Option, /// The chain ID. - #[clap(long, alias = "chain", help_heading = "Environment config")] + #[arg(long, alias = "chain", help_heading = "Environment config")] pub chain_id: Option, /// Enable steps tracing used for debug calls returning geth-style traces - #[clap(long, visible_alias = "tracing")] + #[arg(long, visible_alias = "tracing")] pub steps_tracing: bool, /// Enable autoImpersonate on startup - #[clap(long, visible_alias = "auto-impersonate")] + #[arg(long, visible_alias = "auto-impersonate")] pub auto_impersonate: bool, /// Run an Optimism chain - #[clap(long, visible_alias = "optimism")] + #[arg(long, visible_alias = "optimism")] pub optimism: bool, + + /// Disable the default create2 deployer + #[arg(long, visible_alias = "no-create2")] + pub disable_default_create2_deployer: bool, + + /// The memory limit per EVM execution in bytes. + #[arg(long)] + pub memory_limit: Option, } /// Resolves an alias passed as fork-url to the matching url defined in the rpc_endpoints section @@ -511,7 +514,7 @@ pub struct AnvilEvmArgs { impl AnvilEvmArgs { pub fn resolve_rpc_alias(&mut self) { if let Some(fork_url) = &self.fork_url { - let config = Config::load(); + let config = Config::load_with_providers(FigmentProviders::Anvil); if 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 }); } @@ -591,7 +594,7 @@ impl Future for PeriodicStateDumper { 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(PeriodicStateDumper::dump_state(api, path))); + this.in_progress_dump = Some(Box::pin(Self::dump_state(api, path))); } else { break } @@ -658,17 +661,17 @@ impl FromStr for ForkUrl { fn from_str(s: &str) -> Result { if let Some((url, block)) = s.rsplit_once('@') { if block == "latest" { - return Ok(ForkUrl { url: url.to_string(), block: None }) + 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(ForkUrl { url: url.to_string(), block: Some(block) }) + return Ok(Self { url: url.to_string(), block: Some(block) }) } } - Ok(ForkUrl { url: s.to_string(), block: None }) + Ok(Self { url: s.to_string(), block: None }) } } @@ -677,6 +680,14 @@ 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::*; diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index 5f471e32f..097513847 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -11,34 +11,34 @@ use crate::{ fees::{INITIAL_BASE_FEE, INITIAL_GAS_PRICE}, pool::transactions::TransactionOrder, }, - mem, - mem::in_memory_db::MemDb, - FeeManager, Hardfork, + mem::{self, in_memory_db::MemDb}, + FeeManager, Hardfork, PrecompileFactory, }; use alloy_genesis::Genesis; +use alloy_network::AnyNetwork; use alloy_primitives::{hex, utils::Unit, U256}; -use alloy_providers::provider::TempProvider; +use alloy_provider::Provider; use alloy_rpc_types::BlockNumberOrTag; -use alloy_signer::{ +use alloy_signer::Signer; +use alloy_signer_local::{ coins_bip39::{English, Mnemonic}, - LocalWallet, MnemonicBuilder, Signer as AlloySigner, + MnemonicBuilder, PrivateKeySigner, }; -use alloy_transport::TransportError; +use alloy_transport::{Transport, TransportError}; use anvil_server::ServerConfig; use foundry_common::{ - provider::alloy::ProviderBuilder, ALCHEMY_FREE_TIER_CUPS, NON_ARCHIVE_NODE_WARNING, - REQUEST_TIMEOUT, + provider::ProviderBuilder, ALCHEMY_FREE_TIER_CUPS, NON_ARCHIVE_NODE_WARNING, REQUEST_TIMEOUT, }; use foundry_config::Config; use foundry_evm::{ constants::DEFAULT_CREATE2_DEPLOYER, fork::{BlockchainDb, BlockchainDbMeta, SharedBackend}, - revm, - revm::primitives::{BlockEnv, CfgEnv, SpecId, TxEnv}, + revm::primitives::{BlockEnv, CfgEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, SpecId, TxEnv}, utils::apply_chain_and_block_specific_env_changes, }; use parking_lot::RwLock; use rand::thread_rng; +use revm::primitives::BlobExcessGasAndPrice; use serde_json::{json, to_writer, Value}; use std::{ collections::HashMap, @@ -59,12 +59,8 @@ pub const CHAIN_ID: u64 = 31337; pub const DEFAULT_MNEMONIC: &str = "test test test test test test test test test test test junk"; /// The default IPC endpoint -#[cfg(windows)] -pub const DEFAULT_IPC_ENDPOINT: &str = r"\\.\pipe\anvil.ipc"; - -/// The default IPC endpoint -#[cfg(not(windows))] -pub const DEFAULT_IPC_ENDPOINT: &str = "/tmp/anvil.ipc"; +pub const DEFAULT_IPC_ENDPOINT: &str = + if cfg!(unix) { "/tmp/anvil.ipc" } else { r"\\.\pipe\anvil.ipc" }; /// `anvil 0.1.0 (f01b232bc 2022-04-13T23:28:39.493201+00:00)` pub const VERSION_MESSAGE: &str = concat!( @@ -91,23 +87,25 @@ pub struct NodeConfig { /// Chain ID of the EVM chain pub chain_id: Option, /// Default gas limit for all txs - pub gas_limit: U256, + pub gas_limit: u128, /// If set to `true`, disables the block gas limit pub disable_block_gas_limit: bool, /// Default gas price for all txs - pub gas_price: Option, + pub gas_price: Option, /// Default base fee - pub base_fee: Option, + pub base_fee: Option, + /// 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, + 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, /// Signer accounts that can sign messages/transactions from the EVM node - pub signer_accounts: Vec, + 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 @@ -170,18 +168,20 @@ pub struct NodeConfig { pub disable_default_create2_deployer: bool, /// Enable Optimism deposit transaction pub enable_optimism: 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>, } impl NodeConfig { fn as_string(&self, fork: Option<&ClientFork>) -> String { let mut config_string: String = String::new(); - let _ = write!(config_string, "\n{}", Paint::green(BANNER)); + let _ = write!(config_string, "\n{}", BANNER.green()); let _ = write!(config_string, "\n {VERSION_MESSAGE}"); - let _ = write!( - config_string, - "\n {}", - Paint::green("https://github.com/foundry-rs/foundry") - ); + let _ = write!(config_string, "\n {}", "https://github.com/foundry-rs/foundry".green()); let _ = write!( config_string, @@ -206,7 +206,7 @@ Private Keys ); for (idx, wallet) in self.genesis_accounts.iter().enumerate() { - let hex = hex::encode(wallet.signer().to_bytes()); + let hex = hex::encode(wallet.credential().to_bytes()); let _ = write!(config_string, "\n({idx}) 0x{hex}"); } @@ -249,9 +249,10 @@ Chain ID: {} Chain ID ================== + {} "#, - Paint::green(format!("\n{}", self.get_chain_id())) + self.get_chain_id().green() ); } @@ -261,9 +262,10 @@ Chain ID r#" Gas Price ================== + {} "#, - Paint::green(format!("\n{}", self.get_gas_price())) + self.get_gas_price().green() ); } else { let _ = write!( @@ -271,9 +273,10 @@ Gas Price r#" Base Fee ================== + {} "#, - Paint::green(format!("\n{}", self.get_base_fee())) + self.get_base_fee().green() ); } @@ -282,9 +285,10 @@ Base Fee r#" Gas Limit ================== + {} "#, - Paint::green(format!("\n{}", self.gas_limit)) + self.gas_limit.green() ); let _ = write!( @@ -292,9 +296,10 @@ Gas Limit r#" Genesis Timestamp ================== + {} "#, - Paint::green(format!("\n{}", self.get_genesis_timestamp())) + self.get_genesis_timestamp().green() ); config_string @@ -307,7 +312,7 @@ Genesis Timestamp for wallet in &self.genesis_accounts { available_accounts.push(format!("{:?}", wallet.address())); - private_keys.push(format!("0x{}", hex::encode(wallet.signer().to_bytes()))); + private_keys.push(format!("0x{}", hex::encode(wallet.credential().to_bytes()))); } if let Some(ref gen) = self.account_generator { @@ -345,8 +350,6 @@ Genesis Timestamp } } -// === impl NodeConfig === - 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` @@ -354,6 +357,16 @@ impl NodeConfig { pub fn test() -> Self { Self { enable_tracing: true, silent: true, port: 0, ..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 { @@ -362,7 +375,7 @@ impl Default for NodeConfig { let genesis_accounts = AccountGenerator::new(10).phrase(DEFAULT_MNEMONIC).gen(); Self { chain_id: None, - gas_limit: U256::from(30_000_000), + gas_limit: 30_000_000, disable_block_gas_limit: false, gas_price: None, hardfork: None, @@ -381,6 +394,7 @@ impl Default for NodeConfig { fork_block_number: None, account_generator: None, base_fee: None, + blob_excess_gas_and_price: None, enable_tracing: true, enable_steps_tracing: false, enable_auto_impersonate: false, @@ -404,21 +418,41 @@ impl Default for NodeConfig { transaction_block_keeper: None, disable_default_create2_deployer: false, enable_optimism: false, + slots_in_an_epoch: 32, + memory_limit: None, + precompile_factory: 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) -> U256 { + pub fn get_base_fee(&self) -> u128 { self.base_fee - .or_else(|| self.genesis.as_ref().and_then(|g| g.base_fee_per_gas.map(U256::from))) - .unwrap_or_else(|| U256::from(INITIAL_BASE_FEE)) + .or_else(|| self.genesis.as_ref().and_then(|g| g.base_fee_per_gas)) + .unwrap_or(INITIAL_BASE_FEE) } /// Returns the base fee to use - pub fn get_gas_price(&self) -> U256 { - self.gas_price.unwrap_or_else(|| U256::from(INITIAL_GAS_PRICE)) + 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(blob_excess_gas_and_price) = &self.blob_excess_gas_and_price { + blob_excess_gas_and_price.clone() + } else if let Some(excess_blob_gas) = self.genesis.as_ref().and_then(|g| g.excess_blob_gas) + { + BlobExcessGasAndPrice::new(excess_blob_gas as u64) + } else { + BlobExcessGasAndPrice { blob_gasprice: 0, excess_blob_gas: 0 } + } } /// Returns the base fee to use @@ -475,7 +509,7 @@ impl NodeConfig { /// Sets the gas limit #[must_use] - pub fn with_gas_limit(mut self, gas_limit: Option) -> Self { + pub fn with_gas_limit(mut self, gas_limit: Option) -> Self { if let Some(gas_limit) = gas_limit { self.gas_limit = gas_limit; } @@ -493,8 +527,8 @@ impl NodeConfig { /// Sets the gas price #[must_use] - pub fn with_gas_price(mut self, gas_price: Option) -> Self { - self.gas_price = gas_price.map(Into::into); + pub fn with_gas_price(mut self, gas_price: Option) -> Self { + self.gas_price = gas_price; self } @@ -517,8 +551,8 @@ impl NodeConfig { /// Sets the base fee #[must_use] - pub fn with_base_fee(mut self, base_fee: Option) -> Self { - self.base_fee = base_fee.map(Into::into); + pub fn with_base_fee(mut self, base_fee: Option) -> Self { + self.base_fee = base_fee; self } @@ -554,14 +588,14 @@ impl NodeConfig { /// Sets the genesis accounts #[must_use] - pub fn with_genesis_accounts(mut self, accounts: Vec) -> Self { + 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 { + pub fn with_signer_accounts(mut self, accounts: Vec) -> Self { self.signer_accounts = accounts; self } @@ -596,6 +630,13 @@ impl NodeConfig { 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 { @@ -754,7 +795,7 @@ impl NodeConfig { /// Returns the ipc path for the ipc endpoint if any pub fn get_ipc_path(&self) -> Option { - match self.ipc_path.as_ref() { + match &self.ipc_path { Some(path) => path.clone().or_else(|| Some(DEFAULT_IPC_ENDPOINT.to_string())), None => None, } @@ -796,6 +837,20 @@ impl NodeConfig { self } + /// 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 + } + + /// 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 + } + /// Configures everything related to env, backend and database and returns the /// [Backend](mem::Backend) /// @@ -803,8 +858,8 @@ impl NodeConfig { pub(crate) async fn setup(&mut self) -> mem::Backend { // configure the revm environment - let mut cfg = CfgEnv::default(); - cfg.spec_id = self.get_hardfork().into(); + let mut cfg = + CfgEnvWithHandlerCfg::new_with_spec_id(CfgEnv::default(), 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. @@ -812,18 +867,29 @@ impl NodeConfig { // caller is a contract. So we disable the check by default. cfg.disable_eip3607 = true; cfg.disable_block_gas_limit = self.disable_block_gas_limit; - cfg.optimism = self.enable_optimism; + cfg.handler_cfg.is_optimism = self.enable_optimism; + + if let Some(value) = self.memory_limit { + cfg.memory_limit = value; + } - let mut env = revm::primitives::Env { - cfg, + let env = revm::primitives::Env { + cfg: cfg.cfg_env, block: BlockEnv { - gas_limit: self.gas_limit, - basefee: self.get_base_fee(), + gas_limit: U256::from(self.gas_limit), + basefee: U256::from(self.get_base_fee()), ..Default::default() }, tx: TxEnv { chain_id: self.get_chain_id().into(), ..Default::default() }, }; - let fees = FeeManager::new(env.cfg.spec_id, self.get_base_fee(), self.get_gas_price()); + let mut env = EnvWithHandlerCfg::new(Box::new(env), cfg.handler_cfg); + + let fees = FeeManager::new( + cfg.handler_cfg.spec_id, + self.get_base_fee(), + self.get_gas_price(), + self.get_blob_excess_gas_and_price(), + ); let (db, fork): (Arc>>, Option) = if let Some(eth_rpc_url) = self.eth_rpc_url.clone() { @@ -885,15 +951,15 @@ impl NodeConfig { } /// Configures everything related to forking based on the passed `eth_rpc_url`: - /// - returning a tuple of a [ForkedDatabase](ForkedDatabase) wrapped in an [Arc](Arc) - /// [RwLock](tokio::sync::RwLock) and [ClientFork](ClientFork) wrapped in an [Option](Option) - /// which can be used in a [Backend](mem::Backend) to fork from. + /// - returning a tuple of a [ForkedDatabase] wrapped in an [Arc] [RwLock](tokio::sync::RwLock) + /// 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 revm::primitives::Env, + env: &mut EnvWithHandlerCfg, fees: &FeeManager, ) -> (Arc>>, Option) { let (db, config) = self.setup_fork_db_config(eth_rpc_url, env, fees).await; @@ -907,15 +973,14 @@ impl NodeConfig { } /// Configures everything related to forking based on the passed `eth_rpc_url`: - /// - returning a tuple of a [ForkedDatabase](ForkedDatabase) and - /// [ClientForkConfig](ClientForkConfig) which can be used to build a - /// [ClientFork](ClientFork) to fork from. + /// - 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 revm::primitives::Env, + env: &mut EnvWithHandlerCfg, fees: &FeeManager, ) -> (ForkedDatabase, ClientForkConfig) { // TODO make provider agnostic @@ -942,9 +1007,9 @@ impl NodeConfig { // but only if we're forking mainnet let chain_id = provider.get_chain_id().await.expect("Failed to fetch network chain ID"); - if alloy_chains::NamedChain::Mainnet == chain_id.to::() { + if alloy_chains::NamedChain::Mainnet == chain_id { let hardfork: Hardfork = fork_block_number.into(); - env.cfg.spec_id = hardfork.into(); + env.handler_cfg.spec_id = hardfork.into(); self.hardfork = Some(hardfork); } Some(U256::from(chain_id)) @@ -961,7 +1026,7 @@ impl NodeConfig { }; let block = provider - .get_block(BlockNumberOrTag::Number(fork_block_number).into(), false) + .get_block(BlockNumberOrTag::Number(fork_block_number).into(), false.into()) .await .expect("Failed to get fork block"); @@ -977,7 +1042,7 @@ latest block number: {latest_block}" // 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)); + message.push_str(&format!("\n{NON_ARCHIVE_NODE_WARNING}")); } panic!("{}", message); } @@ -987,33 +1052,30 @@ latest block number: {latest_block}" // 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 - let gas_limit = if self.disable_block_gas_limit || block.header.gas_limit.is_zero() { - U256::from(u64::MAX) + let gas_limit = if self.disable_block_gas_limit || block.header.gas_limit == 0 { + u64::MAX as u128 } else { block.header.gas_limit }; env.block = BlockEnv { number: U256::from(fork_block_number), - timestamp: block.header.timestamp, + 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, + gas_limit: U256::from(gas_limit), // Keep previous `coinbase` and `basefee` value coinbase: env.block.coinbase, basefee: env.block.basefee, ..Default::default() }; - // apply changes such as difficulty -> prevrandao - apply_chain_and_block_specific_env_changes(env, &block); - // if not set explicitly we use the base fee of the latest block if self.base_fee.is_none() { if let Some(base_fee) = block.header.base_fee_per_gas { self.base_fee = Some(base_fee); - env.block.basefee = base_fee; + env.block.basefee = U256::from(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( @@ -1022,7 +1084,18 @@ latest block number: {latest_block}" block.header.base_fee_per_gas.unwrap_or_default(), ); // update next base fee - fees.set_base_fee(U256::from(next_block_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) + { + env.block.blob_excess_gas_and_price = + Some(BlobExcessGasAndPrice::new(blob_excess_gas as u64)); + 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, + )); } } @@ -1040,9 +1113,9 @@ latest block number: {latest_block}" chain_id } else { let chain_id = if let Some(fork_chain_id) = fork_chain_id { - fork_chain_id.to::() + fork_chain_id.to() } else { - provider.get_chain_id().await.unwrap().to::() + provider.get_chain_id().await.unwrap() }; // need to update the dev signers and env with the chain id @@ -1052,8 +1125,10 @@ latest block number: {latest_block}" chain_id }; let override_chain_id = self.chain_id; + // apply changes such as difficulty -> prevrandao and chain specifics for current chain id + apply_chain_and_block_specific_env_changes(env, &block); - let meta = BlockchainDbMeta::new(env.clone(), eth_rpc_url.clone()); + let meta = BlockchainDbMeta::new(*env.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 { @@ -1075,16 +1150,23 @@ latest block number: {latest_block}" provider, chain_id, override_chain_id, - timestamp: block.header.timestamp.to::(), + timestamp: block.header.timestamp, base_fee: block.header.base_fee_per_gas, 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, + blob_excess_gas_and_price: env.block.blob_excess_gas_and_price.clone(), }; - (ForkedDatabase::new(backend, block_chain_db), config) + 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); + + (db, config) } } @@ -1094,8 +1176,6 @@ pub struct PruneStateHistoryConfig { pub max_memory_history: Option, } -// === impl PruneStateHistoryConfig === - impl PruneStateHistoryConfig { /// Returns `true` if writing state history is supported pub fn is_state_history_supported(&self) -> bool { @@ -1163,7 +1243,7 @@ impl AccountGenerator { } impl AccountGenerator { - pub fn gen(&self) -> Vec { + pub fn gen(&self) -> Vec { let builder = MnemonicBuilder::::default().phrase(self.phrase.as_str()); // use the derivation path @@ -1194,13 +1274,15 @@ pub fn anvil_tmp_dir() -> Option { /// /// 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 { +async fn find_latest_fork_block, T: Transport + Clone>( + 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(), false).await? { + if let Some(block) = provider.get_block(num.into(), false.into()).await? { if block.header.hash.is_some() { break; } diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index e9ba3ff84..e28a8c462 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -1,5 +1,5 @@ use super::{ - backend::mem::{state, BlockRequest}, + backend::mem::{state, BlockRequest, State}, sign::build_typed_transaction, }; use crate::{ @@ -14,7 +14,7 @@ use crate::{ error::{ BlockchainError, FeeHistoryError, InvalidTransactionError, Result, ToRpcResponseResult, }, - fees::{FeeDetails, FeeHistoryCache}, + fees::{FeeDetails, FeeHistoryCache, MIN_SUGGESTED_PRIORITY_FEE}, macros::node_info, miner::FixedBlockTimeMiner, pool::{ @@ -28,32 +28,33 @@ use crate::{ }, filter::{EthFilter, Filters, LogsFilter}, mem::transaction_build, - revm::primitives::Output, + revm::primitives::{BlobExcessGasAndPrice, Output}, ClientFork, LoggingManager, Miner, MiningMode, StorageInfo, }; -use alloy_consensus::TxLegacy; +use alloy_consensus::{transaction::eip4844::TxEip4844Variant, TxEnvelope}; use alloy_dyn_abi::TypedData; -use alloy_network::{Signed, TxKind}; -use alloy_primitives::{Address, Bytes, TxHash, B256, B64, U256, U64}; -use alloy_rlp::Decodable; -use alloy_rpc_trace_types::{ - geth::{DefaultFrame, GethDebugTracingOptions, GethDefaultTracingOptions, GethTrace}, - parity::LocalizedTransactionTrace, -}; +use alloy_eips::eip2718::Encodable2718; +use alloy_network::eip2718::Decodable2718; +use alloy_primitives::{Address, Bytes, TxHash, TxKind, B256, B64, U256, U64}; use alloy_rpc_types::{ request::TransactionRequest, state::StateOverride, txpool::{TxpoolContent, TxpoolInspect, TxpoolInspectSummary, TxpoolStatus}, AccessList, AccessListWithGasUsed, Block, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, EIP1186AccountProofResponse, FeeHistory, Filter, FilteredParams, Log, - Transaction, TransactionReceipt, + Transaction, }; +use alloy_rpc_types_trace::{ + geth::{DefaultFrame, GethDebugTracingOptions, GethDefaultTracingOptions, GethTrace}, + parity::LocalizedTransactionTrace, +}; +use alloy_serde::WithOtherFields; use alloy_transport::TransportErrorKind; use anvil_core::{ eth::{ block::BlockInfo, transaction::{ - transaction_request_to_typed, PendingTransaction, TypedTransaction, + transaction_request_to_typed, PendingTransaction, ReceiptResponse, TypedTransaction, TypedTransactionRequest, }, EthRequest, @@ -64,7 +65,7 @@ use anvil_core::{ }, }; use anvil_rpc::{error::RpcError, response::ResponseResult}; -use foundry_common::provider::alloy::ProviderBuilder; +use foundry_common::provider::ProviderBuilder; use foundry_evm::{ backend::DatabaseError, decode::RevertDecoder, @@ -116,8 +117,6 @@ pub struct EthApi { instance_id: Arc>, } -// === impl Eth RPC API === - impl EthApi { /// Creates a new instance #[allow(clippy::too_many_arguments)] @@ -148,7 +147,7 @@ impl EthApi { } } - /// Executes the [EthRequest] and returns an RPC [RpcResponse] + /// Executes the [EthRequest] and returns an RPC [ResponseResult]. pub async fn execute(&self, request: EthRequest) -> ResponseResult { trace!(target: "rpc::api", "executing eth request"); match request { @@ -170,6 +169,7 @@ impl EthApi { 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::EthGetStorageAt(addr, slot, block) => { @@ -235,6 +235,15 @@ impl EthApi { EthRequest::EthEstimateGas(call, block, overrides) => { self.estimate_gas(call, block, overrides).await.to_rpc_result() } + EthRequest::EthGetRawTransactionByHash(hash) => { + self.raw_transaction(hash).await.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() } @@ -265,7 +274,10 @@ impl EthApi { 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() @@ -298,6 +310,9 @@ impl EthApi { 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() } @@ -413,6 +428,9 @@ impl EthApi { 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() + } } } @@ -444,11 +462,6 @@ impl EthApi { Err(BlockchainError::NoSignerAvailable) } - /// Queries the current gas limit - fn current_gas_limit(&self) -> Result { - Ok(self.backend.gas_limit()) - } - async fn block_request(&self, block_number: Option) -> Result { let block_request = match block_number { Some(BlockId::Number(BlockNumber::Pending)) => { @@ -463,6 +476,19 @@ impl EthApi { Ok(block_request) } + 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) => TxEnvelope::try_from(tx.inner) + .map_or(Err(BlockchainError::FailedToDecodeTransaction), |tx| { + Ok(Some(tx.encoded_2718().into())) + }), + None => Ok(None), + }, + } + } + /// Returns the current client version. /// /// Handler for ETH RPC call: `web3_clientVersion` @@ -542,12 +568,21 @@ impl EthApi { /// Returns the current gas price fn eth_gas_price(&self) -> Result { node_info!("eth_gasPrice"); - self.gas_price() + Ok(U256::from(self.gas_price())) } /// Returns the current gas price - pub fn gas_price(&self) -> Result { - Ok(self.backend.gas_price()) + pub fn gas_price(&self) -> u128 { + if self.backend.is_eip1559() { + self.backend.base_fee().saturating_add(self.lowest_suggestion_tip()) + } else { + self.backend.fees().raw_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 @@ -555,12 +590,19 @@ impl EthApi { /// /// Handler for ETH RPC call: `eth_maxPriorityFeePerGas` pub fn gas_max_priority_fee_per_gas(&self) -> Result { - Ok(self.backend.max_priority_fee_per_gas()) + 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 { - self.backend.gas_limit() + U256::from(self.backend.gas_limit()) } /// Returns the accounts list @@ -687,7 +729,7 @@ impl EthApi { block_number: Option, ) -> Result { node_info!("eth_getTransactionCount"); - self.get_transaction_count(address, block_number).await + self.get_transaction_count(address, block_number).await.map(U256::from) } /// Returns the number of transactions in a block with given hash. @@ -842,7 +884,10 @@ impl EthApi { /// Signs a transaction /// /// Handler for ETH RPC call: `eth_signTransaction` - pub async fn sign_transaction(&self, mut request: TransactionRequest) -> Result { + pub async fn sign_transaction( + &self, + mut request: WithOtherFields, + ) -> Result { node_info!("eth_signTransaction"); let from = request.from.map(Ok).unwrap_or_else(|| { @@ -854,22 +899,23 @@ impl EthApi { if request.gas.is_none() { // estimate if not provided if let Ok(gas) = self.estimate_gas(request.clone(), None, None).await { - request.gas = Some(gas); + request.gas = Some(gas.to()); } } let request = self.build_typed_tx_request(request, nonce)?; - let signer = self.get_signer(from).ok_or(BlockchainError::NoSignerAvailable)?; - let signature = - alloy_primitives::hex::encode(signer.sign_transaction(request, &from)?.as_bytes()); - Ok(format!("0x{signature}")) + 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, mut request: TransactionRequest) -> Result { + pub async fn send_transaction( + &self, + mut request: WithOtherFields, + ) -> Result { node_info!("eth_sendTransaction"); let from = request.from.map(Ok).unwrap_or_else(|| { @@ -880,11 +926,12 @@ impl EthApi { if request.gas.is_none() { // estimate if not provided if let Ok(gas) = self.estimate_gas(request.clone(), None, None).await { - request.gas = Some(gas); + request.gas = Some(gas.to()); } } let request = self.build_typed_tx_request(request, nonce)?; + // if the sender is currently impersonated we need to "bypass" signing let pending_transaction = if self.is_impersonated(from) { let bypass_signature = self.backend.cheats().bypass_signature(); @@ -901,7 +948,7 @@ impl EthApi { self.backend.validate_pool_transaction(&pending_transaction).await?; let requires = required_marker(nonce, on_chain_nonce, from); - let provides = vec![to_marker(nonce.to::(), from)]; + let provides = vec![to_marker(nonce, from)]; debug_assert!(requires != provides); self.add_pending_transaction(pending_transaction, requires, provides) @@ -916,26 +963,11 @@ impl EthApi { if data.is_empty() { return Err(BlockchainError::EmptyRawTransactionData); } - let transaction = if data[0] > 0x7f { - // legacy transaction - match Signed::::decode(&mut data) { - Ok(transaction) => TypedTransaction::Legacy(transaction), - Err(_) => return Err(BlockchainError::FailedToDecodeSignedTransaction), - } - } else { - // the [TypedTransaction] requires a valid rlp input, - // but EIP-1559 prepends a version byte, so we need to encode the data first to get a - // valid rlp and then rlp decode impl of `TypedTransaction` will remove and check the - // version byte - let extend = alloy_rlp::encode(data); - let tx = match TypedTransaction::decode(&mut &extend[..]) { - Ok(transaction) => transaction, - Err(_) => return Err(BlockchainError::FailedToDecodeSignedTransaction), - }; + let transaction = TypedTransaction::decode_2718(&mut data) + .map_err(|_| BlockchainError::FailedToDecodeSignedTransaction)?; + + self.ensure_typed_transaction_supported(&transaction)?; - self.ensure_typed_transaction_supported(&tx)?; - tx - }; let pending_transaction = PendingTransaction::new(transaction)?; // pre-validate @@ -949,7 +981,7 @@ impl EthApi { let priority = self.transaction_priority(&pending_transaction.transaction); let pool_transaction = PoolTransaction { requires, - provides: vec![to_marker(nonce.to::(), *pending_transaction.sender())], + provides: vec![to_marker(nonce, *pending_transaction.sender())], pending_transaction, priority, }; @@ -964,7 +996,7 @@ impl EthApi { /// Handler for ETH RPC call: `eth_call` pub async fn call( &self, - request: TransactionRequest, + request: WithOtherFields, block_number: Option, overrides: Option, ) -> Result { @@ -988,6 +1020,7 @@ impl EthApi { 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 @@ -1017,7 +1050,7 @@ impl EthApi { /// Handler for ETH RPC call: `eth_createAccessList` pub async fn create_access_list( &self, - mut request: TransactionRequest, + mut request: WithOtherFields, block_number: Option, ) -> Result { node_info!("eth_createAccessList"); @@ -1066,7 +1099,7 @@ impl EthApi { /// Handler for ETH RPC call: `eth_estimateGas` pub async fn estimate_gas( &self, - request: TransactionRequest, + request: WithOtherFields, block_number: Option, overrides: Option, ) -> Result { @@ -1077,6 +1110,7 @@ impl EthApi { overrides, ) .await + .map(U256::from) } /// Get transaction by its hash. @@ -1085,7 +1119,10 @@ impl EthApi { /// 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> { + 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(); @@ -1115,7 +1152,7 @@ impl EthApi { &self, hash: B256, index: Index, - ) -> Result> { + ) -> Result>> { node_info!("eth_getTransactionByBlockHashAndIndex"); self.backend.transaction_by_block_hash_and_index(hash, index).await } @@ -1127,7 +1164,7 @@ impl EthApi { &self, block: BlockNumber, idx: Index, - ) -> Result> { + ) -> Result>> { node_info!("eth_getTransactionByBlockNumberAndIndex"); self.backend.transaction_by_block_number_and_index(block, idx).await } @@ -1135,7 +1172,7 @@ impl EthApi { /// Returns transaction receipt by transaction hash. /// /// Handler for ETH RPC call: `eth_getTransactionReceipt` - pub async fn transaction_receipt(&self, hash: B256) -> Result> { + pub async fn transaction_receipt(&self, hash: B256) -> Result> { node_info!("eth_getTransactionReceipt"); let tx = self.pool.get_transaction(hash); if tx.is_some() { @@ -1150,7 +1187,7 @@ impl EthApi { pub async fn block_receipts( &self, number: BlockNumber, - ) -> Result>> { + ) -> Result>> { node_info!("eth_getBlockReceipts"); self.backend.block_receipts(number).await } @@ -1187,7 +1224,7 @@ impl EthApi { let number = self.backend.ensure_block_number(Some(BlockId::Number(block_number))).await?; if let Some(fork) = self.get_fork() { if fork.predates_fork_inclusive(number) { - return Ok(fork.uncle_by_block_number_and_index(number, idx.into()).await?); + return Ok(fork.uncle_by_block_number_and_index(number, idx.into()).await?) } } // It's impossible to have uncles outside of fork mode @@ -1234,7 +1271,7 @@ impl EthApi { Err(BlockchainError::RpcUnimplemented) } - /// Introduced in EIP-1159 for getting information on the appropriate priority fee to use. + /// 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( @@ -1263,14 +1300,14 @@ impl EthApi { // efficiently, instead we fetch it from the fork if fork.predates_fork_inclusive(number) { return fork - .fee_history(block_count, BlockNumber::Number(number), &reward_percentiles) + .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::().max(MAX_BLOCK_COUNT); + let block_count = block_count.to::().min(MAX_BLOCK_COUNT); // highest and lowest block num in the requested range let highest = number; @@ -1282,7 +1319,7 @@ impl EthApi { } let mut response = FeeHistory { - oldest_block: U256::from(lowest), + oldest_block: lowest, base_fee_per_gas: Vec::new(), gas_used_ratio: Vec::new(), reward: Some(Default::default()), @@ -1298,7 +1335,9 @@ impl EthApi { for n in lowest..=highest { // if let Some(block) = fee_history.get(&n) { - response.base_fee_per_gas.push(U256::from(block.base_fee)); + 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 @@ -1308,11 +1347,7 @@ impl EthApi { for p in &reward_percentiles { let p = p.clamp(0.0, 100.0); let index = ((p.round() / 2f64) * 2f64) * resolution_per_percentile; - let reward = if let Some(r) = block.rewards.get(index as usize) { - U256::from(*r) - } else { - U256::ZERO - }; + let reward = block.rewards.get(index as usize).map_or(0, |r| *r); block_rewards.push(reward); } rewards.push(block_rewards); @@ -1323,30 +1358,16 @@ impl EthApi { response.reward = Some(rewards); - // calculate next base fee + // 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" - if let (Some(last_gas_used), Some(last_fee_per_gas)) = - (response.gas_used_ratio.last(), response.base_fee_per_gas.last()) - { - let elasticity = self.backend.elasticity(); - let last_fee_per_gas = last_fee_per_gas.to::() as f64; - if last_gas_used > &0.5 { - // increase base gas - let increase = ((last_gas_used - 0.5) * 2f64) * elasticity; - let new_base_fee = (last_fee_per_gas + (last_fee_per_gas * increase)) as u64; - response.base_fee_per_gas.push(U256::from(new_base_fee)); - } else if last_gas_used < &0.5 { - // decrease gas - let increase = ((0.5 - last_gas_used) * 2f64) * elasticity; - let new_base_fee = (last_fee_per_gas - (last_fee_per_gas * increase)) as u64; - response.base_fee_per_gas.push(U256::from(new_base_fee)); - } else { - // same base gas - response.base_fee_per_gas.push(U256::from(last_fee_per_gas as u64)); - } - } + response.base_fee_per_gas.push(self.backend.fees().base_fee()); + + // 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) } @@ -1359,7 +1380,22 @@ impl EthApi { /// Handler for ETH RPC call: `eth_maxPriorityFeePerGas` pub fn max_priority_fee_per_gas(&self) -> Result { node_info!("eth_maxPriorityFeePerGas"); - Ok(self.backend.max_priority_fee_per_gas()) + Ok(U256::from(self.lowest_suggestion_tip())) + } + + /// 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) } /// Creates a filter object, based on filter options, to notify when the state changes (logs). @@ -1427,6 +1463,44 @@ impl EthApi { 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.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.hash).await, + None => Ok(None), + } + } + /// Returns traces for the transaction hash for geth's tracing endpoint /// /// Handler for RPC call: `debug_traceTransaction` @@ -1444,7 +1518,7 @@ impl EthApi { /// Handler for RPC call: `debug_traceCall` pub async fn debug_trace_call( &self, - request: TransactionRequest, + request: WithOtherFields, block_number: Option, opts: GethDefaultTracingOptions, ) -> Result { @@ -1454,6 +1528,7 @@ impl EthApi { request.gas_price, request.max_fee_per_gas, request.max_priority_fee_per_gas, + request.max_fee_per_blob_gas, )? .or_zero_fees(); @@ -1585,6 +1660,15 @@ impl EthApi { 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(()) + } + /// Reset the fork to a fresh forked state, and optionally update the fork config. /// /// If `forking` is `None` then this will disable forking entirely. @@ -1668,7 +1752,7 @@ impl EthApi { ) .into()); } - self.backend.set_gas_price(gas); + self.backend.set_gas_price(gas.to()); Ok(()) } @@ -1683,7 +1767,7 @@ impl EthApi { ) .into()); } - self.backend.set_base_fee(basefee); + self.backend.set_base_fee(basefee.to()); Ok(()) } @@ -1733,7 +1817,7 @@ impl EthApi { current_block_number: U64::from(self.backend.best_number()), current_block_timestamp: env.block.timestamp.try_into().unwrap_or(u64::MAX), current_block_hash: self.backend.best_hash(), - hard_fork: env.cfg.spec_id, + hard_fork: env.handler_cfg.spec_id, transaction_order: match *tx_order { TransactionOrder::Fifo => "fifo".to_string(), TransactionOrder::Fees => "fees".to_string(), @@ -1742,7 +1826,7 @@ impl EthApi { base_fee: self.backend.base_fee(), chain_id: self.backend.chain_id().to::(), gas_limit: self.backend.gas_limit(), - gas_price: self.backend.gas_price(), + gas_price: self.gas_price(), }, fork_config: fork_config .map(|fork| { @@ -1781,6 +1865,12 @@ impl EthApi { }) } + 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` @@ -1833,7 +1923,7 @@ impl EthApi { /// 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); + self.backend.set_gas_limit(gas_limit.to()); Ok(true) } @@ -1898,7 +1988,14 @@ impl EthApi { if let Some(receipt) = self.backend.mined_transaction_receipt(tx.hash) { if let Some(output) = receipt.out { // insert revert reason if failure - if receipt.inner.status_code.unwrap_or_default().to::() == 0 { + if !receipt + .inner + .inner + .as_receipt_with_bloom() + .receipt + .status + .coerce_status() + { if let Some(reason) = RevertDecoder::new().maybe_decode(&output, None) { @@ -1971,7 +2068,7 @@ impl EthApi { /// Handler for ETH RPC call: `eth_sendUnsignedTransaction` pub async fn eth_send_unsigned_transaction( &self, - request: TransactionRequest, + request: WithOtherFields, ) -> Result { node_info!("eth_sendUnsignedTransaction"); // either use the impersonated account of the request's `from` field @@ -1992,7 +2089,7 @@ impl EthApi { self.backend.validate_pool_transaction(&pending_transaction).await?; let requires = required_marker(nonce, on_chain_nonce, from); - let provides = vec![to_marker(nonce.to::(), from)]; + let provides = vec![to_marker(nonce, from)]; self.add_pending_transaction(pending_transaction, requires, provides) } @@ -2067,7 +2164,7 @@ impl EthApi { // we set the from field here explicitly to the set sender of the pending transaction, // in case the transaction is impersonated. tx.from = from; - tx + tx.inner } for pending in self.pool.ready_transactions() { @@ -2085,8 +2182,6 @@ impl EthApi { } } -// === impl EthApi utility functions === - impl EthApi { /// Executes the future on a new blocking task. async fn on_blocking_task(&self, c: C) -> Result @@ -2137,10 +2232,10 @@ impl EthApi { async fn do_estimate_gas( &self, - request: TransactionRequest, + request: WithOtherFields, block_number: Option, overrides: Option, - ) -> Result { + ) -> 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 { @@ -2171,22 +2266,28 @@ impl EthApi { /// Estimates the gas usage of the `request` with the state. /// - /// This will execute the [CallRequest] and find the best gas limit via binary search + /// This will execute the transaction request and find the best gas limit via binary search. fn do_estimate_gas_with_state( &self, - mut request: TransactionRequest, + mut request: WithOtherFields, state: D, block_env: BlockEnv, - ) -> Result + ) -> Result where D: DatabaseRef, { // 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 likely_transfer = request.input.clone().into_input().is_none(); - if likely_transfer { - if let Some(to) = request.to { - if let Ok(target_code) = self.backend.get_code_with_state(&state, to) { + 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.access_list.is_none() && + request.blob_versioned_hashes.is_none(); + + if maybe_transfer { + if let Some(to) = to { + if let Ok(target_code) = self.backend.get_code_with_state(&state, *to) { if target_code.as_ref().is_empty() { return Ok(MIN_TRANSACTION_GAS); } @@ -2198,17 +2299,18 @@ impl EthApi { 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.unwrap_or(block_env.gas_limit); + let mut highest_gas_limit = request.gas.unwrap_or(block_env.gas_limit.to()); - // check with the funds of the sender - if let Some(from) = request.from { - let gas_price = fees.gas_price.unwrap_or_default(); - if gas_price > U256::ZERO { + 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 { + if 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 { @@ -2218,92 +2320,47 @@ impl EthApi { available_funds -= value; } // amount of gas the sender can afford with the `gas_price` - let allowance = available_funds.checked_div(gas_price).unwrap_or_default(); - if highest_gas_limit > allowance { - trace!(target: "node", "eth_estimateGas capped by limited user funds"); - highest_gas_limit = allowance; - } + 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()); } } - // if the provided gas limit is less than computed cap, use that - let gas_limit = std::cmp::min(request.gas.unwrap_or(highest_gas_limit), highest_gas_limit); let mut call_to_estimate = request.clone(); - call_to_estimate.gas = Some(gas_limit); + call_to_estimate.gas = Some(highest_gas_limit); // execute the call without writing to db let ethres = self.backend.call_with_state(&state, call_to_estimate, fees.clone(), block_env.clone()); - // Exceptional case: init used too much gas, we need to increase the gas limit and try - // again - if let Err(BlockchainError::InvalidTransaction(InvalidTransactionError::GasTooHigh(_))) = - ethres - { - // if price or limit was included in the request then we can execute the request - // again with the block's gas limit to check if revert is gas related or not - if request.gas.is_some() || request.gas_price.is_some() { - return Err(map_out_of_gas_err( - request, - state, - self.backend.clone(), - block_env, - fees, - gas_limit, - )); + let gas_used = match ethres.try_into()? { + GasEstimationCallResult::Success(gas) => Ok(gas), + GasEstimationCallResult::OutOfGas => { + Err(InvalidTransactionError::BasicOutOfGas(highest_gas_limit).into()) } - } - - let (exit, out, gas, _) = ethres?; - match exit { - return_ok!() => { - // succeeded - } - InstructionResult::OutOfGas | InstructionResult::OutOfFund => { - return Err(InvalidTransactionError::BasicOutOfGas(gas_limit).into()) - } - // need to check if the revert was due to lack of gas or unrelated reason - // we're also checking for InvalidFEOpcode here because this can be used to trigger an error common usage in openzeppelin - return_revert!() | InstructionResult::InvalidFEOpcode => { - // if price or limit was included in the request then we can execute the request - // again with the max gas limit to check if revert is gas related or not - return if request.gas.is_some() || request.gas_price.is_some() { - Err(map_out_of_gas_err( - request, - state, - self.backend.clone(), - block_env, - fees, - gas_limit, - )) - } else { - // the transaction did fail due to lack of gas from the user - Err(InvalidTransactionError::Revert(Some(convert_transact_out(&out).0.into())) - .into()) - }; + GasEstimationCallResult::Revert(output) => { + Err(InvalidTransactionError::Revert(output).into()) } - reason => { - warn!(target: "node", "estimation failed due to {:?}", reason); - return Err(BlockchainError::EvmError(reason)); + 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 - let gas: U256 = U256::from(gas); + // 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 * U256::from(3), - (highest_gas_limit + lowest_gas_limit) / U256::from(2), - ); + 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) > U256::from(1) { + while (highest_gas_limit - lowest_gas_limit) > 1 { request.gas = Some(mid_gas_limit); let ethres = self.backend.call_with_state( &state, @@ -2312,54 +2369,27 @@ impl EthApi { block_env.clone(), ); - // Exceptional case: init used too much gas, we need to increase the gas limit and try - // again - if let Err(BlockchainError::InvalidTransaction(InvalidTransactionError::GasTooHigh( - _, - ))) = ethres - { - // increase the lowest gas limit - lowest_gas_limit = mid_gas_limit; - - // new midpoint - mid_gas_limit = (highest_gas_limit + lowest_gas_limit) / U256::from(2); - continue; - } - - match ethres { - Ok((exit, _, _gas, _)) => match exit { + 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). - return_ok!() => { - highest_gas_limit = mid_gas_limit; - } - // If the transaction failed due to lack of gas, 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). - InstructionResult::Revert | - InstructionResult::OutOfGas | - InstructionResult::OutOfFund | - // we're also checking for InvalidFEOpcode here because this can be used to trigger an error common usage in openzeppelin - InstructionResult::InvalidFEOpcode => { - lowest_gas_limit = mid_gas_limit; - } - // The tx failed for some other reason. - reason => { - warn!(target: "node", "estimation failed due to {:?}", reason); - return Err(BlockchainError::EvmError(reason)) - } - }, - // We've already checked for the exceptional GasTooHigh case above, so this is a - // real error. - Err(reason) => { - warn!(target: "node", "estimation failed due to {:?}", reason); - return Err(reason); + 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 trasaction is correct + // as it succeeded earlier + lowest_gas_limit = mid_gas_limit; + } + }; // new midpoint - mid_gas_limit = (highest_gas_limit + lowest_gas_limit) / U256::from(2); + mid_gas_limit = (highest_gas_limit + lowest_gas_limit) / 2; } trace!(target : "node", "Estimated Gas for call {:?}", highest_gas_limit); @@ -2460,7 +2490,7 @@ impl EthApi { Some(info), Some(base_fee), ); - block_transactions.push(tx); + block_transactions.push(tx.inner); } Some(partial_block.into_full_block(block_transactions)) @@ -2468,48 +2498,74 @@ impl EthApi { fn build_typed_tx_request( &self, - request: TransactionRequest, - nonce: U256, + request: WithOtherFields, + nonce: u64, ) -> Result { - let chain_id = request.chain_id.map(|c| c.to::()).unwrap_or_else(|| self.chain_id()); + let chain_id = request.chain_id.unwrap_or_else(|| self.chain_id()); let max_fee_per_gas = request.max_fee_per_gas; + let max_fee_per_blob_gas = request.max_fee_per_blob_gas; let gas_price = request.gas_price; - let gas_limit = request.gas.map(Ok).unwrap_or_else(|| self.current_gas_limit())?; + let gas_limit = request.gas.unwrap_or(self.backend.gas_limit()); let request = match transaction_request_to_typed(request) { Some(TypedTransactionRequest::Legacy(mut m)) => { - m.nonce = nonce.to::(); + m.nonce = nonce; m.chain_id = Some(chain_id); - m.gas_limit = gas_limit.to::(); + m.gas_limit = gas_limit; if gas_price.is_none() { - m.gas_price = self.gas_price().unwrap_or_default().to::(); + m.gas_price = self.gas_price(); } TypedTransactionRequest::Legacy(m) } Some(TypedTransactionRequest::EIP2930(mut m)) => { - m.nonce = nonce.to::(); + m.nonce = nonce; m.chain_id = chain_id; - m.gas_limit = gas_limit.to::(); + m.gas_limit = gas_limit; if gas_price.is_none() { - m.gas_price = self.gas_price().unwrap_or_default().to::(); + m.gas_price = self.gas_price(); } TypedTransactionRequest::EIP2930(m) } Some(TypedTransactionRequest::EIP1559(mut m)) => { - m.nonce = nonce.to::(); + m.nonce = nonce; m.chain_id = chain_id; - m.gas_limit = gas_limit.to::(); + m.gas_limit = gas_limit; if max_fee_per_gas.is_none() { - m.max_fee_per_gas = self.gas_price().unwrap_or_default().to::(); + m.max_fee_per_gas = self.gas_price(); } TypedTransactionRequest::EIP1559(m) } + Some(TypedTransactionRequest::EIP4844(m)) => { + TypedTransactionRequest::EIP4844(match m { + // We only accept the TxEip4844 variant which has the sidecar. + TxEip4844Variant::TxEip4844WithSidecar(mut m) => { + m.tx.nonce = nonce; + m.tx.chain_id = chain_id; + m.tx.gas_limit = gas_limit; + if max_fee_per_gas.is_none() { + m.tx.max_fee_per_gas = self.gas_price(); + } + if max_fee_per_blob_gas.is_none() { + m.tx.max_fee_per_blob_gas = self + .excess_blob_gas_and_price() + .unwrap_or_default() + .map_or(0, |g| g.blob_gasprice) + } + TxEip4844Variant::TxEip4844WithSidecar(m) + } + // It is not valid to receive a TxEip4844 without a sidecar, therefore + // we must reject it. + TxEip4844Variant::TxEip4844(_) => { + return Err(BlockchainError::FailedToDecodeTransaction) + } + }) + } Some(TypedTransactionRequest::Deposit(mut m)) => { m.gas_limit = gas_limit; TypedTransactionRequest::Deposit(m) } - _ => return Err(BlockchainError::FailedToDecodeTransaction), + None => return Err(BlockchainError::FailedToDecodeTransaction), }; Ok(request) } @@ -2524,20 +2580,18 @@ impl EthApi { &self, address: Address, block_number: Option, - ) -> Result { + ) -> Result { let block_request = self.block_request(block_number).await?; if let BlockRequest::Number(number) = block_request { if let Some(fork) = self.get_fork() { if fork.predates_fork_inclusive(number) { - return Ok(fork.get_nonce(address, number).await?); + return Ok(fork.get_nonce(address, number).await?) } } } - let nonce = self.backend.get_nonce(address, Some(block_request)).await?; - - Ok(nonce) + self.backend.get_nonce(address, block_request).await } /// Returns the nonce for this request @@ -2551,10 +2605,10 @@ impl EthApi { &self, request: &TransactionRequest, from: Address, - ) -> Result<(U256, U256)> { + ) -> Result<(u64, u64)> { let highest_nonce = self.get_transaction_count(from, Some(BlockId::Number(BlockNumber::Pending))).await?; - let nonce = request.nonce.map(|n| n.to::()).unwrap_or(highest_nonce); + let nonce = request.nonce.unwrap_or(highest_nonce); Ok((nonce, highest_nonce)) } @@ -2585,19 +2639,20 @@ impl EthApi { match &tx { TypedTransaction::EIP2930(_) => self.backend.ensure_eip2930_active(), TypedTransaction::EIP1559(_) => self.backend.ensure_eip1559_active(), + TypedTransaction::EIP4844(_) => self.backend.ensure_eip4844_active(), TypedTransaction::Deposit(_) => self.backend.ensure_op_deposits_active(), TypedTransaction::Legacy(_) => Ok(()), } } } -fn required_marker(provided_nonce: U256, on_chain_nonce: U256, from: Address) -> Vec { +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(U256::from(1)); + let prev_nonce = provided_nonce.saturating_sub(1); if on_chain_nonce <= prev_nonce { - vec![to_marker(prev_nonce.to::(), from)] + vec![to_marker(prev_nonce, from)] } else { Vec::new() } @@ -2621,45 +2676,9 @@ fn ensure_return_ok(exit: InstructionResult, out: &Option) -> Result( - mut request: TransactionRequest, - state: D, - backend: Arc, - block_env: BlockEnv, - fees: FeeDetails, - gas_limit: U256, -) -> BlockchainError -where - D: DatabaseRef, -{ - request.gas = Some(backend.gas_limit()); - let (exit, out, _, _) = match backend.call_with_state(&state, request, fees, block_env) { - Ok(res) => res, - Err(err) => return err, - }; - match exit { - return_ok!() => { - // transaction succeeded by manually increasing the gas limit to - // highest, which means the caller lacks funds to pay for the tx - InvalidTransactionError::BasicOutOfGas(gas_limit).into() - } - return_revert!() => { - // reverted again after bumping the limit - InvalidTransactionError::Revert(Some(convert_transact_out(&out).0.into())).into() - } - reason => { - warn!(target: "node", "estimation failed due to {:?}", reason); - BlockchainError::EvmError(reason) - } - } -} - /// Determines the minimum gas needed for a transaction depending on the transaction kind. #[inline] -fn determine_base_gas_by_kind(request: &TransactionRequest) -> U256 { +fn determine_base_gas_by_kind(request: &WithOtherFields) -> u128 { match transaction_request_to_typed(request.clone()) { Some(request) => match request { TypedTransactionRequest::Legacy(req) => match req.to { @@ -2674,6 +2693,7 @@ fn determine_base_gas_by_kind(request: &TransactionRequest) -> U256 { TxKind::Call(_) => MIN_TRANSACTION_GAS, TxKind::Create => MIN_CREATE_GAS, }, + TypedTransactionRequest::EIP4844(_) => MIN_TRANSACTION_GAS, TypedTransactionRequest::Deposit(req) => match req.kind { TxKind::Call(_) => MIN_TRANSACTION_GAS, TxKind::Create => MIN_CREATE_GAS, @@ -2684,3 +2704,61 @@ fn determine_base_gas_by_kind(request: &TransactionRequest) -> U256 { _ => 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`]. +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!() | InstructionResult::CallOrCreate => Ok(Self::Success(gas)), + + InstructionResult::Revert => Ok(Self::Revert(output.map(|o| o.into_data()))), + + InstructionResult::OutOfGas | + InstructionResult::MemoryOOG | + InstructionResult::MemoryLimitOOG | + InstructionResult::PrecompileOOG | + InstructionResult::InvalidOperandOOG => Ok(Self::OutOfGas), + + 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 | + InstructionResult::OutOfFunds | + InstructionResult::CallTooDeep => Ok(Self::EvmError(exit)), + // Handle Revm EOF InstructionResults: Not supported yet + InstructionResult::ReturnContractInNotInitEOF | + InstructionResult::EOFOpcodeDisabledInLegacy | + InstructionResult::EOFFunctionStackOverflow => Ok(Self::EvmError(exit)), + }, + } + } +} diff --git a/crates/anvil/src/eth/backend/cheats.rs b/crates/anvil/src/eth/backend/cheats.rs index 949ea10e7..dbc58670f 100644 --- a/crates/anvil/src/eth/backend/cheats.rs +++ b/crates/anvil/src/eth/backend/cheats.rs @@ -2,9 +2,8 @@ use alloy_primitives::{Address, Signature}; use anvil_core::eth::transaction::impersonated_signature; -use foundry_evm::hashbrown::HashSet; use parking_lot::RwLock; -use std::sync::Arc; +use std::{collections::HashSet, sync::Arc}; /// Manages user modifications that may affect the node's behavior /// @@ -15,8 +14,6 @@ pub struct CheatsManager { state: Arc>, } -// === impl CheatsManager === - impl CheatsManager { /// Sets the account to impersonate /// diff --git a/crates/anvil/src/eth/backend/db.rs b/crates/anvil/src/eth/backend/db.rs index 02e7db113..9182cdadb 100644 --- a/crates/anvil/src/eth/backend/db.rs +++ b/crates/anvil/src/eth/backend/db.rs @@ -1,36 +1,27 @@ //! Helper types for working with [revm](foundry_evm::revm) -use crate::{mem::state::trie_hash_db, revm::primitives::AccountInfo}; -use alloy_primitives::{keccak256, Address, Bytes, B256, U256}; +use crate::revm::primitives::AccountInfo; +use alloy_consensus::Header; +use alloy_primitives::{keccak256, Address, Bytes, B256, U256, U64}; use alloy_rpc_types::BlockId; -use anvil_core::eth::trie::KeccakHasher; +use anvil_core::eth::{block::Block, transaction::TypedTransaction}; use foundry_common::errors::FsPathError; use foundry_evm::{ backend::{DatabaseError, DatabaseResult, MemDb, RevertSnapshotAction, StateSnapshot}, fork::BlockchainDb, - hashbrown::HashMap, revm::{ db::{CacheDB, DatabaseRef, DbAccount}, - primitives::{BlockEnv, Bytecode, KECCAK_EMPTY}, + primitives::{BlockEnv, Bytecode, HashMap, KECCAK_EMPTY}, Database, DatabaseCommit, }, }; -use hash_db::HashDB; use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, fmt, path::Path}; -/// Type alias for the `HashDB` representation of the Database -pub type AsHashDB = Box>>; - -/// Helper trait get access to the data in `HashDb` form +/// Helper trait get access to the full state data of the database #[auto_impl::auto_impl(Box)] -pub trait MaybeHashDatabase: DatabaseRef { - /// Return the DB as read-only hashdb and the root key - fn maybe_as_hash_db(&self) -> Option<(AsHashDB, B256)> { - None - } - /// Return the storage DB as read-only hashdb and the storage root of the account - fn maybe_account_db(&self, _addr: Address) -> Option<(AsHashDB, B256)> { +pub trait MaybeFullDatabase: DatabaseRef { + fn maybe_as_full_db(&self) -> Option<&HashMap> { None } @@ -44,15 +35,12 @@ pub trait MaybeHashDatabase: DatabaseRef { fn init_from_snapshot(&mut self, snapshot: StateSnapshot); } -impl<'a, T: 'a + MaybeHashDatabase + ?Sized> MaybeHashDatabase for &'a T +impl<'a, T: 'a + MaybeFullDatabase + ?Sized> MaybeFullDatabase for &'a T where &'a T: DatabaseRef, { - fn maybe_as_hash_db(&self) -> Option<(AsHashDB, B256)> { - T::maybe_as_hash_db(self) - } - fn maybe_account_db(&self, addr: Address) -> Option<(AsHashDB, B256)> { - T::maybe_account_db(self, addr) + fn maybe_as_full_db(&self) -> Option<&HashMap> { + T::maybe_as_full_db(self) } fn clear_into_snapshot(&mut self) -> StateSnapshot { @@ -80,7 +68,7 @@ pub trait Db: DatabaseRef + Database + DatabaseCommit - + MaybeHashDatabase + + MaybeFullDatabase + MaybeForkedDatabase + fmt::Debug + Send @@ -114,7 +102,7 @@ pub trait Db: B256::from_slice(&keccak256(code.as_ref())[..]) }; info.code_hash = code_hash; - info.code = Some(Bytecode::new_raw(alloy_primitives::Bytes(code.0)).to_checked()); + info.code = Some(Bytecode::new_raw(alloy_primitives::Bytes(code.0))); self.insert_account(address, info); Ok(()) } @@ -126,7 +114,12 @@ pub trait Db: fn insert_block_hash(&mut self, number: U256, hash: B256); /// Write all chain data to serialized bytes buffer - fn dump_state(&self, at: BlockEnv) -> DatabaseResult>; + fn dump_state( + &self, + at: BlockEnv, + best_number: U64, + blocks: Vec, + ) -> DatabaseResult>; /// Deserialize and add all chain data to the backend storage fn load_state(&mut self, state: SerializableState) -> DatabaseResult { @@ -147,9 +140,7 @@ pub trait Db: code: if account.code.0.is_empty() { None } else { - Some( - Bytecode::new_raw(alloy_primitives::Bytes(account.code.0)).to_checked(), - ) + Some(Bytecode::new_raw(alloy_primitives::Bytes(account.code.0))) }, nonce, }, @@ -196,7 +187,12 @@ impl + Send + Sync + Clone + fmt::Debug> D self.block_hashes.insert(number, hash); } - fn dump_state(&self, _at: BlockEnv) -> DatabaseResult> { + fn dump_state( + &self, + _at: BlockEnv, + _best_number: U64, + _blocks: Vec, + ) -> DatabaseResult> { Ok(None) } @@ -213,10 +209,11 @@ impl + Send + Sync + Clone + fmt::Debug> D } } -impl> MaybeHashDatabase for CacheDB { - fn maybe_as_hash_db(&self) -> Option<(AsHashDB, B256)> { - Some(trie_hash_db(&self.accounts)) +impl> MaybeFullDatabase for CacheDB { + fn maybe_as_full_db(&self) -> Option<&HashMap> { + Some(&self.accounts) } + fn clear_into_snapshot(&mut self) -> StateSnapshot { let db_accounts = std::mem::take(&mut self.accounts); let mut accounts = HashMap::new(); @@ -271,12 +268,10 @@ impl> MaybeForkedDatabase for CacheDB { } /// Represents a state at certain point -pub struct StateDb(pub(crate) Box); - -// === impl StateDB === +pub struct StateDb(pub(crate) Box); impl StateDb { - pub fn new(db: impl MaybeHashDatabase + Send + Sync + 'static) -> Self { + pub fn new(db: impl MaybeFullDatabase + Send + Sync + 'static) -> Self { Self(Box::new(db)) } } @@ -300,13 +295,9 @@ impl DatabaseRef for StateDb { } } -impl MaybeHashDatabase for StateDb { - fn maybe_as_hash_db(&self) -> Option<(AsHashDB, B256)> { - self.0.maybe_as_hash_db() - } - - fn maybe_account_db(&self, addr: Address) -> Option<(AsHashDB, B256)> { - self.0.maybe_account_db(addr) +impl MaybeFullDatabase for StateDb { + fn maybe_as_full_db(&self) -> Option<&HashMap> { + self.0.maybe_as_full_db() } fn clear_into_snapshot(&mut self) -> StateSnapshot { @@ -329,10 +320,12 @@ pub struct SerializableState { /// Note: This is an Option for backwards compatibility: pub block: Option, pub accounts: BTreeMap, + /// The best block number of the state, can be different from block number (Arbitrum chain). + pub best_block_number: Option, + #[serde(default)] + pub blocks: Vec, } -// === impl SerializableState === - impl SerializableState { /// Loads the `Genesis` object from the given json file path pub fn load(path: impl AsRef) -> Result { @@ -357,3 +350,30 @@ pub struct SerializableAccountRecord { pub code: Bytes, pub storage: BTreeMap, } + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SerializableBlock { + pub header: Header, + pub transactions: Vec, + pub ommers: Vec
, +} + +impl From for SerializableBlock { + fn from(block: Block) -> Self { + Self { + header: block.header, + transactions: block.transactions.into_iter().map(Into::into).collect(), + ommers: block.ommers.into_iter().map(Into::into).collect(), + } + } +} + +impl From for Block { + fn from(block: SerializableBlock) -> Self { + Self { + header: block.header, + transactions: block.transactions.into_iter().map(Into::into).collect(), + ommers: block.ommers.into_iter().map(Into::into).collect(), + } + } +} diff --git a/crates/anvil/src/eth/backend/executor.rs b/crates/anvil/src/eth/backend/executor.rs index d4ba69264..163428eeb 100644 --- a/crates/anvil/src/eth/backend/executor.rs +++ b/crates/anvil/src/eth/backend/executor.rs @@ -4,26 +4,32 @@ use crate::{ error::InvalidTransactionError, pool::transactions::PoolTransaction, }, + inject_precompiles, mem::inspector::Inspector, + PrecompileFactory, }; use alloy_consensus::{Header, Receipt, ReceiptWithBloom}; -use alloy_primitives::{Bloom, BloomInput, Log, B256, U256}; +use alloy_eips::eip2718::Encodable2718; +use alloy_primitives::{Bloom, BloomInput, Log, B256}; use anvil_core::eth::{ block::{Block, BlockInfo, PartialHeader}, - transaction::{PendingTransaction, TransactionInfo, TypedReceipt, TypedTransaction}, + transaction::{ + DepositReceipt, PendingTransaction, TransactionInfo, TypedReceipt, TypedTransaction, + }, trie, }; use foundry_evm::{ backend::DatabaseError, - inspectors::{TracingInspector, TracingInspectorConfig}, - revm, revm::{ interpreter::InstructionResult, - primitives::{BlockEnv, CfgEnv, EVMError, Env, ExecutionResult, Output, SpecId}, + primitives::{ + BlockEnv, CfgEnvWithHandlerCfg, EVMError, EnvWithHandlerCfg, ExecutionResult, Output, + SpecId, + }, }, traces::CallTraceNode, - utils::{eval_to_instruction_result, halt_to_instruction_result}, }; +use revm::primitives::MAX_BLOB_GAS_PER_BLOCK; use std::sync::Arc; /// Represents an executed transaction (transacted on the DB) @@ -32,7 +38,7 @@ pub struct ExecutedTransaction { transaction: Arc, exit_reason: InstructionResult, out: Option, - gas_used: u64, + gas_used: u128, logs: Vec, traces: Vec, nonce: u64, @@ -42,46 +48,28 @@ pub struct ExecutedTransaction { impl ExecutedTransaction { /// Creates the receipt for the transaction - fn create_receipt(&self) -> TypedReceipt { - let used_gas = U256::from(self.gas_used); - let mut bloom = Bloom::default(); - logs_bloom(self.logs.clone(), &mut bloom); + fn create_receipt(&self, cumulative_gas_used: &mut u128) -> TypedReceipt { let logs = self.logs.clone(); + *cumulative_gas_used = cumulative_gas_used.saturating_add(self.gas_used); // successful return see [Return] let status_code = u8::from(self.exit_reason as u8 <= InstructionResult::SelfDestruct as u8); + let receipt_with_bloom: ReceiptWithBloom = Receipt { + status: (status_code == 1).into(), + cumulative_gas_used: *cumulative_gas_used, + logs, + } + .into(); + match &self.transaction.pending_transaction.transaction.transaction { - TypedTransaction::Legacy(_) => TypedReceipt::Legacy(ReceiptWithBloom { - receipt: Receipt { - success: status_code == 1, - cumulative_gas_used: used_gas.to::(), - logs, - }, - bloom, - }), - TypedTransaction::EIP2930(_) => TypedReceipt::EIP2930(ReceiptWithBloom { - receipt: Receipt { - success: status_code == 1, - cumulative_gas_used: used_gas.to::(), - logs, - }, - bloom, - }), - TypedTransaction::EIP1559(_) => TypedReceipt::EIP1559(ReceiptWithBloom { - receipt: Receipt { - success: status_code == 1, - cumulative_gas_used: used_gas.to::(), - logs, - }, - bloom, - }), - TypedTransaction::Deposit(_) => TypedReceipt::Deposit(ReceiptWithBloom { - receipt: Receipt { - success: status_code == 1, - cumulative_gas_used: used_gas.to::(), - logs, - }, - bloom, + TypedTransaction::Legacy(_) => TypedReceipt::Legacy(receipt_with_bloom), + TypedTransaction::EIP2930(_) => TypedReceipt::EIP2930(receipt_with_bloom), + TypedTransaction::EIP1559(_) => TypedReceipt::EIP1559(receipt_with_bloom), + TypedTransaction::EIP4844(_) => TypedReceipt::EIP4844(receipt_with_bloom), + TypedTransaction::Deposit(tx) => TypedReceipt::Deposit(DepositReceipt { + inner: receipt_with_bloom, + deposit_nonce: Some(tx.nonce), + deposit_receipt_version: Some(1), }), } } @@ -108,11 +96,16 @@ pub struct TransactionExecutor<'a, Db: ?Sized, Validator: TransactionValidator> /// all pending transactions pub pending: std::vec::IntoIter>, pub block_env: BlockEnv, - pub cfg_env: CfgEnv, + /// The configuration environment and spec id + pub cfg_env: CfgEnvWithHandlerCfg, pub parent_hash: B256, /// Cumulative gas used by all executed transactions - pub gas_used: U256, + pub gas_used: u128, + /// Cumulative blob gas used by all executed transactions + pub blob_gas_used: u128, pub enable_steps_tracing: bool, + /// Precompiles to inject to the EVM. + pub precompile_factory: Option>, } impl<'a, DB: Db + ?Sized, Validator: TransactionValidator> TransactionExecutor<'a, DB, Validator> { @@ -122,21 +115,25 @@ impl<'a, DB: Db + ?Sized, Validator: TransactionValidator> TransactionExecutor<' let mut transaction_infos = Vec::new(); let mut receipts = Vec::new(); let mut bloom = Bloom::default(); - let mut cumulative_gas_used = U256::ZERO; + let mut cumulative_gas_used: u128 = 0; let mut invalid = Vec::new(); let mut included = Vec::new(); - let gas_limit = self.block_env.gas_limit; + let gas_limit = self.block_env.gas_limit.to::(); let parent_hash = self.parent_hash; - let block_number = self.block_env.number; + let block_number = self.block_env.number.to::(); let difficulty = self.block_env.difficulty; let beneficiary = self.block_env.coinbase; let timestamp = self.block_env.timestamp.to::(); - let base_fee = if (self.cfg_env.spec_id as u8) >= (SpecId::LONDON as u8) { - Some(self.block_env.basefee) + let base_fee = if self.cfg_env.handler_cfg.spec_id.is_enabled_in(SpecId::LONDON) { + Some(self.block_env.basefee.to::()) } else { None }; + let is_cancun = self.cfg_env.handler_cfg.spec_id >= SpecId::CANCUN; + let excess_blob_gas = if is_cancun { self.block_env.get_blob_excess_gas() } else { None }; + let mut cumulative_blob_gas_used = if is_cancun { Some(0u128) } else { None }; + for tx in self.into_iter() { let tx = match tx { TransactionExecutionOutcome::Executed(tx) => { @@ -147,6 +144,10 @@ impl<'a, DB: Db + ?Sized, Validator: TransactionValidator> TransactionExecutor<' trace!(target: "backend", tx_gas_limit = %tx.pending_transaction.transaction.gas_limit(), ?tx, "block gas limit exhausting, skipping transaction"); continue } + TransactionExecutionOutcome::BlobGasExhausted(tx) => { + trace!(target: "backend", blob_gas = %tx.pending_transaction.transaction.blob_gas().unwrap_or_default(), ?tx, "block blob gas limit exhausting, skipping transaction"); + continue + } TransactionExecutionOutcome::Invalid(tx, _) => { trace!(target: "backend", ?tx, "skipping invalid transaction"); invalid.push(tx); @@ -159,35 +160,43 @@ impl<'a, DB: Db + ?Sized, Validator: TransactionValidator> TransactionExecutor<' continue } }; - let receipt = tx.create_receipt(); - cumulative_gas_used = cumulative_gas_used.saturating_add(receipt.gas_used()); + if is_cancun { + let tx_blob_gas = tx + .transaction + .pending_transaction + .transaction + .transaction + .blob_gas() + .unwrap_or(0); + cumulative_blob_gas_used = + Some(cumulative_blob_gas_used.unwrap_or(0u128).saturating_add(tx_blob_gas)); + } + let receipt = tx.create_receipt(&mut cumulative_gas_used); + let ExecutedTransaction { transaction, logs, out, traces, exit_reason: exit, .. } = tx; - logs_bloom(logs.clone(), &mut bloom); + build_logs_bloom(logs.clone(), &mut bloom); - let contract_address = if let Some(Output::Create(_, contract_address)) = out { - trace!(target: "backend", "New contract deployed: at {:?}", contract_address); - contract_address - } else { - None - }; + let contract_address = out.as_ref().and_then(|out| { + if let Output::Create(_, contract_address) = out { + trace!(target: "backend", "New contract deployed: at {:?}", contract_address); + *contract_address + } else { + None + } + }); - let transaction_index = transaction_infos.len() as u32; + let transaction_index = transaction_infos.len() as u64; let info = TransactionInfo { transaction_hash: transaction.hash(), transaction_index, from: *transaction.pending_transaction.sender(), to: transaction.pending_transaction.transaction.to(), contract_address, - logs, - logs_bloom: *receipt.logs_bloom(), traces, exit, - out: match out { - Some(Output::Call(b)) => Some(alloy_primitives::Bytes(b.0)), - Some(Output::Create(b, _)) => Some(alloy_primitives::Bytes(b.0)), - _ => None, - }, + out: out.map(Output::into_data), nonce: tx.nonce, + gas_used: tx.gas_used, }; transaction_infos.push(info); @@ -196,7 +205,8 @@ impl<'a, DB: Db + ?Sized, Validator: TransactionValidator> TransactionExecutor<' } let ommers: Vec
= Vec::new(); - let receipts_root = trie::ordered_trie_root(receipts.iter().map(alloy_rlp::encode)); + let receipts_root = + trie::ordered_trie_root(receipts.iter().map(Encodable2718::encoded_2718)); let partial_header = PartialHeader { parent_hash, @@ -205,14 +215,17 @@ impl<'a, DB: Db + ?Sized, Validator: TransactionValidator> TransactionExecutor<' receipts_root, logs_bloom: bloom, difficulty, - number: block_number.to::(), - gas_limit: gas_limit.to::(), - gas_used: cumulative_gas_used.to::(), + number: block_number, + gas_limit, + gas_used: cumulative_gas_used, timestamp, extra_data: Default::default(), mix_hash: Default::default(), nonce: Default::default(), - base_fee: base_fee.map(|b| b.to::()), + base_fee, + parent_beacon_block_root: Default::default(), + blob_gas_used: cumulative_blob_gas_used, + excess_blob_gas: excess_blob_gas.map(|g| g as u128), }; let block = Block::new(partial_header, transactions.clone(), ommers); @@ -220,8 +233,14 @@ impl<'a, DB: Db + ?Sized, Validator: TransactionValidator> TransactionExecutor<' ExecutedTransactions { block, included, invalid } } - fn env_for(&self, tx: &PendingTransaction) -> Env { - Env { cfg: self.cfg_env.clone(), block: self.block_env.clone(), tx: tx.to_revm_tx_env() } + fn env_for(&self, tx: &PendingTransaction) -> EnvWithHandlerCfg { + let mut tx_env = tx.to_revm_tx_env(); + if self.cfg_env.handler_cfg.is_optimism { + tx_env.optimism.enveloped_tx = + Some(alloy_rlp::encode(&tx.transaction.transaction).into()); + } + + EnvWithHandlerCfg::new_with_cfg_env(self.cfg_env.clone(), self.block_env.clone(), tx_env) } } @@ -234,6 +253,8 @@ pub enum TransactionExecutionOutcome { Invalid(Arc, InvalidTransactionError), /// Execution skipped because could exceed gas limit Exhausted(Arc), + /// Execution skipped because it exceeded the blob gas limit + BlobGasExhausted(Arc), /// When an error occurred during execution DatabaseError(Arc, DatabaseError), } @@ -251,12 +272,21 @@ impl<'a, 'b, DB: Db + ?Sized, Validator: TransactionValidator> Iterator Err(err) => return Some(TransactionExecutionOutcome::DatabaseError(transaction, err)), }; let env = self.env_for(&transaction.pending_transaction); - // check that we comply with the block's gas limit - let max_gas = self.gas_used.saturating_add(U256::from(env.tx.gas_limit)); - if max_gas > env.block.gas_limit { + + // check that we comply with the block's gas limit, if not disabled + let max_gas = self.gas_used.saturating_add(env.tx.gas_limit as u128); + if !env.cfg.disable_block_gas_limit && max_gas > env.block.gas_limit.to::() { return Some(TransactionExecutionOutcome::Exhausted(transaction)) } + // check that we comply with the block's blob gas limit + let max_blob_gas = self.blob_gas_used.saturating_add( + transaction.pending_transaction.transaction.transaction.blob_gas().unwrap_or(0u128), + ); + if max_blob_gas > MAX_BLOB_GAS_PER_BLOCK as u128 { + return Some(TransactionExecutionOutcome::BlobGasExhausted(transaction)) + } + // validate before executing if let Err(err) = self.validator.validate_pool_transaction_for( &transaction.pending_transaction, @@ -269,33 +299,41 @@ impl<'a, 'b, DB: Db + ?Sized, Validator: TransactionValidator> Iterator let nonce = account.nonce; - let mut evm = revm::EVM::new(); - evm.env = env; - evm.database(&mut self.db); - // records all call and step traces let mut inspector = Inspector::default().with_tracing(); if self.enable_steps_tracing { inspector = inspector.with_steps_tracing(); } - trace!(target: "backend", "[{:?}] executing", transaction.hash()); - // transact and commit the transaction - let exec_result = match evm.inspect_commit(&mut inspector) { - Ok(exec_result) => exec_result, - Err(err) => { - warn!(target: "backend", "[{:?}] failed to execute: {:?}", transaction.hash(), err); - match err { - EVMError::Database(err) => { - return Some(TransactionExecutionOutcome::DatabaseError(transaction, err)) - } - EVMError::Transaction(err) => { - return Some(TransactionExecutionOutcome::Invalid(transaction, err.into())) - } - // This will correspond to prevrandao not set, and it should never happen. - // If it does, it's a bug. - e => { - panic!("Failed to execute transaction. This is a bug.\n {:?}", e) + let exec_result = { + let mut evm = + foundry_evm::utils::new_evm_with_inspector(&mut *self.db, env, &mut inspector); + if let Some(factory) = &self.precompile_factory { + inject_precompiles(&mut evm, factory.precompiles()); + } + + trace!(target: "backend", "[{:?}] executing", transaction.hash()); + // transact and commit the transaction + match evm.transact_commit() { + Ok(exec_result) => exec_result, + Err(err) => { + warn!(target: "backend", "[{:?}] failed to execute: {:?}", transaction.hash(), err); + match err { + EVMError::Database(err) => { + return Some(TransactionExecutionOutcome::DatabaseError( + transaction, + err, + )) + } + EVMError::Transaction(err) => { + return Some(TransactionExecutionOutcome::Invalid( + transaction, + err.into(), + )) + } + // This will correspond to prevrandao not set, and it should never happen. + // If it does, it's a bug. + e => panic!("failed to execute transaction: {e}"), } } } @@ -304,14 +342,12 @@ impl<'a, 'b, DB: Db + ?Sized, Validator: TransactionValidator> Iterator let (exit_reason, gas_used, out, logs) = match exec_result { ExecutionResult::Success { reason, gas_used, logs, output, .. } => { - (eval_to_instruction_result(reason), gas_used, Some(output), Some(logs)) + (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 } => { - (halt_to_instruction_result(reason), gas_used, None, None) - } + ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None, None), }; if exit_reason == InstructionResult::OutOfGas { @@ -321,7 +357,13 @@ impl<'a, 'b, DB: Db + ?Sized, Validator: TransactionValidator> Iterator trace!(target: "backend", ?exit_reason, ?gas_used, "[{:?}] executed with out={:?}", transaction.hash(), out); - self.gas_used = self.gas_used.saturating_add(U256::from(gas_used)); + // Track the total gas used for total gas per block checks + self.gas_used = self.gas_used.saturating_add(gas_used as u128); + + // Track the total blob gas used for total blob gas per blob checks + if let Some(blob_gas) = transaction.pending_transaction.transaction.transaction.blob_gas() { + self.blob_gas_used = self.blob_gas_used.saturating_add(blob_gas); + } trace!(target: "backend::executor", "transacted [{:?}], result: {:?} gas {}", transaction.hash(), exit_reason, gas_used); @@ -329,18 +371,9 @@ impl<'a, 'b, DB: Db + ?Sized, Validator: TransactionValidator> Iterator transaction, exit_reason, out, - gas_used, - logs: logs - .unwrap_or_default() - .into_iter() - .map(|log| Log::new_unchecked(log.address, log.topics, log.data)) - .collect(), - traces: inspector - .tracer - .unwrap_or(TracingInspector::new(TracingInspectorConfig::all())) - .get_traces() - .clone() - .into_nodes(), + gas_used: gas_used as u128, + logs: logs.unwrap_or_default(), + traces: inspector.tracer.map(|t| t.into_traces().into_nodes()).unwrap_or_default(), nonce, }; @@ -349,7 +382,7 @@ impl<'a, 'b, DB: Db + ?Sized, Validator: TransactionValidator> Iterator } /// Inserts all logs into the bloom -fn logs_bloom(logs: Vec, bloom: &mut Bloom) { +fn build_logs_bloom(logs: Vec, bloom: &mut Bloom) { for log in logs { bloom.accrue(BloomInput::Raw(&log.address[..])); for topic in log.topics() { diff --git a/crates/anvil/src/eth/backend/fork.rs b/crates/anvil/src/eth/backend/fork.rs index 94408756f..25e001da0 100644 --- a/crates/anvil/src/eth/backend/fork.rs +++ b/crates/anvil/src/eth/backend/fork.rs @@ -1,23 +1,29 @@ //! Support for forking off another client use crate::eth::{backend::db::Db, error::BlockchainError}; -use alloy_primitives::{Address, Bytes, StorageValue, B256, U256, U64}; -use alloy_providers::provider::TempProvider; -use alloy_rpc_trace_types::{ - geth::{GethDebugTracingOptions, GethTrace}, - parity::LocalizedTransactionTrace as Trace, +use alloy_primitives::{Address, Bytes, StorageValue, B256, U256}; +use alloy_provider::{ + ext::{DebugApi, TraceApi}, + Provider, }; use alloy_rpc_types::{ request::TransactionRequest, AccessListWithGasUsed, Block, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, EIP1186AccountProofResponse, FeeHistory, - Filter, Log, Transaction, TransactionReceipt, + Filter, Log, Transaction, +}; +use alloy_rpc_types_trace::{ + geth::{GethDebugTracingOptions, GethTrace}, + parity::LocalizedTransactionTrace as Trace, }; +use alloy_serde::WithOtherFields; use alloy_transport::TransportError; -use foundry_common::provider::alloy::{ProviderBuilder, RetryProvider}; +use anvil_core::eth::transaction::{convert_to_anvil_receipt, ReceiptResponse}; +use foundry_common::provider::{ProviderBuilder, RetryProvider}; use parking_lot::{ lock_api::{RwLockReadGuard, RwLockWriteGuard}, RawRwLock, RwLock, }; +use revm::primitives::BlobExcessGasAndPrice; use std::{collections::HashMap, sync::Arc, time::Duration}; use tokio::sync::RwLock as AsyncRwLock; @@ -37,8 +43,6 @@ pub struct ClientFork { pub database: Arc>>, } -// === impl ClientFork === - impl ClientFork { /// Creates a new instance of the fork pub fn new(config: ClientForkConfig, database: Arc>>) -> Self { @@ -66,28 +70,28 @@ impl ClientFork { let chain_id = if let Some(chain_id) = override_chain_id { chain_id } else { - self.provider().get_chain_id().await?.to::() + self.provider().get_chain_id().await? }; self.config.write().chain_id = chain_id; } let provider = self.provider(); - let block = - provider.get_block(block_number, false).await?.ok_or(BlockchainError::BlockNotFound)?; + let block = provider + .get_block(block_number, false.into()) + .await? + .ok_or(BlockchainError::BlockNotFound)?; let block_hash = block.header.hash.ok_or(BlockchainError::BlockNotFound)?; - let timestamp = block.header.timestamp.to::(); + let timestamp = block.header.timestamp; let base_fee = block.header.base_fee_per_gas; let total_difficulty = block.header.total_difficulty.unwrap_or_default(); - self.config.write().update_block( - block.header.number.ok_or(BlockchainError::BlockNotFound)?.to::(), - block_hash, - timestamp, - base_fee, - total_difficulty, - ); + let number = block.header.number.ok_or(BlockchainError::BlockNotFound)?; + self.config.write().update_block(number, block_hash, timestamp, base_fee, total_difficulty); self.clear_cached_storage(); + + self.database.write().await.insert_block_hash(U256::from(number), block_hash); + Ok(()) } @@ -118,7 +122,7 @@ impl ClientFork { self.config.read().total_difficulty } - pub fn base_fee(&self) -> Option { + pub fn base_fee(&self) -> Option { self.config.read().base_fee } @@ -149,7 +153,7 @@ impl ClientFork { /// Returns the fee history `eth_feeHistory` pub async fn fee_history( &self, - block_count: U256, + block_count: u64, newest_block: BlockNumber, reward_percentiles: &[f64], ) -> Result { @@ -163,35 +167,17 @@ impl ClientFork { keys: Vec, block_number: Option, ) -> Result { - self.provider().get_proof(address, keys, block_number).await + self.provider().get_proof(address, keys).block_id(block_number.unwrap_or_default()).await } /// Sends `eth_call` pub async fn call( &self, - request: &TransactionRequest, + request: &WithOtherFields, block: Option, ) -> Result { - let request = Arc::new(request.clone()); let block = block.unwrap_or(BlockNumber::Latest); - - if let BlockNumber::Number(num) = block { - // check if this request was already been sent - let key = (request.clone(), num); - if let Some(res) = self.storage_read().eth_call.get(&key).cloned() { - return Ok(res); - } - } - - let block_id: BlockId = block.into(); - - let res: Bytes = self.provider().call((*request).clone(), Some(block_id)).await?; - - if let BlockNumber::Number(num) = block { - // cache result - let mut storage = self.storage_write(); - storage.eth_call.insert((request, num), res.clone()); - } + let res = self.provider().call(request).block(block.into()).await?; Ok(res) } @@ -199,29 +185,11 @@ impl ClientFork { /// Sends `eth_call` pub async fn estimate_gas( &self, - request: &TransactionRequest, + request: &WithOtherFields, block: Option, - ) -> Result { - let request = Arc::new(request.clone()); - let block = block.unwrap_or(BlockNumber::Latest); - - if let BlockNumber::Number(num) = block { - // check if this request was already been sent - let key = (request.clone(), num); - if let Some(res) = self.storage_read().eth_gas_estimations.get(&key).cloned() { - return Ok(res); - } - } - - let block_id: BlockId = block.into(); - - let res = self.provider().estimate_gas((*request).clone(), Some(block_id)).await?; - - if let BlockNumber::Number(num) = block { - // cache result - let mut storage = self.storage_write(); - storage.eth_gas_estimations.insert((request, num), res); - } + ) -> Result { + let block = block.unwrap_or_default(); + let res = self.provider().estimate_gas(request).block(block.into()).await?; Ok(res) } @@ -229,10 +197,10 @@ impl ClientFork { /// Sends `eth_createAccessList` pub async fn create_access_list( &self, - request: &TransactionRequest, + request: &WithOtherFields, block: Option, ) -> Result { - self.provider().create_access_list(request.clone(), block.map(|b| b.into())).await + self.provider().create_access_list(request).block_id(block.unwrap_or_default().into()).await } pub async fn storage_at( @@ -241,7 +209,10 @@ impl ClientFork { index: U256, number: Option, ) -> Result { - self.provider().get_storage_at(address, index, number.map(Into::into)).await + self.provider() + .get_storage_at(address, index) + .block_id(number.unwrap_or_default().into()) + .await } pub async fn logs(&self, filter: &Filter) -> Result, TransportError> { @@ -249,7 +220,7 @@ impl ClientFork { return Ok(logs); } - let logs = self.provider().get_logs(filter.clone()).await?; + let logs = self.provider().get_logs(filter).await?; let mut storage = self.storage_write(); storage.logs.insert(filter.clone(), logs.clone()); @@ -266,9 +237,9 @@ impl ClientFork { return Ok(code); } - let block_id = BlockId::Number(blocknumber.into()); + let block_id = BlockId::number(blocknumber); - let code = self.provider().get_code_at(address, Some(block_id)).await?; + 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().0.into()); @@ -282,28 +253,24 @@ impl ClientFork { blocknumber: u64, ) -> Result { trace!(target: "backend::fork", "get_balance={:?}", address); - self.provider().get_balance(address, Some(blocknumber.into())).await + self.provider().get_balance(address).block_id(blocknumber.into()).await } - pub async fn get_nonce( - &self, - address: Address, - blocknumber: u64, - ) -> Result { + pub async fn get_nonce(&self, address: Address, block: u64) -> Result { trace!(target: "backend::fork", "get_nonce={:?}", address); - self.provider().get_transaction_count(address, Some(blocknumber.into())).await + self.provider().get_transaction_count(address).block_id(block.into()).await } pub async fn transaction_by_block_number_and_index( &self, number: u64, index: usize, - ) -> Result, TransportError> { + ) -> Result>, TransportError> { if let Some(block) = self.block_by_number(number).await? { match block.transactions { BlockTransactions::Full(txs) => { if let Some(tx) = txs.get(index) { - return Ok(Some(tx.clone())); + return Ok(Some(WithOtherFields::new(tx.clone()))); } } BlockTransactions::Hashes(hashes) => { @@ -322,12 +289,12 @@ impl ClientFork { &self, hash: B256, index: usize, - ) -> Result, TransportError> { + ) -> Result>, TransportError> { if let Some(block) = self.block_by_hash(hash).await? { match block.transactions { BlockTransactions::Full(txs) => { if let Some(tx) = txs.get(index) { - return Ok(Some(tx.clone())); + return Ok(Some(WithOtherFields::new(tx.clone()))); } } BlockTransactions::Hashes(hashes) => { @@ -345,17 +312,18 @@ impl ClientFork { pub async fn transaction_by_hash( &self, hash: B256, - ) -> Result, TransportError> { + ) -> 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?; - - let mut storage = self.storage_write(); - storage.transactions.insert(hash, tx.clone()); - Ok(Some(tx)) + if let Some(tx) = tx.clone() { + let mut storage = self.storage_write(); + storage.transactions.insert(hash, tx); + } + Ok(tx) } pub async fn trace_transaction(&self, hash: B256) -> Result, TransportError> { @@ -405,12 +373,14 @@ impl ClientFork { pub async fn transaction_receipt( &self, hash: B256, - ) -> Result, TransportError> { + ) -> 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 = + convert_to_anvil_receipt(receipt).ok_or(BlockchainError::FailedToDecodeReceipt)?; let mut storage = self.storage_write(); storage.transaction_receipts.insert(hash, receipt.clone()); return Ok(Some(receipt)); @@ -422,7 +392,7 @@ impl ClientFork { pub async fn block_receipts( &self, number: u64, - ) -> Result>, TransportError> { + ) -> Result>, BlockchainError> { if let receipts @ Some(_) = self.storage_read().block_receipts.get(&number).cloned() { return Ok(receipts); } @@ -432,6 +402,16 @@ impl ClientFork { // this is being temporarily implemented in anvil. if self.predates_fork_inclusive(number) { let receipts = self.provider().get_block_receipts(BlockNumber::Number(number)).await?; + let receipts = receipts + .map(|r| { + r.into_iter() + .map(|r| { + convert_to_anvil_receipt(r) + .ok_or(BlockchainError::FailedToDecodeReceipt) + }) + .collect::, _>>() + }) + .transpose()?; if let Some(receipts) = receipts.clone() { let mut storage = self.storage_write(); @@ -505,16 +485,18 @@ impl ClientFork { &self, block_id: impl Into, ) -> Result, TransportError> { - if let Some(block) = self.provider().get_block(block_id.into(), true).await? { + if let Some(block) = self.provider().get_block(block_id.into(), true.into()).await? { let hash = block.header.hash.unwrap(); - let block_number = block.header.number.unwrap().to::(); + let block_number = block.header.number.unwrap(); let mut storage = self.storage_write(); // also insert all transactions let block_txs = match block.clone().transactions { BlockTransactions::Full(txs) => txs, _ => vec![], }; - storage.transactions.extend(block_txs.iter().map(|tx| (tx.hash, tx.clone()))); + storage + .transactions + .extend(block_txs.iter().map(|tx| (tx.hash, WithOtherFields::new(tx.clone())))); storage.hashes.insert(block_number, hash); storage.blocks.insert(hash, block.clone()); return Ok(Some(block)); @@ -558,14 +540,11 @@ impl ClientFork { 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.to::().into(), U64::from(uncle_idx)) - .await? - { - Some(u) => u, - None => return Ok(None), - }; + 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()); @@ -584,9 +563,10 @@ impl ClientFork { let mut transactions = Vec::with_capacity(block_txs_len); for tx in block.transactions.hashes() { if let Some(tx) = storage.transactions.get(tx).cloned() { - transactions.push(tx); + transactions.push(tx.inner); } } + // TODO: fix once blocks have generic transactions block.into_full_block(transactions) } } @@ -606,7 +586,11 @@ pub struct ClientForkConfig { /// The timestamp for the forked block pub timestamp: u64, /// The basefee of the forked block - pub base_fee: Option, + 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 @@ -619,8 +603,6 @@ pub struct ClientForkConfig { pub total_difficulty: U256, } -// === impl ClientForkConfig === - impl ClientForkConfig { /// Updates the provider URL /// @@ -649,7 +631,7 @@ impl ClientForkConfig { block_number: u64, block_hash: B256, timestamp: u64, - base_fee: Option, + base_fee: Option, total_difficulty: U256, ) { self.block_number = block_number; @@ -662,25 +644,23 @@ impl ClientForkConfig { } /// 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, Default)] pub struct ForkedStorage { pub uncles: HashMap>, pub blocks: HashMap, pub hashes: HashMap, - pub transactions: HashMap, - pub transaction_receipts: HashMap, + pub transactions: HashMap>, + pub transaction_receipts: HashMap, pub transaction_traces: HashMap>, pub logs: HashMap>, pub geth_transaction_traces: HashMap, pub block_traces: HashMap>, - pub block_receipts: HashMap>, - pub eth_gas_estimations: HashMap<(Arc, u64), U256>, - pub eth_call: HashMap<(Arc, u64), Bytes>, + pub block_receipts: HashMap>, pub code_at: HashMap<(Address, u64), Bytes>, } -// === impl ForkedStorage === - impl ForkedStorage { /// Clears all data pub fn clear(&mut self) { diff --git a/crates/anvil/src/eth/backend/genesis.rs b/crates/anvil/src/eth/backend/genesis.rs index 14197b63d..ebe6e6f6e 100644 --- a/crates/anvil/src/eth/backend/genesis.rs +++ b/crates/anvil/src/eth/backend/genesis.rs @@ -1,6 +1,6 @@ //! Genesis settings -use crate::eth::backend::db::{Db, MaybeHashDatabase}; +use crate::eth::backend::db::{Db, MaybeFullDatabase}; use alloy_genesis::{Genesis, GenesisAccount}; use alloy_primitives::{Address, B256, U256}; use foundry_evm::{ @@ -32,8 +32,6 @@ pub struct GenesisConfig { pub genesis_init: Option, } -// === impl GenesisConfig === - impl GenesisConfig { /// Returns fresh `AccountInfo`s for the configured `accounts` pub fn account_infos(&self) -> impl Iterator + '_ { @@ -84,7 +82,7 @@ impl GenesisConfig { /// [AccountInfo] pub(crate) fn state_db_at_genesis<'a>( &self, - db: Box, + db: Box, ) -> AtGenesisStateDb<'a> { AtGenesisStateDb { genesis: self.genesis_init.clone(), @@ -103,7 +101,7 @@ impl GenesisConfig { pub(crate) struct AtGenesisStateDb<'a> { genesis: Option, accounts: HashMap, - db: Box, + db: Box, } impl<'a> DatabaseRef for AtGenesisStateDb<'a> { @@ -138,7 +136,7 @@ impl<'a> DatabaseRef for AtGenesisStateDb<'a> { } } -impl<'a> MaybeHashDatabase for AtGenesisStateDb<'a> { +impl<'a> MaybeFullDatabase for AtGenesisStateDb<'a> { fn clear_into_snapshot(&mut self) -> StateSnapshot { self.db.clear_into_snapshot() } diff --git a/crates/anvil/src/eth/backend/info.rs b/crates/anvil/src/eth/backend/info.rs index 7b4c5d9c7..448dc660a 100644 --- a/crates/anvil/src/eth/backend/info.rs +++ b/crates/anvil/src/eth/backend/info.rs @@ -16,8 +16,6 @@ pub struct StorageInfo { backend: Arc, } -// === impl StorageInfo === - impl StorageInfo { pub(crate) fn new(backend: Arc) -> Self { Self { backend } diff --git a/crates/anvil/src/eth/backend/mem/cache.rs b/crates/anvil/src/eth/backend/mem/cache.rs index d4aff5948..e51aaae7e 100644 --- a/crates/anvil/src/eth/backend/mem/cache.rs +++ b/crates/anvil/src/eth/backend/mem/cache.rs @@ -102,7 +102,7 @@ impl DiskStateCache { impl Default for DiskStateCache { fn default() -> Self { - DiskStateCache { temp_path: anvil_tmp_dir(), temp_dir: None } + Self { temp_path: anvil_tmp_dir(), temp_dir: None } } } diff --git a/crates/anvil/src/eth/backend/mem/fork_db.rs b/crates/anvil/src/eth/backend/mem/fork_db.rs index 354971ce5..ae325f975 100644 --- a/crates/anvil/src/eth/backend/mem/fork_db.rs +++ b/crates/anvil/src/eth/backend/mem/fork_db.rs @@ -1,11 +1,11 @@ use crate::{ eth::backend::db::{ - Db, MaybeForkedDatabase, MaybeHashDatabase, SerializableAccountRecord, SerializableState, - StateDb, + Db, MaybeForkedDatabase, MaybeFullDatabase, SerializableAccountRecord, SerializableBlock, + SerializableState, StateDb, }, revm::primitives::AccountInfo, }; -use alloy_primitives::{Address, B256, U256}; +use alloy_primitives::{Address, B256, U256, U64}; use alloy_rpc_types::BlockId; use foundry_evm::{ backend::{DatabaseResult, RevertSnapshotAction, StateSnapshot}, @@ -32,7 +32,12 @@ impl Db for ForkedDatabase { self.inner().block_hashes().write().insert(number, hash); } - fn dump_state(&self, at: BlockEnv) -> DatabaseResult> { + fn dump_state( + &self, + at: BlockEnv, + best_number: U64, + blocks: Vec, + ) -> DatabaseResult> { let mut db = self.database().clone(); let accounts = self .database() @@ -44,8 +49,7 @@ impl Db for ForkedDatabase { code } else { db.code_by_hash(v.info.code_hash)? - } - .to_checked(); + }; Ok(( k, SerializableAccountRecord { @@ -57,7 +61,12 @@ impl Db for ForkedDatabase { )) }) .collect::>()?; - Ok(Some(SerializableState { block: Some(at), accounts })) + Ok(Some(SerializableState { + block: Some(at), + accounts, + best_block_number: Some(best_number), + blocks, + })) } fn snapshot(&mut self) -> U256 { @@ -73,7 +82,7 @@ impl Db for ForkedDatabase { } } -impl MaybeHashDatabase for ForkedDatabase { +impl MaybeFullDatabase for ForkedDatabase { fn clear_into_snapshot(&mut self) -> StateSnapshot { let db = self.inner().db(); let accounts = std::mem::take(&mut *db.accounts.write()); @@ -96,7 +105,7 @@ impl MaybeHashDatabase for ForkedDatabase { } } -impl MaybeHashDatabase for ForkDbSnapshot { +impl MaybeFullDatabase for ForkDbSnapshot { fn clear_into_snapshot(&mut self) -> StateSnapshot { std::mem::take(&mut self.snapshot) } diff --git a/crates/anvil/src/eth/backend/mem/in_memory_db.rs b/crates/anvil/src/eth/backend/mem/in_memory_db.rs index e1c7deb8e..6269727c1 100644 --- a/crates/anvil/src/eth/backend/mem/in_memory_db.rs +++ b/crates/anvil/src/eth/backend/mem/in_memory_db.rs @@ -2,17 +2,18 @@ use crate::{ eth::backend::db::{ - AsHashDB, Db, MaybeForkedDatabase, MaybeHashDatabase, SerializableAccountRecord, + Db, MaybeForkedDatabase, MaybeFullDatabase, SerializableAccountRecord, SerializableBlock, SerializableState, StateDb, }, - mem::state::{state_merkle_trie_root, storage_trie_db, trie_hash_db}, - revm::primitives::AccountInfo, + mem::state::state_root, + revm::{db::DbAccount, primitives::AccountInfo}, }; -use alloy_primitives::{Address, B256, U256}; +use alloy_primitives::{Address, B256, U256, U64}; use alloy_rpc_types::BlockId; use foundry_evm::{ backend::{DatabaseResult, StateSnapshot}, fork::BlockchainDb, + hashbrown::HashMap, }; // reexport for convenience @@ -32,7 +33,12 @@ impl Db for MemDb { self.inner.block_hashes.insert(number, hash); } - fn dump_state(&self, at: BlockEnv) -> DatabaseResult> { + fn dump_state( + &self, + at: BlockEnv, + best_number: U64, + blocks: Vec, + ) -> DatabaseResult> { let accounts = self .inner .accounts @@ -43,8 +49,7 @@ impl Db for MemDb { code } else { self.inner.code_by_hash_ref(v.info.code_hash)? - } - .to_checked(); + }; Ok(( k, SerializableAccountRecord { @@ -57,7 +62,12 @@ impl Db for MemDb { }) .collect::>()?; - Ok(Some(SerializableState { block: Some(at), accounts })) + Ok(Some(SerializableState { + block: Some(at), + accounts, + best_block_number: Some(best_number), + blocks, + })) } /// Creates a new snapshot @@ -82,25 +92,17 @@ impl Db for MemDb { } fn maybe_state_root(&self) -> Option { - Some(state_merkle_trie_root(&self.inner.accounts)) + Some(state_root(&self.inner.accounts)) } fn current_state(&self) -> StateDb { - StateDb::new(MemDb { inner: self.inner.clone(), ..Default::default() }) + StateDb::new(Self { inner: self.inner.clone(), ..Default::default() }) } } -impl MaybeHashDatabase for MemDb { - fn maybe_as_hash_db(&self) -> Option<(AsHashDB, B256)> { - Some(trie_hash_db(&self.inner.accounts)) - } - - fn maybe_account_db(&self, addr: Address) -> Option<(AsHashDB, B256)> { - if let Some(acc) = self.inner.accounts.get(&addr) { - Some(storage_trie_db(&acc.storage)) - } else { - Some(storage_trie_db(&Default::default())) - } +impl MaybeFullDatabase for MemDb { + fn maybe_as_full_db(&self) -> Option<&HashMap> { + Some(&self.inner.accounts) } fn clear_into_snapshot(&mut self) -> StateSnapshot { @@ -137,7 +139,7 @@ mod tests { use foundry_evm::revm::primitives::{Bytecode, KECCAK_EMPTY}; use std::{collections::BTreeMap, str::FromStr}; - // verifies that all substantial aspects of a loaded account remain the state after an account + // 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() { @@ -146,8 +148,7 @@ mod tests { let mut dump_db = MemDb::default(); - let contract_code = Bytecode::new_raw(Bytes::from("fake contract code")).to_checked(); - + let contract_code = Bytecode::new_raw(Bytes::from("fake contract code")); dump_db.insert_account( test_addr, AccountInfo { @@ -157,10 +158,10 @@ mod tests { nonce: 1234, }, ); - dump_db.set_storage_at(test_addr, U256::from(1234567), U256::from(1)).unwrap(); - let state = dump_db.dump_state(Default::default()).unwrap().unwrap(); + // blocks dumping/loading tested in storage.rs + let state = dump_db.dump_state(Default::default(), U64::ZERO, Vec::new()).unwrap().unwrap(); let mut load_db = MemDb::default(); @@ -183,7 +184,7 @@ mod tests { let test_addr2: Address = Address::from_str("0x70997970c51812dc3a010c7d01b50e0d17dc79c8").unwrap(); - let contract_code = Bytecode::new_raw(Bytes::from("fake contract code")).to_checked(); + let contract_code = Bytecode::new_raw(Bytes::from("fake contract code")); let mut db = MemDb::default(); diff --git a/crates/anvil/src/eth/backend/mem/inspector.rs b/crates/anvil/src/eth/backend/mem/inspector.rs index 6072e4f93..0fe0f26af 100644 --- a/crates/anvil/src/eth/backend/mem/inspector.rs +++ b/crates/anvil/src/eth/backend/mem/inspector.rs @@ -1,18 +1,18 @@ //! Anvil specific [`revm::Inspector`] implementation use crate::{eth::macros::node_info, revm::Database}; -use alloy_primitives::Log; +use alloy_primitives::{Address, Log}; use foundry_evm::{ call_inspectors, decode::decode_console_logs, inspectors::{LogCollector, TracingInspector}, - revm, revm::{ - interpreter::{CallInputs, CreateInputs, Gas, InstructionResult, Interpreter}, - primitives::{Address, Bytes, B256}, - EVMData, + interpreter::{CallInputs, CallOutcome, CreateInputs, CreateOutcome, Interpreter}, + primitives::U256, + EvmContext, }, traces::TracingInspectorConfig, + InspectorExt, }; /// The [`revm::Inspector`] used when transacting in the evm @@ -23,8 +23,6 @@ pub struct Inspector { pub log_collector: LogCollector, } -// === impl Inspector === - impl Inspector { /// Called after the inspecting the evm /// @@ -48,97 +46,96 @@ impl Inspector { impl revm::Inspector for Inspector { #[inline] - fn initialize_interp(&mut self, interp: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { + fn initialize_interp(&mut self, interp: &mut Interpreter, ecx: &mut EvmContext) { call_inspectors!([&mut self.tracer], |inspector| { - inspector.initialize_interp(interp, data); + inspector.initialize_interp(interp, ecx); }); } #[inline] - fn step(&mut self, interp: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { + fn step(&mut self, interp: &mut Interpreter, ecx: &mut EvmContext) { call_inspectors!([&mut self.tracer], |inspector| { - inspector.step(interp, data); + inspector.step(interp, ecx); }); } #[inline] - fn log( - &mut self, - evm_data: &mut EVMData<'_, DB>, - address: &Address, - topics: &[B256], - data: &Bytes, - ) { - call_inspectors!([&mut self.tracer, Some(&mut self.log_collector)], |inspector| { - inspector.log(evm_data, address, topics, data); + fn step_end(&mut self, interp: &mut Interpreter, ecx: &mut EvmContext) { + call_inspectors!([&mut self.tracer], |inspector| { + inspector.step_end(interp, ecx); }); } #[inline] - fn step_end(&mut self, interp: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { - call_inspectors!([&mut self.tracer], |inspector| { - inspector.step_end(interp, data); + fn log(&mut self, ecx: &mut EvmContext, log: &Log) { + call_inspectors!([&mut self.tracer, Some(&mut self.log_collector)], |inspector| { + inspector.log(ecx, log); }); } #[inline] - fn call( - &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CallInputs, - ) -> (InstructionResult, Gas, Bytes) { + fn call(&mut self, ecx: &mut EvmContext, inputs: &mut CallInputs) -> Option { call_inspectors!([&mut self.tracer, Some(&mut self.log_collector)], |inspector| { - inspector.call(data, call); + if let Some(outcome) = inspector.call(ecx, inputs) { + return Some(outcome); + } }); - (InstructionResult::Continue, Gas::new(call.gas_limit), Bytes::new()) + None } #[inline] fn call_end( &mut self, - data: &mut EVMData<'_, DB>, + ecx: &mut EvmContext, inputs: &CallInputs, - remaining_gas: Gas, - ret: InstructionResult, - out: Bytes, - ) -> (InstructionResult, Gas, Bytes) { - call_inspectors!([&mut self.tracer], |inspector| { - inspector.call_end(data, inputs, remaining_gas, ret, out.clone()); - }); - (ret, remaining_gas, out) + outcome: CallOutcome, + ) -> CallOutcome { + if let Some(tracer) = &mut self.tracer { + return tracer.call_end(ecx, inputs, outcome); + } + + outcome } #[inline] fn create( &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CreateInputs, - ) -> (InstructionResult, Option
, Gas, Bytes) { - call_inspectors!([&mut self.tracer], |inspector| { - inspector.create(data, call); - }); - - (InstructionResult::Continue, None, Gas::new(call.gas_limit), Bytes::new()) + ecx: &mut EvmContext, + inputs: &mut CreateInputs, + ) -> Option { + if let Some(tracer) = &mut self.tracer { + if let Some(out) = tracer.create(ecx, inputs) { + return Some(out); + } + } + None } #[inline] fn create_end( &mut self, - data: &mut EVMData<'_, DB>, + ecx: &mut EvmContext, inputs: &CreateInputs, - status: InstructionResult, - address: Option
, - gas: Gas, - retdata: Bytes, - ) -> (InstructionResult, Option
, Gas, Bytes) { - call_inspectors!([&mut self.tracer], |inspector| { - inspector.create_end(data, inputs, status, address, gas, retdata.clone()); - }); - (status, address, gas, retdata) + outcome: CreateOutcome, + ) -> CreateOutcome { + if let Some(tracer) = &mut self.tracer { + return tracer.create_end(ecx, inputs, outcome); + } + + outcome + } + + #[inline] + fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) { + if let Some(tracer) = &mut self.tracer { + revm::Inspector::::selfdestruct(tracer, contract, target, value); + } } } +impl InspectorExt for Inspector {} + /// Prints all the logs #[inline] pub fn print_logs(logs: &[Log]) { diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index c4e2a6b78..bb3de1b09 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -1,94 +1,97 @@ -//! In memory blockchain backend +//! In-memory blockchain backend. + +use self::state::trie_storage; use crate::{ config::PruneStateHistoryConfig, eth::{ backend::{ cheats::CheatsManager, - db::{AsHashDB, Db, MaybeHashDatabase, SerializableState}, + db::{Db, MaybeFullDatabase, SerializableState}, executor::{ExecutedTransactions, TransactionExecutor}, fork::ClientFork, genesis::GenesisConfig, - mem::storage::MinedTransactionReceipt, + mem::{ + state::{storage_root, trie_accounts}, + storage::MinedTransactionReceipt, + }, notifications::{NewBlockNotification, NewBlockNotifications}, time::{utc_from_secs, TimeManager}, validate::TransactionValidator, }, error::{BlockchainError, ErrDetail, InvalidTransactionError}, - fees::{FeeDetails, FeeManager}, + fees::{FeeDetails, FeeManager, MIN_SUGGESTED_PRIORITY_FEE}, macros::node_info, pool::transactions::PoolTransaction, util::get_precompiles_for, }, + inject_precompiles, mem::{ inspector::Inspector, storage::{BlockchainStorage, InMemoryBlockStates, MinedBlockOutcome}, }, - revm::{ - db::DatabaseRef, - primitives::{AccountInfo, U256 as rU256}, - }, - NodeConfig, + revm::{db::DatabaseRef, primitives::AccountInfo}, + NodeConfig, PrecompileFactory, }; use alloy_consensus::{Header, Receipt, ReceiptWithBloom}; -use alloy_network::Sealable; -use alloy_primitives::{keccak256, Address, Bytes, TxHash, B256, B64, U128, U256, U64, U8}; -use alloy_rlp::Decodable; -use alloy_rpc_trace_types::{ +use alloy_eips::eip4844::MAX_BLOBS_PER_BLOCK; +use alloy_primitives::{keccak256, Address, Bytes, TxHash, TxKind, B256, U256, U64}; +use alloy_rpc_types::{ + request::TransactionRequest, serde_helpers::JsonStorageKey, state::StateOverride, AccessList, + Block as AlloyBlock, BlockId, BlockNumberOrTag as BlockNumber, + EIP1186AccountProofResponse as AccountProof, EIP1186StorageProof as StorageProof, Filter, + FilteredParams, Header as AlloyHeader, Log, Transaction, TransactionReceipt, +}; +use alloy_rpc_types_trace::{ geth::{DefaultFrame, GethDebugTracingOptions, GethDefaultTracingOptions, GethTrace}, parity::LocalizedTransactionTrace, }; -use alloy_rpc_types::{ - request::TransactionRequest, state::StateOverride, AccessList, Block as AlloyBlock, BlockId, - BlockNumberOrTag as BlockNumber, EIP1186AccountProofResponse as AccountProof, - EIP1186StorageProof as StorageProof, Filter, FilteredParams, Header as AlloyHeader, Log, - Transaction, TransactionReceipt, -}; +use alloy_serde::WithOtherFields; +use alloy_trie::{proof::ProofRetainer, HashBuilder, Nibbles}; use anvil_core::{ eth::{ block::{Block, BlockInfo}, - proof::BasicAccount, transaction::{ - MaybeImpersonatedTransaction, PendingTransaction, TransactionInfo, TypedReceipt, - TypedTransaction, + DepositReceipt, MaybeImpersonatedTransaction, PendingTransaction, ReceiptResponse, + TransactionInfo, TypedReceipt, TypedTransaction, }, - trie::RefTrieDB, - utils::{alloy_to_revm_access_list, meets_eip155}, + utils::meets_eip155, }, types::{Forking, Index}, }; use anvil_rpc::error::RpcError; use flate2::{read::GzDecoder, write::GzEncoder, Compression}; -use foundry_common::types::ToAlloy; use foundry_evm::{ backend::{DatabaseError, DatabaseResult, RevertSnapshotAction}, constants::DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE, decode::RevertDecoder, - inspectors::AccessListTracer, + inspectors::AccessListInspector, revm::{ - self, db::CacheDB, interpreter::InstructionResult, primitives::{ - BlockEnv, CreateScheme, EVMError, Env, ExecutionResult, InvalidHeader, Output, SpecId, + BlockEnv, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, ExecutionResult, Output, SpecId, TransactTo, TxEnv, KECCAK_EMPTY, }, }, - traces::{TracingInspector, TracingInspectorConfig}, - utils::{eval_to_instruction_result, halt_to_instruction_result}, + utils::new_evm_with_inspector_ref, + InspectorExt, }; use futures::channel::mpsc::{unbounded, UnboundedSender}; -use hash_db::HashDB; use parking_lot::{Mutex, RwLock}; +use revm::{ + db::WrapDatabaseRef, + primitives::{ + calc_blob_gasprice, BlobExcessGasAndPrice, HashMap, OptimismFields, ResultAndState, + }, +}; use std::{ - collections::{BTreeMap, HashMap}, + collections::BTreeMap, io::{Read, Write}, - ops::Deref, sync::Arc, time::Duration, }; use storage::{Blockchain, MinedTransaction}; use tokio::sync::RwLock as AsyncRwLock; -use trie_db::{Recorder, Trie}; pub mod cache; pub mod fork_db; @@ -98,9 +101,9 @@ pub mod state; pub mod storage; // Gas per transaction not creating a contract. -pub const MIN_TRANSACTION_GAS: U256 = U256::from_limbs([21_000, 0, 0, 0]); +pub const MIN_TRANSACTION_GAS: u128 = 21000; // Gas per transaction creating a contract. -pub const MIN_CREATE_GAS: U256 = U256::from_limbs([53_000, 0, 0, 0]); +pub const MIN_CREATE_GAS: u128 = 53000; pub type State = foundry_evm::utils::StateChangeset; @@ -114,8 +117,8 @@ pub enum BlockRequest { impl BlockRequest { pub fn block_number(&self) -> BlockNumber { match *self { - BlockRequest::Pending(_) => BlockNumber::Pending, - BlockRequest::Number(n) => BlockNumber::Number(n), + Self::Pending(_) => BlockNumber::Pending, + Self::Number(n) => BlockNumber::Number(n), } } } @@ -129,7 +132,7 @@ pub struct Backend { /// the evm during its execution. /// /// At time of writing, there are two different types of `Db`: - /// - [`MemDb`](crate::mem::MemDb): everything is stored in memory + /// - [`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 /// @@ -140,14 +143,14 @@ pub struct Backend { /// potentially blocks for some time, even taking into account the rate limits of RPC /// endpoints. Therefor 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()`] request is being executed. + /// currently mined and a new [`Self::set_storage_at()`] request is being executed. db: Arc>>, /// stores all block related data in memory blockchain: Blockchain, /// Historic states of previous blocks states: Arc>, /// env data of the chain - env: Arc>, + env: Arc>, /// this is set if this is currently forked off another client fork: Arc>>, /// provides time related info, like timestamp @@ -168,6 +171,10 @@ pub struct Backend { /// 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>, } impl Backend { @@ -175,7 +182,7 @@ impl Backend { #[allow(clippy::too_many_arguments)] pub async fn with_genesis( db: Arc>>, - env: Arc>, + env: Arc>, genesis: GenesisConfig, fees: FeeManager, fork: Arc>>, @@ -214,6 +221,11 @@ impl Backend { Default::default() }; + let (slots_in_an_epoch, precompile_factory) = { + let cfg = node_config.read().await; + (cfg.slots_in_an_epoch, cfg.precompile_factory.clone()) + }; + let backend = Self { db, blockchain, @@ -230,6 +242,8 @@ impl Backend { prune_state_history_config, transaction_block_keeper, node_config, + slots_in_an_epoch, + precompile_factory, }; if let Some(interval_block_time) = automine_block_time { @@ -297,6 +311,10 @@ impl Backend { 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()); } let db = self.db.write().await; @@ -352,7 +370,7 @@ impl Backend { } pub fn precompiles(&self) -> Vec
{ - get_precompiles_for(self.env.read().cfg.spec_id) + get_precompiles_for(self.env.read().handler_cfg.spec_id) } /// Resets the fork to a fresh state @@ -401,9 +419,9 @@ impl Backend { env.cfg.chain_id = fork.chain_id(); env.block = BlockEnv { - number: rU256::from(fork_block_number), - timestamp: fork_block.header.timestamp, - gas_limit: fork_block.header.gas_limit, + number: U256::from(fork_block_number), + timestamp: U256::from(fork_block.header.timestamp), + gas_limit: U256::from(fork_block.header.gas_limit), difficulty: fork_block.header.difficulty, prevrandao: Some(fork_block.header.mix_hash.unwrap_or_default()), // Keep previous `coinbase` and `basefee` value @@ -422,7 +440,7 @@ impl Backend { fork_block.header.base_fee_per_gas.unwrap_or_default(), ); - self.fees.set_base_fee(U256::from(next_block_base_fee)); + self.fees.set_base_fee(next_block_base_fee); // also reset the total difficulty self.blockchain.storage.write().total_difficulty = fork.total_difficulty(); @@ -475,7 +493,7 @@ impl Backend { } /// The env data of the blockchain - pub fn env(&self) -> &Arc> { + pub fn env(&self) -> &Arc> { &self.env } @@ -486,7 +504,7 @@ impl Backend { /// Returns the current best number of the chain pub fn best_number(&self) -> u64 { - self.env.read().block.number.try_into().unwrap_or(u64::MAX) + self.blockchain.storage.read().best_number.try_into().unwrap_or(u64::MAX) } /// Sets the block number @@ -515,8 +533,8 @@ impl Backend { } /// Returns balance of the given account. - pub async fn current_nonce(&self, address: Address) -> DatabaseResult { - Ok(U256::from(self.get_account(address).await?.nonce)) + pub async fn current_nonce(&self, address: Address) -> DatabaseResult { + Ok(self.get_account(address).await?.nonce) } /// Sets the coinbase address @@ -551,7 +569,7 @@ impl Backend { /// Returns the configured specid pub fn spec_id(&self) -> SpecId { - self.env.read().cfg.spec_id + self.env.read().handler_cfg.spec_id } /// Returns true for post London @@ -569,9 +587,14 @@ impl Backend { (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 if op-stack deposits are active pub fn is_optimism(&self) -> bool { - self.env.read().cfg.optimism + self.env.read().handler_cfg.is_optimism } /// Returns an error if EIP1559 is not active (pre Berlin) @@ -590,6 +613,13 @@ impl Backend { Err(BlockchainError::EIP2930TransactionUnsupportedAtHardfork) } + pub fn ensure_eip4844_active(&self) -> Result<(), BlockchainError> { + if self.is_eip4844() { + return Ok(()); + } + Err(BlockchainError::EIP4844TransactionUnsupportedAtHardfork) + } + /// Returns an error if op-stack deposits are not active pub fn ensure_op_deposits_active(&self) -> Result<(), BlockchainError> { if self.is_optimism() { @@ -599,37 +629,31 @@ impl Backend { } /// Returns the block gas limit - pub fn gas_limit(&self) -> U256 { - self.env.read().block.gas_limit + pub fn gas_limit(&self) -> u128 { + self.env.read().block.gas_limit.to() } /// Sets the block gas limit - pub fn set_gas_limit(&self, gas_limit: U256) { - self.env.write().block.gas_limit = gas_limit; + pub fn set_gas_limit(&self, gas_limit: u128) { + self.env.write().block.gas_limit = U256::from(gas_limit); } /// Returns the current base fee - pub fn base_fee(&self) -> U256 { + pub fn base_fee(&self) -> u128 { self.fees.base_fee() } - /// Sets the current basefee - pub fn set_base_fee(&self, basefee: U256) { - self.fees.set_base_fee(basefee) - } - - /// Returns the current gas price - pub fn gas_price(&self) -> U256 { - self.fees.gas_price() + pub fn excess_blob_gas_and_price(&self) -> Option { + self.fees.excess_blob_gas_and_price() } - /// Returns the suggested fee cap - pub fn max_priority_fee_per_gas(&self) -> U256 { - self.fees.max_priority_fee_per_gas() + /// Sets the current basefee + pub fn set_base_fee(&self, basefee: u128) { + self.fees.set_base_fee(basefee) } /// Sets the gas price - pub fn set_gas_price(&self, price: U256) { + pub fn set_gas_price(&self, price: u128) { self.fees.set_gas_price(price) } @@ -685,17 +709,17 @@ impl Backend { let block = self.block_by_hash(best_block_hash).await?.ok_or(BlockchainError::BlockNotFound)?; - let reset_time = block.header.timestamp.to::(); + let reset_time = block.header.timestamp; self.time.reset(reset_time); let mut env = self.env.write(); env.block = BlockEnv { - number: rU256::from(num), - timestamp: block.header.timestamp, + 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, + gas_limit: U256::from(block.header.gas_limit), // Keep previous `coinbase` and `basefee` value coinbase: env.block.coinbase, basefee: env.block.basefee, @@ -712,7 +736,9 @@ impl Backend { /// Get the current state. pub async fn serialized_state(&self) -> Result { let at = self.env.read().block.clone(); - let state = self.db.read().await.dump_state(at)?; + let best_number = self.blockchain.storage.read().best_number; + let blocks = self.blockchain.storage.read().serialized_blocks(); + let state = self.db.read().await.dump_state(at, best_number, blocks)?; state.ok_or_else(|| { RpcError::invalid_params("Dumping state not supported with the current configuration") .into() @@ -733,17 +759,24 @@ impl Backend { pub async fn load_state(&self, state: SerializableState) -> Result { // reset the block env if let Some(block) = state.block.clone() { - self.env.write().block = block; + self.env.write().block = block.clone(); + + // Set the current best block number. + // Defaults to block number for compatibility with existing state files. + self.blockchain.storage.write().best_number = + state.best_block_number.unwrap_or(block.number.to::()); } - if !self.db.write().await.load_state(state)? { - Err(RpcError::invalid_params( + if !self.db.write().await.load_state(state.clone())? { + return Err(RpcError::invalid_params( "Loading state not supported with the current configuration", ) - .into()) - } else { - Ok(true) + .into()); } + + self.blockchain.storage.write().load_blocks(state.blocks.clone()); + + Ok(true) } /// Deserialize and add all chain data to the backend storage @@ -766,15 +799,33 @@ impl Backend { } /// Returns the environment for the next block - fn next_env(&self) -> Env { + fn next_env(&self) -> EnvWithHandlerCfg { let mut env = self.env.read().clone(); // increase block number for this block - env.block.number = env.block.number.saturating_add(rU256::from(1)); - env.block.basefee = self.base_fee(); - env.block.timestamp = rU256::from(self.time.current_call_timestamp()); + env.block.number = env.block.number.saturating_add(U256::from(1)); + env.block.basefee = U256::from(self.base_fee()); + env.block.timestamp = U256::from(self.time.current_call_timestamp()); env } + /// Creates an EVM instance with optionally injected precompiles. + fn new_evm_with_inspector_ref( + &self, + db: DB, + env: EnvWithHandlerCfg, + inspector: I, + ) -> revm::Evm<'_, I, WrapDatabaseRef> + where + DB: revm::DatabaseRef, + I: InspectorExt>, + { + let mut evm = new_evm_with_inspector_ref(db, env, inspector); + if let Some(factory) = &self.precompile_factory { + inject_precompiles(&mut evm, factory.precompiles()); + } + evm + } + /// executes the transactions without writing to the underlying database pub async fn inspect_tx( &self, @@ -785,29 +836,27 @@ impl Backend { > { let mut env = self.next_env(); env.tx = tx.pending_transaction.to_revm_tx_env(); + + if env.handler_cfg.is_optimism { + env.tx.optimism.enveloped_tx = + Some(alloy_rlp::encode(&tx.pending_transaction.transaction.transaction).into()); + } + let db = self.db.read().await; let mut inspector = Inspector::default(); - - let mut evm = revm::EVM::new(); - evm.env = env; - evm.database(&*db); - let result_and_state = match evm.inspect_ref(&mut inspector) { - Ok(res) => res, - Err(e) => return Err(e.into()), - }; - let state = result_and_state.state; - let (exit_reason, gas_used, out, logs) = match result_and_state.result { + let mut evm = self.new_evm_with_inspector_ref(&**db, env, &mut inspector); + let ResultAndState { result, state } = evm.transact()?; + let (exit_reason, gas_used, out, logs) = match result { ExecutionResult::Success { reason, gas_used, logs, output, .. } => { - (eval_to_instruction_result(reason), gas_used, Some(output), Some(logs)) + (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 } => { - (halt_to_instruction_result(reason), gas_used, None, None) - } + ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None, None), }; + drop(evm); inspector.print_logs(); Ok((exit_reason, out, gas_used, state, logs.unwrap_or_default())) @@ -829,7 +878,7 @@ impl Backend { f: F, ) -> T where - F: FnOnce(Box, BlockInfo) -> T, + F: FnOnce(Box, BlockInfo) -> T, { let db = self.db.read().await; let env = self.next_env(); @@ -838,15 +887,18 @@ impl Backend { let storage = self.blockchain.storage.read(); + let cfg_env = CfgEnvWithHandlerCfg::new(env.cfg.clone(), env.handler_cfg); let executor = TransactionExecutor { db: &mut cache_db, validator: self, pending: pool_transactions.into_iter(), block_env: env.block.clone(), - cfg_env: env.cfg, + cfg_env, parent_hash: storage.best_hash, - gas_used: U256::ZERO, + gas_used: 0, + blob_gas_used: 0, enable_steps_tracing: self.enable_steps_tracing, + precompile_factory: self.precompile_factory.clone(), }; // create a new pending block @@ -873,6 +925,7 @@ impl Backend { 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(); @@ -883,9 +936,13 @@ impl Backend { } // increase block number for this block - env.block.number = env.block.number.saturating_add(rU256::from(1)); - env.block.basefee = current_base_fee; - env.block.timestamp = rU256::from(self.time.next_timestamp()); + env.block.number = env.block.number.saturating_add(U256::from(1)); + env.block.basefee = U256::from(current_base_fee); + env.block.blob_excess_gas_and_price = current_excess_blob_gas_and_price; + env.block.timestamp = U256::from(self.time.next_timestamp()); + + // pick a random value for prevrandao + env.block.prevrandao = Some(B256::random()); let best_hash = self.blockchain.storage.read().best_hash; @@ -902,15 +959,17 @@ impl Backend { validator: self, pending: pool_transactions.into_iter(), block_env: env.block.clone(), - cfg_env: env.cfg.clone(), + cfg_env: CfgEnvWithHandlerCfg::new(env.cfg.clone(), env.handler_cfg), parent_hash: best_hash, - gas_used: U256::ZERO, + gas_used: 0, + blob_gas_used: 0, enable_steps_tracing: self.enable_steps_tracing, + precompile_factory: self.precompile_factory.clone(), }; let executed_tx = executor.execute(); // we also need to update the new blockhash in the db itself - let block_hash = executed_tx.block.block.header.hash(); + let block_hash = executed_tx.block.block.header.hash_slow(); db.insert_block_hash(U256::from(executed_tx.block.block.header.number), block_hash); (executed_tx, block_hash) @@ -920,8 +979,9 @@ impl Backend { let ExecutedTransactions { block, included, invalid } = executed_tx; let BlockInfo { block, transactions, receipts } = block; + let mut storage = self.blockchain.storage.write(); let header = block.header.clone(); - let block_number: U64 = env.block.number.to::(); + let block_number = storage.best_number.saturating_add(U64::from(1)); trace!( target: "backend", @@ -931,7 +991,6 @@ impl Backend { 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; @@ -953,7 +1012,7 @@ impl Backend { if let Some(contract) = &info.contract_address { node_info!(" Contract created: {contract:?}"); } - node_info!(" Gas used: {}", receipt.gas_used()); + 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(), @@ -983,7 +1042,7 @@ impl Backend { } // we intentionally set the difficulty to `0` for newer blocks - env.block.difficulty = rU256::from(0); + env.block.difficulty = U256::from(0); // update env with new values *self.env.write() = env; @@ -999,17 +1058,23 @@ impl Backend { (outcome, header, block_hash) }; let next_block_base_fee = self.fees.get_next_block_base_fee_per_gas( - U256::from(header.gas_used), - U256::from(header.gas_limit), - U256::from(header.base_fee_per_gas.unwrap_or_default()), + 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)); // notify all listeners self.notify_on_new_block(header, block_hash); - // update next base fee - self.fees.set_base_fee(U256::from(next_block_base_fee)); - outcome } @@ -1020,11 +1085,11 @@ impl Backend { /// Returns an error if the `block_number` is greater than the current height pub async fn call( &self, - request: TransactionRequest, + request: WithOtherFields, fee_details: FeeDetails, block_request: Option, overrides: Option, - ) -> Result<(InstructionResult, Option, u64, State), BlockchainError> { + ) -> Result<(InstructionResult, Option, u128, State), BlockchainError> { self.with_database_at(block_request, |state, block| { let block_number = block.number.to::(); let (exit, out, gas, state) = match overrides { @@ -1041,15 +1106,37 @@ impl Backend { fn build_call_env( &self, - request: TransactionRequest, + request: WithOtherFields, fee_details: FeeDetails, block_env: BlockEnv, - ) -> Env { - let TransactionRequest { from, to, gas, value, input, nonce, access_list, .. } = request; + ) -> EnvWithHandlerCfg { + let WithOtherFields:: { + inner: + TransactionRequest { + from, + to, + gas, + value, + input, + nonce, + access_list, + blob_versioned_hashes, + sidecar: _, + chain_id: _, + transaction_type: _, + .. // Rest of the gas fees related fields are taken from `fee_details` + }, + .. + } = request; - let FeeDetails { gas_price, max_fee_per_gas, max_priority_fee_per_gas } = fee_details; + 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 gas_limit = gas.unwrap_or(block_env.gas_limit.to()); let mut env = self.env.read().clone(); env.block = block_env; // we want to disable this in eth_call, since this is common practice used by other node @@ -1057,27 +1144,31 @@ impl Backend { env.cfg.disable_block_gas_limit = true; if let Some(base) = max_fee_per_gas { - env.block.basefee = base; + env.block.basefee = U256::from(base); } - let gas_price = gas_price.or(max_fee_per_gas).unwrap_or_else(|| self.gas_price()); + 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); env.tx = TxEnv { caller, - gas_limit: gas_limit.to::(), - gas_price, - gas_priority_fee: max_priority_fee_per_gas, + gas_limit: gas_limit as u64, + gas_price: U256::from(gas_price), + gas_priority_fee: max_priority_fee_per_gas.map(U256::from), + max_fee_per_blob_gas: max_fee_per_blob_gas.map(U256::from), transact_to: match to { - Some(addr) => TransactTo::Call(addr), - None => TransactTo::Create(CreateScheme::Create), + Some(addr) => TransactTo::Call(*addr), + None => TransactTo::Create, }, value: value.unwrap_or_default(), data: input.into_input().unwrap_or_default(), chain_id: None, - nonce: nonce.map(|n| n.to::()), - access_list: alloy_to_revm_access_list(access_list.unwrap_or_default().0), - ..Default::default() + nonce, + access_list: access_list.unwrap_or_default().flattened(), + blob_hashes: blob_versioned_hashes.unwrap_or_default(), + optimism: OptimismFields { enveloped_tx: Some(Bytes::new()), ..Default::default() }, }; if env.block.basefee == revm::primitives::U256::ZERO { @@ -1092,53 +1183,35 @@ impl Backend { pub fn call_with_state( &self, state: D, - request: TransactionRequest, + request: WithOtherFields, fee_details: FeeDetails, block_env: BlockEnv, - ) -> Result<(InstructionResult, Option, u64, State), BlockchainError> + ) -> Result<(InstructionResult, Option, u128, State), BlockchainError> where D: DatabaseRef, { let mut inspector = Inspector::default(); - let mut evm = revm::EVM::new(); - evm.env = self.build_call_env(request, fee_details, block_env); - evm.database(state); - let result_and_state = match evm.inspect_ref(&mut inspector) { - Ok(result_and_state) => result_and_state, - Err(e) => match e { - EVMError::Transaction(invalid_tx) => { - return Err(BlockchainError::InvalidTransaction(invalid_tx.into())) - } - EVMError::Database(e) => return Err(BlockchainError::DatabaseError(e)), - EVMError::Header(e) => match e { - InvalidHeader::ExcessBlobGasNotSet => { - return Err(BlockchainError::ExcessBlobGasNotSet) - } - InvalidHeader::PrevrandaoNotSet => { - return Err(BlockchainError::PrevrandaoNotSet) - } - }, - }, - }; - let state = result_and_state.state; - let (exit_reason, gas_used, out) = match result_and_state.result { + + let env = self.build_call_env(request, fee_details, block_env); + let mut evm = self.new_evm_with_inspector_ref(state, env, &mut inspector); + let ResultAndState { result, state } = evm.transact()?; + let (exit_reason, gas_used, out) = match result { ExecutionResult::Success { reason, gas_used, output, .. } => { - (eval_to_instruction_result(reason), gas_used, Some(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 } => { - (halt_to_instruction_result(reason), gas_used, None) - } + ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None), }; + drop(evm); inspector.print_logs(); - Ok((exit_reason, out, gas_used, state)) + Ok((exit_reason, out, gas_used as u128, state)) } pub async fn call_with_tracing( &self, - request: TransactionRequest, + request: WithOtherFields, fee_details: FeeDetails, block_request: Option, opts: GethDefaultTracingOptions, @@ -1146,30 +1219,26 @@ impl Backend { self.with_database_at(block_request, |state, block| { let mut inspector = Inspector::default().with_steps_tracing(); let block_number = block.number; - let mut evm = revm::EVM::new(); - evm.env = self.build_call_env(request, fee_details, block); - evm.database(state); - let result_and_state = - match evm.inspect_ref(&mut inspector) { - Ok(result_and_state) => result_and_state, - Err(e) => return Err(e.into()), - }; - let (exit_reason, gas_used, out, ) = match result_and_state.result { + + let env = self.build_call_env(request, fee_details, block); + let mut evm = self.new_evm_with_inspector_ref(state, env, &mut inspector); + let ResultAndState { result, state: _ } = evm.transact()?; + + let (exit_reason, gas_used, out) = match result { ExecutionResult::Success { reason, gas_used, output, .. } => { - (eval_to_instruction_result(reason), gas_used, Some(output), ) - }, - ExecutionResult::Revert { 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 } => { - (halt_to_instruction_result(reason), gas_used, None) - }, + } + ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None), }; - let res = inspector.tracer.unwrap_or(TracingInspector::new(TracingInspectorConfig::all())).into_geth_builder().geth_traces(gas_used, match &out { - Some(out) => out.data().clone(), - None => Bytes::new() - }, opts); - trace!(target: "backend", "trace call return {:?} out: {:?} gas {} on block {}", exit_reason, out, gas_used, block_number); + + drop(evm); + let tracer = inspector.tracer.expect("tracer disappeared"); + let return_value = out.as_ref().map(|o| o.data().clone()).unwrap_or_default(); + let res = tracer.into_geth_builder().geth_traces(gas_used, return_value, opts); + trace!(target: "backend", ?exit_reason, ?out, %gas_used, %block_number, "trace call"); Ok(res) }) .await? @@ -1178,7 +1247,7 @@ impl Backend { pub fn build_access_list_with_state( &self, state: D, - request: TransactionRequest, + request: WithOtherFields, fee_details: FeeDetails, block_env: BlockEnv, ) -> Result<(InstructionResult, Option, u64, AccessList), BlockchainError> @@ -1186,40 +1255,34 @@ impl Backend { D: DatabaseRef, { let from = request.from.unwrap_or_default(); - let to = if let Some(to) = request.to { + let to = if let Some(TxKind::Call(to)) = request.to { to } else { let nonce = state.basic_ref(from)?.unwrap_or_default().nonce; from.create(nonce) }; - let mut tracer = AccessListTracer::new( + let mut inspector = AccessListInspector::new( request.access_list.clone().unwrap_or_default(), from, to, self.precompiles(), ); - let mut evm = revm::EVM::new(); - evm.env = self.build_call_env(request, fee_details, block_env); - evm.database(state); - let result_and_state = match evm.inspect_ref(&mut tracer) { - Ok(result_and_state) => result_and_state, - Err(e) => return Err(e.into()), - }; - println!("BUILD-LIST"); - let (exit_reason, gas_used, out) = match result_and_state.result { + let env = self.build_call_env(request, fee_details, block_env); + let mut evm = self.new_evm_with_inspector_ref(state, env, &mut inspector); + let ResultAndState { result, state: _ } = evm.transact()?; + let (exit_reason, gas_used, out) = match result { ExecutionResult::Success { reason, gas_used, output, .. } => { - (eval_to_instruction_result(reason), gas_used, Some(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 } => { - (halt_to_instruction_result(reason), gas_used, None) - } + ExecutionResult::Halt { reason, gas_used } => (reason.into(), gas_used, None), }; - let access_list = tracer.access_list(); + drop(evm); + let access_list = inspector.access_list(); Ok((exit_reason, out, gas_used, access_list)) } @@ -1258,53 +1321,43 @@ impl Backend { fn mined_logs_for_block(&self, filter: Filter, block: Block) -> Vec { let params = FilteredParams::new(Some(filter.clone())); let mut all_logs = Vec::new(); - let block_hash = block.header.hash(); + let block_hash = block.header.hash_slow(); let mut block_log_index = 0u32; - let transactions: Vec<_> = { - let storage = self.blockchain.storage.read(); - block - .transactions - .iter() - .filter_map(|tx| storage.transactions.get(&tx.hash()).map(|tx| tx.info.clone())) - .collect() - }; + let storage = self.blockchain.storage.read(); - for transaction in transactions { - let logs = transaction.logs.clone(); - let transaction_hash = transaction.transaction_hash; - - for log in logs.into_iter() { - let mut log = Log { - address: log.address, - topics: log.topics().to_vec(), - data: log.data.data, - block_hash: None, - block_number: None, - transaction_hash: None, - transaction_index: None, - log_index: None, - removed: false, - }; + for tx in block.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 { let mut is_match: bool = true; if !filter.address.is_empty() && filter.has_topics() { - if !params.filter_address(&log) || !params.filter_topics(&log) { + if !params.filter_address(&log.address) || !params.filter_topics(log.topics()) { is_match = false; } } else if !filter.address.is_empty() { - if !params.filter_address(&log) { + if !params.filter_address(&log.address) { is_match = false; } - } else if filter.has_topics() && !params.filter_topics(&log) { + } else if filter.has_topics() && !params.filter_topics(log.topics()) { is_match = false; } if is_match { - log.block_hash = Some(block_hash); - log.block_number = Some(block.header.number.to_alloy()); - log.transaction_hash = Some(transaction_hash); - log.transaction_index = Some(U256::from(transaction.transaction_index)); - log.log_index = Some(U256::from(block_log_index)); + let log = 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, + }; all_logs.push(log); } block_log_index += 1; @@ -1408,7 +1461,7 @@ impl Backend { pub(crate) async fn mined_transactions_by_block_number( &self, number: BlockNumber, - ) -> Option> { + ) -> Option>> { if let Some(block) = self.get_block(number) { return self.mined_transactions_in_block(&block); } @@ -1416,7 +1469,10 @@ impl Backend { } /// Returns all transactions given a block - pub(crate) fn mined_transactions_in_block(&self, block: &Block) -> Option> { + pub(crate) fn mined_transactions_in_block( + &self, + block: &Block, + ) -> Option>> { let mut transactions = Vec::with_capacity(block.transactions.len()); let base_fee = block.header.base_fee_per_gas; let storage = self.blockchain.storage.read(); @@ -1424,13 +1480,7 @@ impl Backend { let info = storage.transactions.get(&hash)?.info.clone(); let tx = block.transactions.get(info.transaction_index as usize)?.clone(); - let tx = transaction_build( - Some(hash), - tx, - Some(block), - Some(info), - base_fee.map(|f| f.to_alloy()), - ); + let tx = transaction_build(Some(hash), tx, Some(block), Some(info), base_fee); transactions.push(tx); } Some(transactions) @@ -1479,7 +1529,7 @@ impl Backend { BlockId::Hash(hash) => hash.block_hash, BlockId::Number(number) => { let storage = self.blockchain.storage.read(); - let slots_in_an_epoch = U64::from(32u64); + let slots_in_an_epoch = U64::from(self.slots_in_an_epoch); match number { BlockNumber::Latest => storage.best_hash, BlockNumber::Earliest => storage.genesis_hash, @@ -1522,7 +1572,7 @@ impl Backend { let block = self.get_block(id)?; let transactions = self.mined_transactions_in_block(&block)?; let block = self.convert_block(block); - Some(block.into_full_block(transactions)) + Some(block.into_full_block(transactions.into_iter().map(|t| t.inner).collect())) } /// Takes a block as it's stored internally and returns the eth api conform block format @@ -1531,7 +1581,7 @@ impl Backend { let Block { header, transactions, .. } = block; - let hash = header.hash(); + let hash = header.hash_slow(); let Header { parent_hash, ommers_hash, @@ -1545,14 +1595,15 @@ impl Backend { gas_limit, gas_used, timestamp, + requests_root, extra_data, mix_hash, nonce, base_fee_per_gas, withdrawals_root: _, - blob_gas_used: _, - excess_blob_gas: _, - parent_beacon_block_root: _, + blob_gas_used, + excess_blob_gas, + parent_beacon_block_root, } = header; AlloyBlock { @@ -1564,21 +1615,22 @@ impl Backend { state_root, transactions_root, receipts_root, - number: Some(number.to_alloy()), - gas_used: gas_used.to_alloy(), - gas_limit: gas_limit.to_alloy(), + number: Some(number), + gas_used, + gas_limit, extra_data: extra_data.0.into(), logs_bloom, - timestamp: U256::from(timestamp), + timestamp, total_difficulty: Some(self.total_difficulty()), difficulty, mix_hash: Some(mix_hash), - nonce: Some(B64::from(nonce)), - base_fee_per_gas: base_fee_per_gas.map(|f| f.to_alloy()), + nonce: Some(nonce), + base_fee_per_gas, withdrawals_root: None, - blob_gas_used: None, - excess_blob_gas: None, - parent_beacon_block_root: None, + blob_gas_used, + excess_blob_gas, + parent_beacon_block_root, + requests_root, }, size: Some(size), transactions: alloy_rpc_types::BlockTransactions::Hashes( @@ -1600,7 +1652,6 @@ impl Backend { block_id: Option, ) -> Result { let current = self.best_number(); - let slots_in_an_epoch = 32u64; let requested = match block_id.map(Into::into).unwrap_or(BlockId::Number(BlockNumber::Latest)) { BlockId::Hash(hash) => self @@ -1609,18 +1660,13 @@ impl Backend { .ok_or(BlockchainError::BlockNotFound)? .header .number - .ok_or(BlockchainError::BlockNotFound)? - .to::(), + .ok_or(BlockchainError::BlockNotFound)?, BlockId::Number(num) => match num { BlockNumber::Latest | BlockNumber::Pending => self.best_number(), BlockNumber::Earliest => U64::ZERO.to::(), BlockNumber::Number(num) => num, - BlockNumber::Safe => { - U64::from(current).saturating_sub(U64::from(slots_in_an_epoch)).to::() - } - BlockNumber::Finalized => U64::from(current) - .saturating_sub(U64::from(slots_in_an_epoch) * U64::from(2)) - .to::(), + BlockNumber::Safe => current.saturating_sub(self.slots_in_an_epoch), + BlockNumber::Finalized => current.saturating_sub(self.slots_in_an_epoch * 2), }, }; @@ -1633,13 +1679,12 @@ impl Backend { pub fn convert_block_number(&self, block: Option) -> u64 { let current = self.best_number(); - let slots_in_an_epoch = 32u64; match block.unwrap_or(BlockNumber::Latest) { BlockNumber::Latest | BlockNumber::Pending => current, BlockNumber::Earliest => 0, BlockNumber::Number(num) => num, - BlockNumber::Safe => current.saturating_sub(slots_in_an_epoch), - BlockNumber::Finalized => current.saturating_sub(slots_in_an_epoch * 2), + BlockNumber::Safe => current.saturating_sub(self.slots_in_an_epoch), + BlockNumber::Finalized => current.saturating_sub(self.slots_in_an_epoch * 2), } } @@ -1650,7 +1695,7 @@ impl Backend { f: F, ) -> Result where - F: FnOnce(Box, BlockEnv) -> T, + F: FnOnce(Box, BlockEnv) -> T, { let block_number = match block_request { Some(BlockRequest::Pending(pool_transactions)) => { @@ -1658,13 +1703,13 @@ impl Backend { .with_pending_block(pool_transactions, |state, block| { let block = block.block; let block = BlockEnv { - number: block.header.number.to_alloy(), + number: U256::from(block.header.number), coinbase: block.header.beneficiary, - timestamp: rU256::from(block.header.timestamp), + timestamp: U256::from(block.header.timestamp), difficulty: block.header.difficulty, prevrandao: Some(block.header.mix_hash), - basefee: block.header.base_fee_per_gas.unwrap_or_default().to_alloy(), - gas_limit: block.header.gas_limit.to_alloy(), + basefee: U256::from(block.header.base_fee_per_gas.unwrap_or_default()), + gas_limit: U256::from(block.header.gas_limit), ..Default::default() }; f(state, block) @@ -1683,16 +1728,16 @@ impl Backend { if let Some((state, block)) = self .get_block(block_number.to::()) - .and_then(|block| Some((states.get(&block.header.hash())?, block))) + .and_then(|block| Some((states.get(&block.header.hash_slow())?, block))) { let block = BlockEnv { - number: block.header.number.to_alloy(), + number: U256::from(block.header.number), coinbase: block.header.beneficiary, - timestamp: rU256::from(block.header.timestamp), + timestamp: U256::from(block.header.timestamp), difficulty: block.header.difficulty, prevrandao: Some(block.header.mix_hash), - basefee: block.header.base_fee_per_gas.unwrap_or_default().to_alloy(), - gas_limit: block.header.gas_limit.to_alloy(), + basefee: U256::from(block.header.base_fee_per_gas.unwrap_or_default()), + gas_limit: U256::from(block.header.gas_limit), ..Default::default() }; return Ok(f(Box::new(state), block)); @@ -1711,8 +1756,8 @@ impl Backend { let gen_db = self.genesis.state_db_at_genesis(Box::new(&*db)); block.number = block_number; - block.timestamp = rU256::from(fork.timestamp()); - block.basefee = fork.base_fee().unwrap_or_default(); + block.timestamp = U256::from(fork.timestamp()); + block.basefee = U256::from(fork.base_fee().unwrap_or_default()); return Ok(f(Box::new(&gen_db), block)); } @@ -1808,21 +1853,21 @@ impl Backend { pub async fn get_nonce( &self, address: Address, - block_request: Option, - ) -> Result { - if let Some(BlockRequest::Pending(pool_transactions)) = block_request.as_ref() { + block_request: BlockRequest, + ) -> Result { + if let BlockRequest::Pending(pool_transactions) = &block_request { if let Some(value) = get_pool_transactions_nonce(pool_transactions, address) { return Ok(value); } } let final_block_request = match block_request { - Some(BlockRequest::Pending(_)) => Some(BlockRequest::Number(self.best_number())), - Some(BlockRequest::Number(bn)) => Some(BlockRequest::Number(bn)), - None => None, + BlockRequest::Pending(_) => BlockRequest::Number(self.best_number()), + BlockRequest::Number(bn) => BlockRequest::Number(bn), }; - self.with_database_at(final_block_request, |db, _| { + + self.with_database_at(Some(final_block_request), |db, _| { trace!(target: "backend", "get nonce for {:?}", address); - Ok(U256::from(db.basic_ref(address)?.unwrap_or_default().nonce)) + Ok(db.basic_ref(address)?.unwrap_or_default().nonce) }) .await? } @@ -1917,7 +1962,7 @@ impl Backend { pub async fn transaction_receipt( &self, hash: B256, - ) -> Result, BlockchainError> { + ) -> Result, BlockchainError> { if let Some(receipt) = self.mined_transaction_receipt(hash) { return Ok(Some(receipt.inner)); } @@ -1925,10 +1970,7 @@ impl Backend { 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(|n| BlockNumber::from(n.to::())), + receipt.clone().and_then(|r| r.block_number).map(BlockNumber::from), ); if fork.predates_fork_inclusive(number) { @@ -1952,7 +1994,7 @@ impl Backend { } /// Returns all transaction receipts of the block - pub fn mined_block_receipts(&self, id: impl Into) -> Option> { + pub fn mined_block_receipts(&self, id: impl Into) -> Option> { let mut receipts = Vec::new(); let block = self.get_block(id)?; @@ -1966,97 +2008,88 @@ impl Backend { /// Returns the transaction receipt for the given hash pub(crate) fn mined_transaction_receipt(&self, hash: B256) -> Option { - let MinedTransaction { info, receipt, block_hash, .. } = + let MinedTransaction { info, receipt: tx_receipt, block_hash, .. } = self.blockchain.get_transaction_by_hash(&hash)?; - let ReceiptWithBloom { receipt, bloom } = receipt.into(); - let Receipt { success, cumulative_gas_used: _, logs } = receipt; - let logs_bloom = bloom; - let index = info.transaction_index as usize; - let block = self.blockchain.get_block_by_hash(&block_hash)?; - - // TODO store cumulative gas used in receipt instead - let receipts = self.get_receipts(block.transactions.iter().map(|tx| tx.hash())); - - let mut cumulative_gas_used = U256::ZERO; - for receipt in receipts.iter().take(index + 1) { - cumulative_gas_used = cumulative_gas_used.saturating_add(receipt.gas_used()); - } - - // cumulative_gas_used = cumulative_gas_used.saturating_sub(gas_used); - - let mut cumulative_receipts = receipts; - cumulative_receipts.truncate(index + 1); - let transaction = block.transactions[index].clone(); - let transaction_type = transaction.transaction.r#type(); + // Cancun specific + let excess_blob_gas = block.header.excess_blob_gas; + let blob_gas_price = calc_blob_gasprice(excess_blob_gas.map_or(0, |g| g as u64)); + let blob_gas_used = transaction.blob_gas(); let effective_gas_price = match transaction.transaction { - TypedTransaction::Legacy(t) => t.gas_price, - TypedTransaction::EIP2930(t) => t.gas_price, + TypedTransaction::Legacy(t) => t.tx().gas_price, + TypedTransaction::EIP2930(t) => t.tx().gas_price, TypedTransaction::EIP1559(t) => block .header .base_fee_per_gas - .map_or(self.base_fee().to::(), |b| b as u128) - .checked_add(t.max_priority_fee_per_gas) - .unwrap_or(u128::MAX), + .unwrap_or_else(|| self.base_fee()) + .saturating_add(t.tx().max_priority_fee_per_gas), + TypedTransaction::EIP4844(t) => block + .header + .base_fee_per_gas + .unwrap_or_else(|| self.base_fee()) + .saturating_add(t.tx().tx().max_priority_fee_per_gas), TypedTransaction::Deposit(_) => 0_u128, }; - let deposit_nonce = transaction_type.and_then(|x| (x == 0x7E).then_some(info.nonce)); + let receipts = self.get_receipts(block.transactions.iter().map(|tx| tx.hash())); + let next_log_index = receipts[..index].iter().map(|r| r.logs().len()).sum::(); + + let receipt = tx_receipt.as_receipt_with_bloom().receipt.clone(); + let receipt = Receipt { + status: receipt.status, + cumulative_gas_used: receipt.cumulative_gas_used, + logs: receipt + .logs + .into_iter() + .enumerate() + .map(|(index, log)| alloy_rpc_types::Log { + inner: log, + block_hash: Some(block_hash), + block_number: Some(block.header.number), + block_timestamp: Some(block.header.timestamp), + transaction_hash: Some(info.transaction_hash), + transaction_index: Some(info.transaction_index), + log_index: Some((next_log_index + index) as u64), + removed: false, + }) + .collect(), + }; + let receipt_with_bloom = + ReceiptWithBloom { receipt, logs_bloom: tx_receipt.as_receipt_with_bloom().logs_bloom }; + + let inner = match tx_receipt { + TypedReceipt::EIP1559(_) => TypedReceipt::EIP1559(receipt_with_bloom), + TypedReceipt::Legacy(_) => TypedReceipt::Legacy(receipt_with_bloom), + TypedReceipt::EIP2930(_) => TypedReceipt::EIP2930(receipt_with_bloom), + TypedReceipt::EIP4844(_) => TypedReceipt::EIP4844(receipt_with_bloom), + TypedReceipt::Deposit(r) => TypedReceipt::Deposit(DepositReceipt { + inner: receipt_with_bloom, + deposit_nonce: r.deposit_nonce, + deposit_receipt_version: r.deposit_receipt_version, + }), + }; - let mut inner = TransactionReceipt { - transaction_hash: Some(info.transaction_hash), - transaction_index: U64::from(info.transaction_index), + let inner = TransactionReceipt { + inner, + 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), - block_number: Some(U256::from(block.header.number)), from: info.from, to: info.to, - cumulative_gas_used, - gas_used: Some(cumulative_gas_used), - contract_address: info.contract_address, - logs: { - let mut pre_receipts_log_index = None; - if !cumulative_receipts.is_empty() { - cumulative_receipts.truncate(cumulative_receipts.len() - 1); - pre_receipts_log_index = - Some(cumulative_receipts.iter().map(|_r| logs.len() as u32).sum::()); - } - logs.iter() - .enumerate() - .map(|(i, log)| Log { - address: log.address, - topics: log.topics().to_vec(), - data: log.data.data.clone(), - block_hash: Some(block_hash), - block_number: Some(U256::from(block.header.number)), - transaction_hash: Some(info.transaction_hash), - transaction_index: Some(U256::from(info.transaction_index)), - log_index: Some(U256::from( - (pre_receipts_log_index.unwrap_or(0)) + i as u32, - )), - removed: false, - }) - .collect() - }, - status_code: Some(U64::from(success)), - state_root: None, - logs_bloom, - transaction_type: transaction_type.map(U8::from).unwrap_or_default(), - effective_gas_price: U128::from(effective_gas_price), - blob_gas_price: None, - blob_gas_used: None, - other: Default::default(), + state_root: Some(block.header.state_root), + blob_gas_price: Some(blob_gas_price), + blob_gas_used, }; - inner.other.insert( - "depositNonce".to_string(), - serde_json::to_value(deposit_nonce).expect("Infallible"), - ); - Some(MinedTransactionReceipt { inner, out: info.out.map(|o| o.0.into()) }) } @@ -2064,7 +2097,7 @@ impl Backend { pub async fn block_receipts( &self, number: BlockNumber, - ) -> Result>, BlockchainError> { + ) -> Result>, BlockchainError> { if let Some(receipts) = self.mined_block_receipts(number) { return Ok(Some(receipts)); } @@ -2073,10 +2106,7 @@ impl Backend { let number = self.convert_block_number(Some(number)); if fork.predates_fork_inclusive(number) { - let receipts = fork - .block_receipts(number) - .await - .map_err(BlockchainError::AlloyForkProvider)?; + let receipts = fork.block_receipts(number).await?; return Ok(receipts); } @@ -2089,7 +2119,7 @@ impl Backend { &self, number: BlockNumber, index: Index, - ) -> Result, BlockchainError> { + ) -> Result>, BlockchainError> { if let Some(hash) = self.mined_block_by_number(number).and_then(|b| b.header.hash) { return Ok(self.mined_transaction_by_block_hash_and_index(hash, index)); } @@ -2108,7 +2138,7 @@ impl Backend { &self, hash: B256, index: Index, - ) -> Result, BlockchainError> { + ) -> Result>, BlockchainError> { if let tx @ Some(_) = self.mined_transaction_by_block_hash_and_index(hash, index) { return Ok(tx); } @@ -2124,7 +2154,7 @@ impl Backend { &self, block_hash: B256, index: Index, - ) -> Option { + ) -> Option> { let (info, block, tx) = { let storage = self.blockchain.storage.read(); let block = storage.blocks.get(&block_hash).cloned()?; @@ -2139,14 +2169,14 @@ impl Backend { tx, Some(&block), Some(info), - block.header.base_fee_per_gas.map(|g| g.to_alloy()), + block.header.base_fee_per_gas, )) } pub async fn transaction_by_hash( &self, hash: B256, - ) -> Result, BlockchainError> { + ) -> Result>, BlockchainError> { trace!(target: "backend", "transaction_by_hash={:?}", hash); if let tx @ Some(_) = self.mined_transaction_by_hash(hash) { return Ok(tx); @@ -2159,7 +2189,7 @@ impl Backend { Ok(None) } - fn mined_transaction_by_hash(&self, hash: B256) -> Option { + fn mined_transaction_by_hash(&self, hash: B256) -> Option> { let (info, block) = { let storage = self.blockchain.storage.read(); let MinedTransaction { info, block_hash, .. } = @@ -2174,7 +2204,7 @@ impl Backend { tx, Some(&block), Some(info), - block.header.base_fee_per_gas.map(|g| g.to_alloy()), + block.header.base_fee_per_gas, )) } @@ -2187,74 +2217,41 @@ impl Backend { keys: Vec, block_request: Option, ) -> Result { - let account_key = B256::from(alloy_primitives::utils::keccak256(address)); 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, root) = block_db.maybe_as_hash_db().ok_or(BlockchainError::DataUnavailable)?; + let db = block_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?; + let account = db.get(&address).cloned().unwrap_or_default(); - let data: &dyn HashDB<_, _> = db.deref(); - let mut recorder = Recorder::new(); - let trie = RefTrieDB::new(&data, &root.0) - .map_err(|err| BlockchainError::TrieError(err.to_string()))?; + let mut builder = HashBuilder::default() + .with_proof_retainer(ProofRetainer::new(vec![Nibbles::unpack(keccak256(address))])); - let maybe_account: Option = { - let acc_decoder = |mut bytes: &[u8]| { - BasicAccount::decode(&mut bytes).unwrap_or_else(|_| { - panic!("prove_account_at, could not query trie for account={:?}", &address) - }) - }; - let query = (&mut recorder, acc_decoder); - trie.get_with(account_key.as_slice(), query) - .map_err(|err| BlockchainError::TrieError(err.to_string()))? - }; - let account = maybe_account.unwrap_or_default(); + for (key, account) in trie_accounts(db) { + builder.add_leaf(key, &account); + } - let proof = recorder - .drain() - .into_iter() - .map(|r| r.data) - .map(|record| { - // proof is rlp encoded: - // - // - alloy_rlp::encode(record).to_vec().into() - }) - .collect::>(); + let _ = builder.root(); - let account_db = - block_db.maybe_account_db(address).ok_or(BlockchainError::DataUnavailable)?; + let proof = builder.take_proofs().values().cloned().collect::>(); + let storage_proofs = prove_storage(&account.storage, &keys); let account_proof = AccountProof { address, - balance: account.balance, - nonce: account.nonce.to::(), - code_hash: account.code_hash, - storage_hash: account.storage_root, + balance: account.info.balance, + nonce: U64::from(account.info.nonce), + code_hash: account.info.code_hash, + storage_hash: storage_root(&account.storage), account_proof: proof, storage_proof: keys .into_iter() - .map(|storage_key| { - // the key that should be proofed is the keccak256 of the storage key - let key = B256::from(keccak256(storage_key)); - prove_storage(&account, &account_db.0, key).map( - |(storage_proof, storage_value)| StorageProof { - key: alloy_rpc_types::JsonStorageKey(storage_key), - value: U256::from_be_bytes(storage_value.0), - proof: storage_proof - .into_iter() - .map(|proof| { - // proof is rlp encoded: - // - // - alloy_rlp::encode(proof).to_vec().into() - }) - .collect(), - }, - ) + .zip(storage_proofs) + .map(|(key, proof)| { + let storage_key: U256 = key.into(); + let value = account.storage.get(&storage_key).cloned().unwrap_or_default(); + StorageProof { key: JsonStorageKey(key), value, proof } }) - .collect::, _>>()?, + .collect(), }; Ok(account_proof) @@ -2288,20 +2285,15 @@ impl Backend { fn get_pool_transactions_nonce( pool_transactions: &[Arc], address: Address, -) -> Option { - let highest_nonce_tx = pool_transactions +) -> Option { + if let Some(highest_nonce) = pool_transactions .iter() .filter(|tx| *tx.pending_transaction.sender() == address) - .reduce(|accum, item| { - let nonce = item.pending_transaction.nonce(); - if nonce > accum.pending_transaction.nonce() { - item - } else { - accum - } - }); - if let Some(highest_nonce_tx) = highest_nonce_tx { - return Some(highest_nonce_tx.pending_transaction.nonce().saturating_add(U256::from(1))); + .map(|tx| tx.pending_transaction.nonce()) + .max() + { + let tx_count = highest_nonce.saturating_add(1); + return Some(tx_count) } None } @@ -2322,7 +2314,7 @@ impl TransactionValidator for Backend { &self, pending: &PendingTransaction, account: &AccountInfo, - env: &Env, + env: &EnvWithHandlerCfg, ) -> Result<(), InvalidTransactionError> { let tx = &pending.transaction; @@ -2331,7 +2323,7 @@ impl TransactionValidator for Backend { if chain_id.to::() != tx_chain_id { if let Some(legacy) = tx.as_legacy() { // - if env.cfg.spec_id >= SpecId::SPURIOUS_DRAGON && + if env.handler_cfg.spec_id >= SpecId::SPURIOUS_DRAGON && !meets_eip155(chain_id.to::(), legacy.signature().v()) { warn!(target: "backend", ?chain_id, ?tx_chain_id, "incompatible EIP155-based V"); @@ -2350,7 +2342,7 @@ impl TransactionValidator for Backend { } // Check gas limit, iff block gas limit is set. - if !env.cfg.disable_block_gas_limit && tx.gas_limit() > env.block.gas_limit { + if !env.cfg.disable_block_gas_limit && tx.gas_limit() > env.block.gas_limit.to() { warn!(target: "backend", "[{:?}] gas too high", tx.hash()); return Err(InvalidTransactionError::GasTooHigh(ErrDetail { detail: String::from("tx.gas_limit > env.block.gas_limit"), @@ -2360,15 +2352,14 @@ impl TransactionValidator for Backend { // check nonce let is_deposit_tx = matches!(&pending.transaction.transaction, TypedTransaction::Deposit(_)); - let nonce: u64 = - tx.nonce().try_into().map_err(|_| InvalidTransactionError::NonceMaxValue)?; + let nonce = tx.nonce(); if nonce < account.nonce && !is_deposit_tx { warn!(target: "backend", "[{:?}] nonce too low", tx.hash()); return Err(InvalidTransactionError::NonceTooLow); } - if (env.cfg.spec_id as u8) >= (SpecId::LONDON as u8) { - if tx.gas_price() < env.block.basefee && !is_deposit_tx { + if (env.handler_cfg.spec_id as u8) >= (SpecId::LONDON as u8) { + if tx.gas_price() < env.block.basefee.to() && !is_deposit_tx { warn!(target: "backend", "max fee per gas={}, too low, block basefee={}",tx.gas_price(), env.block.basefee); return Err(InvalidTransactionError::FeeCapTooLow); } @@ -2383,15 +2374,51 @@ impl TransactionValidator for Backend { } } + // EIP-4844 Cancun hard fork validation steps + if env.spec_id() >= SpecId::CANCUN && tx.transaction.is_eip4844() { + // Light checks first: see if the blob fee cap is too low. + if let Some(max_fee_per_blob_gas) = tx.essentials().max_fee_per_blob_gas { + if let Some(blob_gas_and_price) = &env.block.blob_excess_gas_and_price { + if max_fee_per_blob_gas.to::() < 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); + } + } + } + + // Heavy (blob validation) checks + let tx = match &tx.transaction { + TypedTransaction::EIP4844(tx) => tx.tx(), + _ => unreachable!(), + }; + + let blob_count = 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 block. + if blob_count > MAX_BLOBS_PER_BLOCK { + return Err(InvalidTransactionError::TooManyBlobs(MAX_BLOBS_PER_BLOCK, blob_count)) + } + + // Check for any blob validation errors + if let Err(err) = tx.validate(env.cfg.kzg_settings.get()) { + return Err(InvalidTransactionError::BlobTransactionValidationError(err)) + } + } + let max_cost = tx.max_cost(); let value = tx.value(); // check sufficient funds: `gas * price + value` - let req_funds = max_cost.checked_add(value).ok_or_else(|| { + let req_funds = max_cost.checked_add(value.to()).ok_or_else(|| { warn!(target: "backend", "[{:?}] cost too high", tx.hash()); InvalidTransactionError::InsufficientFunds })?; - if account.balance < req_funds { + if account.balance < U256::from(req_funds) { warn!(target: "backend", "[{:?}] insufficient allowance={}, required={} account={:?}", tx.hash(), account.balance, req_funds, *pending.sender()); return Err(InvalidTransactionError::InsufficientFunds); } @@ -2402,10 +2429,10 @@ impl TransactionValidator for Backend { &self, tx: &PendingTransaction, account: &AccountInfo, - env: &Env, + env: &EnvWithHandlerCfg, ) -> Result<(), InvalidTransactionError> { self.validate_pool_transaction_for(tx, account, env)?; - if tx.nonce().to::() > account.nonce { + if tx.nonce() > account.nonce { return Err(InvalidTransactionError::NonceTooHigh); } Ok(()) @@ -2419,11 +2446,11 @@ pub fn transaction_build( eth_transaction: MaybeImpersonatedTransaction, block: Option<&Block>, info: Option, - base_fee: Option, -) -> Transaction { + base_fee: Option, +) -> WithOtherFields { let mut transaction: Transaction = eth_transaction.clone().into(); - if info.is_some() && transaction.transaction_type.unwrap_or(U64::ZERO).to::() == 0x7E { - transaction.nonce = U64::from(info.as_ref().unwrap().nonce); + if info.is_some() && transaction.transaction_type == Some(0x7E) { + transaction.nonce = info.as_ref().unwrap().nonce; } if eth_transaction.is_dynamic_fee() { @@ -2433,12 +2460,9 @@ pub fn transaction_build( } else { // if transaction is already mined, gas price is considered base fee + priority fee: the // effective gas price. - let base_fee = base_fee.unwrap_or(U256::ZERO); - let max_priority_fee_per_gas = - transaction.max_priority_fee_per_gas.map(|g| g.to::()).unwrap_or(U256::ZERO); - transaction.gas_price = Some( - base_fee.checked_add(max_priority_fee_per_gas).unwrap_or(U256::MAX).to::(), - ); + let base_fee = base_fee.unwrap_or(0u128); + let max_priority_fee_per_gas = transaction.max_priority_fee_per_gas.unwrap_or(0); + transaction.gas_price = Some(base_fee.saturating_add(max_priority_fee_per_gas)); } } else { transaction.max_fee_per_gas = None; @@ -2448,10 +2472,9 @@ pub fn transaction_build( transaction.block_hash = block.as_ref().map(|block| B256::from(keccak256(alloy_rlp::encode(&block.header)))); - transaction.block_number = block.as_ref().map(|block| U256::from(block.header.number)); + transaction.block_number = block.as_ref().map(|block| block.header.number); - transaction.transaction_index = - info.as_ref().map(|status| U256::from(status.transaction_index)); + transaction.transaction_index = info.as_ref().map(|info| info.transaction_index); // need to check if the signature of the transaction is impersonated, if so then we // can't recover the sender, instead we use the sender from the executed transaction and set the @@ -2473,7 +2496,7 @@ pub fn transaction_build( } transaction.to = info.as_ref().map_or(eth_transaction.to(), |status| status.to); - transaction + WithOtherFields::new(transaction) } /// Prove a storage key's existence or nonexistence in the account's storage @@ -2481,25 +2504,29 @@ pub fn transaction_build( /// `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( - acc: &BasicAccount, - data: &AsHashDB, - storage_key: B256, -) -> Result<(Vec>, B256), BlockchainError> { - let data: &dyn HashDB<_, _> = data.deref(); - let mut recorder = Recorder::new(); - let trie = RefTrieDB::new(&data, &acc.storage_root.0) - .map_err(|err| BlockchainError::TrieError(err.to_string())) - .unwrap(); - - let item: U256 = { - let decode_value = - |mut bytes: &[u8]| U256::decode(&mut bytes).expect("decoding db value failed"); - let query = (&mut recorder, decode_value); - trie.get_with(storage_key.as_slice(), query) - .map_err(|err| BlockchainError::TrieError(err.to_string()))? - .unwrap_or(U256::ZERO) - }; - - Ok((recorder.drain().into_iter().map(|r| r.data).collect(), B256::from(item))) +pub fn prove_storage(storage: &HashMap, keys: &[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 _ = builder.root(); + + let mut proofs = Vec::new(); + let all_proof_nodes = builder.take_proofs(); + + 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 + .iter() + .filter(|(path, _)| proof_key.starts_with(path)) + .map(|(_, node)| node.clone()); + proofs.push(matching_proof_nodes.collect()); + } + + proofs } diff --git a/crates/anvil/src/eth/backend/mem/state.rs b/crates/anvil/src/eth/backend/mem/state.rs index af936578d..dd52eedfa 100644 --- a/crates/anvil/src/eth/backend/mem/state.rs +++ b/crates/anvil/src/eth/backend/mem/state.rs @@ -1,86 +1,73 @@ //! Support for generating the state root for memdb storage -use crate::eth::{backend::db::AsHashDB, error::BlockchainError}; -use alloy_primitives::{Address, Bytes, B256, U256}; +use crate::eth::error::BlockchainError; +use alloy_primitives::{keccak256, Address, B256, U256}; use alloy_rlp::Encodable; use alloy_rpc_types::state::StateOverride; -use anvil_core::eth::trie::RefSecTrieDBMut; +use alloy_trie::{HashBuilder, Nibbles}; use foundry_evm::{ backend::DatabaseError, - hashbrown::HashMap as Map, revm::{ db::{CacheDB, DatabaseRef, DbAccount}, - primitives::{AccountInfo, Bytecode}, + primitives::{AccountInfo, Bytecode, HashMap}, }, }; -use memory_db::HashKey; -use trie_db::TrieMut; -/// Returns storage trie of an account as `HashDB` -pub fn storage_trie_db(storage: &Map) -> (AsHashDB, B256) { - // Populate DB with full trie from entries. - let (db, root) = { - let mut db = , _>>::default(); - let mut root = Default::default(); - { - let mut trie = RefSecTrieDBMut::new(&mut db, &mut root); - for (k, v) in storage.iter().filter(|(_k, v)| *v != &U256::from(0)) { - let key = B256::from(*k); - let mut value: Vec = Vec::new(); - U256::encode(v, &mut value); - trie.insert(key.as_slice(), value.as_ref()).unwrap(); - } - } - (db, root) - }; +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() +} - (Box::new(db), B256::from(root)) +/// Builds state root from the given accounts +pub fn state_root(accounts: &HashMap) -> B256 { + build_root(trie_accounts(accounts)) } -/// Returns the account data as `HashDB` -pub fn trie_hash_db(accounts: &Map) -> (AsHashDB, B256) { - let accounts = trie_accounts(accounts); +/// Builds storage root from the given storage +pub fn storage_root(storage: &HashMap) -> B256 { + build_root(trie_storage(storage)) +} - // Populate DB with full trie from entries. - let (db, root) = { - let mut db = , _>>::default(); - let mut root = Default::default(); - { - let mut trie = RefSecTrieDBMut::new(&mut db, &mut root); - for (address, value) in accounts { - trie.insert(address.as_ref(), value.as_ref()).unwrap(); - } - } - (db, root) - }; +/// 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(|(key1, _), (key2, _)| key1.cmp(key2)); - (Box::new(db), B256::from(root)) + storage } -/// Returns all RLP-encoded Accounts -pub fn trie_accounts(accounts: &Map) -> Vec<(Address, Bytes)> { - accounts +/// Builds iterator over stored key-value pairs ready for account trie root calculation. +pub fn trie_accounts(accounts: &HashMap) -> Vec<(Nibbles, Vec)> { + let mut accounts = accounts .iter() .map(|(address, account)| { - let storage_root = trie_account_rlp(&account.info, &account.storage); - (*address, storage_root) + let data = trie_account_rlp(&account.info, &account.storage); + (Nibbles::unpack(keccak256(*address)), data) }) - .collect() -} + .collect::>(); + accounts.sort_by(|(key1, _), (key2, _)| key1.cmp(key2)); -pub fn state_merkle_trie_root(accounts: &Map) -> B256 { - trie_hash_db(accounts).1 + accounts } /// Returns the RLP for this account. -pub fn trie_account_rlp(info: &AccountInfo, storage: &Map) -> Bytes { +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_trie_db(storage).1, &info.code_hash]; + [&info.nonce, &info.balance, &storage_root(storage), &info.code_hash]; alloy_rlp::encode_list::<_, dyn Encodable>(&list, &mut out); - out.into() + out } /// Applies the given state overrides to the state, returning a new CacheDB state @@ -122,13 +109,13 @@ where *account, new_account_state .iter() - .map(|(key, value)| ((*key).into(), (*value))) + .map(|(key, value)| ((*key).into(), (*value).into())) .collect(), )?; } (None, Some(account_state_diff)) => { for (key, value) in account_state_diff.iter() { - cache_db.insert_account_storage(*account, (*key).into(), *value)?; + cache_db.insert_account_storage(*account, (*key).into(), (*value).into())?; } } }; diff --git a/crates/anvil/src/eth/backend/mem/storage.rs b/crates/anvil/src/eth/backend/mem/storage.rs index 9e907cff4..3531057ad 100644 --- a/crates/anvil/src/eth/backend/mem/storage.rs +++ b/crates/anvil/src/eth/backend/mem/storage.rs @@ -1,23 +1,20 @@ //! In-memory blockchain storage use crate::eth::{ backend::{ - db::{MaybeHashDatabase, StateDb}, + db::{MaybeFullDatabase, SerializableBlock, StateDb}, mem::cache::DiskStateCache, }, pool::transactions::PoolTransaction, }; -use alloy_network::Sealable; use alloy_primitives::{Bytes, TxHash, B256, U256, U64}; -use alloy_rpc_trace_types::{ +use alloy_rpc_types::{BlockId, BlockNumberOrTag, TransactionInfo as RethTransactionInfo}; +use alloy_rpc_types_trace::{ geth::{DefaultFrame, GethDefaultTracingOptions}, parity::LocalizedTransactionTrace, }; -use alloy_rpc_types::{ - BlockId, BlockNumberOrTag, TransactionInfo as RethTransactionInfo, TransactionReceipt, -}; use anvil_core::eth::{ block::{Block, PartialHeader}, - transaction::{MaybeImpersonatedTransaction, TransactionInfo, TypedReceipt}, + transaction::{MaybeImpersonatedTransaction, ReceiptResponse, TransactionInfo, TypedReceipt}, }; use foundry_evm::{ revm::primitives::Env, @@ -38,8 +35,6 @@ const MIN_HISTORY_LIMIT: usize = 10; // 1hr of up-time at lowest 1s interval const MAX_ON_DISK_HISTORY_LIMIT: usize = 3_600; -// === impl DiskStateCache === - /// Represents the complete state of single block pub struct InMemoryBlockStates { /// The states at a certain block @@ -62,8 +57,6 @@ pub struct InMemoryBlockStates { disk_cache: DiskStateCache, } -// === impl InMemoryBlockStates === - impl InMemoryBlockStates { /// Creates a new instance with limited slots pub fn new(limit: usize) -> Self { @@ -228,18 +221,20 @@ pub struct BlockchainStorage { impl BlockchainStorage { /// Creates a new storage with a genesis block - pub fn new(env: &Env, base_fee: Option, timestamp: u64) -> Self { + pub fn new(env: &Env, base_fee: Option, timestamp: u64) -> Self { // create a dummy genesis block let partial_header = PartialHeader { timestamp, - base_fee: base_fee.map(|b| b.to::()), - gas_limit: env.block.gas_limit.to::(), + base_fee, + gas_limit: env.block.gas_limit.to::(), beneficiary: env.block.coinbase, difficulty: env.block.difficulty, + blob_gas_used: env.block.blob_excess_gas_and_price.as_ref().map(|_| 0), + excess_blob_gas: env.block.get_blob_excess_gas().map(|v| v as u128), ..Default::default() }; let block = Block::new::(partial_header, vec![], vec![]); - let genesis_hash = block.header.hash(); + let genesis_hash = block.header.hash_slow(); let best_hash = genesis_hash; let best_number: U64 = U64::from(0u64); @@ -255,7 +250,7 @@ impl BlockchainStorage { } pub fn forked(block_number: u64, block_hash: B256, total_difficulty: U256) -> Self { - BlockchainStorage { + Self { blocks: Default::default(), hashes: HashMap::from([(U64::from(block_number), block_hash)]), best_hash: block_hash, @@ -297,8 +292,6 @@ impl BlockchainStorage { } } -// === impl BlockchainStorage === - impl BlockchainStorage { /// Returns the hash for [BlockNumberOrTag] pub fn hash(&self, number: BlockNumberOrTag) -> Option { @@ -326,6 +319,21 @@ impl BlockchainStorage { } } } + + 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.iter() { + 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(U64::from(block_number), block_hash); + } + } } /// A simple in-memory blockchain @@ -335,11 +343,9 @@ pub struct Blockchain { pub storage: Arc>, } -// === impl BlockchainStorage === - impl Blockchain { /// Creates a new storage with a genesis block - pub fn new(env: &Env, base_fee: Option, timestamp: u64) -> Self { + pub fn new(env: &Env, base_fee: Option, timestamp: u64) -> Self { Self { storage: Arc::new(RwLock::new(BlockchainStorage::new(env, base_fee, timestamp))) } } @@ -396,8 +402,6 @@ pub struct MinedTransaction { pub block_number: u64, } -// === impl MinedTransaction === - impl MinedTransaction { /// Returns the traces of the transaction for `trace_transaction` pub fn parity_traces(&self) -> Vec { @@ -408,7 +412,7 @@ impl MinedTransaction { ) .into_localized_transaction_traces(RethTransactionInfo { hash: Some(self.info.transaction_hash), - index: Some(self.info.transaction_index as u64), + index: Some(self.info.transaction_index), block_hash: Some(self.block_hash), block_number: Some(self.block_number), base_fee: None, @@ -418,7 +422,7 @@ impl MinedTransaction { pub fn geth_trace(&self, opts: GethDefaultTracingOptions) -> DefaultFrame { GethTraceBuilder::new(self.info.traces.clone(), TracingInspectorConfig::default_geth()) .geth_traces( - self.receipt.gas_used().to::(), + self.receipt.cumulative_gas_used() as u64, self.info.out.clone().unwrap_or_default().0.into(), opts, ) @@ -429,7 +433,7 @@ impl MinedTransaction { #[derive(Clone, Debug)] pub struct MinedTransactionReceipt { /// The actual json rpc receipt object - pub inner: TransactionReceipt, + pub inner: ReceiptResponse, /// Output data fo the transaction pub out: Option, } @@ -438,12 +442,14 @@ pub struct MinedTransactionReceipt { mod tests { use super::*; use crate::eth::backend::db::Db; - use alloy_primitives::Address; + use alloy_primitives::{hex, Address}; + use alloy_rlp::Decodable; + use anvil_core::eth::transaction::TypedTransaction; use foundry_evm::{ backend::MemDb, revm::{ db::DatabaseRef, - primitives::{AccountInfo, U256 as rU256}, + primitives::{AccountInfo, U256}, }, }; @@ -462,7 +468,7 @@ mod tests { let mut state = MemDb::default(); let addr = Address::random(); - let info = AccountInfo::from_balance(rU256::from(1337)); + 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())); @@ -471,12 +477,12 @@ mod tests { tokio::time::sleep(std::time::Duration::from_secs(2)).await; assert_eq!(storage.on_disk_states.len(), 1); - assert!(storage.on_disk_states.get(&one).is_some()); + assert!(storage.on_disk_states.contains_key(&one)); let loaded = storage.get(&one).unwrap(); let acc = loaded.basic_ref(addr).unwrap().unwrap(); - assert_eq!(acc.balance, rU256::from(1337u64)); + assert_eq!(acc.balance, U256::from(1337u64)); } #[tokio::test(flavor = "multi_thread")] @@ -490,7 +496,7 @@ mod tests { let hash = B256::from(U256::from(idx)); let addr = Address::from_word(hash); let balance = (idx * 2) as u64; - let info = AccountInfo::from_balance(rU256::from(balance)); + let info = AccountInfo::from_balance(U256::from(balance)); state.insert_account(addr, info); storage.insert(hash, StateDb::new(state)); } @@ -507,7 +513,36 @@ mod tests { let loaded = storage.get(&hash).unwrap(); let acc = loaded.basic_ref(addr).unwrap().unwrap(); let balance = (idx * 2) as u64; - assert_eq!(acc.balance, rU256::from(balance)); + assert_eq!(acc.balance, U256::from(balance)); } } + + // verifies that blocks in BlockchainStorage remain the same when dumped and reloaded + #[test] + fn test_storage_dump_reload_cycle() { + let mut dump_storage = BlockchainStorage::empty(); + + let partial_header = PartialHeader { gas_limit: 123456, ..Default::default() }; + let bytes_first = &mut &hex::decode("f86b02843b9aca00830186a094d3e8763675e4c425df46cc3b5c0f6cbdac39604687038d7ea4c68000802ba00eb96ca19e8a77102767a41fc85a36afd5c61ccb09911cec5d3e86e193d9c5aea03a456401896b1b6055311536bf00a718568c744d8c1f9df59879e8350220ca18").unwrap()[..]; + let tx: MaybeImpersonatedTransaction = + TypedTransaction::decode(&mut &bytes_first[..]).unwrap().into(); + let block = Block::new::( + partial_header.clone(), + vec![tx.clone()], + vec![], + ); + let block_hash = block.header.hash_slow(); + dump_storage.blocks.insert(block_hash, block); + + let serialized_blocks = dump_storage.serialized_blocks(); + + let mut load_storage = BlockchainStorage::empty(); + + load_storage.load_blocks(serialized_blocks); + + let loaded_block = load_storage.blocks.get(&block_hash).unwrap(); + assert_eq!(loaded_block.header.gas_limit, partial_header.gas_limit); + let loaded_tx = loaded_block.transactions.first().unwrap(); + assert_eq!(loaded_tx, &tx); + } } diff --git a/crates/anvil/src/eth/backend/time.rs b/crates/anvil/src/eth/backend/time.rs index f51ff93fe..0822baa04 100644 --- a/crates/anvil/src/eth/backend/time.rs +++ b/crates/anvil/src/eth/backend/time.rs @@ -1,16 +1,13 @@ //! Manages the block time use crate::eth::error::BlockchainError; -use chrono::{DateTime, NaiveDateTime, Utc}; +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_naive_utc_and_offset( - NaiveDateTime::from_timestamp_opt(secs as i64, 0).unwrap(), - Utc, - ) + DateTime::from_timestamp(secs as i64, 0).unwrap() } /// Manages block time @@ -28,11 +25,9 @@ pub struct TimeManager { interval: Arc>>, } -// === impl TimeManager === - impl TimeManager { - pub fn new(start_timestamp: u64) -> TimeManager { - let time_manager = TimeManager { + pub fn new(start_timestamp: u64) -> Self { + let time_manager = Self { last_timestamp: Default::default(), offset: Default::default(), next_exact_timestamp: Default::default(), diff --git a/crates/anvil/src/eth/backend/validate.rs b/crates/anvil/src/eth/backend/validate.rs index 3ea666f15..650ce24a5 100644 --- a/crates/anvil/src/eth/backend/validate.rs +++ b/crates/anvil/src/eth/backend/validate.rs @@ -2,7 +2,7 @@ use crate::eth::error::{BlockchainError, InvalidTransactionError}; use anvil_core::eth::transaction::PendingTransaction; -use foundry_evm::revm::primitives::{AccountInfo, Env}; +use foundry_evm::revm::primitives::{AccountInfo, EnvWithHandlerCfg}; /// A trait for validating transactions #[async_trait::async_trait] @@ -22,7 +22,7 @@ pub trait TransactionValidator { &self, tx: &PendingTransaction, account: &AccountInfo, - env: &Env, + env: &EnvWithHandlerCfg, ) -> Result<(), InvalidTransactionError>; /// Validates the transaction against a specific account @@ -32,6 +32,6 @@ pub trait TransactionValidator { &self, tx: &PendingTransaction, account: &AccountInfo, - env: &Env, + env: &EnvWithHandlerCfg, ) -> Result<(), InvalidTransactionError>; } diff --git a/crates/anvil/src/eth/error.rs b/crates/anvil/src/eth/error.rs index cc7af40ac..fe31fbc2a 100644 --- a/crates/anvil/src/eth/error.rs +++ b/crates/anvil/src/eth/error.rs @@ -1,8 +1,9 @@ //! Aggregated error type for this module use crate::eth::pool::transactions::PoolTransaction; -use alloy_primitives::{Bytes, SignatureError as AlloySignatureError, U256}; -use alloy_signer::Error as AlloySignerError; +use alloy_primitives::{Bytes, SignatureError}; +use alloy_rpc_types::BlockNumberOrTag; +use alloy_signer::Error as SignerError; use alloy_transport::TransportError; use anvil_rpc::{ error::{ErrorCode, RpcError}, @@ -12,7 +13,6 @@ use foundry_evm::{ backend::DatabaseError, decode::RevertDecoder, revm::{ - self, interpreter::InstructionResult, primitives::{EVMError, InvalidHeader}, }, @@ -37,14 +37,16 @@ pub enum BlockchainError { FailedToDecodeSignedTransaction, #[error("Failed to decode transaction")] FailedToDecodeTransaction, + #[error("Failed to decode receipt")] + FailedToDecodeReceipt, #[error("Failed to decode state")] FailedToDecodeStateDump, #[error("Prevrandao not in th EVM's environment after merge")] PrevrandaoNotSet, #[error(transparent)] - AlloySignatureError(#[from] AlloySignatureError), + SignatureError(#[from] SignatureError), #[error(transparent)] - AlloySignerError(#[from] AlloySignerError), + SignerError(#[from] SignerError), #[error("Rpc Endpoint not implemented")] RpcUnimplemented, #[error("Rpc error {0:?}")] @@ -81,30 +83,36 @@ pub enum BlockchainError { 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("op-stack deposit tx received but is not supported.\n\nYou can use it by running anvil with '--optimism'.")] DepositTransactionUnsupported, #[error("Excess blob gas not set.")] ExcessBlobGasNotSet, + #[error("{0}")] + Message(String), } impl From for BlockchainError { fn from(err: RpcError) -> Self { - BlockchainError::RpcError(err) + Self::RpcError(err) } } impl From> for BlockchainError where - T: Into, + T: Into, { fn from(err: EVMError) -> Self { match err { EVMError::Transaction(err) => InvalidTransactionError::from(err).into(), EVMError::Header(err) => match err { - InvalidHeader::ExcessBlobGasNotSet => BlockchainError::ExcessBlobGasNotSet, - InvalidHeader::PrevrandaoNotSet => BlockchainError::PrevrandaoNotSet, + InvalidHeader::ExcessBlobGasNotSet => Self::ExcessBlobGasNotSet, + InvalidHeader::PrevrandaoNotSet => Self::PrevrandaoNotSet, }, EVMError::Database(err) => err.into(), + EVMError::Precompile(err) => Self::Message(err), + EVMError::Custom(err) => Self::Message(err), } } } @@ -124,8 +132,10 @@ pub enum PoolError { /// Errors that can occur with `eth_feeHistory` #[derive(Debug, thiserror::Error)] pub enum FeeHistoryError { - #[error("Requested block range is out of bounds")] + #[error("requested block range is out of bounds")] InvalidBlockRange, + #[error("could not find newest block number requested: {0}")] + BlockNotFound(BlockNumberOrTag), } #[derive(Debug)] @@ -175,7 +185,7 @@ pub enum InvalidTransactionError { FeeCapTooLow, /// Thrown during estimate if caller has insufficient funds to cover the tx. #[error("Out of gas: gas required exceeds allowance: {0:?}")] - BasicOutOfGas(U256), + BasicOutOfGas(u128), /// Thrown if executing a transaction failed during estimate/call #[error("execution reverted: {0:?}")] Revert(Option), @@ -194,7 +204,7 @@ pub enum InvalidTransactionError { /// 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`")] - BlobGasPriceGreaterThanMax, + BlobFeeCapTooLow, /// 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")] @@ -202,56 +212,55 @@ pub enum InvalidTransactionError { /// 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, max: {0}, have: {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, } impl From for InvalidTransactionError { fn from(err: revm::primitives::InvalidTransaction) -> Self { use revm::primitives::InvalidTransaction; match err { - InvalidTransaction::InvalidChainId => InvalidTransactionError::InvalidChainId, - InvalidTransaction::PriorityFeeGreaterThanMaxFee => { - InvalidTransactionError::TipAboveFeeCap - } - InvalidTransaction::GasPriceLessThanBasefee => InvalidTransactionError::FeeCapTooLow, + InvalidTransaction::InvalidChainId => Self::InvalidChainId, + InvalidTransaction::PriorityFeeGreaterThanMaxFee => Self::TipAboveFeeCap, + InvalidTransaction::GasPriceLessThanBasefee => Self::FeeCapTooLow, InvalidTransaction::CallerGasLimitMoreThanBlock => { - InvalidTransactionError::GasTooHigh(ErrDetail { - detail: String::from("CallerGasLimitMoreThanBlock"), - }) + Self::GasTooHigh(ErrDetail { detail: String::from("CallerGasLimitMoreThanBlock") }) } InvalidTransaction::CallGasCostMoreThanGasLimit => { - InvalidTransactionError::GasTooHigh(ErrDetail { - detail: String::from("CallGasCostMoreThanGasLimit"), - }) - } - InvalidTransaction::RejectCallerWithCode => InvalidTransactionError::SenderNoEOA, - InvalidTransaction::LackOfFundForMaxFee { .. } => { - InvalidTransactionError::InsufficientFunds - } - InvalidTransaction::OverflowPaymentInTransaction => { - InvalidTransactionError::GasUintOverflow - } - InvalidTransaction::NonceOverflowInTransaction => { - InvalidTransactionError::NonceMaxValue - } - InvalidTransaction::CreateInitcodeSizeLimit => { - InvalidTransactionError::MaxInitCodeSizeExceeded - } - InvalidTransaction::NonceTooHigh { .. } => InvalidTransactionError::NonceTooHigh, - InvalidTransaction::NonceTooLow { .. } => InvalidTransactionError::NonceTooLow, - InvalidTransaction::AccessListNotSupported => { - InvalidTransactionError::AccessListNotSupported - } - InvalidTransaction::BlobGasPriceGreaterThanMax => { - InvalidTransactionError::BlobGasPriceGreaterThanMax + Self::GasTooHigh(ErrDetail { detail: String::from("CallGasCostMoreThanGasLimit") }) } + 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 => Self::BlobFeeCapTooLow, InvalidTransaction::BlobVersionedHashesNotSupported => { - InvalidTransactionError::BlobVersionedHashesNotSupported - } - InvalidTransaction::MaxFeePerBlobGasNotSupported => { - InvalidTransactionError::MaxFeePerBlobGasNotSupported + Self::BlobVersionedHashesNotSupported } - // TODO: Blob-related errors should be handled once the Reth migration is done and code - // is moved over. + InvalidTransaction::MaxFeePerBlobGasNotSupported => Self::MaxFeePerBlobGasNotSupported, + InvalidTransaction::BlobCreateTransaction => Self::BlobCreateTransaction, + InvalidTransaction::BlobVersionNotSupported => Self::BlobVersionNotSupported, + InvalidTransaction::EmptyBlobs => Self::EmptyBlobs, + InvalidTransaction::TooManyBlobs { max, have } => Self::TooManyBlobs(max, have), _ => todo!(), } } @@ -343,13 +352,14 @@ impl ToRpcResponseResult for Result { 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::AlloySignerError(err) => RpcError::invalid_params(err.to_string()), - BlockchainError::AlloySignatureError(err) => { - RpcError::invalid_params(err.to_string()) - } + 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") } @@ -405,12 +415,16 @@ impl ToRpcResponseResult for Result { err @ BlockchainError::EIP2930TransactionUnsupportedAtHardfork => { RpcError::invalid_params(err.to_string()) } + err @ BlockchainError::EIP4844TransactionUnsupportedAtHardfork => { + 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()), } .into(), } diff --git a/crates/anvil/src/eth/fees.rs b/crates/anvil/src/eth/fees.rs index 4f341858b..e0c8685fd 100644 --- a/crates/anvil/src/eth/fees.rs +++ b/crates/anvil/src/eth/fees.rs @@ -2,9 +2,12 @@ use crate::eth::{ backend::{info::StorageInfo, notifications::NewBlockNotifications}, error::BlockchainError, }; -use alloy_primitives::{B256, U256}; +use alloy_eips::{ + calc_next_block_base_fee, eip1559::BaseFeeParams, eip4844::MAX_DATA_GAS_PER_BLOCK, +}; +use alloy_primitives::B256; use anvil_core::eth::transaction::TypedTransaction; -use foundry_evm::revm::primitives::SpecId; +use foundry_evm::revm::primitives::{BlobExcessGasAndPrice, SpecId}; use futures::StreamExt; use parking_lot::{Mutex, RwLock}; use std::{ @@ -20,19 +23,19 @@ use std::{ 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; +pub const INITIAL_BASE_FEE: u128 = 1_000_000_000; /// Initial default gas price for the first block -pub const INITIAL_GAS_PRICE: u64 = 1_875_000_000; +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: u64 = 8; +pub const BASE_FEE_CHANGE_DENOMINATOR: u128 = 8; -/// Elasticity multiplier as defined in [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) -pub const EIP1559_ELASTICITY_MULTIPLIER: u64 = 2; +/// Minimum suggested priority fee +pub const MIN_SUGGESTED_PRIORITY_FEE: u128 = 1e9 as u128; pub fn default_elasticity() -> f64 { - 1f64 / BASE_FEE_CHANGE_DENOMINATOR as f64 + 1f64 / BaseFeeParams::ethereum().elasticity_multiplier as f64 } /// Stores the fee related information @@ -43,22 +46,30 @@ pub struct FeeManager { /// Tracks the base fee for the next block post London /// /// This value will be updated after a new block was mined - base_fee: Arc>, + base_fee: Arc>, + /// 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>, + gas_price: Arc>, elasticity: Arc>, } -// === impl FeeManager === - impl FeeManager { - pub fn new(spec_id: SpecId, base_fee: U256, gas_price: U256) -> Self { + pub fn new( + spec_id: SpecId, + base_fee: u128, + gas_price: u128, + blob_excess_gas_and_price: BlobExcessGasAndPrice, + ) -> Self { Self { spec_id, base_fee: Arc::new(RwLock::new(base_fee)), 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(default_elasticity())), } } @@ -72,95 +83,105 @@ impl FeeManager { (self.spec_id as u8) >= (SpecId::LONDON as u8) } - /// Calculates the current gas price - pub fn gas_price(&self) -> U256 { + 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) -> u128 { if self.is_eip1559() { - self.base_fee().saturating_add(self.suggested_priority_fee()) + *self.base_fee.read() } else { - *self.gas_price.read() + 0 } } - /// Suggested priority fee to add to the base fee - pub fn suggested_priority_fee(&self) -> U256 { - U256::from(1e9 as u64) + /// Raw base gas price + pub fn raw_gas_price(&self) -> u128 { + *self.gas_price.read() } - pub fn base_fee(&self) -> U256 { - if self.is_eip1559() { - *self.base_fee.read() + pub fn excess_blob_gas_and_price(&self) -> Option { + if self.is_eip4844() { + Some(self.blob_excess_gas_and_price.read().clone()) } else { - U256::ZERO + None } } - /// Returns the suggested fee cap - /// - /// Note: This currently returns a constant value: [Self::suggested_priority_fee] - pub fn max_priority_fee_per_gas(&self) -> U256 { - self.suggested_priority_fee() + 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: U256) { + 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: U256) { + pub fn set_base_fee(&self, fee: u128) { 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: U256, - gas_limit: U256, - last_fee_per_gas: U256, - ) -> u64 { + gas_used: u128, + gas_limit: u128, + last_fee_per_gas: u128, + ) -> u128 { // 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().is_zero() { + if self.base_fee() == 0 { return 0 } - calculate_next_block_base_fee( - gas_used.to::(), - gas_limit.to::(), - last_fee_per_gas.to::(), - ) + calculate_next_block_base_fee(gas_used, gas_limit, last_fee_per_gas) } -} - -/// Calculate base fee for next block. [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) spec -pub fn calculate_next_block_base_fee(gas_used: u64, gas_limit: u64, base_fee: u64) -> u64 { - let gas_target = gas_limit / EIP1559_ELASTICITY_MULTIPLIER; - if gas_used == gas_target { - return base_fee + /// Calculates the next block blob base fee, using the provided excess blob gas + pub fn get_next_block_blob_base_fee_per_gas(&self, excess_blob_gas: u128) -> u128 { + crate::revm::primitives::calc_blob_gasprice(excess_blob_gas as u64) } - if gas_used > gas_target { - let gas_used_delta = gas_used - gas_target; - let base_fee_delta = std::cmp::max( - 1, - base_fee as u128 * gas_used_delta as u128 / - gas_target as u128 / - BASE_FEE_CHANGE_DENOMINATOR as u128, - ); - base_fee + (base_fee_delta as u64) - } else { - let gas_used_delta = gas_target - gas_used; - let base_fee_per_gas_delta = base_fee as u128 * gas_used_delta as u128 / - gas_target as u128 / - BASE_FEE_CHANGE_DENOMINATOR as u128; - - base_fee.saturating_sub(base_fee_per_gas_delta as u64) + + /// Calculates the next block blob excess gas, using the provided parent blob gas used and + /// parent blob excess gas + pub fn get_next_block_blob_excess_gas( + &self, + blob_gas_used: u128, + blob_excess_gas: u128, + ) -> u64 { + crate::revm::primitives::calc_excess_blob_gas(blob_gas_used as u64, blob_excess_gas as u64) } } +/// Calculate base fee for next block. [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) spec +pub fn calculate_next_block_base_fee(gas_used: u128, gas_limit: u128, base_fee: u128) -> u128 { + calc_next_block_base_fee(gas_used, gas_limit, base_fee, BaseFeeParams::ethereum()) +} + /// An async service that takes care of the `FeeHistory` cache pub struct FeeHistoryService { /// incoming notifications about new blocks @@ -175,8 +196,6 @@ pub struct FeeHistoryService { storage_info: StorageInfo, } -// === impl FeeHistoryService === - impl FeeHistoryService { pub fn new( new_blocks: NewBlockNotifications, @@ -206,7 +225,6 @@ impl FeeHistoryService { /// Create a new history entry for the block fn create_cache_entry(&self, hash: B256) -> (FeeHistoryCacheItem, Option) { - let elasticity = self.fees.elasticity(); // percentile list from 0.0 to 100.0 with a 0.5 resolution. // this will create 200 percentile points let reward_percentiles: Vec = { @@ -222,10 +240,15 @@ impl FeeHistoryService { let mut block_number: Option = None; let base_fee = self.fees.base_fee(); + let excess_blob_gas_and_price = self.fees.excess_blob_gas_and_price(); let mut item = FeeHistoryCacheItem { - base_fee: base_fee.to::(), + base_fee, gas_used_ratio: 0f64, + blob_gas_used_ratio: 0f64, rewards: Vec::new(), + excess_blob_gas: excess_blob_gas_and_price.as_ref().map(|g| g.excess_blob_gas as u128), + base_fee_per_blob_gas: excess_blob_gas_and_price.as_ref().map(|g| g.blob_gasprice), + blob_gas_used: excess_blob_gas_and_price.as_ref().map(|_| 0), }; let current_block = self.storage_info.block(hash); @@ -235,35 +258,40 @@ impl FeeHistoryService { block_number = Some(block.header.number); let gas_used = block.header.gas_used as f64; - let gas_limit = block.header.gas_limit as f64; - - let gas_target = gas_limit / elasticity; - item.gas_used_ratio = gas_used / (gas_target * elasticity); + let blob_gas_used = block.header.blob_gas_used.map(|g| g as f64); + item.gas_used_ratio = gas_used / block.header.gas_limit as f64; + item.blob_gas_used_ratio = + blob_gas_used.map(|g| g / MAX_DATA_GAS_PER_BLOCK as f64).unwrap_or(0 as f64); // extract useful tx info (gas_used, effective_reward) - let mut transactions: Vec<(u64, u64)> = receipts + let mut transactions: Vec<(u128, u128)> = receipts .iter() .enumerate() .map(|(i, receipt)| { - let gas_used = receipt.gas_used(); + let gas_used = receipt.cumulative_gas_used(); let effective_reward = match block.transactions.get(i).map(|tx| &tx.transaction) { Some(TypedTransaction::Legacy(t)) => { - U256::from(t.gas_price).saturating_sub(base_fee).to::() + t.tx().gas_price.saturating_sub(base_fee) } Some(TypedTransaction::EIP2930(t)) => { - U256::from(t.gas_price).saturating_sub(base_fee).to::() - } - Some(TypedTransaction::EIP1559(t)) => { - U256::from(t.max_priority_fee_per_gas) - .min(U256::from(t.max_fee_per_gas).saturating_sub(base_fee)) - .to::() + t.tx().gas_price.saturating_sub(base_fee) } + Some(TypedTransaction::EIP1559(t)) => t + .tx() + .max_priority_fee_per_gas + .min(t.tx().max_fee_per_gas.saturating_sub(base_fee)), + // TODO: This probably needs to be extended to extract 4844 info. + Some(TypedTransaction::EIP4844(t)) => t + .tx() + .tx() + .max_priority_fee_per_gas + .min(t.tx().tx().max_fee_per_gas.saturating_sub(base_fee)), Some(TypedTransaction::Deposit(_)) => 0, None => 0, }; - (gas_used.to::(), effective_reward) + (gas_used, effective_reward) }) .collect(); @@ -274,7 +302,7 @@ impl FeeHistoryService { item.rewards = reward_percentiles .into_iter() .filter_map(|p| { - let target_gas = (p * gas_used / 100f64) as u64; + let target_gas = (p * gas_used / 100f64) as u128; let mut sum_gas = 0; for (gas_used, effective_reward) in transactions.iter().cloned() { sum_gas += gas_used; @@ -332,61 +360,88 @@ pub type FeeHistoryCache = Arc>>; /// A single item in the whole fee history cache #[derive(Clone, Debug)] pub struct FeeHistoryCacheItem { - pub base_fee: u64, + pub base_fee: u128, pub gas_used_ratio: f64, - pub rewards: Vec, + 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 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(U256::ZERO), - max_fee_per_gas: Some(U256::ZERO), - max_priority_fee_per_gas: Some(U256::ZERO), + 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 FeeDetails { gas_price, max_fee_per_gas, max_priority_fee_per_gas } = 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(U256::ZERO) } else { gas_price }; - let max_fee_per_gas = if no_fees { Some(U256::ZERO) } else { max_fee_per_gas }; + 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 } + 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) { - let Self { gas_price, max_fee_per_gas, max_priority_fee_per_gas } = self; - (gas_price, max_fee_per_gas, max_priority_fee_per_gas) + 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, - ) -> Result { - match (request_gas_price, request_max_fee, request_priority) { - (gas_price, None, None) => { + 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(FeeDetails { + 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, 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 { @@ -395,10 +450,11 @@ impl FeeDetails { return Err(BlockchainError::InvalidFeeInput) } } - Ok(FeeDetails { + Ok(Self { gas_price: max_fee, max_fee_per_gas: max_fee, max_priority_fee_per_gas: max_priority, + max_fee_per_blob_gas, }) } } diff --git a/crates/anvil/src/eth/miner.rs b/crates/anvil/src/eth/miner.rs index 2199a5061..b559351fe 100644 --- a/crates/anvil/src/eth/miner.rs +++ b/crates/anvil/src/eth/miner.rs @@ -27,8 +27,6 @@ pub struct Miner { inner: Arc, } -// === impl Miner === - impl Miner { /// Returns a new miner with that operates in the given `mode` pub fn new(mode: MiningMode) -> Self { @@ -79,8 +77,6 @@ pub struct MinerInner { waker: AtomicWaker, } -// === impl MinerInner === - impl MinerInner { /// Call the waker again fn wake(&self) { @@ -112,11 +108,9 @@ pub enum MiningMode { FixedBlockTime(FixedBlockTimeMiner), } -// === impl MiningMode === - impl MiningMode { pub fn instant(max_transactions: usize, listener: Receiver) -> Self { - MiningMode::Auto(ReadyTransactionMiner { + Self::Auto(ReadyTransactionMiner { max_transactions, has_pending_txs: None, rx: listener.fuse(), @@ -124,7 +118,7 @@ impl MiningMode { } pub fn interval(duration: Duration) -> Self { - MiningMode::FixedBlockTime(FixedBlockTimeMiner::new(duration)) + Self::FixedBlockTime(FixedBlockTimeMiner::new(duration)) } /// polls the [Pool] and returns those transactions that should be put in a block, if any. @@ -134,9 +128,9 @@ impl MiningMode { cx: &mut Context<'_>, ) -> Poll>> { match self { - MiningMode::None => Poll::Pending, - MiningMode::Auto(miner) => miner.poll(pool, cx), - MiningMode::FixedBlockTime(miner) => miner.poll(pool, cx), + Self::None => Poll::Pending, + Self::Auto(miner) => miner.poll(pool, cx), + Self::FixedBlockTime(miner) => miner.poll(pool, cx), } } } @@ -151,8 +145,6 @@ pub struct FixedBlockTimeMiner { interval: Interval, } -// === impl FixedBlockTimeMiner === - impl FixedBlockTimeMiner { /// Creates a new instance with an interval of `duration` pub fn new(duration: Duration) -> Self { @@ -185,8 +177,6 @@ pub struct ReadyTransactionMiner { rx: Fuse>, } -// === impl ReadyTransactionMiner === - impl ReadyTransactionMiner { fn poll(&mut self, pool: &Arc, cx: &mut Context<'_>) -> Poll>> { // drain the notification stream diff --git a/crates/anvil/src/eth/otterscan/api.rs b/crates/anvil/src/eth/otterscan/api.rs index ac092f36c..a830855d4 100644 --- a/crates/anvil/src/eth/otterscan/api.rs +++ b/crates/anvil/src/eth/otterscan/api.rs @@ -7,18 +7,16 @@ use crate::eth::{ macros::node_info, EthApi, }; -use alloy_primitives::{Address, Bytes, B256, U256, U64}; -use alloy_rpc_trace_types::parity::{ - Action, CallAction, CreateAction, CreateOutput, RewardAction, TraceOutput, -}; -use alloy_rpc_types::{Block, BlockId, BlockNumberOrTag as BlockNumber, Transaction}; +use alloy_primitives::{Address, Bytes, B256, U256}; +use alloy_rpc_types::{Block, BlockId, BlockNumberOrTag as BlockNumber}; +use alloy_rpc_types_trace::parity::{Action, CreateAction, CreateOutput, TraceOutput}; use itertools::Itertools; impl EthApi { - /// Otterscan currently requires this endpoint, even though it's not part of the ots_* - /// https://github.com/otterscan/otterscan/blob/071d8c55202badf01804f6f8d53ef9311d4a9e47/src/useProvider.ts#L71 + /// 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 + /// 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!("ots_getApiLevel"); @@ -26,8 +24,8 @@ impl EthApi { self.backend.block_by_number(number).await } - /// As per the latest Otterscan source code, at least version 8 is needed - /// https://github.com/otterscan/otterscan/blob/071d8c55202badf01804f6f8d53ef9311d4a9e47/src/params.ts#L1C2-L1C2 + /// 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"); @@ -64,16 +62,16 @@ impl EthApi { } /// Given a transaction hash, returns its raw revert reason. - pub async fn ots_get_transaction_error(&self, hash: B256) -> Result> { + 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) { - if receipt.inner.status_code == Some(U64::ZERO) { - return Ok(receipt.out.map(|b| b.0.into())) + if !receipt.inner.inner.as_receipt_with_bloom().receipt.status.coerce_status() { + return Ok(receipt.out.map(|b| b.0.into()).unwrap_or(Bytes::default())) } } - Ok(Default::default()) + Ok(Bytes::default()) } /// For simplicity purposes, we return the entire block instead of emptying the values that @@ -134,38 +132,31 @@ impl EthApi { 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 }; + 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 first_page = from >= best; let mut last_page = false; let mut res: Vec<_> = vec![]; for n in (to..=from).rev() { - if n == to { - last_page = true; - } - if let Some(traces) = self.backend.mined_parity_trace_block(n) { let hashes = traces .into_iter() .rev() - .filter_map(|trace| match trace.trace.action { - Action::Call(CallAction { from, to, .. }) - if from == address || to == address => - { - trace.transaction_hash - } - _ => None, - }) + .filter_map(|trace| OtsSearchTransactions::mentions_address(trace, address)) .unique(); - res.extend(hashes); - if res.len() >= page_size { break } + + res.extend(hashes); + } + + if n == to { + last_page = true; } } @@ -183,20 +174,17 @@ impl EthApi { let best = self.backend.best_number(); // we go from the first post-fork block, up to the tip - let from = if block_number == 0 { - self.get_fork().map(|f| f.block_number() + 1).unwrap_or(1) - } else { - block_number - }; + 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 first_page = from == best; + let mut first_page = from >= best; let mut last_page = false; let mut res: Vec<_> = vec![]; for n in from..=to { - if n == to { + if n == first_block { last_page = true; } @@ -204,30 +192,23 @@ impl EthApi { let hashes = traces .into_iter() .rev() - .filter_map(|trace| match trace.trace.action { - Action::Call(CallAction { from, to, .. }) - if from == address || to == address => - { - trace.transaction_hash - } - Action::Create(CreateAction { from, .. }) if from == address => { - trace.transaction_hash - } - Action::Reward(RewardAction { author, .. }) if author == address => { - trace.transaction_hash - } - _ => None, - }) + .filter_map(|trace| OtsSearchTransactions::mentions_address(trace, address)) .unique(); - res.extend(hashes); - 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(); OtsSearchTransactions::build(res, &self.backend, first_page, last_page).await } @@ -238,7 +219,7 @@ impl EthApi { &self, address: Address, nonce: U256, - ) -> Result> { + ) -> Result> { node_info!("ots_getTransactionBySenderAndNonce"); let from = self.get_fork().map(|f| f.block_number() + 1).unwrap_or_default(); @@ -248,7 +229,7 @@ impl EthApi { 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)) + return Ok(Some(tx.hash)) } } } diff --git a/crates/anvil/src/eth/otterscan/types.rs b/crates/anvil/src/eth/otterscan/types.rs index ff1f99ce5..048e264a3 100644 --- a/crates/anvil/src/eth/otterscan/types.rs +++ b/crates/anvil/src/eth/otterscan/types.rs @@ -2,10 +2,15 @@ use crate::eth::{ backend::mem::{storage::MinedTransaction, Backend}, error::{BlockchainError, Result}, }; -use alloy_primitives::{Address, Bytes, B256, U256 as rU256, U256}; -use alloy_rpc_trace_types::parity::{Action, CallType, LocalizedTransactionTrace}; -use alloy_rpc_types::{Block, BlockTransactions, Transaction, TransactionReceipt}; -use foundry_evm::{revm::interpreter::InstructionResult, traces::CallKind}; +use alloy_primitives::{Address, Bytes, FixedBytes, B256, U256}; +use alloy_rpc_types::{Block, BlockTransactions, Transaction}; +use alloy_rpc_types_trace::parity::{ + Action, CallAction, CallType, CreateAction, CreateOutput, LocalizedTransactionTrace, + RewardAction, TraceOutput, +}; +use alloy_serde::WithOtherFields; +use anvil_core::eth::transaction::ReceiptResponse; +use foundry_evm::traces::CallKind; use futures::future::join_all; use serde::Serialize; use serde_repr::Serialize_repr; @@ -40,7 +45,7 @@ pub struct Issuance { #[derive(Clone, Serialize, Debug)] pub struct OtsBlockTransactions { pub fullblock: OtsBlock, - pub receipts: Vec, + pub receipts: Vec, } /// Patched Receipt struct, to include the additional `timestamp` field expected by Otterscan @@ -48,7 +53,7 @@ pub struct OtsBlockTransactions { #[serde(rename_all = "camelCase")] pub struct OtsTransactionReceipt { #[serde(flatten)] - receipt: TransactionReceipt, + receipt: ReceiptResponse, timestamp: u64, } @@ -63,13 +68,15 @@ pub struct OtsContractCreator { #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct OtsSearchTransactions { - pub txs: Vec, + pub txs: Vec>, pub receipts: Vec, pub first_page: bool, pub last_page: bool, } -/// Otterscan format for listing relevant internal operations +/// Otterscan format for listing relevant internal operations. +/// +/// Ref: #[derive(Debug, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct OtsInternalOperation { @@ -79,7 +86,9 @@ pub struct OtsInternalOperation { pub value: U256, } -/// Types of internal operations recognized by Otterscan +/// Types of internal operations recognized by Otterscan. +/// +/// Ref: #[derive(Debug, PartialEq, Serialize_repr)] #[repr(u8)] pub enum OtsInternalOperationType { @@ -98,6 +107,8 @@ pub struct OtsTrace { pub to: Address, pub value: U256, pub input: Bytes, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub output: Option, } /// The type of call being described by an Otterscan trace. Only CALL, STATICCALL and DELEGATECALL @@ -113,41 +124,47 @@ pub enum OtsTraceType { impl OtsBlockDetails { /// 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://github.com/otterscan/otterscan/blob/develop/docs/custom-jsonrpc.md#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: + /// The [original spec](https://github.com/otterscan/otterscan/blob/develop/docs/custom-jsonrpc.md#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. + /// based on the existing list. + /// /// Therefore we keep it simple by keeping the data in the response pub async fn build(block: Block, backend: &Backend) -> Result { - let block_txs = match block.transactions.clone() { - BlockTransactions::Full(txs) => txs.into_iter().map(|tx| tx.hash).collect(), - BlockTransactions::Hashes(txs) => txs, - BlockTransactions::Uncle => return Err(BlockchainError::DataUnavailable), - }; - let receipts_futs = - block_txs.iter().map(|tx| async { backend.transaction_receipt(*tx).await }); + if block.transactions.is_uncle() { + return Err(BlockchainError::DataUnavailable); + } + let receipts_futs = block + .transactions + .hashes() + .map(|hash| async { backend.transaction_receipt(*hash).await }); // fetch all receipts - let receipts: Vec = join_all(receipts_futs) + let receipts = join_all(receipts_futs) .await .into_iter() .map(|r| match r { Ok(Some(r)) => Ok(r), _ => Err(BlockchainError::DataUnavailable), }) - .collect::>()?; + .collect::>>()?; - let total_fees = receipts.iter().fold(U256::ZERO, |acc, receipt| { - acc + receipt.gas_used.unwrap() * (U256::from(receipt.effective_gas_price)) - }); + let total_fees = receipts + .iter() + .fold(0, |acc, receipt| acc + receipt.gas_used * receipt.effective_gas_price); Ok(Self { block: block.into(), - total_fees, + total_fees: U256::from(total_fees), // issuance has no meaningful value in anvil's backend. just default to 0 issuance: Default::default(), }) @@ -158,32 +175,24 @@ impl OtsBlockDetails { /// which includes the `transaction_count` field impl From for OtsBlock { fn from(block: Block) -> Self { - let transaction_count = match block.transactions { - BlockTransactions::Full(ref txs) => txs.len(), - BlockTransactions::Hashes(ref txs) => txs.len(), - BlockTransactions::Uncle => 0, - }; - - Self { block, transaction_count } + Self { transaction_count: block.transactions.len(), block } } } impl OtsBlockTransactions { - /// Fetches all receipts for the blocks's transactions, as required by the [`ots_getBlockTransactions`](https://github.com/otterscan/otterscan/blob/develop/docs/custom-jsonrpc.md#ots_getblockdetails) endpoint spec, and returns the final response object. + /// 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://github.com/otterscan/otterscan/blob/develop/docs/custom-jsonrpc.md#ots_getblockdetails pub async fn build( mut block: Block, backend: &Backend, page: usize, page_size: usize, ) -> Result { - let block_txs = match block.transactions.clone() { - BlockTransactions::Full(txs) => txs.into_iter().map(|tx| tx.hash).collect(), - BlockTransactions::Hashes(txs) => txs, - BlockTransactions::Uncle => return Err(BlockchainError::DataUnavailable), - }; - - let block_txs = - block_txs.into_iter().skip(page * page_size).take(page_size).collect::>(); + if block.transactions.is_uncle() { + return Err(BlockchainError::DataUnavailable); + } block.transactions = match block.transactions { BlockTransactions::Full(txs) => BlockTransactions::Full( @@ -192,13 +201,13 @@ impl OtsBlockTransactions { BlockTransactions::Hashes(txs) => BlockTransactions::Hashes( txs.into_iter().skip(page * page_size).take(page_size).collect(), ), - BlockTransactions::Uncle => return Err(BlockchainError::DataUnavailable), + BlockTransactions::Uncle => unreachable!(), }; let receipt_futs = - block_txs.iter().map(|tx| async { backend.transaction_receipt(*tx).await }); + block.transactions.hashes().map(|hash| backend.transaction_receipt(*hash)); - let receipts: Vec = join_all(receipt_futs) + let receipts = join_all(receipt_futs) .await .into_iter() .map(|r| match r { @@ -225,7 +234,7 @@ impl OtsSearchTransactions { ) -> Result { let txs_futs = hashes.iter().map(|hash| async { backend.transaction_by_hash(*hash).await }); - let txs: Vec = join_all(txs_futs) + let txs: Vec<_> = join_all(txs_futs) .await .into_iter() .map(|t| match t { @@ -237,11 +246,8 @@ impl OtsSearchTransactions { join_all(hashes.iter().map(|hash| async { match backend.transaction_receipt(*hash).await { Ok(Some(receipt)) => { - let timestamp = backend - .get_block(receipt.block_number.unwrap().to::()) - .unwrap() - .header - .timestamp; + let timestamp = + backend.get_block(receipt.block_number.unwrap()).unwrap().header.timestamp; Ok(OtsTransactionReceipt { receipt, timestamp }) } Ok(None) => Err(BlockchainError::DataUnavailable), @@ -253,47 +259,56 @@ impl OtsSearchTransactions { .collect::>>() .map(|receipts| Self { txs, receipts, first_page, last_page }) } + + pub fn mentions_address( + trace: LocalizedTransactionTrace, + address: Address, + ) -> Option> { + match (trace.trace.action, trace.trace.result) { + (Action::Call(CallAction { from, to, .. }), _) if from == address || to == address => { + trace.transaction_hash + } + (_, Some(TraceOutput::Create(CreateOutput { address: created_address, .. }))) + if created_address == address => + { + trace.transaction_hash + } + (Action::Create(CreateAction { from, .. }), _) if from == address => { + trace.transaction_hash + } + (Action::Reward(RewardAction { author, .. }), _) if author == address => { + trace.transaction_hash + } + _ => None, + } + } } impl OtsInternalOperation { /// Converts a batch of traces into a batch of internal operations, to comply with the spec for /// [`ots_getInternalOperations`](https://github.com/otterscan/otterscan/blob/develop/docs/custom-jsonrpc.md#ots_getinternaloperations) - pub fn batch_build(traces: MinedTransaction) -> Vec { + pub fn batch_build(traces: MinedTransaction) -> Vec { traces .info .traces .iter() .filter_map(|node| { - match (node.trace.kind, node.trace.status) { - (CallKind::Call, _) if node.trace.value != rU256::ZERO => Some(Self { - r#type: OtsInternalOperationType::Transfer, - from: node.trace.caller, - to: node.trace.address, - value: node.trace.value, - }), - (CallKind::Create, _) => Some(Self { - r#type: OtsInternalOperationType::Create, - from: node.trace.caller, - to: node.trace.address, - value: node.trace.value, - }), - (CallKind::Create2, _) => Some(Self { - r#type: OtsInternalOperationType::Create2, - from: node.trace.caller, - to: node.trace.address, - value: node.trace.value, - }), - (_, InstructionResult::SelfDestruct) => { - Some(Self { - r#type: OtsInternalOperationType::SelfDestruct, - from: node.trace.address, - // the foundry CallTraceNode doesn't have a refund address - to: Default::default(), - value: node.trace.value, - }) + let r#type = match node.trace.kind { + _ if node.is_selfdestruct() => OtsInternalOperationType::SelfDestruct, + CallKind::Call if node.trace.value != U256::ZERO => { + OtsInternalOperationType::Transfer } - _ => None, + CallKind::Create => OtsInternalOperationType::Create, + CallKind::Create2 => OtsInternalOperationType::Create2, + _ => return None, + }; + let mut from = node.trace.caller; + let mut to = node.trace.address; + if node.is_selfdestruct() { + from = node.trace.address; + to = node.trace.selfdestruct_refund_target.unwrap_or_default(); } + Some(Self { r#type, from, to, value: node.trace.value }) }) .collect() } @@ -308,21 +323,20 @@ impl OtsTrace { .filter_map(|trace| match trace.trace.action { Action::Call(call) => { if let Ok(ots_type) = call.call_type.try_into() { - Some(OtsTrace { + Some(Self { r#type: ots_type, depth: trace.trace.trace_address.len(), from: call.from, to: call.to, value: call.value, input: call.input.0.into(), + output: None, }) } else { None } } - Action::Create(_) => None, - Action::Selfdestruct(_) => None, - Action::Reward(_) => None, + Action::Create(_) | Action::Selfdestruct(_) | Action::Reward(_) => None, }) .collect() } @@ -333,9 +347,9 @@ impl TryFrom for OtsTraceType { fn try_from(value: CallType) -> std::result::Result { match value { - CallType::Call => Ok(OtsTraceType::Call), - CallType::StaticCall => Ok(OtsTraceType::StaticCall), - CallType::DelegateCall => Ok(OtsTraceType::DelegateCall), + CallType::Call => Ok(Self::Call), + CallType::StaticCall => Ok(Self::StaticCall), + CallType::DelegateCall => Ok(Self::DelegateCall), _ => Err(()), } } diff --git a/crates/anvil/src/eth/pool/mod.rs b/crates/anvil/src/eth/pool/mod.rs index 8848a954b..9ef45ace2 100644 --- a/crates/anvil/src/eth/pool/mod.rs +++ b/crates/anvil/src/eth/pool/mod.rs @@ -36,7 +36,7 @@ use crate::{ }, mem::storage::MinedBlockOutcome, }; -use alloy_primitives::{TxHash, U64}; +use alloy_primitives::{Address, TxHash, U64}; use alloy_rpc_types::txpool::TxpoolStatus; use anvil_core::eth::transaction::PendingTransaction; use futures::channel::mpsc::{channel, Receiver, Sender}; @@ -75,8 +75,8 @@ impl Pool { /// 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::from(self.ready_transactions().count()); - let queued = U64::from(self.inner.read().pending_transactions.len()); + let pending: u64 = self.ready_transactions().count().try_into().unwrap_or(0); + let queued: u64 = self.inner.read().pending_transactions.len().try_into().unwrap_or(0); TxpoolStatus { pending, queued } } @@ -141,6 +141,11 @@ impl Pool { 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. @@ -161,6 +166,12 @@ impl Pool { dropped } + /// Removes all transactions from the pool + pub fn clear(&self) { + let mut pool = self.inner.write(); + pool.clear(); + } + /// notifies all listeners about the transaction fn notify_listener(&self, hash: TxHash) { let mut listener = self.transaction_listener.lock(); @@ -206,6 +217,12 @@ impl PoolInner { self.ready_transactions.get_transactions() } + /// Clears + fn clear(&mut self) { + self.ready_transactions.clear(); + self.pending_transactions.clear(); + } + /// checks both pools for the matching transaction /// /// Returns `None` if the transaction does not exist in the pool @@ -218,6 +235,24 @@ impl PoolInner { ) } + /// 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) @@ -342,6 +377,25 @@ impl PoolInner { removed } + + /// Remove transactions by sender address + pub 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); + + removed + } } /// Represents the outcome of a prune @@ -410,8 +464,8 @@ pub enum AddedTransaction { impl AddedTransaction { pub fn hash(&self) -> &TxHash { match self { - AddedTransaction::Ready(tx) => &tx.hash, - AddedTransaction::Pending { hash } => hash, + Self::Ready(tx) => &tx.hash, + Self::Pending { hash } => hash, } } } diff --git a/crates/anvil/src/eth/pool/transactions.rs b/crates/anvil/src/eth/pool/transactions.rs index 312bde481..026268231 100644 --- a/crates/anvil/src/eth/pool/transactions.rs +++ b/crates/anvil/src/eth/pool/transactions.rs @@ -1,5 +1,5 @@ use crate::eth::{error::PoolError, util::hex_fmt_many}; -use alloy_primitives::{Address, TxHash, U256}; +use alloy_primitives::{Address, TxHash}; use anvil_core::eth::transaction::{PendingTransaction, TypedTransaction}; use parking_lot::RwLock; use std::{ @@ -37,14 +37,12 @@ pub enum TransactionOrder { Fees, } -// === impl TransactionOrder === - impl TransactionOrder { /// Returns the priority of the transactions pub fn priority(&self, tx: &TypedTransaction) -> TransactionPriority { match self { - TransactionOrder::Fifo => TransactionPriority::default(), - TransactionOrder::Fees => TransactionPriority(tx.gas_price()), + Self::Fifo => TransactionPriority::default(), + Self::Fees => TransactionPriority(tx.gas_price()), } } } @@ -55,8 +53,8 @@ impl FromStr for TransactionOrder { fn from_str(s: &str) -> Result { let s = s.to_lowercase(); let order = match s.as_str() { - "fees" => TransactionOrder::Fees, - "fifo" => TransactionOrder::Fifo, + "fees" => Self::Fees, + "fifo" => Self::Fifo, _ => return Err(format!("Unknown TransactionOrder: `{s}`")), }; Ok(order) @@ -65,10 +63,10 @@ impl FromStr for TransactionOrder { /// Metric value for the priority of a transaction. /// -/// The `TransactionPriority` determines the ordering of two transactions that have all their +/// 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 U256); +pub struct TransactionPriority(pub u128); /// Internal Transaction type #[derive(Clone, PartialEq, Eq)] @@ -92,7 +90,7 @@ impl PoolTransaction { } /// Returns the gas pric of this transaction - pub fn gas_price(&self) -> U256 { + pub fn gas_price(&self) -> u128 { self.pending_transaction.transaction.gas_price() } } @@ -134,6 +132,13 @@ impl PendingTransactions { 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()) @@ -377,6 +382,13 @@ impl ReadyTransactions { } } + /// 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) @@ -397,12 +409,12 @@ impl ReadyTransactions { id } - /// Adds a new transactions to the ready queue + /// Adds a new transactions to the ready queue. /// /// # Panics /// - /// if the pending transaction is not ready: [PendingTransaction::is_ready()] - /// or the transaction is already included + /// If the pending transaction is not ready ([`PendingPoolTransaction::is_ready`]) + /// or the transaction is already included. pub fn add_transaction( &mut self, tx: PendingPoolTransaction, @@ -672,14 +684,12 @@ pub struct ReadyTransaction { pub requires_offset: usize, } -// === impl ReadyTransaction == - impl ReadyTransaction { pub fn provides(&self) -> &[TxMarker] { &self.transaction.transaction.provides } - pub fn gas_price(&self) -> U256 { + pub fn gas_price(&self) -> u128 { self.transaction.transaction.gas_price() } } diff --git a/crates/anvil/src/eth/sign.rs b/crates/anvil/src/eth/sign.rs index 34b3fa285..c122d54dd 100644 --- a/crates/anvil/src/eth/sign.rs +++ b/crates/anvil/src/eth/sign.rs @@ -1,8 +1,10 @@ use crate::eth::error::BlockchainError; +use alloy_consensus::{SignableTransaction, Signed}; use alloy_dyn_abi::TypedData; -use alloy_network::{Signed, Transaction}; -use alloy_primitives::{Address, Signature, B256, U256}; -use alloy_signer::{LocalWallet, Signer as AlloySigner, SignerSync as AlloySignerSync}; +use alloy_network::TxSignerSync; +use alloy_primitives::{Address, Signature, B256}; +use alloy_signer::Signer as AlloySigner; +use alloy_signer_local::PrivateKeySigner; use anvil_core::eth::transaction::{ optimism::{DepositTransaction, DepositTransactionRequest}, TypedTransaction, TypedTransactionRequest, @@ -45,11 +47,11 @@ pub trait Signer: Send + Sync { /// Maintains developer keys pub struct DevSigner { addresses: Vec
, - accounts: HashMap, + accounts: HashMap, } impl DevSigner { - pub fn new(accounts: Vec) -> Self { + pub fn new(accounts: Vec) -> Self { let addresses = accounts.iter().map(|wallet| wallet.address()).collect::>(); let accounts = addresses.iter().cloned().zip(accounts).collect(); Self { addresses, accounts } @@ -84,17 +86,13 @@ impl Signer for DevSigner { // typed data. signer.set_chain_id(None); - Ok(signer - .sign_hash( - payload.eip712_signing_hash().map_err(|_| BlockchainError::NoSignerAvailable)?, - ) - .await?) + 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?) + Ok(signer.sign_hash(&hash).await?) } fn sign_transaction( @@ -107,6 +105,7 @@ impl Signer for DevSigner { TypedTransactionRequest::Legacy(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), TypedTransactionRequest::EIP2930(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), TypedTransactionRequest::EIP1559(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), + TypedTransactionRequest::EIP4844(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), TypedTransactionRequest::Deposit(mut tx) => Ok(signer.sign_transaction_sync(&mut tx)?), } } @@ -134,6 +133,10 @@ pub fn build_typed_transaction( let sighash = tx.signature_hash(); TypedTransaction::EIP1559(Signed::new_unchecked(tx, signature, sighash)) } + TypedTransactionRequest::EIP4844(tx) => { + let sighash = tx.signature_hash(); + TypedTransaction::EIP4844(Signed::new_unchecked(tx, signature, sighash)) + } TypedTransactionRequest::Deposit(tx) => { let DepositTransactionRequest { from, @@ -155,7 +158,7 @@ pub fn build_typed_transaction( source_hash, mint, is_system_tx, - nonce: U256::ZERO, + nonce: 0, }) } }; diff --git a/crates/anvil/src/eth/util.rs b/crates/anvil/src/eth/util.rs index 5153178f5..6bcde67d5 100644 --- a/crates/anvil/src/eth/util.rs +++ b/crates/anvil/src/eth/util.rs @@ -1,9 +1,12 @@ use alloy_primitives::Address; -use foundry_evm::revm::{self, precompile::Precompiles, primitives::SpecId}; +use foundry_evm::revm::{ + precompile::{PrecompileSpecId, Precompiles}, + primitives::SpecId, +}; use std::fmt; pub fn get_precompiles_for(spec_id: SpecId) -> Vec
{ - Precompiles::new(to_precompile_id(spec_id)).addresses().into_iter().copied().collect() + Precompiles::new(PrecompileSpecId::from_spec_id(spec_id)).addresses().copied().collect() } /// wrapper type that displays byte as hex @@ -53,29 +56,3 @@ impl<'a> fmt::Debug for HexDisplay<'a> { Ok(()) } } - -pub fn to_precompile_id(spec_id: SpecId) -> revm::precompile::SpecId { - match spec_id { - SpecId::FRONTIER | - SpecId::FRONTIER_THAWING | - SpecId::HOMESTEAD | - SpecId::DAO_FORK | - SpecId::TANGERINE | - SpecId::SPURIOUS_DRAGON => revm::precompile::SpecId::HOMESTEAD, - SpecId::BYZANTIUM | SpecId::CONSTANTINOPLE | SpecId::PETERSBURG => { - revm::precompile::SpecId::BYZANTIUM - } - SpecId::ISTANBUL | SpecId::MUIR_GLACIER => revm::precompile::SpecId::ISTANBUL, - SpecId::BERLIN | - SpecId::LONDON | - SpecId::ARROW_GLACIER | - SpecId::GRAY_GLACIER | - SpecId::MERGE | - SpecId::SHANGHAI | - SpecId::CANCUN | - SpecId::BEDROCK | - SpecId::REGOLITH | - SpecId::CANYON | - SpecId::LATEST => revm::precompile::SpecId::BERLIN, - } -} diff --git a/crates/anvil/src/evm.rs b/crates/anvil/src/evm.rs new file mode 100644 index 000000000..794d2ce85 --- /dev/null +++ b/crates/anvil/src/evm.rs @@ -0,0 +1,81 @@ +use alloy_primitives::Address; +use foundry_evm::revm::precompile::Precompile; +use std::{fmt::Debug, sync::Arc}; + +/// Object-safe trait that enables injecting extra precompiles when using +/// `anvil` as a library. +pub trait PrecompileFactory: Send + Sync + Unpin + Debug { + /// Returns a set of precompiles to extend the EVM with. + fn precompiles(&self) -> Vec<(Address, Precompile)>; +} + +/// Appends a handler register to `evm` that injects the given `precompiles`. +/// +/// This will add an additional handler that extends the default precompiles with the given set of +/// precompiles. +pub fn inject_precompiles( + evm: &mut revm::Evm<'_, I, DB>, + precompiles: Vec<(Address, Precompile)>, +) { + evm.handler.append_handler_register_box(Box::new(move |handler| { + let precompiles = precompiles.clone(); + let prev = handler.pre_execution.load_precompiles.clone(); + handler.pre_execution.load_precompiles = Arc::new(move || { + let mut cx = prev(); + cx.extend(precompiles.iter().cloned().map(|(a, b)| (a, b.into()))); + cx + }); + })); +} + +#[cfg(test)] +mod tests { + use crate::{evm::inject_precompiles, PrecompileFactory}; + use alloy_primitives::Address; + use foundry_evm::revm::primitives::{address, Bytes, Precompile, PrecompileResult, SpecId}; + use revm::primitives::PrecompileOutput; + + #[test] + fn build_evm_with_extra_precompiles() { + const PRECOMPILE_ADDR: Address = address!("0000000000000000000000000000000000000071"); + + fn my_precompile(_bytes: &Bytes, _gas_limit: u64) -> PrecompileResult { + Ok(PrecompileOutput { bytes: Bytes::new(), gas_used: 0 }) + } + + #[derive(Debug)] + struct CustomPrecompileFactory; + + impl PrecompileFactory for CustomPrecompileFactory { + fn precompiles(&self) -> Vec<(Address, Precompile)> { + vec![(PRECOMPILE_ADDR, Precompile::Standard(my_precompile))] + } + } + + let db = revm::db::EmptyDB::default(); + let env = Box::::default(); + let spec = SpecId::LATEST; + let handler_cfg = revm::primitives::HandlerCfg::new(spec); + let inspector = revm::inspectors::NoOpInspector; + let context = revm::Context::new(revm::EvmContext::new_with_env(db, env), inspector); + let handler = revm::Handler::new(handler_cfg); + let mut evm = revm::Evm::new(context, handler); + assert!(!evm + .handler + .pre_execution() + .load_precompiles() + .addresses() + .any(|&addr| addr == PRECOMPILE_ADDR)); + + inject_precompiles(&mut evm, CustomPrecompileFactory.precompiles()); + assert!(evm + .handler + .pre_execution() + .load_precompiles() + .addresses() + .any(|&addr| addr == PRECOMPILE_ADDR)); + + let result = evm.transact().unwrap(); + assert!(result.result.is_success()); + } +} diff --git a/crates/anvil/src/filter.rs b/crates/anvil/src/filter.rs index 60dce6b66..2144ce27c 100644 --- a/crates/anvil/src/filter.rs +++ b/crates/anvil/src/filter.rs @@ -5,7 +5,7 @@ use crate::{ StorageInfo, }; use alloy_primitives::TxHash; -use alloy_rpc_types::{Filter, FilteredParams, Log as AlloyLog}; +use alloy_rpc_types::{Filter, FilteredParams, Log}; use anvil_core::eth::subscription::SubscriptionId; use anvil_rpc::response::ResponseResult; use futures::{channel::mpsc::Receiver, Stream, StreamExt}; @@ -33,8 +33,6 @@ pub struct Filters { keepalive: Duration, } -// === impl Filters === - impl Filters { /// Adds a new `EthFilter` to the set pub async fn add_filter(&self, filter: EthFilter) -> String { @@ -123,23 +121,21 @@ pub enum EthFilter { PendingTransactions(Receiver), } -// === impl EthFilter === - impl Stream for EthFilter { type Item = ResponseResult; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let pin = self.get_mut(); match pin { - EthFilter::Logs(logs) => Poll::Ready(Some(Ok(logs.poll(cx)).to_rpc_result())), - EthFilter::Blocks(blocks) => { + 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())) } - EthFilter::PendingTransactions(tx) => { + 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); @@ -162,14 +158,12 @@ pub struct LogsFilter { /// existing logs that matched the filter when the listener was installed /// /// They'll be returned on the first pill - pub historic: Option>, + pub historic: Option>, } -// === impl LogsFilter === - impl LogsFilter { /// Returns all the logs since the last time this filter was polled - pub fn poll(&mut self, cx: &mut Context<'_>) -> Vec { + 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); diff --git a/crates/anvil/src/hardfork.rs b/crates/anvil/src/hardfork.rs index a5e92284c..3c6eb0aa8 100644 --- a/crates/anvil/src/hardfork.rs +++ b/crates/anvil/src/hardfork.rs @@ -1,9 +1,8 @@ use alloy_rpc_types::BlockNumberOrTag; -use ethereum_forkid::{ForkHash, ForkId}; use foundry_evm::revm::primitives::SpecId; use std::str::FromStr; -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Hardfork { Frontier, Homestead, @@ -30,76 +29,22 @@ impl Hardfork { /// Get the first block number of the hardfork. pub fn fork_block(&self) -> u64 { match *self { - Hardfork::Frontier => 0, - Hardfork::Homestead => 1150000, - Hardfork::Dao => 1920000, - Hardfork::Tangerine => 2463000, - Hardfork::SpuriousDragon => 2675000, - Hardfork::Byzantium => 4370000, - Hardfork::Constantinople | Hardfork::Petersburg => 7280000, - Hardfork::Istanbul => 9069000, - Hardfork::Muirglacier => 9200000, - Hardfork::Berlin => 12244000, - Hardfork::London => 12965000, - Hardfork::ArrowGlacier => 13773000, - Hardfork::GrayGlacier => 15050000, - Hardfork::Paris => 15537394, - Hardfork::Shanghai | Hardfork::Latest => 17034870, - - // TODO: set block number after activation - Hardfork::Cancun => unreachable!(), - } - } - - /// Get the EIP-2124 fork id for a given hardfork - /// - /// The [`ForkId`](ethereum_forkid::ForkId) includes a CRC32 checksum of the all fork block - /// numbers from genesis, and the next upcoming fork block number. - /// If the next fork block number is not yet known, it is set to 0. - pub fn fork_id(&self) -> ForkId { - match *self { - Hardfork::Frontier => { - ForkId { hash: ForkHash([0xfc, 0x64, 0xec, 0x04]), next: 1150000 } - } - Hardfork::Homestead => { - ForkId { hash: ForkHash([0x97, 0xc2, 0xc3, 0x4c]), next: 1920000 } - } - Hardfork::Dao => ForkId { hash: ForkHash([0x91, 0xd1, 0xf9, 0x48]), next: 2463000 }, - Hardfork::Tangerine => { - ForkId { hash: ForkHash([0x7a, 0x64, 0xda, 0x13]), next: 2675000 } - } - Hardfork::SpuriousDragon => { - ForkId { hash: ForkHash([0x3e, 0xdd, 0x5b, 0x10]), next: 4370000 } - } - Hardfork::Byzantium => { - ForkId { hash: ForkHash([0xa0, 0x0b, 0xc3, 0x24]), next: 7280000 } - } - Hardfork::Constantinople | Hardfork::Petersburg => { - ForkId { hash: ForkHash([0x66, 0x8d, 0xb0, 0xaf]), next: 9069000 } - } - Hardfork::Istanbul => { - ForkId { hash: ForkHash([0x87, 0x9d, 0x6e, 0x30]), next: 9200000 } - } - Hardfork::Muirglacier => { - ForkId { hash: ForkHash([0xe0, 0x29, 0xe9, 0x91]), next: 12244000 } - } - Hardfork::Berlin => ForkId { hash: ForkHash([0x0e, 0xb4, 0x40, 0xf6]), next: 12965000 }, - Hardfork::London => ForkId { hash: ForkHash([0xb7, 0x15, 0x07, 0x7d]), next: 13773000 }, - Hardfork::ArrowGlacier => { - ForkId { hash: ForkHash([0x20, 0xc3, 0x27, 0xfc]), next: 15050000 } - } - Hardfork::GrayGlacier => { - ForkId { hash: ForkHash([0xf0, 0xaf, 0xd0, 0xe3]), next: 15537394 } - } - Hardfork::Paris => ForkId { hash: ForkHash([0x4f, 0xb8, 0xa8, 0x72]), next: 17034870 }, - Hardfork::Shanghai | Hardfork::Latest => { - // update `next` when another fork block num is known - ForkId { hash: ForkHash([0xc1, 0xfd, 0xf1, 0x81]), next: 0 } - } - Hardfork::Cancun => { - // TODO: set fork hash once known - ForkId { hash: ForkHash([0xc1, 0xfd, 0xf1, 0x81]), next: 0 } - } + Self::Frontier => 0, + Self::Homestead => 1150000, + Self::Dao => 1920000, + Self::Tangerine => 2463000, + Self::SpuriousDragon => 2675000, + Self::Byzantium => 4370000, + Self::Constantinople | Self::Petersburg => 7280000, + Self::Istanbul => 9069000, + Self::Muirglacier => 9200000, + Self::Berlin => 12244000, + Self::London => 12965000, + Self::ArrowGlacier => 13773000, + Self::GrayGlacier => 15050000, + Self::Paris => 15537394, + Self::Shanghai => 17034870, + Self::Cancun | Self::Latest => 19426587, } } } @@ -110,24 +55,24 @@ impl FromStr for Hardfork { fn from_str(s: &str) -> Result { let s = s.to_lowercase(); let hardfork = match s.as_str() { - "frontier" | "1" => Hardfork::Frontier, - "homestead" | "2" => Hardfork::Homestead, - "dao" | "3" => Hardfork::Dao, - "tangerine" | "4" => Hardfork::Tangerine, - "spuriousdragon" | "5" => Hardfork::SpuriousDragon, - "byzantium" | "6" => Hardfork::Byzantium, - "constantinople" | "7" => Hardfork::Constantinople, - "petersburg" | "8" => Hardfork::Petersburg, - "istanbul" | "9" => Hardfork::Istanbul, - "muirglacier" | "10" => Hardfork::Muirglacier, - "berlin" | "11" => Hardfork::Berlin, - "london" | "12" => Hardfork::London, - "arrowglacier" | "13" => Hardfork::ArrowGlacier, - "grayglacier" | "14" => Hardfork::GrayGlacier, - "paris" | "merge" | "15" => Hardfork::Paris, - "shanghai" | "16" => Hardfork::Shanghai, - "cancun" | "17" => Hardfork::Cancun, - "latest" => Hardfork::Latest, + "frontier" | "1" => Self::Frontier, + "homestead" | "2" => Self::Homestead, + "dao" | "3" => Self::Dao, + "tangerine" | "4" => Self::Tangerine, + "spuriousdragon" | "5" => Self::SpuriousDragon, + "byzantium" | "6" => Self::Byzantium, + "constantinople" | "7" => Self::Constantinople, + "petersburg" | "8" => Self::Petersburg, + "istanbul" | "9" => Self::Istanbul, + "muirglacier" | "10" => Self::Muirglacier, + "berlin" | "11" => Self::Berlin, + "london" | "12" => Self::London, + "arrowglacier" | "13" => Self::ArrowGlacier, + "grayglacier" | "14" => Self::GrayGlacier, + "paris" | "merge" | "15" => Self::Paris, + "shanghai" | "16" => Self::Shanghai, + "cancun" | "17" => Self::Cancun, + "latest" => Self::Latest, _ => return Err(format!("Unknown hardfork {s}")), }; Ok(hardfork) @@ -137,25 +82,23 @@ impl FromStr for Hardfork { impl From for SpecId { fn from(fork: Hardfork) -> Self { match fork { - Hardfork::Frontier => SpecId::FRONTIER, - Hardfork::Homestead => SpecId::HOMESTEAD, - Hardfork::Dao => SpecId::HOMESTEAD, - Hardfork::Tangerine => SpecId::TANGERINE, - Hardfork::SpuriousDragon => SpecId::SPURIOUS_DRAGON, - Hardfork::Byzantium => SpecId::BYZANTIUM, - Hardfork::Constantinople => SpecId::CONSTANTINOPLE, - Hardfork::Petersburg => SpecId::PETERSBURG, - Hardfork::Istanbul => SpecId::ISTANBUL, - Hardfork::Muirglacier => SpecId::MUIR_GLACIER, - Hardfork::Berlin => SpecId::BERLIN, - Hardfork::London => SpecId::LONDON, - Hardfork::ArrowGlacier => SpecId::LONDON, - Hardfork::GrayGlacier => SpecId::GRAY_GLACIER, - Hardfork::Paris => SpecId::MERGE, - Hardfork::Shanghai | Hardfork::Latest => SpecId::SHANGHAI, - - // TODO: switch to latest after activation - Hardfork::Cancun => SpecId::CANCUN, + Hardfork::Frontier => Self::FRONTIER, + Hardfork::Homestead => Self::HOMESTEAD, + Hardfork::Dao => Self::HOMESTEAD, + Hardfork::Tangerine => Self::TANGERINE, + Hardfork::SpuriousDragon => Self::SPURIOUS_DRAGON, + Hardfork::Byzantium => Self::BYZANTIUM, + Hardfork::Constantinople => Self::CONSTANTINOPLE, + Hardfork::Petersburg => Self::PETERSBURG, + Hardfork::Istanbul => Self::ISTANBUL, + Hardfork::Muirglacier => Self::MUIR_GLACIER, + Hardfork::Berlin => Self::BERLIN, + Hardfork::London => Self::LONDON, + Hardfork::ArrowGlacier => Self::LONDON, + Hardfork::GrayGlacier => Self::GRAY_GLACIER, + Hardfork::Paris => Self::MERGE, + Hardfork::Shanghai => Self::SHANGHAI, + Hardfork::Cancun | Hardfork::Latest => Self::CANCUN, } } } @@ -169,20 +112,21 @@ impl> From for Hardfork { }; match num { - _i if num < 1_150_000 => Hardfork::Frontier, - _i if num < 1_920_000 => Hardfork::Dao, - _i if num < 2_463_000 => Hardfork::Homestead, - _i if num < 2_675_000 => Hardfork::Tangerine, - _i if num < 4_370_000 => Hardfork::SpuriousDragon, - _i if num < 7_280_000 => Hardfork::Byzantium, - _i if num < 9_069_000 => Hardfork::Constantinople, - _i if num < 9_200_000 => Hardfork::Istanbul, - _i if num < 12_244_000 => Hardfork::Muirglacier, - _i if num < 12_965_000 => Hardfork::Berlin, - _i if num < 13_773_000 => Hardfork::London, - _i if num < 15_050_000 => Hardfork::ArrowGlacier, - _i if num < 17_034_870 => Hardfork::Paris, - _ => Hardfork::Latest, + _i if num < 1_150_000 => Self::Frontier, + _i if num < 1_920_000 => Self::Dao, + _i if num < 2_463_000 => Self::Homestead, + _i if num < 2_675_000 => Self::Tangerine, + _i if num < 4_370_000 => Self::SpuriousDragon, + _i if num < 7_280_000 => Self::Byzantium, + _i if num < 9_069_000 => Self::Constantinople, + _i if num < 9_200_000 => Self::Istanbul, + _i if num < 12_244_000 => Self::Muirglacier, + _i if num < 12_965_000 => Self::Berlin, + _i if num < 13_773_000 => Self::London, + _i if num < 15_050_000 => Self::ArrowGlacier, + _i if num < 17_034_870 => Self::Paris, + _i if num < 19_426_587 => Self::Shanghai, + _ => Self::Latest, } } } @@ -190,8 +134,6 @@ impl> From for Hardfork { #[cfg(test)] mod tests { use crate::Hardfork; - use alloy_primitives::hex; - use crc::{Crc, CRC_32_ISO_HDLC}; #[test] fn test_hardfork_blocks() { @@ -204,52 +146,4 @@ mod tests { let hf: Hardfork = 12244000u64.into(); assert_eq!(hf, Hardfork::Berlin); } - - #[test] - // this test checks that the fork hash assigned to forks accurately map to the fork_id method - fn test_forkhash_from_fork_blocks() { - // set the genesis hash - let genesis = - hex::decode("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") - .unwrap(); - - // instantiate the crc "hasher" - let crc_hasher = Crc::::new(&CRC_32_ISO_HDLC); - let mut crc_digest = crc_hasher.digest(); - - // check frontier forkhash - crc_digest.update(&genesis); - - // now we go through enum members - let frontier_forkid = Hardfork::Frontier.fork_id(); - let frontier_forkhash = u32::from_be_bytes(frontier_forkid.hash.0); - // clone the digest for finalization so we can update it again - assert_eq!(crc_digest.clone().finalize(), frontier_forkhash); - - // list of the above hardforks - let hardforks = vec![ - Hardfork::Homestead, - Hardfork::Dao, - Hardfork::Tangerine, - Hardfork::SpuriousDragon, - Hardfork::Byzantium, - Hardfork::Constantinople, - Hardfork::Istanbul, - Hardfork::Muirglacier, - Hardfork::Berlin, - Hardfork::London, - Hardfork::ArrowGlacier, - Hardfork::GrayGlacier, - ]; - - // now loop through each hardfork, conducting each forkhash test - for hardfork in hardforks { - // this could also be done with frontier_forkhash.next, but fork_block is used for more - // coverage - let fork_block = hardfork.fork_block().to_be_bytes(); - crc_digest.update(&fork_block); - let fork_hash = u32::from_be_bytes(hardfork.fork_id().hash.0); - assert_eq!(crc_digest.clone().finalize(), fork_hash); - } - } } diff --git a/crates/anvil/src/lib.rs b/crates/anvil/src/lib.rs index 23a328d7a..471f7b05c 100644 --- a/crates/anvil/src/lib.rs +++ b/crates/anvil/src/lib.rs @@ -1,3 +1,6 @@ +#![doc = include_str!("../README.md")] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + #[macro_use] extern crate tracing; @@ -12,17 +15,19 @@ use crate::{ }, filter::Filters, logging::{LoggingManager, NodeLogLayer}, + server::error::{NodeError, NodeResult}, service::NodeService, shutdown::Signal, tasks::TaskManager, }; use alloy_primitives::{Address, U256}; -use alloy_signer::{LocalWallet, Signer as AlloySigner}; +use alloy_signer_local::PrivateKeySigner; use eth::backend::fork::ClientFork; -use foundry_common::provider::alloy::{ProviderBuilder, RetryProvider}; +use foundry_common::provider::{ProviderBuilder, RetryProvider}; use foundry_evm::revm; use futures::{FutureExt, TryFutureExt}; use parking_lot::Mutex; +use server::try_spawn_ipc; use std::{ future::Future, io, @@ -41,15 +46,15 @@ mod service; mod config; pub use config::{AccountGenerator, NodeConfig, CHAIN_ID, VERSION_MESSAGE}; + mod hardfork; -use crate::server::{ - error::{NodeError, NodeResult}, - spawn_ipc, -}; pub use hardfork::Hardfork; /// ethereum related implementations pub mod eth; +/// Evm related abstractions +mod evm; +pub use evm::{inject_precompiles, PrecompileFactory}; /// support for polling filters pub mod filter; /// commandline output @@ -67,26 +72,56 @@ mod tasks; #[cfg(feature = "cmd")] pub mod cmd; -/// Creates the node and runs the server +/// 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. /// -/// # Example +/// # Panics +/// +/// Panics if any error occurs. For a non-panicking version, use [`try_spawn`]. +/// +/// +/// # Examples /// -/// ```rust +/// ```no_run /// # use anvil::NodeConfig; -/// # async fn spawn() { +/// # async fn spawn() -> eyre::Result<()> { /// let config = NodeConfig::default(); /// let (api, handle) = anvil::spawn(config).await; /// /// // use api /// /// // wait forever -/// handle.await.unwrap(); +/// handle.await.unwrap().unwrap(); +/// # Ok(()) /// # } /// ``` -pub async fn spawn(mut config: NodeConfig) -> (EthApi, NodeHandle) { +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) -> io::Result<(EthApi, NodeHandle)> { let logger = if config.enable_tracing { init_tracing() } else { Default::default() }; logger.set_enabled(!config.silent); @@ -130,7 +165,7 @@ pub async fn spawn(mut config: NodeConfig) -> (EthApi, NodeHandle) { .alloc .values() .filter_map(|acc| acc.private_key) - .flat_map(|k| LocalWallet::from_bytes(&k)) + .flat_map(|k| PrivateKeySigner::from_bytes(&k)) .collect::>(); if !genesis_signers.is_empty() { signers.push(Box::new(DevSigner::new(genesis_signers))); @@ -171,25 +206,27 @@ pub async fn spawn(mut config: NodeConfig) -> (EthApi, NodeHandle) { let node_service = tokio::task::spawn(NodeService::new(pool, backend, miner, fee_history_service, filters)); - let mut servers = Vec::new(); - let mut addresses = Vec::new(); + let mut servers = Vec::with_capacity(config.host.len()); + let mut addresses = Vec::with_capacity(config.host.len()); - for addr in config.host.iter() { - let sock_addr = SocketAddr::new(addr.to_owned(), port); - let srv = server::serve(sock_addr, api.clone(), server_config.clone()); + for addr in &config.host { + let sock_addr = SocketAddr::new(*addr, port); - addresses.push(srv.local_addr()); + // Create a TCP listener. + let tcp_listener = tokio::net::TcpListener::bind(sock_addr).await?; + addresses.push(tcp_listener.local_addr()?); - // spawn the server on a new task - let srv = tokio::task::spawn(srv.map_err(NodeError::from)); - servers.push(srv); + // 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| spawn_ipc(api.clone(), path)); + let ipc_task = + config.get_ipc_path().map(|path| try_spawn_ipc(api.clone(), path)).transpose()?; let handle = NodeHandle { config, @@ -203,10 +240,10 @@ pub async fn spawn(mut config: NodeConfig) -> (EthApi, NodeHandle) { handle.print(fork.as_ref()); - (api, handle) + Ok((api, handle)) } -type IpcTask = JoinHandle>; +type IpcTask = JoinHandle<()>; /// A handle to the spawned node and server tasks /// @@ -237,6 +274,9 @@ impl NodeHandle { pub(crate) fn print(&self, fork: Option<&ClientFork>) { self.config.print(fork); if !self.config.silent { + if let Some(ipc_path) = self.ipc_path() { + println!("IPC path: {ipc_path}"); + } println!( "Listening on {}", self.addresses @@ -244,7 +284,7 @@ impl NodeHandle { .map(|addr| { addr.to_string() }) .collect::>() .join(", ") - ) + ); } } @@ -274,7 +314,6 @@ impl NodeHandle { /// 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") - // .interval(Duration::from_millis(500)) } /// Constructs a [`RetryProvider`] for this handle's WS endpoint. @@ -293,7 +332,7 @@ impl NodeHandle { } /// Signer accounts that can sign messages/transactions from the EVM node - pub fn dev_wallets(&self) -> impl Iterator + '_ { + pub fn dev_wallets(&self) -> impl Iterator + '_ { self.config.signer_accounts.iter().cloned() } @@ -308,7 +347,7 @@ impl NodeHandle { } /// Default gas price for all txs - pub fn gas_price(&self) -> U256 { + pub fn gas_price(&self) -> u128 { self.config.get_gas_price() } @@ -353,7 +392,7 @@ impl Future for NodeHandle { // 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(|res| res.map_err(NodeError::from))); + return Poll::Ready(res.map(|()| Ok(()))); } else { pin.ipc_task = Some(ipc); } diff --git a/crates/anvil/src/logging.rs b/crates/anvil/src/logging.rs index 5819aafc9..ba97ab7ef 100644 --- a/crates/anvil/src/logging.rs +++ b/crates/anvil/src/logging.rs @@ -17,8 +17,6 @@ pub struct NodeLogLayer { state: LoggingManager, } -// === impl NodeLogLayer === - impl NodeLogLayer { /// Returns a new instance of this layer pub fn new(state: LoggingManager) -> Self { @@ -51,8 +49,6 @@ pub struct LoggingManager { pub enabled: Arc>, } -// === impl LoggingManager === - impl LoggingManager { /// Returns true if logging is currently enabled pub fn is_enabled(&self) -> bool { diff --git a/crates/anvil/src/pubsub.rs b/crates/anvil/src/pubsub.rs index ce2f7e245..21a5c631c 100644 --- a/crates/anvil/src/pubsub.rs +++ b/crates/anvil/src/pubsub.rs @@ -2,10 +2,8 @@ use crate::{ eth::{backend::notifications::NewBlockNotifications, error::to_rpc_result}, StorageInfo, }; -use alloy_consensus::ReceiptWithBloom; -use alloy_network::Sealable; -use alloy_primitives::{Log, TxHash, B256, U256}; -use alloy_rpc_types::{pubsub::SubscriptionResult, FilteredParams, Log as AlloyLog}; +use alloy_primitives::{TxHash, B256}; +use alloy_rpc_types::{pubsub::SubscriptionResult, FilteredParams, Log}; use anvil_core::eth::{block::Block, subscription::SubscriptionId, transaction::TypedReceipt}; use anvil_rpc::{request::Version, response::ResponseResult}; use futures::{channel::mpsc::Receiver, ready, Stream, StreamExt}; @@ -22,12 +20,10 @@ pub struct LogsSubscription { pub blocks: NewBlockNotifications, pub storage: StorageInfo, pub filter: FilteredParams, - pub queued: VecDeque, + pub queued: VecDeque, pub id: SubscriptionId, } -// === impl LogsSubscription === - impl LogsSubscription { fn poll(&mut self, cx: &mut Context<'_>) -> Poll> { loop { @@ -70,8 +66,6 @@ pub struct EthSubscriptionResponse { params: EthSubscriptionParams, } -// === impl EthSubscriptionResponse === - impl EthSubscriptionResponse { pub fn new(params: EthSubscriptionParams) -> Self { Self { jsonrpc: Version::V2, method: "eth_subscription", params } @@ -94,13 +88,11 @@ pub enum EthSubscription { PendingTransactions(Receiver, SubscriptionId), } -// === impl EthSubscription === - impl EthSubscription { fn poll_response(&mut self, cx: &mut Context<'_>) -> Poll> { match self { - EthSubscription::Logs(listener) => listener.poll(cx), - EthSubscription::Header(blocks, storage, id) => { + 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()`] @@ -118,7 +110,7 @@ impl EthSubscription { } } } - EthSubscription::PendingTransactions(tx, id) => { + Self::PendingTransactions(tx, id) => { let res = ready!(tx.poll_next_unpin(cx)) .map(SubscriptionResult::TransactionHash) .map(to_rpc_result) @@ -145,30 +137,20 @@ impl Stream for EthSubscription { } /// Returns all the logs that match the given filter -pub fn filter_logs( - block: Block, - receipts: Vec, - filter: &FilteredParams, -) -> Vec { +pub fn filter_logs(block: Block, receipts: Vec, filter: &FilteredParams) -> Vec { /// Determines whether to add this log - fn add_log(block_hash: B256, l: &Log, block: &Block, params: &FilteredParams) -> bool { - let log = AlloyLog { - address: l.address, - topics: l.topics().to_vec(), - data: l.data.data.clone(), - block_hash: None, - block_number: None, - transaction_hash: None, - transaction_index: None, - log_index: None, - removed: false, - }; + 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(&log) || - !params.filter_topics(&log) + !params.filter_address(&l.address) || + !params.filter_topics(l.topics()) { return false; } @@ -176,29 +158,22 @@ pub fn filter_logs( true } - let block_hash = block.header.hash(); + 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 receipt: ReceiptWithBloom = receipt.into(); - let receipt_logs = receipt.receipt.logs; - let transaction_hash: Option = if !receipt_logs.is_empty() { - Some(block.transactions[receipt_index].hash()) - } else { - None - }; - for log in receipt_logs.into_iter() { - if add_log(block_hash, &log, &block, filter) { - logs.push(AlloyLog { - address: log.address, - topics: log.topics().to_vec(), - data: log.data.data, + let transaction_hash = block.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(U256::from(block.header.number)), - transaction_hash, - transaction_index: Some(U256::from(receipt_index)), - log_index: Some(U256::from(log_index)), + 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; diff --git a/crates/anvil/src/server/handler.rs b/crates/anvil/src/server/handler.rs index 2cab0d5b7..79adb87df 100644 --- a/crates/anvil/src/server/handler.rs +++ b/crates/anvil/src/server/handler.rs @@ -19,8 +19,6 @@ pub struct HttpEthRpcHandler { api: EthApi, } -// === impl WsEthRpcHandler === - impl HttpEthRpcHandler { /// Creates a new instance of the handler using the given `EthApi` pub fn new(api: EthApi) -> Self { diff --git a/crates/anvil/src/server/mod.rs b/crates/anvil/src/server/mod.rs index 10d86f671..c488bcdc1 100644 --- a/crates/anvil/src/server/mod.rs +++ b/crates/anvil/src/server/mod.rs @@ -1,48 +1,67 @@ -//! Contains the code to launch an ethereum RPC-Server -use crate::EthApi; -use anvil_server::{ipc::IpcEndpoint, AnvilServer, ServerConfig}; +//! Contains the code to launch an Ethereum RPC server. + +use crate::{EthApi, IpcTask}; +use anvil_server::{ipc::IpcEndpoint, ServerConfig}; +use axum::Router; use futures::StreamExt; use handler::{HttpEthRpcHandler, PubSubEthRpcHandler}; -use std::net::SocketAddr; -use tokio::{io, task::JoinHandle}; +use std::{future::Future, io, net::SocketAddr, pin::pin}; +use tokio::net::TcpListener; +pub mod error; mod handler; -pub mod error; +/// 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 an [axum::Server] that handles [EthApi] related JSON-RPC calls via HTTP and WS -pub fn serve(addr: SocketAddr, api: EthApi, config: ServerConfig) -> AnvilServer { +/// 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. +pub fn router(api: EthApi, config: ServerConfig) -> Router { let http = HttpEthRpcHandler::new(api.clone()); let ws = PubSubEthRpcHandler::new(api); - anvil_server::serve_http_ws(addr, config, http, ws) + anvil_server::http_ws_router(config, http, ws) } /// Launches an ipc server at the given path in a new task /// /// # Panics /// -/// if setting up the ipc connection was unsuccessful -pub fn spawn_ipc(api: EthApi, path: impl Into) -> JoinHandle> { +/// 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: impl Into, -) -> io::Result>> { - let path = path.into(); +/// 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 { - tokio::pin!(incoming); + let mut incoming = pin!(incoming); while let Some(stream) = incoming.next().await { trace!(target: "ipc", "new ipc connection"); tokio::task::spawn(stream); } - Ok(()) }); Ok(task) diff --git a/crates/anvil/src/service.rs b/crates/anvil/src/service.rs index 18ee20d99..0f70ad3b0 100644 --- a/crates/anvil/src/service.rs +++ b/crates/anvil/src/service.rs @@ -18,22 +18,22 @@ use std::{ sync::Arc, task::{Context, Poll}, }; -use tokio::time::Interval; +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](backend::mem::Backend) to construct a new block, if all transactions were successfully -/// included in a new block they get purged from the `Pool`. +/// 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 + /// The pool that holds all transactions. pool: Arc, - /// creates new blocks + /// Creates new blocks. block_producer: BlockProducer, - /// the miner responsible to select transactions from the `pool´ + /// The miner responsible to select transactions from the `pool`. miner: Miner, - /// maintenance task for fee history related tasks + /// Maintenance task for fee history related tasks. fee_history: FeeHistoryService, /// Tracks all active filters filters: Filters, @@ -101,23 +101,17 @@ impl Future for NodeService { } } -// The type of the future that mines a new block -type BlockMiningFuture = - Pin)> + Send + Sync>>; - /// 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, + block_mining: Option)>>, /// backlog of sets of transactions ready to be mined queued: VecDeque>>, } -// === impl BlockProducer === - impl BlockProducer { fn new(backend: Arc) -> Self { Self { idle_backend: Some(backend), block_mining: None, queued: Default::default() } @@ -133,19 +127,33 @@ impl Stream for BlockProducer { if !pin.queued.is_empty() { if let Some(backend) = pin.idle_backend.take() { let transactions = pin.queued.pop_front().expect("not empty; qed"); - pin.block_mining = Some(Box::pin(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) - })); + + // we spawn this on as blocking task because in 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((outcome, backend)) = mining.poll_unpin(cx) { - pin.idle_backend = Some(backend); - return Poll::Ready(Some(outcome)) + 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) } diff --git a/crates/anvil/src/tasks/mod.rs b/crates/anvil/src/tasks/mod.rs index 429f8d5d3..d2ceb0dca 100644 --- a/crates/anvil/src/tasks/mod.rs +++ b/crates/anvil/src/tasks/mod.rs @@ -1,12 +1,15 @@ //! Task management support +#![allow(rustdoc::private_doc_tests)] + use crate::{shutdown::Shutdown, tasks::block_listener::BlockListener, EthApi}; +use alloy_network::AnyNetwork; +use alloy_primitives::B256; +use alloy_provider::Provider; +use alloy_rpc_types::Block; +use alloy_transport::Transport; use anvil_core::types::Forking; -use ethers::{ - prelude::Middleware, - providers::{JsonRpcClient, PubsubClient}, - types::{Block, H256}, -}; +use futures::StreamExt; use std::{fmt, future::Future}; use tokio::{runtime::Handle, task::JoinHandle}; @@ -21,8 +24,6 @@ pub struct TaskManager { on_shutdown: Shutdown, } -// === impl TaskManager === - impl TaskManager { /// Creates a new instance of the task manager pub fn new(tokio_handle: Handle, on_shutdown: Shutdown) -> Self { @@ -51,32 +52,33 @@ impl TaskManager { /// block /// /// ``` + /// use alloy_network::Ethereum; + /// use alloy_provider::RootProvider; /// use anvil::{spawn, NodeConfig}; - /// use ethers::providers::Provider; - /// use std::sync::Arc; + /// /// # async fn t() { /// let endpoint = "http://...."; /// let (api, handle) = spawn(NodeConfig::default().with_eth_rpc_url(Some(endpoint))).await; /// - /// let provider = Arc::new(Provider::try_from(endpoint).unwrap()); + /// let provider = RootProvider::connect_builtin(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) + pub fn spawn_reset_on_new_polled_blocks(&self, provider: P, api: EthApi) where - P: Middleware + Clone + Unpin + 'static + Send + Sync, -

::Provider: JsonRpcClient, + P: Provider + Clone + Unpin + 'static, + T: Transport + Clone, { 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).await { + if let Ok(Some(block)) = provider.get_block(hash.into(), false.into()).await { let _ = api .anvil_reset(Some(Forking { json_rpc_url: None, - block_number: block.number.map(|b| b.as_u64()), + block_number: block.header.number, })) .await; } @@ -87,16 +89,21 @@ impl TaskManager { /// 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) + pub fn spawn_block_poll_listener(&self, provider: P, task_factory: F) where - P: Middleware + Unpin + 'static, -

::Provider: JsonRpcClient, - F: Fn(H256) -> Fut + Unpin + Send + Sync + 'static, + P: Provider + 'static, + T: Transport + Clone, + 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(); + let blocks = provider + .watch_blocks() + .await + .unwrap() + .into_stream() + .flat_map(futures::stream::iter); BlockListener::new(shutdown, blocks, task_factory).await; }); } @@ -105,21 +112,23 @@ impl TaskManager { /// block /// /// ``` + /// use alloy_network::Ethereum; + /// use alloy_provider::RootProvider; /// use anvil::{spawn, NodeConfig}; - /// use ethers::providers::Provider; + /// /// # async fn t() { /// let (api, handle) = spawn(NodeConfig::default().with_eth_rpc_url(Some("http://...."))).await; /// - /// let provider = Provider::connect("ws://...").await.unwrap(); + /// let provider = RootProvider::connect_builtin("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) + pub fn spawn_reset_on_subscribed_blocks(&self, provider: P, api: EthApi) where - P: Middleware + Unpin + 'static + Send + Sync, -

::Provider: PubsubClient, + P: Provider + 'static, + T: Transport + Clone, { self.spawn_block_subscription(provider, move |block| { let api = api.clone(); @@ -127,7 +136,7 @@ impl TaskManager { let _ = api .anvil_reset(Some(Forking { json_rpc_url: None, - block_number: block.number.map(|b| b.as_u64()), + block_number: block.header.number, })) .await; } @@ -137,16 +146,16 @@ impl TaskManager { /// 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) + pub fn spawn_block_subscription(&self, provider: P, task_factory: F) where - P: Middleware + Unpin + 'static, -

::Provider: PubsubClient, - F: Fn(Block) -> Fut + Unpin + Send + Sync + 'static, + P: Provider + 'static, + T: Transport + Clone, + F: Fn(Block) -> 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(); + let blocks = provider.subscribe_blocks().await.unwrap().into_stream(); BlockListener::new(shutdown, blocks, task_factory).await; }); } diff --git a/crates/anvil/test-data/SimpleStorage.json b/crates/anvil/test-data/SimpleStorage.json index 3d4a8b81a..8ff4ab381 100644 --- a/crates/anvil/test-data/SimpleStorage.json +++ b/crates/anvil/test-data/SimpleStorage.json @@ -1,117 +1 @@ -{ - "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" -} \ No newline at end of file +{"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"} \ No newline at end of file diff --git a/crates/anvil/test-data/emit_logs.json b/crates/anvil/test-data/emit_logs.json index 0113e1c49..635019ae3 100644 --- a/crates/anvil/test-data/emit_logs.json +++ b/crates/anvil/test-data/emit_logs.json @@ -1,67 +1 @@ -{ - "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" -} \ No newline at end of file +{"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"} \ No newline at end of file diff --git a/crates/anvil/test-data/greeter.json b/crates/anvil/test-data/greeter.json index 0892e4d43..93c50f01e 100644 --- a/crates/anvil/test-data/greeter.json +++ b/crates/anvil/test-data/greeter.json @@ -1,44 +1 @@ -{ - "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" - } - ] -} \ No newline at end of file +{"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"}]} \ No newline at end of file diff --git a/crates/anvil/test-data/multicall.json b/crates/anvil/test-data/multicall.json index e0be8fc5d..e7f7d9f11 100644 --- a/crates/anvil/test-data/multicall.json +++ b/crates/anvil/test-data/multicall.json @@ -1,144 +1 @@ -{ - "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" -} \ No newline at end of file +{"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"} \ No newline at end of file diff --git a/crates/anvil/test-data/storage_sample.json b/crates/anvil/test-data/storage_sample.json new file mode 100644 index 000000000..5241cb08e --- /dev/null +++ b/crates/anvil/test-data/storage_sample.json @@ -0,0 +1 @@ +{"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"} \ No newline at end of file diff --git a/crates/anvil/tests/it/abi.rs b/crates/anvil/tests/it/abi.rs index c9cb0d7b1..3356b02f1 100644 --- a/crates/anvil/tests/it/abi.rs +++ b/crates/anvil/tests/it/abi.rs @@ -1,67 +1,73 @@ -//! commonly used abigen generated types +//! commonly used sol generated types +use alloy_sol_types::sol; -use ethers::{ - contract::{abigen, EthEvent}, - types::Address, -}; +sol!( + #[sol(rpc)] + Greeter, + "test-data/greeter.json" +); -#[derive(Clone, Debug, EthEvent)] -pub struct ValueChanged { - #[ethevent(indexed)] - pub old_author: Address, - #[ethevent(indexed)] - pub new_author: Address, - pub old_value: String, - pub new_value: String, -} +sol!( + #[derive(Debug)] + #[sol(rpc)] + SimpleStorage, + "test-data/SimpleStorage.json" +); -abigen!(Greeter, "test-data/greeter.json"); -abigen!(SimpleStorage, "test-data/SimpleStorage.json"); -abigen!(MulticallContract, "test-data/multicall.json"); -abigen!( - Erc721, - r#"[ - balanceOf(address)(uint256) - ownerOf(uint256)(address) - name()(string) - symbol()(string) - tokenURI(uint256)(string) - getApproved(uint256)(address) - setApprovalForAll(address,bool) - isApprovedForAll(address,address) - transferFrom(address,address,uint256) - safeTransferFrom(address,address,uint256,bytes) - _transfer(address,address,uint256) - _approve(address, uint256) - _burn(uint256) - _safeMint(address,uint256,bytes) - _mint(address,uint256) - _exists(uint256)(bool) -]"# +sol!( + #[sol(rpc)] + MulticallContract, + "test-data/multicall.json" ); -abigen!( - BUSD, - r#"[ - balanceOf(address)(uint256) -]"# + +sol!( + #[sol(rpc)] + contract BUSD { + function balanceOf(address) external view returns (uint256); + } ); -// -pub(crate) const VENDING_MACHINE_CONTRACT: &str = r#"// SPDX-License-Identifier: GPL-3.0 -pragma solidity ^0.8.13; +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(); - function buyRevert(uint amount) public payable { + #[derive(Debug)] + function buy(uint amount) public payable { if (amount > msg.value / 2 ether) revert("Not enough Ether provided."); - } - function buyRequire(uint amount) public payable { + // 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) @@ -69,4 +75,5 @@ contract VendingMachine { payable(msg.sender).transfer(address(this).balance); } -}"#; +} +); diff --git a/crates/anvil/tests/it/anvil.rs b/crates/anvil/tests/it/anvil.rs index 3a39f7e74..b8aed751d 100644 --- a/crates/anvil/tests/it/anvil.rs +++ b/crates/anvil/tests/it/anvil.rs @@ -1,52 +1,46 @@ //! tests for anvil specific logic -use crate::utils::ethers_http_provider; +use alloy_primitives::Address; +use alloy_provider::Provider; use anvil::{spawn, NodeConfig}; -use ethers::{prelude::Middleware, types::Address}; -use foundry_common::types::ToAlloy; #[tokio::test(flavor = "multi_thread")] async fn test_can_change_mining_mode() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); assert!(api.anvil_get_auto_mine().unwrap()); let num = provider.get_block_number().await.unwrap(); - assert_eq!(num.as_u64(), 0); + assert_eq!(num, 0); api.anvil_set_interval_mining(1).unwrap(); assert!(!api.anvil_get_auto_mine().unwrap()); // 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.as_u64(), 0); + 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.as_u64(), 1); + 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()); tokio::time::sleep(std::time::Duration::from_millis(1000)).await; let num = provider.get_block_number().await.unwrap(); - assert_eq!(num.as_u64(), 1); + 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 = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); let dev_accounts = handle.dev_accounts().collect::>(); - let accounts = provider - .get_accounts() - .await - .unwrap() - .into_iter() - .map(ToAlloy::to_alloy) - .collect::>(); + let accounts = provider.get_accounts().await.unwrap(); + assert_eq!(dev_accounts, accounts); } @@ -54,8 +48,8 @@ async fn can_get_default_dev_keys() { async fn can_set_empty_code() { let (api, _handle) = spawn(NodeConfig::test()).await; let addr = Address::random(); - api.anvil_set_code(addr.to_alloy(), Vec::new().into()).await.unwrap(); - let code = api.get_code(addr.to_alloy(), None).await.unwrap(); + 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()); } @@ -64,15 +58,21 @@ 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 = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); - assert_eq!(genesis_timestamp, provider.get_block(0).await.unwrap().unwrap().timestamp.as_u64()); + assert_eq!( + genesis_timestamp, + provider.get_block(0.into(), false.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 = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); - assert_ne!(0u64, provider.get_block(0).await.unwrap().unwrap().timestamp.as_u64()); + assert_ne!( + 0u64, + provider.get_block(0.into(), false.into()).await.unwrap().unwrap().header.timestamp + ); } diff --git a/crates/anvil/tests/it/anvil_api.rs b/crates/anvil/tests/it/anvil_api.rs index e487033cc..d83365eac 100644 --- a/crates/anvil/tests/it/anvil_api.rs +++ b/crates/anvil/tests/it/anvil_api.rs @@ -1,49 +1,46 @@ //! tests for custom anvil endpoints -use crate::{abi::*, fork::fork_config, utils::ethers_http_provider}; -use alloy_rpc_types::BlockNumberOrTag; + +use crate::{ + abi::{Greeter, MulticallContract, BUSD}, + fork::fork_config, + utils::http_provider_with_signer, +}; +use alloy_network::{EthereumWallet, TransactionBuilder}; +use alloy_primitives::{address, fixed_bytes, Address, U256, U64}; +use alloy_provider::{ext::TxPoolApi, Provider}; +use alloy_rpc_types::{BlockId, BlockNumberOrTag, TransactionRequest}; +use alloy_serde::WithOtherFields; use anvil::{eth::api::CLIENT_VERSION, spawn, Hardfork, NodeConfig}; use anvil_core::{ eth::EthRequest, types::{AnvilMetadata, ForkedNetwork, Forking, NodeEnvironment, NodeForkConfig, NodeInfo}, }; -use ethers::{ - abi::{ethereum_types::BigEndianHash, AbiDecode}, - prelude::{Middleware, SignerMiddleware}, - types::{ - transaction::eip2718::TypedTransaction, Address, BlockNumber, Eip1559TransactionRequest, - TransactionRequest, H256, U256, U64, - }, - utils::hex, -}; -use foundry_common::types::{ToAlloy, ToEthers}; use foundry_evm::revm::primitives::SpecId; use std::{ str::FromStr, - sync::Arc, time::{Duration, SystemTime}, }; #[tokio::test(flavor = "multi_thread")] async fn can_set_gas_price() { let (api, handle) = spawn(NodeConfig::test().with_hardfork(Some(Hardfork::Berlin))).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); - let gas_price: U256 = 1337u64.into(); - api.anvil_set_min_gas_price(gas_price.to_alloy()).await.unwrap(); - assert_eq!(gas_price, provider.get_gas_price().await.unwrap()); + 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(Hardfork::Berlin))).await; - let block_gas_limit: U256 = 1337u64.into(); - assert!(api.evm_set_block_gas_limit(block_gas_limit.to_alloy()).unwrap()); + 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(alloy_rpc_types::BlockNumberOrTag::Latest).await.unwrap().unwrap(); - assert_eq!(block_gas_limit.to_alloy(), latest_block.header.gas_limit); + let latest_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); + assert_eq!(block_gas_limit.to::(), latest_block.header.gas_limit); } // Ref @@ -61,227 +58,226 @@ async fn can_set_storage() { let storage_value = api.storage_at(addr, slot, None).await.unwrap(); assert_eq!(val, storage_value); - assert_eq!(val.to_ethers(), H256::from_uint(&U256::from(12345))); } #[tokio::test(flavor = "multi_thread")] async fn can_impersonate_account() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + + let provider = handle.http_provider(); let impersonate = Address::random(); let to = Address::random(); - let val = 1337u64; + let val = U256::from(1337); let funding = U256::from(1e18 as u64); // fund the impersonated account - api.anvil_set_balance(impersonate.to_alloy(), funding.to_alloy()).await.unwrap(); + api.anvil_set_balance(impersonate, funding).await.unwrap(); - let balance = api.balance(impersonate.to_alloy(), None).await.unwrap(); - assert_eq!(balance, funding.to_alloy()); + let balance = api.balance(impersonate, None).await.unwrap(); + assert_eq!(balance, funding); - let tx = TransactionRequest::new().from(impersonate).to(to).value(val); + 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(), None).await; + let res = provider.send_transaction(tx.clone()).await; res.unwrap_err(); - api.anvil_impersonate_account(impersonate.to_alloy()).await.unwrap(); - assert!(api.accounts().unwrap().contains(&impersonate.to_alloy())); + api.anvil_impersonate_account(impersonate).await.unwrap(); + assert!(api.accounts().unwrap().contains(&impersonate)); - let res = provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().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, None).await.unwrap(); - assert_eq!(nonce, 1u64.into()); + let nonce = provider.get_transaction_count(impersonate).await.unwrap(); + assert_eq!(nonce, 1); - let balance = provider.get_balance(to, None).await.unwrap(); - assert_eq!(balance, val.into()); + let balance = provider.get_balance(to).await.unwrap(); + assert_eq!(balance, val); - api.anvil_stop_impersonating_account(impersonate.to_alloy()).await.unwrap(); - let res = provider.send_transaction(tx, None).await; + 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 = ethers_http_provider(&handle.http_endpoint()); + + let provider = handle.http_provider(); let impersonate = Address::random(); let to = Address::random(); - let val = 1337u64; + let val = U256::from(1337); let funding = U256::from(1e18 as u64); // fund the impersonated account - api.anvil_set_balance(impersonate.to_alloy(), funding.to_alloy()).await.unwrap(); + api.anvil_set_balance(impersonate, funding).await.unwrap(); - let balance = api.balance(impersonate.to_alloy(), None).await.unwrap(); - assert_eq!(balance, funding.to_alloy()); + let balance = api.balance(impersonate, None).await.unwrap(); + assert_eq!(balance, funding); - let tx = TransactionRequest::new().from(impersonate).to(to).value(val); + 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(), None).await; + 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(), None).await.unwrap().await.unwrap().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, None).await.unwrap(); - assert_eq!(nonce, 1u64.into()); + let nonce = provider.get_transaction_count(impersonate).await.unwrap(); + assert_eq!(nonce, 1); - let balance = provider.get_balance(to, None).await.unwrap(); - assert_eq!(balance, val.into()); + 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, None).await; + let res = provider.send_transaction(tx).await; res.unwrap_err(); // explicitly impersonated accounts get returned by `eth_accounts` - api.anvil_impersonate_account(impersonate.to_alloy()).await.unwrap(); - assert!(api.accounts().unwrap().contains(&impersonate.to_alloy())); + 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 = ethers_http_provider(&handle.http_endpoint()); - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let provider = Arc::new(SignerMiddleware::new(provider, wallet)); + let provider = handle.http_provider(); - let greeter_contract = - Greeter::deploy(provider, "Hello World!".to_string()).unwrap().send().await.unwrap(); - let impersonate = greeter_contract.address(); + 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 = 1337u64; - - let provider = ethers_http_provider(&handle.http_endpoint()); + let val = U256::from(1337); - // fund the impersonated account - api.anvil_set_balance(impersonate.to_alloy(), U256::from(1e18 as u64).to_alloy()) - .await - .unwrap(); + // // fund the impersonated account + api.anvil_set_balance(impersonate, U256::from(1e18 as u64)).await.unwrap(); - let tx = TransactionRequest::new().from(impersonate).to(to).value(val); + let tx = TransactionRequest::default().with_from(impersonate).to(to).with_value(val); + let tx = WithOtherFields::new(tx); - let res = provider.send_transaction(tx.clone(), None).await; + let res = provider.send_transaction(tx.clone()).await; res.unwrap_err(); - let greeting = greeter_contract.greet().call().await.unwrap(); + let greeting = greeter_contract.greet().call().await.unwrap()._0; assert_eq!("Hello World!", greeting); - api.anvil_impersonate_account(impersonate.to_alloy()).await.unwrap(); + api.anvil_impersonate_account(impersonate).await.unwrap(); - let res = provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().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, None).await.unwrap(); - assert_eq!(balance, val.into()); + let balance = provider.get_balance(to).await.unwrap(); + assert_eq!(balance, val); - api.anvil_stop_impersonating_account(impersonate.to_alloy()).await.unwrap(); - let res = provider.send_transaction(tx, None).await; + 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(); + let greeting = greeter_contract.greet().call().await.unwrap()._0; 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 = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); // - let safe: Address = "0xA063Cb7CFd8E57c30c788A0572CBbf2129ae56B6".parse().unwrap(); + let safe = address!("A063Cb7CFd8E57c30c788A0572CBbf2129ae56B6"); - let code = provider.get_code(safe, None).await.unwrap(); + let code = provider.get_code_at(safe).await.unwrap(); assert!(!code.is_empty()); - api.anvil_impersonate_account(safe.to_alloy()).await.unwrap(); + api.anvil_impersonate_account(safe).await.unwrap(); - let code = provider.get_code(safe, None).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.to_alloy(), balance.to_alloy()).await.unwrap(); + api.anvil_set_balance(safe, balance).await.unwrap(); - let on_chain_balance = provider.get_balance(safe, None).await.unwrap(); + let on_chain_balance = provider.get_balance(safe).await.unwrap(); assert_eq!(on_chain_balance, balance); - api.anvil_stop_impersonating_account(safe.to_alloy()).await.unwrap(); + api.anvil_stop_impersonating_account(safe).await.unwrap(); - let code = provider.get_code(safe, None).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_account() { +async fn can_impersonate_multiple_accounts() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); let impersonate0 = Address::random(); let impersonate1 = Address::random(); let to = Address::random(); - let val = 1337u64; + let val = U256::from(1337); let funding = U256::from(1e18 as u64); // fund the impersonated accounts - api.anvil_set_balance(impersonate0.to_alloy(), funding.to_alloy()).await.unwrap(); - api.anvil_set_balance(impersonate1.to_alloy(), funding.to_alloy()).await.unwrap(); + api.anvil_set_balance(impersonate0, funding).await.unwrap(); + api.anvil_set_balance(impersonate1, funding).await.unwrap(); - let tx = TransactionRequest::new().from(impersonate0).to(to).value(val); + let tx = TransactionRequest::default().with_from(impersonate0).to(to).with_value(val); + let tx = WithOtherFields::new(tx); - api.anvil_impersonate_account(impersonate0.to_alloy()).await.unwrap(); - api.anvil_impersonate_account(impersonate1.to_alloy()).await.unwrap(); + api.anvil_impersonate_account(impersonate0).await.unwrap(); + api.anvil_impersonate_account(impersonate1).await.unwrap(); - let res0 = provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().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, None).await.unwrap(); - assert_eq!(nonce, 1u64.into()); + 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, receipt); + assert_eq!(res0.inner, receipt.inner); let res1 = provider - .send_transaction(tx.from(impersonate1), None) + .send_transaction(tx.with_from(impersonate1)) .await .unwrap() + .get_receipt() .await - .unwrap() .unwrap(); + assert_eq!(res1.from, impersonate1); - let nonce = provider.get_transaction_count(impersonate1, None).await.unwrap(); - assert_eq!(nonce, 1u64.into()); + 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, receipt); + assert_eq!(res1.inner, receipt.inner); - assert_ne!(res0, res1); + 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 = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); let start_num = provider.get_block_number().await.unwrap(); for (idx, _) in std::iter::repeat(()).take(10).enumerate() { api.evm_mine(None).await.unwrap(); let num = provider.get_block_number().await.unwrap(); - assert_eq!(num, start_num + idx + 1); + 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 = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); @@ -292,23 +288,23 @@ async fn test_set_next_timestamp() { api.evm_mine(None).await.unwrap(); - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); - assert_eq!(block.number.unwrap().as_u64(), 1); - assert_eq!(block.timestamp.as_u64(), next_timestamp.as_secs()); + assert_eq!(block.header.number.unwrap(), 1); + assert_eq!(block.header.timestamp, next_timestamp.as_secs()); api.evm_mine(None).await.unwrap(); - let next = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); - assert_eq!(next.number.unwrap().as_u64(), 2); + let next = provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); + assert_eq!(next.header.number.unwrap(), 2); - assert!(next.timestamp > block.timestamp); + 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 = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); @@ -319,20 +315,20 @@ async fn test_evm_set_time() { // mine a block api.evm_mine(None).await.unwrap(); - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); - assert!(block.timestamp.as_u64() >= timestamp.as_secs()); + assert!(block.header.timestamp >= timestamp.as_secs()); api.evm_mine(None).await.unwrap(); - let next = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let next = provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); - assert!(next.timestamp > block.timestamp); + 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 = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); @@ -343,59 +339,61 @@ async fn test_evm_set_time_in_past() { // mine a block api.evm_mine(None).await.unwrap(); - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); - assert!(block.timestamp.as_u64() >= timestamp.as_secs()); - assert!(block.timestamp.as_u64() < now.as_secs()); + 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 = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); api.evm_mine(None).await.unwrap(); let interval = 10; for _ in 0..5 { - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::default(), false.into()).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(BlockNumber::Latest).await.unwrap().unwrap(); + let new_block = + provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); - assert_eq!(new_block.timestamp, block.timestamp + interval); + assert_eq!(new_block.header.timestamp, block.header.timestamp + interval); } - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); - let next_timestamp = block.timestamp + 50; - api.evm_set_next_block_timestamp(next_timestamp.as_u64()).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(BlockNumber::Latest).await.unwrap().unwrap(); - assert_eq!(block.timestamp, next_timestamp); + let block = provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); + assert_eq!(block.header.timestamp, next_timestamp); api.evm_mine(None).await.unwrap(); - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); // interval also works after setting the next timestamp manually - assert_eq!(block.timestamp, next_timestamp + interval); + 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(BlockNumber::Latest).await.unwrap().unwrap(); + let new_block = provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); // offset is applied correctly after resetting the interval - assert!(new_block.timestamp > block.timestamp); + assert!(new_block.header.timestamp > block.header.timestamp); api.evm_mine(None).await.unwrap(); - let another_block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let another_block = + provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); // check interval is disabled - assert!(another_block.timestamp - new_block.timestamp < U256::from(interval)); + assert!(another_block.header.timestamp - new_block.header.timestamp < interval); } // @@ -403,26 +401,25 @@ async fn test_timestamp_interval() { 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 = Arc::new(ethers_http_provider(&handle.http_endpoint())); + let provider = handle.http_provider(); - let busd_addr: Address = "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56".parse().unwrap(); - let idx: U256 = - "0xa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49".parse().unwrap(); - let value: H256 = - "0x0000000000000000000000000000000000000000000000000000000000003039".parse().unwrap(); - - api.anvil_set_storage_at(busd_addr.to_alloy(), idx.to_alloy(), value.to_alloy()).await.unwrap(); - let storage = api.storage_at(busd_addr.to_alloy(), idx.to_alloy(), None).await.unwrap(); - assert_eq!(storage.to_ethers(), value); + let busd_addr = address!("e9e7CEA3DedcA5984780Bafc599bD69ADd087D56"); + let idx = U256::from_str("0xa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49") + .unwrap(); + let value = fixed_bytes!("0000000000000000000000000000000000000000000000000000000000003039"); - let input = - hex::decode("70a082310000000000000000000000000000000000000000000000000000000000000000") - .unwrap(); + 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 = BUSD::new(busd_addr, provider); - let call = busd::BalanceOfCall::decode(&input).unwrap(); + let busd_contract = BUSD::new(busd_addr, &provider); - let balance = busd.balance_of(call.0).call().await.unwrap(); + let BUSD::balanceOfReturn { _0 } = busd_contract + .balanceOf(address!("0000000000000000000000000000000000000000")) + .call() + .await + .unwrap(); + let balance = _0; assert_eq!(balance, U256::from(12345u64)); } @@ -432,22 +429,23 @@ async fn can_get_node_info() { let node_info = api.anvil_node_info().await.unwrap(); - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); let block_number = provider.get_block_number().await.unwrap(); - let block = provider.get_block(block_number).await.unwrap().unwrap(); + let block = + provider.get_block(BlockId::from(block_number), false.into()).await.unwrap().unwrap(); let expected_node_info = NodeInfo { - current_block_number: U64([0]).to_alloy(), + current_block_number: U64::from(0), current_block_timestamp: 1, - current_block_hash: block.hash.unwrap().to_alloy(), - hard_fork: SpecId::SHANGHAI, + current_block_hash: block.header.hash.unwrap(), + hard_fork: SpecId::CANCUN, transaction_order: "fees".to_owned(), environment: NodeEnvironment { - base_fee: U256::from_str("0x3b9aca00").unwrap().to_alloy(), + base_fee: U256::from_str("0x3b9aca00").unwrap().to(), chain_id: 0x7a69, - gas_limit: U256::from_str("0x1c9c380").unwrap().to_alloy(), - gas_price: U256::from_str("0x77359400").unwrap().to_alloy(), + gas_limit: U256::from_str("0x1c9c380").unwrap().to(), + gas_price: U256::from_str("0x77359400").unwrap().to(), }, fork_config: NodeForkConfig { fork_url: None, @@ -465,14 +463,15 @@ async fn can_get_metadata() { let metadata = api.anvil_metadata().await.unwrap(); - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); - let block_number = provider.get_block_number().await.unwrap().as_u64(); - let chain_id = provider.get_chainid().await.unwrap().as_u64(); - let block = provider.get_block(block_number).await.unwrap().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), false.into()).await.unwrap().unwrap(); let expected_metadata = AnvilMetadata { - latest_block_hash: block.hash.unwrap().to_alloy(), + latest_block_hash: block.header.hash.unwrap(), latest_block_number: block_number, chain_id, client_version: CLIENT_VERSION, @@ -488,16 +487,17 @@ async fn can_get_metadata() { 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 = Arc::new(ethers_http_provider(&handle.http_endpoint())); + let provider = handle.http_provider(); let metadata = api.anvil_metadata().await.unwrap(); - let block_number = provider.get_block_number().await.unwrap().as_u64(); - let chain_id = provider.get_chainid().await.unwrap().as_u64(); - let block = provider.get_block(block_number).await.unwrap().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), false.into()).await.unwrap().unwrap(); let expected_metadata = AnvilMetadata { - latest_block_hash: block.hash.unwrap().to_alloy(), + latest_block_hash: block.header.hash.unwrap(), latest_block_number: block_number, chain_id, client_version: CLIENT_VERSION, @@ -505,7 +505,7 @@ async fn can_get_metadata_on_fork() { forked_network: Some(ForkedNetwork { chain_id, fork_block_number: block_number, - fork_block_hash: block.hash.unwrap().to_alloy(), + fork_block_hash: block.header.hash.unwrap(), }), snapshots: Default::default(), }; @@ -532,46 +532,45 @@ async fn metadata_changes_on_reset() { #[tokio::test(flavor = "multi_thread")] async fn test_get_transaction_receipt() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); // set the base fee - let new_base_fee = U256::from(1_000); - api.anvil_set_next_block_base_fee_per_gas(new_base_fee.to_alloy()).await.unwrap(); + 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 tx = - TypedTransaction::Eip1559(Eip1559TransactionRequest::new().gas(U256::from(30_000_000))); - let receipt = - provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); + 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(BlockNumber::Latest).await.unwrap().unwrap(); - assert_eq!(block.base_fee_per_gas.unwrap().as_u64(), new_base_fee.as_u64()); + let block = provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); + assert_eq!(block.header.base_fee_per_gas.unwrap(), new_base_fee.to::()); - // mine block + // 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.unwrap().as_u64(), - new_receipt.unwrap().effective_gas_price.unwrap().as_u64() - ); + 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 = ethers_http_provider(&handle.http_endpoint()); - let chain_id = provider.get_chainid().await.unwrap(); - assert_eq!(chain_id, U256::from(31337)); + 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_chainid().await.unwrap(); - assert_eq!(chain_id, U256::from(1234)); + let chain_id = provider.get_chain_id().await.unwrap(); + assert_eq!(chain_id, 1234); } // @@ -599,7 +598,7 @@ async fn test_fork_revert_next_block_timestamp() { #[tokio::test(flavor = "multi_thread")] async fn test_fork_revert_call_latest_block_timestamp() { let (api, handle) = spawn(fork_config()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); // Mine a new block, and check the new block gas limit api.mine_one().await; @@ -609,25 +608,49 @@ async fn test_fork_revert_call_latest_block_timestamp() { api.mine_one().await; api.evm_revert(snapshot_id).await.unwrap(); - let multicall = MulticallContract::new( - Address::from_str("0xeefba1e63905ef1d7acba5a8513c70307c1ce441").unwrap(), - provider.into(), - ); - - assert_eq!( - multicall.get_current_block_timestamp().await.unwrap(), - latest_block.header.timestamp.to_ethers() - ); - assert_eq!( - multicall.get_current_block_difficulty().await.unwrap(), - latest_block.header.difficulty.to_ethers() - ); - assert_eq!( - multicall.get_current_block_gas_limit().await.unwrap(), - latest_block.header.gas_limit.to_ethers() - ); - assert_eq!( - multicall.get_current_block_coinbase().await.unwrap(), - latest_block.header.miner.to_ethers() - ); + let multicall_contract = + MulticallContract::new(address!("eefba1e63905ef1d7acba5a8513c70307c1ce441"), &provider); + + let MulticallContract::getCurrentBlockTimestampReturn { timestamp } = + multicall_contract.getCurrentBlockTimestamp().call().await.unwrap(); + assert_eq!(timestamp, U256::from(latest_block.header.timestamp)); + + let MulticallContract::getCurrentBlockDifficultyReturn { difficulty } = + multicall_contract.getCurrentBlockDifficulty().call().await.unwrap(); + assert_eq!(difficulty, U256::from(latest_block.header.difficulty)); + + let MulticallContract::getCurrentBlockGasLimitReturn { gaslimit } = + multicall_contract.getCurrentBlockGasLimit().call().await.unwrap(); + assert_eq!(gaslimit, U256::from(latest_block.header.gas_limit)); + + let MulticallContract::getCurrentBlockCoinbaseReturn { coinbase } = + multicall_contract.getCurrentBlockCoinbase().call().await.unwrap(); + assert_eq!(coinbase, latest_block.header.miner); +} + +#[tokio::test(flavor = "multi_thread")] +async fn can_remove_pool_transactions() { + let (api, handle) = spawn(NodeConfig::test()).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); } diff --git a/crates/anvil/tests/it/api.rs b/crates/anvil/tests/it/api.rs index fb3c3742b..13f8200ef 100644 --- a/crates/anvil/tests/it/api.rs +++ b/crates/anvil/tests/it/api.rs @@ -2,40 +2,30 @@ use crate::{ abi::{MulticallContract, SimpleStorage}, - utils::ethers_http_provider, + utils::{connect_pubsub_with_wallet, http_provider_with_signer}, }; -use alloy_primitives::{Address as rAddress, B256, U256 as rU256}; -use alloy_providers::provider::TempProvider; +use alloy_network::{EthereumWallet, TransactionBuilder}; +use alloy_primitives::{Address, ChainId, B256, U256}; +use alloy_provider::Provider; use alloy_rpc_types::{ - request::{TransactionInput as CallInput, TransactionRequest as CallRequest}, - state::{AccountOverride, StateOverride}, + request::TransactionRequest, state::AccountOverride, BlockId, BlockNumberOrTag, + BlockTransactions, }; -use alloy_signer::Signer as AlloySigner; -use anvil::{ - eth::{api::CLIENT_VERSION, EthApi}, - spawn, NodeConfig, CHAIN_ID, -}; -use ethers::{ - abi::{Address, Tokenizable}, - prelude::{builders::ContractCall, decode_function_data, Middleware, SignerMiddleware}, - signers::Signer, - types::{Block, BlockNumber, Chain, Transaction, TransactionRequest, U256}, - utils::get_contract_address, -}; -use foundry_common::types::{ToAlloy, ToEthers}; -use std::{collections::HashMap, sync::Arc, time::Duration}; +use alloy_serde::WithOtherFields; +use anvil::{eth::api::CLIENT_VERSION, spawn, NodeConfig, CHAIN_ID}; +use std::{collections::HashMap, 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::zero().to_alloy()); + assert_eq!(block_num, U256::from(0)); - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); let num = provider.get_block_number().await.unwrap(); - assert_eq!(num, block_num.to::().into()); + assert_eq!(num, block_num.to::()); } #[tokio::test(flavor = "multi_thread")] @@ -45,7 +35,7 @@ async fn can_dev_get_balance() { let genesis_balance = handle.genesis_balance(); for acc in handle.genesis_accounts() { - let balance = provider.get_balance(acc, None).await.unwrap(); + let balance = provider.get_balance(acc).await.unwrap(); assert_eq!(balance, genesis_balance); } } @@ -53,7 +43,7 @@ async fn can_dev_get_balance() { #[tokio::test(flavor = "multi_thread")] async fn can_get_price() { let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); let _ = provider.get_gas_price().await.unwrap(); } @@ -61,7 +51,7 @@ async fn can_get_price() { #[tokio::test(flavor = "multi_thread")] async fn can_get_accounts() { let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); let _ = provider.get_accounts().await.unwrap(); } @@ -69,31 +59,32 @@ async fn can_get_accounts() { #[tokio::test(flavor = "multi_thread")] async fn can_get_client_version() { let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); - let version = provider.client_version().await.unwrap(); + 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 = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); - let chain_id = provider.get_chainid().await.unwrap(); - assert_eq!(chain_id, CHAIN_ID.into()); + 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(Chain::Goerli))).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + 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_chainid().await.unwrap(); - assert_eq!(chain_id, Chain::Goerli.into()); + 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, (Chain::Goerli as u64).to_string()); + assert_eq!(chain_id, 777); } #[tokio::test(flavor = "multi_thread")] @@ -107,252 +98,215 @@ async fn can_get_network_id() { #[tokio::test(flavor = "multi_thread")] async fn can_get_block_by_number() { let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + 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 amount = handle.genesis_balance().checked_div(rU256::from(2u64)).unwrap(); - // send a dummy transactions - let tx = TransactionRequest::new() - .to(to.to_ethers()) - .value(amount.to_ethers()) - .from(from.to_ethers()); - let _ = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - - let block: Block = provider.get_block_with_txs(1u64).await.unwrap().unwrap(); - assert_eq!(block.transactions.len(), 1); - let block = provider.get_block(1u64).await.unwrap().unwrap(); + 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), true.into()).await.unwrap().unwrap(); assert_eq!(block.transactions.len(), 1); - let block = provider.get_block(block.hash.unwrap()).await.unwrap().unwrap(); + let block = provider + .get_block(BlockId::hash(block.header.hash.unwrap()), true.into()) + .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 provider = ethers_http_provider(&handle.http_endpoint()); + 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 block = provider.get_block(BlockNumber::Pending).await.unwrap().unwrap(); + let provider = connect_pubsub_with_wallet(&handle.http_endpoint(), signer).await; - assert_eq!(block.number.unwrap().as_u64(), 1u64); + let block = provider.get_block(BlockId::pending(), false.into()).await.unwrap().unwrap(); + assert_eq!(block.header.number.unwrap(), 1); let num = provider.get_block_number().await.unwrap(); - assert_eq!(num.as_u64(), 0u64); + assert_eq!(num, 0); api.anvil_set_auto_mine(false).await.unwrap(); - let from = accounts[0].address(); - let to = accounts[1].address(); - let tx = TransactionRequest::new().to(to.to_ethers()).value(100u64).from(from.to_ethers()); + let tx = TransactionRequest::default().with_from(from).with_to(to).with_value(U256::from(100)); - let tx = provider.send_transaction(tx, None).await.unwrap(); + let pending = provider.send_transaction(tx.clone()).await.unwrap().register().await.unwrap(); let num = provider.get_block_number().await.unwrap(); - assert_eq!(num.as_u64(), 0u64); + assert_eq!(num, 0); - let block = provider.get_block(BlockNumber::Pending).await.unwrap().unwrap(); - assert_eq!(block.number.unwrap().as_u64(), 1u64); + let block = provider.get_block(BlockId::pending(), false.into()).await.unwrap().unwrap(); + assert_eq!(block.header.number.unwrap(), 1); assert_eq!(block.transactions.len(), 1); - assert_eq!(block.transactions, vec![tx.tx_hash()]); + assert_eq!(block.transactions, BlockTransactions::Hashes(vec![*pending.tx_hash()])); - let block = provider.get_block_with_txs(BlockNumber::Pending).await.unwrap().unwrap(); - assert_eq!(block.number.unwrap().as_u64(), 1u64); + let block = provider.get_block(BlockId::pending(), true.into()).await.unwrap().unwrap(); + assert_eq!(block.header.number.unwrap(), 1); assert_eq!(block.transactions.len(), 1); } #[tokio::test(flavor = "multi_thread")] async fn can_call_on_pending_block() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); - - let num = provider.get_block_number().await.unwrap(); - assert_eq!(num.as_u64(), 0u64); - api.anvil_set_auto_mine(false).await.unwrap(); - - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); + let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumWallet = wallet.clone().into(); let sender = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let mut deploy_tx = MulticallContract::deploy(Arc::clone(&client), ()).unwrap().deployer.tx; - deploy_tx.set_nonce(0); - let pending_contract_address = get_contract_address(sender, deploy_tx.nonce().unwrap()); + let provider = http_provider_with_signer(&handle.http_endpoint(), signer); + + let num = provider.get_block_number().await.unwrap(); + assert_eq!(num, 0); - client.send_transaction(deploy_tx, None).await.unwrap(); + api.anvil_set_auto_mine(false).await.unwrap(); - let pending_contract = MulticallContract::new(pending_contract_address, client.clone()); + let _contract_pending = MulticallContract::deploy_builder(&provider) + .from(wallet.address()) + .send() + .await + .unwrap() + .register() + .await + .unwrap(); + let contract_address = sender.create(0); + let contract = MulticallContract::new(contract_address, &provider); - let num = client.get_block_number().await.unwrap(); - assert_eq!(num.as_u64(), 0u64); + 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 (ret_block_number, _) = - pending_contract.aggregate(vec![]).block(BlockNumber::Pending).call().await.unwrap(); - assert_eq!(ret_block_number.as_u64(), 1u64); + let MulticallContract::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(); - 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(rU256::from(30_000_000 + i)).unwrap(); + api.evm_set_block_gas_limit(U256::from(30_000_000 + i)).unwrap(); - api.anvil_mine(Some(rU256::from(1)), None).await.unwrap(); - tokio::time::sleep(Duration::from_secs(1)).await; + 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 block_number in 1..(api.block_number().unwrap().to::() + 1) { - let block_number_alloy = alloy_rpc_types::BlockNumberOrTag::Number(block_number as u64); - let block_number_ethers = BlockNumber::Number((block_number as u64).into()); - let block = api.block_by_number(block_number_alloy).await.unwrap().unwrap(); - - let block_timestamp = pending_contract - .get_current_block_timestamp() - .block(block_number_ethers) - .call() - .await - .unwrap(); - assert_eq!(block.header.timestamp, block_timestamp.to_alloy()); - - let block_gas_limit = pending_contract - .get_current_block_gas_limit() - .block(block_number_ethers) - .call() - .await - .unwrap(); - assert_eq!(block.header.gas_limit, block_gas_limit.to_alloy()); - - let block_coinbase = pending_contract - .get_current_block_coinbase() - .block(block_number_ethers) - .call() - .await - .unwrap(); - assert_eq!(block.header.miner, block_coinbase.to_alloy()); + 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 MulticallContract::getCurrentBlockTimestampReturn { timestamp: ret_timestamp, .. } = + contract + .getCurrentBlockTimestamp() + .block(BlockId::number(anvil_block_number as u64)) + .call() + .await + .unwrap(); + assert_eq!(block.header.timestamp, ret_timestamp.to::()); + + let MulticallContract::getCurrentBlockGasLimitReturn { gaslimit: 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 MulticallContract::getCurrentBlockCoinbaseReturn { coinbase: ret_coinbase, .. } = + contract + .getCurrentBlockCoinbase() + .block(BlockId::number(anvil_block_number as u64)) + .call() + .await + .unwrap(); + assert_eq!(block.header.miner, ret_coinbase); } } -async fn call_with_override( - api: &EthApi, - call: ContractCall, - to: Address, - overrides: StateOverride, -) -> D -where - D: Tokenizable, -{ - let result = api - .call( - CallRequest { - input: CallInput::maybe_input(call.tx.data().cloned().map(|b| b.0.into())), - to: Some(to.to_alloy()), - ..Default::default() - }, - None, - Some(overrides), - ) - .await - .unwrap(); - decode_function_data(&call.function, result.as_ref(), false).unwrap() -} - #[tokio::test(flavor = "multi_thread")] async fn can_call_with_state_override() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + 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 wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let account = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let multicall_contract = MulticallContract::deploy(&provider).await.unwrap(); let init_value = "toto".to_string(); - let multicall = - MulticallContract::deploy(Arc::clone(&client), ()).unwrap().send().await.unwrap(); - let simple_storage = SimpleStorage::deploy(Arc::clone(&client), init_value.clone()) - .unwrap() - .send() - .await - .unwrap(); + + let simple_storage_contract = + SimpleStorage::deploy(&provider, init_value.clone()).await.unwrap(); // Test the `balance` account override - let balance = rU256::from(42u64); - let result = call_with_override( - &api, - multicall.get_eth_balance(account), - multicall.address(), - HashMap::from([( - account.to_alloy(), - AccountOverride { balance: Some(balance), ..Default::default() }, - )]), - ) - .await; - assert_eq!(result, balance.to_ethers()); + let balance = U256::from(42u64); + let overrides = HashMap::from([( + account, + AccountOverride { balance: Some(balance), ..Default::default() }, + )]); + let result = + multicall_contract.getEthBalance(account).state(overrides).call().await.unwrap().balance; + assert_eq!(result, balance); // Test the `state_diff` account override let overrides = HashMap::from([( - simple_storage.address().to_alloy(), + *simple_storage_contract.address(), AccountOverride { // The `lastSender` is in the first storage slot - state_diff: Some(HashMap::from([( - B256::ZERO, - rU256::from_be_slice(B256::from(account.to_alloy().into_word()).as_slice()), - )])), + state_diff: Some(HashMap::from([(B256::ZERO, account.into_word())])), ..Default::default() }, )]); - let last_sender = call_with_override( - &api, - simple_storage.last_sender(), - simple_storage.address(), - Default::default(), - ) - .await; + let last_sender = + simple_storage_contract.lastSender().state(HashMap::default()).call().await.unwrap()._0; // No `sender` set without override - assert_eq!(last_sender, Address::zero()); - let last_sender = call_with_override( - &api, - simple_storage.last_sender(), - simple_storage.address(), - overrides.clone(), - ) - .await; + assert_eq!(last_sender, Address::ZERO); + + let last_sender = + simple_storage_contract.lastSender().state(overrides.clone()).call().await.unwrap()._0; // `sender` *is* set with override assert_eq!(last_sender, account); - let value = - call_with_override(&api, simple_storage.get_value(), simple_storage.address(), overrides) - .await; + + let value = simple_storage_contract.getValue().state(overrides).call().await.unwrap()._0; // `value` *is not* changed with state-diff assert_eq!(value, init_value); // Test the `state` account override let overrides = HashMap::from([( - simple_storage.address().to_alloy(), + *simple_storage_contract.address(), AccountOverride { // The `lastSender` is in the first storage slot - state: Some(HashMap::from([( - B256::ZERO, - rU256::from_be_slice(B256::from(account.to_alloy().into_word()).as_slice()), - )])), + state: Some(HashMap::from([(B256::ZERO, account.into_word())])), ..Default::default() }, )]); - let last_sender = call_with_override( - &api, - simple_storage.last_sender(), - simple_storage.address(), - overrides.clone(), - ) - .await; + let last_sender = + simple_storage_contract.lastSender().state(overrides.clone()).call().await.unwrap()._0; // `sender` *is* set with override assert_eq!(last_sender, account); - let value = - call_with_override(&api, simple_storage.get_value(), simple_storage.address(), overrides) - .await; + + let value = simple_storage_contract.getValue().state(overrides).call().await.unwrap()._0; // `value` *is* changed with state assert_eq!(value, ""); } diff --git a/crates/anvil/tests/it/eip4844.rs b/crates/anvil/tests/it/eip4844.rs new file mode 100644 index 000000000..19842aa75 --- /dev/null +++ b/crates/anvil/tests/it/eip4844.rs @@ -0,0 +1,206 @@ +use crate::utils::http_provider; +use alloy_consensus::{SidecarBuilder, SimpleCoder}; +use alloy_eips::eip4844::{DATA_GAS_PER_BLOB, MAX_DATA_GAS_PER_BLOCK}; +use alloy_network::TransactionBuilder; +use alloy_primitives::U256; +use alloy_provider::Provider; +use alloy_rpc_types::{BlockId, TransactionRequest}; +use alloy_serde::WithOtherFields; +use anvil::{spawn, Hardfork, NodeConfig}; + +#[tokio::test(flavor = "multi_thread")] +async fn can_send_eip4844_transaction() { + let node_config = NodeConfig::test().with_hardfork(Some(Hardfork::Cancun)); + 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(None).await.unwrap(); + let gas_price = provider.get_gas_price().await.unwrap(); + + let sidecar: SidecarBuilder = SidecarBuilder::from_slice("Hello World".as_bytes()); + + 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(sidecar) + .value(U256::from(5)); + + let mut tx = WithOtherFields::new(tx); + + tx.populate_blob_hashes(); + + 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_multiple_blobs_in_one_tx() { + let node_config = NodeConfig::test().with_hardfork(Some(Hardfork::Cancun)); + 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(None).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(sidecar); + let mut tx = WithOtherFields::new(tx); + + tx.populate_blob_hashes(); + + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); + + assert_eq!(receipt.blob_gas_used, Some(MAX_DATA_GAS_PER_BLOCK as u128)); + 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(Hardfork::Cancun)); + 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(None).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(sidecar); + let mut tx = WithOtherFields::new(tx); + + tx.populate_blob_hashes(); + + 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(Hardfork::Cancun)); + 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(None).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(); + + 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(sidecar); + let mut tx = WithOtherFields::new(tx); + + tx.populate_blob_hashes(); + + 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(); + + let sidecar = sidecar.build().unwrap(); + tx.set_blob_sidecar(sidecar); + tx.set_nonce(1); + tx.populate_blob_hashes(); + 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(), false), + provider.get_block_by_number(second_receipt.block_number.unwrap().into(), false) + ); + assert_eq!( + first_block.unwrap().unwrap().header.blob_gas_used, + Some(DATA_GAS_PER_BLOB as u128 * num_blobs_first as u128) + ); + + assert_eq!( + second_block.unwrap().unwrap().header.blob_gas_used, + Some(DATA_GAS_PER_BLOB as u128 * num_blobs_second as u128) + ); + // 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(Hardfork::Cancun)); + let (_api, handle) = spawn(node_config).await; + + let provider = http_provider(&handle.http_endpoint()); + + let block = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); + + assert_eq!(block.header.blob_gas_used, Some(0)); + assert_eq!(block.header.excess_blob_gas, Some(0)); +} diff --git a/crates/anvil/tests/it/fork.rs b/crates/anvil/tests/it/fork.rs index 8399dc85a..83c235f5c 100644 --- a/crates/anvil/tests/it/fork.rs +++ b/crates/anvil/tests/it/fork.rs @@ -1,34 +1,23 @@ //! various fork related test use crate::{ - abi::*, - utils::{self, ethers_http_provider}, + abi::{Greeter, ERC721}, + utils::{http_provider, http_provider_with_signer}, }; -use alloy_primitives::{address, U256 as rU256}; -use alloy_providers::provider::TempProvider; +use alloy_network::{EthereumWallet, TransactionBuilder}; +use alloy_primitives::{address, Address, Bytes, TxKind, U256}; +use alloy_provider::Provider; use alloy_rpc_types::{ - request::{TransactionInput, TransactionRequest as CallRequest}, - BlockNumberOrTag, + request::{TransactionInput, TransactionRequest}, + BlockId, BlockNumberOrTag, }; -use alloy_signer::Signer as AlloySigner; +use alloy_serde::WithOtherFields; +use alloy_signer_local::PrivateKeySigner; use anvil::{eth::EthApi, spawn, NodeConfig, NodeHandle}; use anvil_core::types::Forking; -use ethers::{ - prelude::{Bytes, LocalWallet, Middleware, SignerMiddleware}, - providers::{Http, Provider}, - signers::Signer, - types::{ - transaction::eip2718::TypedTransaction, Address, BlockNumber, Chain, TransactionRequest, - U256, - }, -}; -use foundry_common::{ - provider::ethers::get_http_provider, - rpc, - rpc::next_http_rpc_endpoint, - types::{ToAlloy, ToEthers}, -}; +use foundry_common::provider::get_http_provider; use foundry_config::Config; +use foundry_test_utils::rpc::{self, next_http_rpc_endpoint}; use futures::StreamExt; use std::{sync::Arc, time::Duration}; @@ -46,7 +35,6 @@ pub struct LocalFork { fork_handle: NodeHandle, } -// === impl LocalFork === #[allow(dead_code)] impl LocalFork { /// Spawns two nodes with the test config @@ -77,18 +65,18 @@ async fn test_spawn_fork() { assert!(api.is_fork()); let head = api.block_number().unwrap(); - assert_eq!(head, rU256::from(BLOCK_NUMBER)) + 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 = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); for _ in 0..10 { let addr = Address::random(); - let balance = api.balance(addr.to_alloy(), None).await.unwrap(); - let provider_balance = provider.get_balance(addr, None).await.unwrap(); - assert_eq!(balance, provider_balance.to_alloy()) + let balance = api.balance(addr, None).await.unwrap(); + let provider_balance = provider.get_balance(addr).await.unwrap(); + assert_eq!(balance, provider_balance) } } @@ -96,66 +84,65 @@ async fn test_fork_eth_get_balance() { #[tokio::test(flavor = "multi_thread")] async fn test_fork_eth_get_balance_after_mine() { let (api, handle) = spawn(fork_config()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + 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, Some(BlockNumber::Number(number.into()).into())) - .await - .unwrap(); + let _balance = provider.get_balance(address).await.unwrap(); api.evm_mine(None).await.unwrap(); - let _balance = provider - .get_balance(address, Some(BlockNumber::Number(number.into()).into())) - .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 = ethers_http_provider(&handle.http_endpoint()); + 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(address, Some(BlockNumber::Number(number.into()).into())).await.unwrap(); + 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(address, Some(BlockNumber::Number(number.into()).into())).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 = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); for _ in 0..10 { let addr = Address::random(); - let code = api.get_code(addr.to_alloy(), None).await.unwrap(); - let provider_code = provider.get_code(addr, None).await.unwrap(); - assert_eq!(code, provider_code.to_alloy()) + let code = api.get_code(addr, None).await.unwrap(); + let provider_code = provider.get_code_at(addr).await.unwrap(); + assert_eq!(code, provider_code) } - for address in utils::contract_addresses(Chain::Mainnet) { + 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.to_alloy(), Some(BlockNumberOrTag::Number(BLOCK_NUMBER - 10).into())) + .get_code(address, Some(BlockNumberOrTag::Number(BLOCK_NUMBER - 10).into())) .await .unwrap(); - let code = api.get_code(address.to_alloy(), None).await.unwrap(); - let provider_code = provider.get_code(address, None).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.to_alloy()); + assert_eq!(code, provider_code); assert!(!code.as_ref().is_empty()); } } @@ -163,72 +150,70 @@ async fn test_fork_eth_get_code() { #[tokio::test(flavor = "multi_thread")] async fn test_fork_eth_get_nonce() { let (api, handle) = spawn(fork_config()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); for _ in 0..10 { let addr = Address::random(); - let api_nonce = api.transaction_count(addr.to_alloy(), None).await.unwrap(); - let provider_nonce = provider.get_transaction_count(addr, None).await.unwrap(); - assert_eq!(api_nonce, provider_nonce.to_alloy()); + 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(); - let provider_nonce = provider.get_transaction_count(addr.to_ethers(), None).await.unwrap(); - assert_eq!(api_nonce, provider_nonce.to_alloy()); + 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_eth_fee_history() { let (api, handle) = spawn(fork_config()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); let count = 10u64; let _history = - api.fee_history(rU256::from(count), BlockNumberOrTag::Latest, vec![]).await.unwrap(); - let _provider_history = provider.fee_history(count, BlockNumber::Latest, &[]).await.unwrap(); + 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 = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); let accounts: Vec<_> = handle.dev_wallets().collect(); - let from = accounts[0].address().to_ethers(); - let to = accounts[1].address().to_ethers(); + 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, None).await.unwrap(); - let amount = handle.genesis_balance().checked_div(rU256::from(2u64)).unwrap(); - - let initial_nonce = provider.get_transaction_count(from, None).await.unwrap(); + let balance_before = provider.get_balance(to).await.unwrap(); + let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap(); - let tx = TransactionRequest::new().to(to).value(amount.to_ethers()).from(from); + let initial_nonce = provider.get_transaction_count(from).await.unwrap(); - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - assert_eq!(tx.transaction_index, 0u64.into()); + 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, None).await.unwrap(); + let nonce = provider.get_transaction_count(from).await.unwrap(); assert_eq!(nonce, initial_nonce + 1); - let to_balance = provider.get_balance(to, None).await.unwrap(); - assert_eq!(balance_before.saturating_add(amount.to_ethers()), to_balance); - api.anvil_reset(Some(Forking { - json_rpc_url: None, - block_number: Some(block_number.as_u64()), - })) - .await - .unwrap(); + 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, None).await.unwrap(); + let nonce = provider.get_transaction_count(from).await.unwrap(); assert_eq!(nonce, initial_nonce); - let balance = provider.get_balance(from, None).await.unwrap(); - assert_eq!(balance, handle.genesis_balance().to_ethers()); - let balance = provider.get_balance(to, None).await.unwrap(); - assert_eq!(balance, handle.genesis_balance().to_ethers()); + 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(); @@ -240,15 +225,15 @@ async fn test_fork_reset() { #[tokio::test(flavor = "multi_thread")] async fn test_fork_reset_setup() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + 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.into()); + assert_eq!(block_number, 0); - let local_balance = provider.get_balance(dead_addr, None).await.unwrap(); - assert_eq!(local_balance, 0.into()); + 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_endpoint()), @@ -258,10 +243,10 @@ async fn test_fork_reset_setup() { .unwrap(); let block_number = provider.get_block_number().await.unwrap(); - assert_eq!(block_number, BLOCK_NUMBER.into()); + assert_eq!(block_number, BLOCK_NUMBER); - let remote_balance = provider.get_balance(dead_addr, None).await.unwrap(); - assert_eq!(remote_balance, DEAD_BALANCE_AT_BLOCK_NUMBER.into()); + 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")] @@ -275,32 +260,30 @@ async fn test_fork_snapshotting() { let to = accounts[1].address(); let block_number = provider.get_block_number().await.unwrap(); - let initial_nonce = provider.get_transaction_count(from, None).await.unwrap(); - let balance_before = provider.get_balance(to, None).await.unwrap(); - let amount = handle.genesis_balance().checked_div(rU256::from(2u64)).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 = ethers_http_provider(&handle.http_endpoint()); - let tx = TransactionRequest::new() - .to(to.to_ethers()) - .value(amount.to_ethers()) - .from(from.to_ethers()); + 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, None).await.unwrap().await.unwrap().unwrap(); + let _ = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let provider = handle.http_provider(); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); - assert_eq!(nonce, initial_nonce + rU256::from(1)); - let to_balance = provider.get_balance(to, None).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); assert!(api.evm_revert(snapshot).await.unwrap()); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); + let nonce = provider.get_transaction_count(from).await.unwrap(); assert_eq!(nonce, initial_nonce); - let balance = provider.get_balance(from, None).await.unwrap(); + let balance = provider.get_balance(from).await.unwrap(); assert_eq!(balance, handle.genesis_balance()); - let balance = provider.get_balance(to, None).await.unwrap(); + let balance = provider.get_balance(to).await.unwrap(); assert_eq!(balance, handle.genesis_balance()); assert_eq!(block_number, provider.get_block_number().await.unwrap()); } @@ -317,31 +300,29 @@ async fn test_fork_snapshotting_repeated() { let to = accounts[1].address(); let block_number = provider.get_block_number().await.unwrap(); - let initial_nonce = provider.get_transaction_count(from, None).await.unwrap(); - let balance_before = provider.get_balance(to, None).await.unwrap(); - let amount = handle.genesis_balance().checked_div(rU256::from(92u64)).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::new() - .to(to.to_ethers()) - .value(amount.to_ethers()) - .from(from.to_ethers()); - let tx_provider = ethers_http_provider(&handle.http_endpoint()); - let _ = tx_provider.send_transaction(tx, None).await.unwrap().await.unwrap().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, None).await.unwrap(); - assert_eq!(nonce, initial_nonce + rU256::from(1)); - let to_balance = provider.get_balance(to, None).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_snapshot = api.evm_snapshot().await.unwrap(); assert!(api.evm_revert(snapshot).await.unwrap()); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); + let nonce = provider.get_transaction_count(from).await.unwrap(); assert_eq!(nonce, initial_nonce); - let balance = provider.get_balance(from, None).await.unwrap(); + let balance = provider.get_balance(from).await.unwrap(); assert_eq!(balance, handle.genesis_balance()); - let balance = provider.get_balance(to, None).await.unwrap(); + let balance = provider.get_balance(to).await.unwrap(); assert_eq!(balance, handle.genesis_balance()); assert_eq!(block_number, provider.get_block_number().await.unwrap()); @@ -358,7 +339,6 @@ async fn test_fork_snapshotting_repeated() { async fn test_fork_snapshotting_blocks() { let (api, handle) = spawn(fork_config()).await; let provider = handle.http_provider(); - let tx_provider = ethers_http_provider(&handle.http_endpoint()); // create a snapshot let snapshot = api.evm_snapshot().await.unwrap(); @@ -368,41 +348,39 @@ async fn test_fork_snapshotting_blocks() { let to = accounts[1].address(); let block_number = provider.get_block_number().await.unwrap(); - let initial_nonce = provider.get_transaction_count(from, None).await.unwrap(); - let balance_before = provider.get_balance(to, None).await.unwrap(); - let amount = handle.genesis_balance().checked_div(rU256::from(2u64)).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::new() - .to(to.to_ethers()) - .value(amount.to_ethers()) - .from(from.to_ethers()); - let _ = tx_provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); + 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, None).await.unwrap(); - assert_eq!(nonce, initial_nonce + rU256::from(1)); - let to_balance = provider.get_balance(to, None).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); // revert snapshot assert!(api.evm_revert(snapshot).await.unwrap()); - assert_eq!(initial_nonce, provider.get_transaction_count(from, None).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 _ = tx_provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); - assert_eq!(nonce, initial_nonce + rU256::from(1)); + 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 snapshot gone assert!(!api.evm_revert(snapshot).await.unwrap()); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); - assert_eq!(nonce, initial_nonce + rU256::from(1)); + 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); } @@ -413,16 +391,16 @@ async fn test_fork_snapshotting_blocks() { #[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 = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); let addr: Address = "000000000000000000000000000000000000dEaD".parse().unwrap(); - let remote_balance = provider.get_balance(addr, None).await.unwrap(); - assert_eq!(remote_balance, 12556104082473169733500u128.into()); + let remote_balance = provider.get_balance(addr).await.unwrap(); + assert_eq!(remote_balance, U256::from(12556104082473169733500u128)); - api.anvil_set_balance(addr.to_alloy(), rU256::from(1337u64)).await.unwrap(); - let balance = provider.get_balance(addr, None).await.unwrap(); - assert_eq!(balance, 1337u64.into()); + 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; @@ -432,35 +410,31 @@ async fn test_separate_states() { .db() .accounts .read() - .get(&addr.to_alloy()) + .get(&addr) .cloned() .unwrap(); - assert_eq!(acc.balance, remote_balance.to_alloy()) + 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 provider = ethers_http_provider(&handle.http_endpoint()); - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let wallet = handle.dev_wallets().next().unwrap(); + let signer: EthereumWallet = wallet.into(); - let greeter_contract = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .send() - .await - .unwrap(); + 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); + assert_eq!("Hello World!", greeting._0); - let greeter_contract = - Greeter::deploy(client, "Hello World!".to_string()).unwrap().send().await.unwrap(); + 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); + assert_eq!("Hello World!", greeting._0); } #[tokio::test(flavor = "multi_thread")] @@ -468,40 +442,38 @@ 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 = rU256::from(1u64); - origin_api.anvil_set_nonce(account, origin_nonce).await.unwrap(); + 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, None).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 = ethers_http_provider(&fork_handle.http_endpoint()); - assert_eq!(origin_nonce, fork_provider.get_transaction_count(account, None).await.unwrap()); + 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.to_alloy(), None).await.unwrap(); - let tx = TransactionRequest::new().from(account.to_ethers()).to(to).value(1337u64); - let tx = fork_tx_provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + 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 + rU256::from(1), - fork_provider.get_transaction_count(account, None).await.unwrap() - ); + 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, None).await.unwrap()); + assert_eq!(origin_nonce, fork_provider.get_transaction_count(account).await.unwrap()); // balance is reset - assert_eq!(to_balance, fork_provider.get_balance(to.to_alloy(), None).await.unwrap()); + assert_eq!(to_balance, fork_provider.get_balance(to).await.unwrap()); // tx does not exist anymore - assert!(fork_tx_provider.get_transaction(tx.transaction_hash).await.is_err()) + assert!(fork_tx_provider.get_transaction_by_hash(tx.transaction_hash).await.unwrap().is_none()) } #[tokio::test(flavor = "multi_thread")] @@ -509,41 +481,54 @@ async fn test_fork_timestamp() { let start = std::time::Instant::now(); let (api, handle) = spawn(fork_config()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); - let block = provider.get_block(BLOCK_NUMBER).await.unwrap().unwrap(); - assert_eq!(block.timestamp.as_u64(), BLOCK_TIMESTAMP); + let block = provider + .get_block(BlockId::Number(BLOCK_NUMBER.into()), false.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::new().to(Address::random()).value(1337u64).from(from.to_ethers()); - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - assert_eq!(tx.status, Some(1u64.into())); + 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(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::latest(), false.into()).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.timestamp - BLOCK_TIMESTAMP; - assert!(diff <= elapsed.into(), "diff={diff}, elapsed={elapsed}"); + 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(BLOCK_NUMBER).await.unwrap().unwrap(); - assert_eq!(block.timestamp.as_u64(), BLOCK_TIMESTAMP); + let block = provider + .get_block(BlockId::Number(BLOCK_NUMBER.into()), false.into()) + .await + .unwrap() + .unwrap(); + assert_eq!(block.header.timestamp, BLOCK_TIMESTAMP); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64).from(from.to_ethers()); - let _tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + 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(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); let elapsed = start.elapsed().as_secs() + 1; - let diff = block.timestamp - BLOCK_TIMESTAMP; - assert!(diff <= elapsed.into()); + 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(); @@ -551,19 +536,23 @@ async fn test_fork_timestamp() { .await .unwrap(); api.evm_set_next_block_timestamp(BLOCK_TIMESTAMP + 1).unwrap(); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64).from(from.to_ethers()); - let _tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().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(BlockNumber::Latest).await.unwrap().unwrap(); - assert_eq!(block.timestamp.as_u64(), BLOCK_TIMESTAMP + 1); + let block = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); + assert_eq!(block.header.timestamp, BLOCK_TIMESTAMP + 1); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64).from(from.to_ethers()); - let _tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + 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(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); let elapsed = start.elapsed().as_secs() + 1; - let diff = block.timestamp - (BLOCK_TIMESTAMP + 1); - assert!(diff <= elapsed.into()); + let diff = block.header.timestamp - (BLOCK_TIMESTAMP + 1); + assert!(diff <= elapsed); } #[tokio::test(flavor = "multi_thread")] @@ -582,22 +571,25 @@ 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 = LocalWallet::new(&mut rand::thread_rng()); - let provider = ethers_http_provider(&handle.http_endpoint()); - let provider = SignerMiddleware::new(provider, wallet); + let wallet = PrivateKeySigner::random(); + let signer = wallet.address(); + let provider = handle.http_provider(); + // let provider = SignerMiddleware::new(provider, wallet); - api.anvil_set_balance(provider.address().to_alloy(), rU256::MAX).await.unwrap(); - let balance = provider.get_balance(provider.address(), None).await.unwrap(); - assert_eq!(balance, rU256::MAX.to_ethers()); + 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 = 1337u64; - let tx = TransactionRequest::new().to(addr).value(val); + 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, None).await.unwrap().await.unwrap().unwrap(); + let _ = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); - let balance = provider.get_balance(addr, None).await.unwrap(); - assert_eq!(balance, val.into()); + let balance = provider.get_balance(addr).await.unwrap(); + assert_eq!(balance, val); } // @@ -612,42 +604,50 @@ async fn test_fork_nft_set_approve_all() { .await; // create and fund a random wallet - let wallet = LocalWallet::new(&mut rand::thread_rng()); - api.anvil_set_balance(wallet.address().to_alloy(), rU256::from(1000e18 as u64)).await.unwrap(); + let wallet = PrivateKeySigner::random(); + let signer = wallet.address(); + api.anvil_set_balance(signer, U256::from(1000e18)).await.unwrap(); - let provider = ethers_http_provider(&handle.http_endpoint()); - let provider = Arc::new(SignerMiddleware::new(provider, wallet.clone())); + 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 = 154u64.into(); - - let nouns = Erc721::new(nouns_addr, Arc::clone(&provider)); - - let real_owner = nouns.owner_of(token_id).call().await.unwrap(); - assert_eq!(real_owner, owner); - let approval = nouns.set_approval_for_all(nouns_addr, true); - let tx = approval.send().await.unwrap().await.unwrap().unwrap(); - assert_eq!(tx.status, Some(1u64.into())); - - let real_owner = real_owner.to_alloy(); + 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._0, 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, rU256::from(10000e18 as u64)).await.unwrap(); - - let call = nouns.transfer_from(real_owner.to_ethers(), wallet.address(), token_id); - let mut tx: TypedTransaction = call.tx; - tx.set_from(real_owner.to_ethers()); - provider.fill_transaction(&mut tx, None).await.unwrap(); - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - assert_eq!(tx.status, Some(1u64.into())); - - let real_owner = nouns.owner_of(token_id).call().await.unwrap(); - assert_eq!(real_owner, wallet.address()); + api.anvil_impersonate_account(real_owner._0).await.unwrap(); + + api.anvil_set_balance(real_owner._0, U256::from(10000e18 as u64)).await.unwrap(); + + let call = nouns.transferFrom(real_owner._0, signer, token_id); + let tx = TransactionRequest::default() + .from(real_owner._0) + .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._0, wallet.address()); } // @@ -688,22 +688,24 @@ async fn test_fork_can_send_opensea_tx() { let sender: Address = "0x8fdbae54b6d9f3fc2c649e3dd4602961967fd42f".parse().unwrap(); // transfer: impersonate real sender - api.anvil_impersonate_account(sender.to_alloy()).await.unwrap(); + api.anvil_impersonate_account(sender).await.unwrap(); - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); let input: Bytes = "0xfb0f3ee1000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003ff2e795f5000000000000000000000000000023f28ae3e9756ba982a6290f9081b6a84900b758000000000000000000000000004c00500000ad104d7dbd00e3ae0a5c00560c0000000000000000000000000003235b597a78eabcb08ffcb4d97411073211dbcb0000000000000000000000000000000000000000000000000000000000000e72000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000062ad47c20000000000000000000000000000000000000000000000000000000062d43104000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000df44e65d2a2cf40000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f00000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000002e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000001c6bf526340000000000000000000000000008de9c5a032463c561423387a9648c5c7bcc5bc900000000000000000000000000000000000000000000000000005543df729c0000000000000000000000000006eb234847a9e3a546539aac57a071c01dc3f398600000000000000000000000000000000000000000000000000000000000000416d39b5352353a22cf2d44faa696c2089b03137a13b5acfee0366306f2678fede043bc8c7e422f6f13a3453295a4a063dac7ee6216ab7bade299690afc77397a51c00000000000000000000000000000000000000000000000000000000000000".parse().unwrap(); let to: Address = "0x00000000006c3852cbef3e08e8df289169ede581".parse().unwrap(); - let tx = TransactionRequest::new() + let tx = TransactionRequest::default() .from(sender) .to(to) - .value(20000000000000000u64) - .data(input) - .gas_price(22180711707u64) - .gas(150_000u64); - - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - assert_eq!(tx.status, Some(1u64.into())); + .value(U256::from(20000000000000000u64)) + .with_input(input) + .with_gas_price(22180711707u128) + .with_gas_limit(150_000u128); + 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")] @@ -713,34 +715,34 @@ async fn test_fork_base_fee() { let accounts: Vec<_> = handle.dev_wallets().collect(); let from = accounts[0].address(); - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); - api.anvil_set_next_block_base_fee_per_gas(rU256::ZERO).await.unwrap(); + api.anvil_set_next_block_base_fee_per_gas(U256::ZERO).await.unwrap(); let addr = Address::random(); - let val = 1337u64; - let tx = TransactionRequest::new().from(from.to_ethers()).to(addr).value(val); - - let _res = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + 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 = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); // - assert_eq!(block.number.unwrap().as_u64(), 13184859u64); - let init_base_fee = block.base_fee_per_gas.unwrap(); - assert_eq!(init_base_fee, 63739886069u64.into()); + assert_eq!(block.header.number.unwrap(), 13184859u64); + let init_base_fee = block.header.base_fee_per_gas.unwrap(); + assert_eq!(init_base_fee, 63739886069u128); api.mine_one().await; - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); + let block = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); - let next_base_fee = block.base_fee_per_gas.unwrap(); + let next_base_fee = block.header.base_fee_per_gas.unwrap(); assert!(next_base_fee < init_base_fee); } @@ -751,19 +753,24 @@ async fn test_reset_fork_on_new_blocks() { ) .await; - let anvil_provider = ethers_http_provider(&handle.http_endpoint()); - + let anvil_provider = handle.http_provider(); let endpoint = next_http_rpc_endpoint(); - let provider = Arc::new(get_http_provider(&endpoint).interval(Duration::from_secs(2))); + 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(); + 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(Chain::Mainnet.average_blocktime_hint().unwrap()).await; + tokio::time::sleep(Duration::from_secs(12)).await; stream.next().await.unwrap(); stream.next().await.unwrap(); @@ -778,28 +785,27 @@ async fn test_fork_call() { let to: Address = "0x99d1Fa417f94dcD62BfE781a1213c092a47041Bc".parse().unwrap(); let block_number = 14746300u64; - let provider = Provider::::try_from(rpc::next_http_archive_rpc_endpoint()).unwrap(); - let mut tx = TypedTransaction::default(); - tx.set_to(to).set_data(input.clone()); - let res0 = - provider.call(&tx, Some(BlockNumber::Number(block_number.into()).into())).await.unwrap(); + let provider = http_provider(rpc::next_http_archive_rpc_endpoint().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( - CallRequest { - to: Some(to.to_alloy()), - input: input.to_alloy().into(), + WithOtherFields::new(TransactionRequest { + to: Some(TxKind::from(to)), + input: input.into(), ..Default::default() - }, + }), None, None, ) .await .unwrap(); - assert_eq!(res0, res1.to_ethers()); + assert_eq!(res0, res1); } #[tokio::test(flavor = "multi_thread")] @@ -807,10 +813,10 @@ 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(rU256::from(1)), None).await.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.to::() < latest_block.header.timestamp.to::()); + assert!(initial_block.header.timestamp < latest_block.header.timestamp); } #[tokio::test(flavor = "multi_thread")] @@ -818,23 +824,20 @@ 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(rU256::from(1)), None).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.to::()).unwrap(); - api.anvil_mine(Some(rU256::from(1)), None).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.to::(), - latest_block.header.timestamp.to::() - ); + 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 = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); // Block on ETH mainnet with 2 uncles let block_with_uncles = 190u64; @@ -844,35 +847,36 @@ async fn test_fork_uncles_fetch() { assert_eq!(block.uncles.len(), 2); - let count = provider.get_uncle_count(block_with_uncles).await.unwrap(); - assert_eq!(count.as_usize(), block.uncles.len()); + let count = provider.get_uncle_count(block_with_uncles.into()).await.unwrap(); + assert_eq!(count as usize, block.uncles.len()); - let count = provider.get_uncle_count(block.header.hash.unwrap().to_ethers()).await.unwrap(); - assert_eq!(count.as_usize(), block.uncles.len()); + let hash = BlockId::hash(block.header.hash.unwrap()); + 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(block_with_uncles, (uncle_idx as u64).into()) + .get_uncle(BlockId::number(block_with_uncles), uncle_idx as u64) .await .unwrap() .unwrap(); - assert_eq!(*uncle_hash, uncle.hash.unwrap().to_alloy()); + assert_eq!(*uncle_hash, uncle.header.hash.unwrap()); // Try with block hash let uncle = provider - .get_uncle(block.header.hash.unwrap().to_ethers(), (uncle_idx as u64).into()) + .get_uncle(BlockId::hash(block.header.hash.unwrap()), uncle_idx as u64) .await .unwrap() .unwrap(); - assert_eq!(*uncle_hash, uncle.hash.unwrap().to_alloy()); + assert_eq!(*uncle_hash, uncle.header.hash.unwrap()); } } #[tokio::test(flavor = "multi_thread")] async fn test_fork_block_transaction_count() { let (api, handle) = spawn(fork_config()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); let accounts: Vec<_> = handle.dev_wallets().collect(); let sender = accounts[0].address(); @@ -882,8 +886,10 @@ async fn test_fork_block_transaction_count() { // transfer: impersonate real sender api.anvil_impersonate_account(sender).await.unwrap(); - let tx = TransactionRequest::new().from(sender.to_ethers()).value(42u64).gas(100_000); - provider.send_transaction(tx, None).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(); @@ -927,31 +933,32 @@ async fn test_fork_block_transaction_count() { #[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 = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); let token_holder: Address = "0x2f0b23f53734252bda2277357e97e1517d6b042a".parse().unwrap(); let to = Address::random(); - let val = 1337u64; + let val = U256::from(1337u64); // fund the impersonated account - api.anvil_set_balance(token_holder.to_alloy(), rU256::from(1e18 as u64)).await.unwrap(); - - let tx = TransactionRequest::new().from(token_holder).to(to).value(val); + api.anvil_set_balance(token_holder, U256::from(1e18)).await.unwrap(); - let res = provider.send_transaction(tx.clone(), None).await; + 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.to_alloy()).await.unwrap(); + api.anvil_impersonate_account(token_holder).await.unwrap(); - let res = provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); + let res = provider.send_transaction(tx.clone()).await.unwrap().get_receipt().await.unwrap(); assert_eq!(res.from, token_holder); - assert_eq!(res.status, Some(1u64.into())); + let status = res.inner.inner.inner.receipt.status.coerce_status(); + assert!(status); - let balance = provider.get_balance(to, None).await.unwrap(); - assert_eq!(balance, val.into()); + let balance = provider.get_balance(to).await.unwrap(); + assert_eq!(balance, val); - api.anvil_stop_impersonating_account(token_holder.to_alloy()).await.unwrap(); - let res = provider.send_transaction(tx, None).await; + api.anvil_stop_impersonating_account(token_holder).await.unwrap(); + let res = provider.send_transaction(tx).await; res.unwrap_err(); } @@ -960,22 +967,22 @@ async fn can_impersonate_in_fork() { async fn test_total_difficulty_fork() { let (api, handle) = spawn(fork_config()).await; - let total_difficulty: U256 = 46_673_965_560_973_856_260_636u128.into(); - let difficulty: U256 = 13_680_435_288_526_144u128.into(); + 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 = ethers_http_provider(&handle.http_endpoint()); - let block = provider.get_block(BlockNumber::Latest).await.unwrap().unwrap(); - assert_eq!(block.total_difficulty, Some(total_difficulty)); - assert_eq!(block.difficulty, difficulty); + let provider = handle.http_provider(); + let block = provider.get_block(BlockId::latest(), false.into()).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(BlockNumber::Latest).await.unwrap().unwrap(); - assert_eq!(block.total_difficulty, Some(next_total_difficulty)); - assert_eq!(block.difficulty, U256::zero()); + let block = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); + assert_eq!(block.header.total_difficulty, Some(next_total_difficulty)); + assert_eq!(block.header.difficulty, U256::ZERO); } // @@ -1025,29 +1032,24 @@ async fn can_override_fork_chain_id() { .with_chain_id(Some(chain_id_override)), ) .await; - let provider = ethers_http_provider(&handle.http_endpoint()); - - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let greeter_contract = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .send() - .await - .unwrap(); + 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); + assert_eq!("Hello World!", greeting._0); let greeter_contract = - Greeter::deploy(client, "Hello World!".to_string()).unwrap().send().await.unwrap(); - + Greeter::deploy(provider.clone(), "Hello World!".to_string()).await.unwrap(); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); + assert_eq!("Hello World!", greeting._0); - let provider = ethers_http_provider(&handle.http_endpoint()); - let chain_id = provider.get_chainid().await.unwrap(); - assert_eq!(chain_id.as_u64(), chain_id_override); + let provider = handle.http_provider(); + let chain_id = provider.get_chain_id().await.unwrap(); + assert_eq!(chain_id, chain_id_override); } // @@ -1060,14 +1062,18 @@ async fn test_fork_reset_moonbeam() { .with_fork_block_number(None::), ) .await; - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); let accounts: Vec<_> = handle.dev_wallets().collect(); let from = accounts[0].address(); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64).from(from.to_ethers()); - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - assert_eq!(tx.status, Some(1u64.into())); + 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 { @@ -1077,9 +1083,12 @@ async fn test_fork_reset_moonbeam() { .await .unwrap(); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64).from(from.to_ethers()); - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - assert_eq!(tx.status, Some(1u64.into())); + 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); } // - assert_eq!(latest.header.base_fee_per_gas.unwrap(), rU256::from(59455969592u64)); + assert_eq!(latest.header.base_fee_per_gas.unwrap(), 59455969592u128); // now reset to block 18835000 -1 api.anvil_reset(Some(Forking { json_rpc_url: None, block_number: Some(18835000u64 - 1) })) @@ -1103,7 +1112,7 @@ async fn test_fork_reset_basefee() { 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(), rU256::from(59017001138u64)); + assert_eq!(latest.header.base_fee_per_gas.unwrap(), 59017001138u128); } // @@ -1119,10 +1128,59 @@ async fn test_arbitrum_fork_dev_balance() { 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, rU256::from(100000000000000000000u128)); + assert_eq!(balance, U256::from(100000000000000000000u128)); } } +// +#[tokio::test(flavor = "multi_thread")] +async fn 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("https://arb1.arbitrum.io/rpc".to_string())), + ) + .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("https://arb1.arbitrum.io/rpc".to_string())), + ) + .await; + let block_number = api.block_number().unwrap().to::(); + assert_eq!(block_number, initial_block_number); + + // take snapshot at initial block number + let snapshot = 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); + + // revert to recorded snapshot and check block number + assert!(api.evm_revert(snapshot).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("https://arb1.arbitrum.io/rpc".to_string()), + 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_fork_execution_reverted() { @@ -1131,11 +1189,11 @@ async fn test_fork_execution_reverted() { let resp = api .call( - CallRequest { - to: Some(address!("Fd6CC4F251eaE6d02f9F7B41D1e80464D3d2F377")), + WithOtherFields::new(TransactionRequest { + to: Some(TxKind::from(address!("Fd6CC4F251eaE6d02f9F7B41D1e80464D3d2F377"))), input: TransactionInput::new("0x8f283b3c".as_bytes().into()), ..Default::default() - }, + }), Some(target.into()), None, ) diff --git a/crates/anvil/tests/it/ganache.rs b/crates/anvil/tests/it/ganache.rs deleted file mode 100644 index 3ea0e84f5..000000000 --- a/crates/anvil/tests/it/ganache.rs +++ /dev/null @@ -1,185 +0,0 @@ -//! tests against local ganache for local debug purposes -#![allow(unused)] -use crate::init_tracing; -use ethers::{ - abi::Address, - contract::{Contract, ContractFactory, ContractInstance}, - core::k256::SecretKey, - prelude::{abigen, Middleware, Signer, SignerMiddleware, TransactionRequest, Ws}, - providers::{Http, Provider}, - signers::LocalWallet, - types::{BlockNumber, U256}, - utils::hex, -}; -use ethers_solc::{project_util::TempProject, Artifact}; -use std::sync::Arc; - -// the mnemonic used to start the local ganache instance -const MNEMONIC: &str = - "amazing discover palace once resource choice flush horn wink shift planet relief"; - -fn ganache_wallet() -> LocalWallet { - wallet("552dd2534c4984f892191997d6b1dd9e6a23c7e07b908a6cebfad1d3f2af4c4c") -} - -fn ganache_wallet2() -> LocalWallet { - wallet("305b526d493844b63466be6d48a424ab83f5216011eef860acc6db4c1821adc9") -} - -fn wallet(key_str: &str) -> LocalWallet { - let key_hex = hex::decode(key_str).expect("could not parse as hex"); - let key = SecretKey::from_bytes(key_hex.as_slice().into()).expect("did not get private key"); - key.into() -} - -fn http_client() -> Arc, LocalWallet>> { - let provider = Provider::::try_from("http://127.0.0.1:8545").unwrap(); - Arc::new(SignerMiddleware::new(provider, ganache_wallet())) -} - -async fn ws_client() -> Arc, LocalWallet>> { - let provider = Provider::::connect("ws://127.0.0.1:8545").await.unwrap(); - Arc::new(SignerMiddleware::new(provider, ganache_wallet())) -} - -#[tokio::test(flavor = "multi_thread")] -#[ignore] -async fn test_ganache_block_number() { - let client = http_client(); - let balance = client - .get_balance(Address::random(), Some(BlockNumber::Number(100u64.into()).into())) - .await; -} - -#[tokio::test(flavor = "multi_thread")] -#[ignore] -async fn test_ganache_deploy() { - abigen!(Greeter, "test-data/greeter.json"); - let client = http_client(); - - let greeter_contract = - Greeter::deploy(client, "Hello World!".to_string()).unwrap().legacy().send().await.unwrap(); - - let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); -} - -#[tokio::test(flavor = "multi_thread")] -#[ignore] -async fn test_ganache_emit_logs() { - abigen!(EmitLogs, "test-data/emit_logs.json"); - let client = ws_client().await; - - let msg = "First Message".to_string(); - let contract = - EmitLogs::deploy(Arc::clone(&client), msg.clone()).unwrap().legacy().send().await.unwrap(); - - let val = contract.get_value().call().await.unwrap(); - assert_eq!(val, msg); - - let val = contract - .set_value("Next Message".to_string()) - .legacy() - .send() - .await - .unwrap() - .await - .unwrap() - .unwrap(); -} - -#[tokio::test(flavor = "multi_thread")] -#[ignore] -async fn test_ganache_deploy_reverting() { - let client = http_client(); - - let prj = TempProject::dapptools().unwrap(); - prj.add_source( - "Contract", - r#" -pragma solidity 0.8.13; -contract Contract { - constructor() { - require(false, ""); - } -} -"#, - ) - .unwrap(); - - let mut compiled = prj.compile().unwrap(); - println!("{compiled}"); - assert!(!compiled.has_compiler_errors()); - - let contract = compiled.remove_first("Contract").unwrap(); - - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); - - let factory = ContractFactory::new(abi.unwrap(), bytecode.unwrap(), Arc::clone(&client)); - let contract = factory.deploy(()).unwrap().legacy().send().await; - contract.unwrap_err(); -} - -#[tokio::test(flavor = "multi_thread")] -#[ignore] -async fn test_ganache_tx_reverting() { - let prj = TempProject::dapptools().unwrap(); - prj.add_source( - "Contract", - r#" -pragma solidity 0.8.13; -contract Contract { - address owner; - constructor() public { - owner = msg.sender; - } - modifier onlyOwner() { - require(msg.sender == owner, "!authorized"); - _; - } - function getSecret() public onlyOwner view returns(uint256 secret) { - return 123; - } -} -"#, - ) - .unwrap(); - - let mut compiled = prj.compile().unwrap(); - assert!(!compiled.has_compiler_errors()); - let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); - - let client = Arc::new(http_client()); - - // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().legacy().send().await.unwrap(); - let provider = SignerMiddleware::new( - Provider::::try_from("http://127.0.0.1:8545").unwrap(), - ganache_wallet2(), - ); - let contract = ContractInstance::new(contract.address(), abi.unwrap(), provider); - let resp = contract.method::<_, U256>("getSecret", ()).unwrap().legacy().call().await; - resp.unwrap_err(); - - /* Ganache rpc errors look like: - < { - < "id": 1627277502538, - < "jsonrpc": "2.0", - < "error": { - < "message": "VM Exception while processing transaction: revert !authorized", - < "code": -32000, - < "data": { - < "0x90264de254689f1d4e7f8670cd97f60d9bc803874fdecb34d249bd1cc3ca823a": { - < "error": "revert", - < "program_counter": 223, - < "return": "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000b21617574686f72697a6564000000000000000000000000000000000000000000", - < "reason": "!authorized" - < }, - < "stack": "c: VM Exception while processing transaction: revert !authorized\n at Function.c.fromResults (/usr/local/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:4:192416)\n at /usr/local/lib/node_modules/ganache-cli/build/ganache-core.node.cli.js:42:50402", - < "name": "c" - < } - < } - */ -} diff --git a/crates/anvil/tests/it/gas.rs b/crates/anvil/tests/it/gas.rs index 4f6098bfb..af28983f1 100644 --- a/crates/anvil/tests/it/gas.rs +++ b/crates/anvil/tests/it/gas.rs @@ -1,77 +1,129 @@ //! Gas related tests -use crate::utils::ethers_http_provider; -use alloy_primitives::U256; +use crate::utils::http_provider_with_signer; +use alloy_network::{EthereumWallet, TransactionBuilder}; +use alloy_primitives::{Address, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::{BlockId, TransactionRequest}; +use alloy_serde::WithOtherFields; use anvil::{eth::fees::INITIAL_BASE_FEE, spawn, NodeConfig}; -use ethers::{ - prelude::Middleware, - types::{ - transaction::eip2718::TypedTransaction, Address, BlockNumber, Eip1559TransactionRequest, - TransactionRequest, - }, -}; -use foundry_common::types::ToAlloy; -const GAS_TRANSFER: u64 = 21_000u64; +const GAS_TRANSFER: u128 = 21_000; #[tokio::test(flavor = "multi_thread")] async fn test_basefee_full_block() { let (_api, handle) = spawn( - NodeConfig::test() - .with_base_fee(Some(INITIAL_BASE_FEE.to_alloy())) - .with_gas_limit(Some(GAS_TRANSFER.to_alloy())), + NodeConfig::test().with_base_fee(Some(INITIAL_BASE_FEE)).with_gas_limit(Some(GAS_TRANSFER)), ) .await; - let provider = ethers_http_provider(&handle.http_endpoint()); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64); - provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); - let base_fee = - provider.get_block(BlockNumber::Latest).await.unwrap().unwrap().base_fee_per_gas.unwrap(); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64); - provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); - let next_base_fee = - provider.get_block(BlockNumber::Latest).await.unwrap().unwrap().base_fee_per_gas.unwrap(); + + 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(), false.into()) + .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(), false.into()) + .await + .unwrap() + .unwrap() + .header + .base_fee_per_gas + .unwrap(); assert!(next_base_fee > base_fee); + // max increase, full block - assert_eq!(next_base_fee.as_u64(), INITIAL_BASE_FEE + 125_000_000); + 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.to_alloy())) - .with_gas_limit(Some(GAS_TRANSFER.to_alloy() * U256::from(2))), + .with_base_fee(Some(INITIAL_BASE_FEE)) + .with_gas_limit(Some(GAS_TRANSFER * 2)), ) .await; - let provider = ethers_http_provider(&handle.http_endpoint()); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64); - provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64); - provider.send_transaction(tx.clone(), None).await.unwrap().await.unwrap().unwrap(); - let next_base_fee = - provider.get_block(BlockNumber::Latest).await.unwrap().unwrap().base_fee_per_gas.unwrap(); + + 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(), false.into()) + .await + .unwrap() + .unwrap() + .header + .base_fee_per_gas + .unwrap(); // unchanged, half block - assert_eq!(next_base_fee.as_u64(), INITIAL_BASE_FEE); + 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.to_alloy()))).await; + let (api, handle) = spawn(NodeConfig::test().with_base_fee(Some(INITIAL_BASE_FEE))).await; - let provider = ethers_http_provider(&handle.http_endpoint()); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64); - provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - let base_fee = - provider.get_block(BlockNumber::Latest).await.unwrap().unwrap().base_fee_per_gas.unwrap(); + 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(), false.into()) + .await + .unwrap() + .unwrap() + .header + .base_fee_per_gas + .unwrap(); // mine empty block api.mine_one().await; - let next_base_fee = - provider.get_block(BlockNumber::Latest).await.unwrap().unwrap().base_fee_per_gas.unwrap(); + let next_base_fee = provider + .get_block(BlockId::latest(), false.into()) + .await + .unwrap() + .unwrap() + .header + .base_fee_per_gas + .unwrap(); // empty block, decreased base fee assert!(next_base_fee < base_fee); @@ -79,40 +131,65 @@ async fn test_basefee_empty_block() { #[tokio::test(flavor = "multi_thread")] async fn test_respect_base_fee() { - let base_fee = 50u64; - let (_api, handle) = spawn(NodeConfig::test().with_base_fee(Some(base_fee.to_alloy()))).await; - let provider = ethers_http_provider(&handle.http_endpoint()); - let mut tx = TypedTransaction::default(); - tx.set_value(100u64); - tx.set_to(Address::random()); + let base_fee = 50u128; + let (_api, handle) = spawn(NodeConfig::test().with_base_fee(Some(base_fee))).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, None).await; + + 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); - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - assert_eq!(tx.status, Some(1u64.into())); + 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 = 50u64; - let (_api, handle) = spawn(NodeConfig::test().with_base_fee(Some(base_fee.to_alloy()))).await; - let provider = ethers_http_provider(&handle.http_endpoint()); - let tx = TypedTransaction::Eip1559( - Eip1559TransactionRequest::new() - .max_fee_per_gas(base_fee) - .max_priority_fee_per_gas(base_fee + 1) - .to(Address::random()) - .value(100u64), - ); - let res = provider.send_transaction(tx, None).await; + let base_fee = 50u128; + let (_api, handle) = spawn(NodeConfig::test().with_base_fee(Some(base_fee))).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))).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()); + } +} diff --git a/crates/anvil/tests/it/genesis.rs b/crates/anvil/tests/it/genesis.rs index 9b822aff7..139a2fe20 100644 --- a/crates/anvil/tests/it/genesis.rs +++ b/crates/anvil/tests/it/genesis.rs @@ -1,11 +1,10 @@ //! genesis.json tests -use std::str::FromStr; - use alloy_genesis::Genesis; -use alloy_primitives::{Address, U256, U64}; -use alloy_providers::provider::TempProvider; +use alloy_primitives::{Address, U256}; +use alloy_provider::Provider; use anvil::{spawn, NodeConfig}; +use std::str::FromStr; #[tokio::test(flavor = "multi_thread")] async fn can_apply_genesis() { @@ -41,10 +40,10 @@ async fn can_apply_genesis() { let provider = handle.http_provider(); - assert_eq!(provider.get_chain_id().await.unwrap(), U64::from(19763u64)); + assert_eq!(provider.get_chain_id().await.unwrap(), 19763u64); let addr: Address = Address::from_str("71562b71999873db5b286df957af199ec94617f7").unwrap(); - let balance = provider.get_balance(addr, None).await.unwrap(); + let balance = provider.get_balance(addr).await.unwrap(); let expected: U256 = U256::from_str_radix("ffffffffffffffffffffffffff", 16).unwrap(); assert_eq!(balance, expected); diff --git a/crates/anvil/tests/it/geth.rs b/crates/anvil/tests/it/geth.rs deleted file mode 100644 index 31429d0bd..000000000 --- a/crates/anvil/tests/it/geth.rs +++ /dev/null @@ -1,91 +0,0 @@ -//! tests against local geth for local debug purposes - -use crate::abi::VENDING_MACHINE_CONTRACT; -use ethers::{ - abi::Address, - contract::{Contract, ContractFactory}, - prelude::{Middleware, TransactionRequest}, - providers::Provider, - types::U256, - utils::WEI_IN_ETHER, -}; -use ethers_solc::{project_util::TempProject, Artifact}; -use futures::StreamExt; -use std::sync::Arc; -use tokio::time::timeout; - -#[tokio::test(flavor = "multi_thread")] -#[ignore] -async fn test_geth_pending_transaction() { - let client = Provider::try_from("http://127.0.0.1:8545").unwrap(); - let accounts = client.get_accounts().await.unwrap(); - let tx = TransactionRequest::new() - .from(accounts[0]) - .to(Address::random()) - .value(1337u64) - .nonce(2u64); - - let mut watch_tx_stream = - client.watch_pending_transactions().await.unwrap().transactions_unordered(1).fuse(); - - let _res = client.send_transaction(tx, None).await.unwrap(); - - let pending = timeout(std::time::Duration::from_secs(3), watch_tx_stream.next()).await; - pending.unwrap_err(); -} - -// check how geth returns reverts -#[tokio::test(flavor = "multi_thread")] -#[ignore] -async fn test_geth_revert_transaction() { - let prj = TempProject::dapptools().unwrap(); - prj.add_source("VendingMachine", VENDING_MACHINE_CONTRACT).unwrap(); - - let mut compiled = prj.compile().unwrap(); - assert!(!compiled.has_compiler_errors()); - let contract = compiled.remove_first("VendingMachine").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); - - let client = Arc::new(Provider::try_from("http://127.0.0.1:8545").unwrap()); - - let account = client.get_accounts().await.unwrap().remove(0); - - // deploy successfully - let factory = - ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), Arc::clone(&client)); - - let mut tx = factory.deploy(()).unwrap().tx; - tx.set_from(account); - - let resp = client.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - - let contract = - Contract::>::new(resp.contract_address.unwrap(), abi.unwrap(), client); - - let ten = WEI_IN_ETHER.saturating_mul(10u64.into()); - let call = contract.method::<_, ()>("buyRevert", ten).unwrap().value(ten).from(account); - let resp = call.call().await; - let err = resp.unwrap_err().to_string(); - assert!(err.contains("execution reverted: Not enough Ether provided.")); - assert!(err.contains("code: 3")); -} - -#[tokio::test(flavor = "multi_thread")] -#[ignore] -async fn test_geth_low_gas_limit() { - let provider = Arc::new(Provider::try_from("http://127.0.0.1:8545").unwrap()); - - let account = provider.get_accounts().await.unwrap().remove(0); - - let gas = 21_000u64 - 1; - let tx = TransactionRequest::new() - .to(Address::random()) - .value(U256::from(1337u64)) - .from(account) - .gas(gas); - - let resp = provider.send_transaction(tx, None).await; - - let err = resp.unwrap_err().to_string(); - assert!(err.contains("intrinsic gas too low")); -} diff --git a/crates/anvil/tests/it/ipc.rs b/crates/anvil/tests/it/ipc.rs index 8b7de0884..786217ecd 100644 --- a/crates/anvil/tests/it/ipc.rs +++ b/crates/anvil/tests/it/ipc.rs @@ -1,50 +1,60 @@ //! IPC tests -use crate::utils::ethers_ipc_provider; +use crate::{init_tracing, utils::connect_pubsub}; use alloy_primitives::U256; +use alloy_provider::Provider; use anvil::{spawn, NodeConfig}; -use ethers::prelude::Middleware; use futures::StreamExt; - -pub fn rand_ipc_endpoint() -> String { - let num: u64 = rand::Rng::gen(&mut rand::thread_rng()); - if cfg!(windows) { - format!(r"\\.\pipe\anvil-ipc-{num}") +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 { - format!(r"/tmp/anvil-ipc-{num}") + dir = None; + path = format!(r"\\.\pipe\anvil_test_{}.ipc", rand::random::()); } -} - -fn ipc_config() -> NodeConfig { - NodeConfig::test().with_ipc(Some(Some(rand_ipc_endpoint()))) + let config = NodeConfig::test().with_ipc(Some(Some(path))); + (dir, config) } #[tokio::test(flavor = "multi_thread")] +#[cfg_attr(windows, ignore = "TODO")] async fn can_get_block_number_ipc() { - let (api, handle) = spawn(ipc_config()).await; + 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 = ethers_ipc_provider(handle.ipc_path()).unwrap(); + let provider = handle.ipc_provider().unwrap(); let num = provider.get_block_number().await.unwrap(); - assert_eq!(num.as_u64(), block_num.to::()); + assert_eq!(num, block_num.to::()); } #[tokio::test(flavor = "multi_thread")] +#[cfg_attr(windows, ignore = "TODO")] async fn test_sub_new_heads_ipc() { - let (api, handle) = spawn(ipc_config()).await; - - let provider = ethers_ipc_provider(handle.ipc_path()).unwrap(); + init_tracing(); - let blocks = provider.subscribe_blocks().await.unwrap(); + 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.unwrap().as_u64()).collect::>(); + let block_numbers = blocks.into_iter().map(|b| b.header.number.unwrap()).collect::>(); assert_eq!(block_numbers, vec![1, 2, 3]); } diff --git a/crates/anvil/tests/it/logs.rs b/crates/anvil/tests/it/logs.rs index 5fc9cf9f2..40d032d7c 100644 --- a/crates/anvil/tests/it/logs.rs +++ b/crates/anvil/tests/it/logs.rs @@ -1,180 +1,204 @@ //! log/event related tests use crate::{ - abi::*, - utils::{ethers_http_provider, ethers_ws_provider}, + abi::SimpleStorage::{self}, + utils::{http_provider_with_signer, ws_provider_with_signer}, }; +use alloy_network::EthereumWallet; +use alloy_primitives::B256; +use alloy_provider::Provider; +use alloy_rpc_types::{BlockNumberOrTag, Filter}; use anvil::{spawn, NodeConfig}; -use ethers::{ - middleware::SignerMiddleware, - prelude::{BlockNumber, Filter, FilterKind, Middleware, Signer, H256}, - types::Log, -}; -use foundry_common::types::ToEthers; use futures::StreamExt; -use std::sync::Arc; #[tokio::test(flavor = "multi_thread")] async fn get_past_events() { let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let address = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let wallet = handle.dev_wallets().next().unwrap(); + let account = wallet.address(); + let signer: EthereumWallet = wallet.into(); - let contract = SimpleStorage::deploy(Arc::clone(&client), "initial value".to_string()) - .unwrap() + 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 func = contract.method::<_, H256>("setValue", "hi".to_owned()).unwrap(); - let tx = func.send().await.unwrap(); - let _receipt = tx.await.unwrap(); + let filter = Filter::new() + .address(simple_storage_address) + .topic1(B256::from(account.into_word())) + .from_block(BlockNumberOrTag::from(0)); - // and we can fetch the events - let logs: Vec = - contract.event().from_block(0u64).topic1(address).query().await.unwrap(); + 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].new_value, "initial value"); - assert_eq!(logs[1].new_value, "hi"); + 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 = client.get_block(1).await.unwrap().unwrap().hash.unwrap(); + // let hash = provider.get_block(1).await.unwrap().unwrap().hash.unwrap(); + let hash = provider + .get_block_by_number(BlockNumberOrTag::from(1), false) + .await + .unwrap() + .unwrap() + .header + .hash + .unwrap(); + + 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::>(); - let logs: Vec = - contract.event().at_block_hash(hash).topic1(address).query().await.unwrap(); - assert_eq!(logs[0].new_value, "initial value"); + 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 provider = ethers_http_provider(&handle.http_endpoint()); - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let wallet = handle.dev_wallets().next().unwrap(); + let account = wallet.address(); + let signer: EthereumWallet = wallet.into(); - let contract = SimpleStorage::deploy(Arc::clone(&client), "initial value".to_string()) - .unwrap() - .send() - .await - .unwrap(); + 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 = client.get_logs(&Filter::new().from_block(BlockNumber::Earliest)).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 = - client.get_logs(&Filter::new().from_block(BlockNumber::Number(0u64.into()))).await.unwrap(); + 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 func = contract.method::<_, H256>("setValue", "hi".to_owned()).unwrap(); - let tx = func.send().await.unwrap(); + let tx = tx.send().await.unwrap(); api.mine_one().await; - let _receipt = tx.await.unwrap(); + tx.get_receipt().await.unwrap(); } - let logs = client.get_logs(&Filter::new().from_block(BlockNumber::Earliest)).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); -} - -#[tokio::test(flavor = "multi_thread")] -async fn can_install_filter() { - let (api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + // 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 = std::collections::HashSet::new(); + 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 contract = SimpleStorage::deploy(Arc::clone(&client), "initial value".to_string()) - .unwrap() - .send() + let receipt_logs = futures::future::join_all(tasks) .await - .unwrap(); - - let filter = Filter::new().from_block(BlockNumber::Number(0u64.into())); - - let filter = client.new_filter(FilterKind::Logs(&filter)).await.unwrap(); - - let logs = client.get_filter_changes::<_, Log>(filter).await.unwrap(); - assert_eq!(logs.len(), 1); - - let logs = client.get_filter_changes::<_, Log>(filter).await.unwrap(); - assert!(logs.is_empty()); - api.anvil_set_auto_mine(false).await.unwrap(); - // create some logs - let num_logs = 10; - for _ in 0..num_logs { - let func = contract.method::<_, H256>("setValue", "hi".to_owned()).unwrap(); - let tx = func.send().await.unwrap(); - api.mine_one().await; - let _receipt = tx.await.unwrap(); - let logs = client.get_filter_changes::<_, Log>(filter).await.unwrap(); - assert_eq!(logs.len(), 1); + .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); } - let all_logs = api - .get_filter_logs(&serde_json::to_string(&filter).unwrap().replace('\"', "")) - .await - .unwrap(); - - assert_eq!(all_logs.len(), num_logs + 1); } #[tokio::test(flavor = "multi_thread")] async fn watch_events() { let (_api, handle) = spawn(NodeConfig::test()).await; - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let provider = ethers_http_provider(&handle.http_endpoint()); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - - let contract = SimpleStorage::deploy(Arc::clone(&client), "initial value".to_string()) - .unwrap() - .send() - .await - .unwrap(); - - // We spawn the event listener: - let event = contract.event::(); - let mut stream = event.stream().await.unwrap(); - - // Also set up a subscription for the same thing - let ws = Arc::new(ethers_ws_provider(&handle.ws_endpoint())); - let contract2 = SimpleStorage::new(contract.address(), ws); - let event2 = contract2.event::(); - let mut subscription = event2.subscribe().await.unwrap(); - - let mut subscription_meta = event2.subscribe().await.unwrap().with_meta(); - - let num_calls = 3u64; - - // and we make a few calls - let num = client.get_block_number().await.unwrap(); - for i in 0..num_calls { - let call = contract.method::<_, H256>("setValue", i.to_string()).unwrap().legacy(); - let pending_tx = call.send().await.unwrap(); - let _receipt = pending_tx.await.unwrap(); - } - for i in 0..num_calls { - // unwrap the option of the stream, then unwrap the decoding result - let log = stream.next().await.unwrap().unwrap(); - let log2 = subscription.next().await.unwrap().unwrap(); - let (log3, meta) = subscription_meta.next().await.unwrap().unwrap(); - assert_eq!(log.new_value, log3.new_value); - assert_eq!(log.new_value, log2.new_value); - assert_eq!(log.new_value, i.to_string()); - assert_eq!(meta.block_number, num + i + 1); - let hash = client.get_block(num + i + 1).await.unwrap().unwrap().hash.unwrap(); - assert_eq!(meta.block_hash, hash); + 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), false) + .await + .unwrap() + .unwrap() + .header + .hash + .unwrap(); + assert_eq!(log.1.block_hash.unwrap(), hash); } } diff --git a/crates/anvil/tests/it/main.rs b/crates/anvil/tests/it/main.rs index 1ba8e3fe9..559593b72 100644 --- a/crates/anvil/tests/it/main.rs +++ b/crates/anvil/tests/it/main.rs @@ -1,20 +1,19 @@ +#![allow(clippy::octal_escapes)] mod abi; mod anvil; mod anvil_api; mod api; +mod eip4844; mod fork; -mod ganache; mod gas; mod genesis; -mod geth; mod ipc; mod logs; mod optimism; +mod otterscan; mod proof; mod pubsub; -// mod revert; // TODO uncomment -mod otterscan; - +mod revert; mod sign; mod state; mod traces; diff --git a/crates/anvil/tests/it/optimism.rs b/crates/anvil/tests/it/optimism.rs index 78dcf9227..3406e6865 100644 --- a/crates/anvil/tests/it/optimism.rs +++ b/crates/anvil/tests/it/optimism.rs @@ -1,52 +1,43 @@ //! Tests for OP chain support. -use crate::utils::ethers_http_provider; +use crate::utils::http_provider_with_signer; +use alloy_eips::eip2718::Encodable2718; +use alloy_network::{EthereumWallet, TransactionBuilder}; +use alloy_primitives::{b256, U128, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::{optimism::OptimismTransactionFields, TransactionRequest}; +use alloy_serde::WithOtherFields; use anvil::{spawn, Hardfork, NodeConfig}; -use ethers::{ - abi::Address, - providers::Middleware, - types::{ - transaction::{eip2718::TypedTransaction, optimism::DepositTransaction}, - TransactionRequest, U256, - }, -}; -use ethers_core::types::{Bytes, H256}; -use foundry_common::types::ToAlloy; -use std::str::FromStr; #[tokio::test(flavor = "multi_thread")] async fn test_deposits_not_supported_if_optimism_disabled() { - // optimism disabled by default - let (_, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); - - let from_addr: Address = "cf7f9e66af820a19257a2108375b180b0ec49167".parse().unwrap(); - let to_addr: Address = "71562b71999873db5b286df957af199ec94617f7".parse().unwrap(); - let deposit_tx: TypedTransaction = TypedTransaction::DepositTransaction(DepositTransaction { - tx: TransactionRequest { - chain_id: None, - from: Some(from_addr), - to: Some(ethers::types::NameOrAddress::Address(to_addr)), - value: Some("1234".parse().unwrap()), - gas: Some(U256::from(21000)), - gas_price: None, - data: Some(Bytes::default()), - nonce: None, - }, - source_hash: H256::from_str( - "0000000000000000000000000000000000000000000000000000000000000000", - ) - .unwrap(), - mint: Some(U256::zero()), - is_system_tx: true, - }); - - // sending the deposit transaction should fail with error saying not supported - let res = provider.send_transaction(deposit_tx.clone(), None).await; - assert!(res - .unwrap_err() - .to_string() - .contains("op-stack deposit tx received but is not supported")); + 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 tx = WithOtherFields { + inner: tx, + other: OptimismTransactionFields { + source_hash: Some(b256!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + mint: Some(U128::from(0)), + is_system_tx: Some(true), + } + .into(), + }; + + 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")] @@ -54,46 +45,47 @@ async fn test_send_value_deposit_transaction() { // enable the Optimism flag let (api, handle) = spawn(NodeConfig::test().with_optimism(true).with_hardfork(Some(Hardfork::Paris))).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + + 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 from_addr: Address = "cf7f9e66af820a19257a2108375b180b0ec49167".parse().unwrap(); - let to_addr: Address = "71562b71999873db5b286df957af199ec94617f7".parse().unwrap(); - - // fund the sender - api.anvil_set_balance(from_addr.to_alloy(), send_value.to_alloy()).await.unwrap(); - - let deposit_tx: TypedTransaction = TypedTransaction::DepositTransaction(DepositTransaction { - tx: TransactionRequest { - chain_id: None, - from: Some(from_addr), - to: Some(ethers::types::NameOrAddress::Address(to_addr)), - value: Some(send_value), - gas: Some(U256::from(21000)), - gas_price: None, - data: Some(Bytes::default()), - nonce: None, - }, - source_hash: H256::from_str( - "0000000000000000000000000000000000000000000000000000000000000000", - ) - .unwrap(), - mint: Some(U256::zero()), - is_system_tx: true, - }); - - let pending = provider.send_transaction(deposit_tx.clone(), None).await.unwrap(); + let before_balance_to = provider.get_balance(to).await.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: OptimismTransactionFields { + source_hash: Some(b256!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + mint: Some(U128::from(0)), + is_system_tx: Some(true), + } + .into(), + }; + + 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()).await.unwrap().unwrap(); - assert_eq!(receipt.from, from_addr); - assert_eq!(receipt.to, Some(to_addr)); + 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 balance = provider.get_balance(to_addr, None).await.unwrap(); - assert_eq!(balance, send_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")] @@ -101,46 +93,54 @@ async fn test_send_value_raw_deposit_transaction() { // enable the Optimism flag let (api, handle) = spawn(NodeConfig::test().with_optimism(true).with_hardfork(Some(Hardfork::Paris))).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + + 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 from_addr: Address = "cf7f9e66af820a19257a2108375b180b0ec49167".parse().unwrap(); - let to_addr: Address = "71562b71999873db5b286df957af199ec94617f7".parse().unwrap(); - - // fund the sender - api.anvil_set_balance(from_addr.to_alloy(), send_value.to_alloy()).await.unwrap(); - - let deposit_tx: TypedTransaction = TypedTransaction::DepositTransaction(DepositTransaction { - tx: TransactionRequest { - chain_id: None, - from: Some(from_addr), - to: Some(ethers::types::NameOrAddress::Address(to_addr)), - value: Some(send_value), - gas: Some(U256::from(21000)), - gas_price: None, - data: Some(Bytes::default()), - nonce: None, - }, - source_hash: H256::from_str( - "0000000000000000000000000000000000000000000000000000000000000000", - ) - .unwrap(), - mint: Some(U256::zero()), - is_system_tx: true, - }); - - let rlpbytes = deposit_tx.rlp(); - let pending = provider.send_raw_transaction(rlpbytes).await.unwrap(); + 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 tx = WithOtherFields { + inner: tx, + other: OptimismTransactionFields { + source_hash: Some(b256!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + mint: Some(U128::from(0)), + is_system_tx: Some(true), + } + .into(), + }; + 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()).await.unwrap().unwrap(); - assert_eq!(receipt.from, from_addr); - assert_eq!(receipt.to, Some(to_addr)); - assert_eq!(receipt.other.get_deserialized::("depositNonce").unwrap().unwrap(), 0); + 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 balance = provider.get_balance(to_addr, None).await.unwrap(); - assert_eq!(balance, send_value); + let after_balance_to = provider.get_balance(to).await.unwrap(); + assert_eq!(after_balance_to, before_balance_to + send_value); } diff --git a/crates/anvil/tests/it/otterscan.rs b/crates/anvil/tests/it/otterscan.rs index 8dca5f7a2..dc0f297fd 100644 --- a/crates/anvil/tests/it/otterscan.rs +++ b/crates/anvil/tests/it/otterscan.rs @@ -1,449 +1,363 @@ -//! tests for otterscan endpoints -use crate::{ - abi::MulticallContract, - utils::{ethers_http_provider, ethers_ws_provider}, -}; -use alloy_primitives::U256 as rU256; -use alloy_rpc_types::{BlockNumberOrTag, BlockTransactions}; -use alloy_signer::Signer as AlloySigner; +//! Tests for otterscan endpoints. + +use crate::abi::MulticallContract; +use alloy_primitives::{address, Address, Bytes, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::{BlockNumberOrTag, BlockTransactions, TransactionRequest}; +use alloy_serde::WithOtherFields; +use alloy_sol_types::{sol, SolCall, SolError}; use anvil::{ eth::otterscan::types::{ OtsInternalOperation, OtsInternalOperationType, OtsTrace, OtsTraceType, }, - spawn, NodeConfig, -}; -use ethers::{ - abi::Address, - prelude::{ContractFactory, ContractInstance, Middleware, SignerMiddleware}, - signers::Signer, - types::{Bytes, TransactionRequest}, - utils::get_contract_address, + spawn, Hardfork, NodeConfig, }; -use ethers_solc::{project_util::TempProject, Artifact}; -use foundry_common::types::{ToAlloy, ToEthers}; -use std::{collections::VecDeque, str::FromStr, sync::Arc}; +use std::collections::VecDeque; #[tokio::test(flavor = "multi_thread")] -async fn can_call_erigon_get_header_by_number() { +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(); - let res1 = api.erigon_get_header_by_number(1.into()).await.unwrap().unwrap(); + assert_eq!(res0.header.number, Some(0)); - assert_eq!(res0.header.number, Some(rU256::from(0))); - assert_eq!(res1.header.number, Some(rU256::from(1))); + let res1 = api.erigon_get_header_by_number(1.into()).await.unwrap().unwrap(); + assert_eq!(res1.header.number, Some(1)); } #[tokio::test(flavor = "multi_thread")] -async fn can_call_ots_get_api_level() { +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 can_call_ots_get_internal_operations_contract_deploy() { +async fn ots_get_internal_operations_contract_deploy() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); - - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let sender = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - - let mut deploy_tx = MulticallContract::deploy(Arc::clone(&client), ()).unwrap().deployer.tx; - deploy_tx.set_nonce(0); - let contract_address = get_contract_address(sender, deploy_tx.nonce().unwrap()); + let provider = handle.http_provider(); + let sender = handle.dev_accounts().next().unwrap(); - let receipt = client.send_transaction(deploy_tx, None).await.unwrap().await.unwrap().unwrap(); - - let res = api.ots_get_internal_operations(receipt.transaction_hash.to_alloy()).await.unwrap(); + let contract_receipt = MulticallContract::deploy_builder(&provider) + .send() + .await + .unwrap() + .get_receipt() + .await + .unwrap(); - assert_eq!(res.len(), 1); + let res = api.ots_get_internal_operations(contract_receipt.transaction_hash).await.unwrap(); assert_eq!( - res[0], - OtsInternalOperation { + res, + [OtsInternalOperation { r#type: OtsInternalOperationType::Create, - from: sender.to_alloy(), - to: contract_address.to_alloy(), - value: rU256::from(0) - } + from: sender, + to: contract_receipt.contract_address.unwrap(), + value: U256::from(0) + }], ); } #[tokio::test(flavor = "multi_thread")] -async fn can_call_ots_get_internal_operations_contract_transfer() { +async fn ots_get_internal_operations_contract_transfer() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + 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(rU256::from(2u64)).unwrap(); + let amount = handle.genesis_balance().checked_div(U256::from(2u64)).unwrap(); - let tx = TransactionRequest::new() - .to(to.to_ethers()) - .value(amount.to_ethers()) - .from(from.to_ethers()); + let tx = TransactionRequest::default().to(to).value(amount).from(from); + let tx = WithOtherFields::new(tx); - let receipt = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let receipt = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); - let res = api.ots_get_internal_operations(receipt.transaction_hash.to_alloy()).await.unwrap(); - - assert_eq!(res.len(), 1); + let res = api.ots_get_internal_operations(receipt.transaction_hash).await.unwrap(); assert_eq!( - res[0], - OtsInternalOperation { + res, + [OtsInternalOperation { r#type: OtsInternalOperationType::Transfer, from, to, value: amount - } + }], ); } #[tokio::test(flavor = "multi_thread")] -async fn can_call_ots_get_internal_operations_contract_create2() { - let prj = TempProject::dapptools().unwrap(); - prj.add_source( - "Contract", - r" -pragma solidity 0.8.13; -contract Contract { - address constant CREATE2_DEPLOYER = 0x4e59b44847b379578588920cA78FbF26c0B4956C; - constructor() {} - function deploy() 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); - } -} -", - ) - .unwrap(); - - let mut compiled = prj.compile().unwrap(); - assert!(!compiled.has_compiler_errors()); - let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); +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 = ethers_ws_provider(&handle.ws_endpoint()); - let wallets = handle.dev_wallets().collect::>().to_ethers(); - let client = Arc::new(SignerMiddleware::new(provider, wallets[0].clone())); - - // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); - - let contract = ContractInstance::new( - contract.address(), - abi.unwrap(), - SignerMiddleware::new(ethers_http_provider(&handle.http_endpoint()), wallets[1].clone()), - ); - let call = contract.method::<_, ()>("deploy", ()).unwrap(); + let provider = handle.http_provider(); - let receipt = call.send().await.unwrap().await.unwrap().unwrap(); - let res = api.ots_get_internal_operations(receipt.transaction_hash.to_alloy()).await.unwrap(); + let contract = Contract::deploy(&provider).await.unwrap(); - assert_eq!(res.len(), 1); + 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[0], - OtsInternalOperation { + res, + [OtsInternalOperation { r#type: OtsInternalOperationType::Create2, - from: Address::from_str("0x4e59b44847b379578588920cA78FbF26c0B4956C") - .unwrap() - .to_alloy(), - to: Address::from_str("0x347bcdad821abc09b8c275881b368de36476b62c").unwrap().to_alloy(), - value: rU256::from(0) - } + from: address!("4e59b44847b379578588920cA78FbF26c0B4956C"), + to: address!("347bcdad821abc09b8c275881b368de36476b62c"), + value: U256::from(0), + }], ); } #[tokio::test(flavor = "multi_thread")] -async fn can_call_ots_get_internal_operations_contract_selfdestruct() { - let prj = TempProject::dapptools().unwrap(); - prj.add_source( - "Contract", - r" -pragma solidity 0.8.13; -contract Contract { - address payable private owner; - constructor() public { - owner = payable(msg.sender); - } - function goodbye() public { - selfdestruct(owner); - } +async fn ots_get_internal_operations_contract_selfdestruct_london() { + ots_get_internal_operations_contract_selfdestruct(Hardfork::London).await; } -", - ) - .unwrap(); - let mut compiled = prj.compile().unwrap(); - assert!(!compiled.has_compiler_errors()); - let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); +#[tokio::test(flavor = "multi_thread")] +async fn ots_get_internal_operations_contract_selfdestruct_cancun() { + ots_get_internal_operations_contract_selfdestruct(Hardfork::Cancun).await; +} - let (api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_ws_provider(&handle.ws_endpoint()); - let wallets = handle.dev_wallets().collect::>().to_ethers(); - let client = Arc::new(SignerMiddleware::new(provider, wallets[0].clone())); - - // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); - - let contract = ContractInstance::new( - contract.address(), - abi.unwrap(), - SignerMiddleware::new(ethers_http_provider(&handle.http_endpoint()), wallets[1].clone()), +async fn ots_get_internal_operations_contract_selfdestruct(hardfork: Hardfork) { + sol!( + #[sol(rpc, bytecode = "608080604052607f908160108239f3fe6004361015600c57600080fd5b6000803560e01c6375fc8e3c14602157600080fd5b346046578060031936011260465773dcdd539da22bffaa499dbea4d37d086dde196e75ff5b80fdfea264697066735822122080a9ad005cc408b2d4e30ca11216d8e310700fbcdf58a629d6edbb91531f9c6164736f6c63430008190033")] + contract Contract { + constructor() payable {} + function goodbye() public { + selfdestruct(payable(0xDcDD539DA22bfFAa499dBEa4d37d086Dde196E75)); + } + } ); - let call = contract.method::<_, ()>("goodbye", ()).unwrap(); - let receipt = call.send().await.unwrap().await.unwrap().unwrap(); + let (api, handle) = spawn(NodeConfig::test().with_hardfork(Some(hardfork))).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 res = api.ots_get_internal_operations(receipt.transaction_hash.to_alloy()).await.unwrap(); + let receipt = contract.goodbye().send().await.unwrap().get_receipt().await.unwrap(); + + // TODO: This is currently not supported by revm-inspectors + let (expected_to, expected_value) = if hardfork < Hardfork::Cancun { + (address!("DcDD539DA22bfFAa499dBEa4d37d086Dde196E75"), value) + } else { + (Address::ZERO, U256::ZERO) + }; - assert_eq!(res.len(), 1); + let res = api.ots_get_internal_operations(receipt.transaction_hash).await.unwrap(); assert_eq!( - res[0], - OtsInternalOperation { + res, + [OtsInternalOperation { r#type: OtsInternalOperationType::SelfDestruct, - from: contract.address().to_alloy(), - to: Default::default(), - value: rU256::from(0) - } + from: contract_address, + to: expected_to, + value: expected_value, + }], ); } #[tokio::test(flavor = "multi_thread")] -async fn can_call_ots_has_code() { +async fn ots_has_code() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); + let sender = handle.dev_accounts().next().unwrap(); - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let sender = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); api.mine_one().await; - let mut deploy_tx = MulticallContract::deploy(Arc::clone(&client), ()).unwrap().deployer.tx; - deploy_tx.set_nonce(0); - - let pending_contract_address = get_contract_address(sender, deploy_tx.nonce().unwrap()); + let contract_address = sender.create(0); // no code in the address before deploying - assert!(!api - .ots_has_code(pending_contract_address.to_alloy(), BlockNumberOrTag::Number(1)) - .await - .unwrap()); + assert!(!api.ots_has_code(contract_address, BlockNumberOrTag::Number(1)).await.unwrap()); - let pending = client.send_transaction(deploy_tx, None).await.unwrap(); - let receipt = pending.await.unwrap().unwrap(); + let contract_builder = MulticallContract::deploy_builder(&provider); + let contract_receipt = contract_builder.send().await.unwrap().get_receipt().await.unwrap(); - let num = client.get_block_number().await.unwrap(); - assert_eq!(num, receipt.block_number.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(pending_contract_address.to_alloy(), BlockNumberOrTag::Number(num.as_u64())) - .await - .unwrap()); + 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( - pending_contract_address.to_alloy(), - BlockNumberOrTag::Number(num.as_u64() - 1) - ) - .await - .unwrap()); + assert!(!api.ots_has_code(contract_address, BlockNumberOrTag::Number(num - 1)).await.unwrap()); } #[tokio::test(flavor = "multi_thread")] -async fn test_call_call_ots_trace_transaction() { - let prj = TempProject::dapptools().unwrap(); - prj.add_source( - "Contract", - r#" -pragma solidity 0.8.13; -contract Contract { - address payable private owner; - constructor() public { - owner = payable(msg.sender); - } - function run() payable public { - this.do_staticcall(); - this.do_call(); - } - - function do_staticcall() external view returns (bool) { - return true; - } +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 {} + } + ); - function do_call() external { - owner.call{value: address(this).balance}(""); - address(this).delegatecall(abi.encodeWithSignature("do_delegatecall()")); - } - - function do_delegatecall() internal { - } + 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![ + OtsTrace { + r#type: OtsTraceType::Call, + depth: 0, + from: sender, + to: contract_address, + value: U256::from(1337), + input: Contract::runCall::SELECTOR.into(), + output: None, + }, + OtsTrace { + r#type: OtsTraceType::StaticCall, + depth: 1, + from: contract_address, + to: contract_address, + value: U256::ZERO, + input: Contract::do_staticcallCall::SELECTOR.into(), + output: None, + }, + OtsTrace { + r#type: OtsTraceType::Call, + depth: 1, + from: contract_address, + to: contract_address, + value: U256::ZERO, + input: Contract::do_callCall::SELECTOR.into(), + output: None, + }, + OtsTrace { + r#type: OtsTraceType::Call, + depth: 2, + from: contract_address, + to: sender, + value: U256::from(1337), + input: Bytes::new(), + output: None, + }, + OtsTrace { + r#type: OtsTraceType::DelegateCall, + depth: 2, + from: contract_address, + to: contract_address, + value: U256::ZERO, + input: Contract::do_delegatecallCall::SELECTOR.into(), + output: None, + }, + ]; + assert_eq!(res, expected); } -"#, - ) - .unwrap(); - let mut compiled = prj.compile().unwrap(); - assert!(!compiled.has_compiler_errors()); - let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); +#[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 = ethers_ws_provider(&handle.ws_endpoint()); - let wallets = handle.dev_wallets().collect::>().to_ethers(); - let client = Arc::new(SignerMiddleware::new(provider, wallets[0].clone())); - - // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); - - let contract = ContractInstance::new( - contract.address(), - abi.unwrap(), - SignerMiddleware::new(ethers_http_provider(&handle.http_endpoint()), wallets[1].clone()), - ); - let call = contract.method::<_, ()>("run", ()).unwrap().value(1337); - let receipt = call.send().await.unwrap().await.unwrap().unwrap(); + let provider = handle.http_provider(); - let res = api.ots_trace_transaction(receipt.transaction_hash.to_alloy()).await.unwrap(); + let contract = Contract::deploy(&provider).await.unwrap(); - assert_eq!( - res, - vec![ - OtsTrace { - r#type: OtsTraceType::Call, - depth: 0, - from: wallets[1].address().to_alloy(), - to: contract.address().to_alloy(), - value: rU256::from(1337), - input: Bytes::from_str("0xc0406226").unwrap().0.into() - }, - OtsTrace { - r#type: OtsTraceType::StaticCall, - depth: 1, - from: contract.address().to_alloy(), - to: contract.address().to_alloy(), - value: rU256::ZERO, - input: Bytes::from_str("0x6a6758fe").unwrap().0.into() - }, - OtsTrace { - r#type: OtsTraceType::Call, - depth: 1, - from: contract.address().to_alloy(), - to: contract.address().to_alloy(), - value: rU256::ZERO, - input: Bytes::from_str("0x96385e39").unwrap().0.into() - }, - OtsTrace { - r#type: OtsTraceType::Call, - depth: 2, - from: contract.address().to_alloy(), - to: wallets[0].address().to_alloy(), - value: rU256::from(1337), - input: Bytes::from_str("0x").unwrap().0.into() - }, - OtsTrace { - r#type: OtsTraceType::DelegateCall, - depth: 2, - from: contract.address().to_alloy(), - to: contract.address().to_alloy(), - value: rU256::ZERO, - input: Bytes::from_str("0xa1325397").unwrap().0.into() - }, - ] - ); -} + let receipt = contract.trigger_revert().send().await.unwrap().get_receipt().await.unwrap(); -#[tokio::test(flavor = "multi_thread")] -async fn can_call_ots_get_transaction_error() { - let prj = TempProject::dapptools().unwrap(); - prj.add_source( - "Contract", - r#" -pragma solidity 0.8.13; -contract Contract { - error CustomError(string msg); - - function trigger_revert() public { - revert CustomError("RevertStringFooBar"); - } + 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)); } -"#, - ) - .unwrap(); - - let mut compiled = prj.compile().unwrap(); - assert!(!compiled.has_compiler_errors()); - let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); +#[tokio::test(flavor = "multi_thread")] +async fn ots_get_transaction_error_no_error() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_ws_provider(&handle.ws_endpoint()); - - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - - // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); + let provider = handle.http_provider(); - let call = contract.method::<_, ()>("trigger_revert", ()).unwrap().gas(150_000u64); - let receipt = call.send().await.unwrap().await.unwrap().unwrap(); + // 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.to_alloy()).await.unwrap().unwrap(); - let res: Bytes = res.0.into(); - assert_eq!(res, Bytes::from_str("0x8d6ea8be00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000012526576657274537472696e67466f6f4261720000000000000000000000000000").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 can_call_ots_get_block_details() { +async fn ots_get_block_details() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - - let tx = TransactionRequest::new().to(Address::random()).value(100u64); - let receipt = client.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + 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 result = api.ots_get_block_details(1.into()).await.unwrap(); assert_eq!(result.block.transaction_count, 1); - let hash = match result.block.block.transactions { - BlockTransactions::Full(txs) => txs[0].hash, - BlockTransactions::Hashes(hashes) => hashes[0], - BlockTransactions::Uncle => unreachable!(), - }; - assert_eq!(hash, receipt.transaction_hash.to_alloy()); + let hash = result.block.block.transactions.hashes().next().unwrap(); + assert_eq!(*hash, receipt.transaction_hash); } #[tokio::test(flavor = "multi_thread")] -async fn can_call_ots_get_block_details_by_hash() { +async fn ots_get_block_details_by_hash() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); - - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let provider = handle.http_provider(); - let tx = TransactionRequest::new().to(Address::random()).value(100u64); - let receipt = client.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + 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.to_alloy()).await.unwrap(); + let result = api.ots_get_block_details_by_hash(block_hash).await.unwrap(); assert_eq!(result.block.transaction_count, 1); let hash = match result.block.block.transactions { @@ -451,25 +365,25 @@ async fn can_call_ots_get_block_details_by_hash() { BlockTransactions::Hashes(hashes) => hashes[0], BlockTransactions::Uncle => unreachable!(), }; - assert_eq!(hash.to_ethers(), receipt.transaction_hash); + assert_eq!(hash, receipt.transaction_hash); } #[tokio::test(flavor = "multi_thread")] -async fn can_call_ots_get_block_transactions() { +async fn ots_get_block_transactions() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); - - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + 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::new().to(Address::random()).value(100u64).nonce(i); - let receipt = client.send_transaction(tx, None).await.unwrap(); - hashes.push_back(receipt.tx_hash()); + 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; @@ -485,11 +399,8 @@ async fn can_call_ots_get_block_transactions() { result.receipts.iter().enumerate().for_each(|(i, receipt)| { let expected = hashes.pop_front(); - assert_eq!(expected, receipt.transaction_hash.map(|h| h.to_ethers())); - assert_eq!( - expected.map(|h| h.to_alloy()), - result.fullblock.block.transactions.hashes().nth(i).copied(), - ); + assert_eq!(expected, Some(receipt.transaction_hash)); + assert_eq!(expected, result.fullblock.block.transactions.hashes().nth(i).copied()); }); } @@ -497,126 +408,125 @@ async fn can_call_ots_get_block_transactions() { } #[tokio::test(flavor = "multi_thread")] -async fn can_call_ots_search_transactions_before() { +async fn ots_search_transactions_before() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); - - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let sender = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let provider = handle.http_provider(); + let sender = handle.dev_accounts().next().unwrap(); let mut hashes = vec![]; for i in 0..7 { - let tx = TransactionRequest::new().to(Address::random()).value(100u64).nonce(i); - let receipt = client.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + 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 _ in 0..4 { - let result = - api.ots_search_transactions_before(sender.to_alloy(), block, page_size).await.unwrap(); + for i in 0..4 { + let result = api.ots_search_transactions_before(sender, block, page_size).await.unwrap(); - assert!(result.txs.len() <= page_size); + 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.hash.to_ethers())); + assert_eq!(hashes.pop(), Some(tx.hash)); }); - block = result.txs.last().unwrap().block_number.unwrap().to::() - 1; + block = result.txs.last().unwrap().block_number.unwrap(); } assert!(hashes.is_empty()); } #[tokio::test(flavor = "multi_thread")] -async fn can_call_ots_search_transactions_after() { +async fn ots_search_transactions_after() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); - - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let sender = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + 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::new().to(Address::random()).value(100u64).nonce(i); - let receipt = client.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + 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 _ in 0..4 { - let result = - api.ots_search_transactions_after(sender.to_alloy(), block, page_size).await.unwrap(); + for i in 0..4 { + let result = api.ots_search_transactions_after(sender, block, page_size).await.unwrap(); - assert!(result.txs.len() <= page_size); + assert_eq!(result.first_page, i == 3); + assert_eq!(result.last_page, i == 0); // check each individual hash - result.txs.iter().for_each(|tx| { - assert_eq!(hashes.pop_back(), Some(tx.hash.to_ethers())); + result.txs.iter().rev().for_each(|tx| { + assert_eq!(hashes.pop_back(), Some(tx.hash)); }); - block = result.txs.last().unwrap().block_number.unwrap().to::() + 1; + block = result.txs.first().unwrap().block_number.unwrap(); } assert!(hashes.is_empty()); } #[tokio::test(flavor = "multi_thread")] -async fn can_call_ots_get_transaction_by_sender_and_nonce() { +async fn ots_get_transaction_by_sender_and_nonce() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); - api.mine_one().await; - - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let sender = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - - let tx1 = TransactionRequest::new().to(Address::random()).value(100u64); - let tx2 = TransactionRequest::new().to(Address::random()).value(100u64); + 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 = client.send_transaction(tx1, None).await.unwrap().await.unwrap().unwrap(); - let receipt2 = client.send_transaction(tx2, None).await.unwrap().await.unwrap().unwrap(); + 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.to_alloy(), rU256::from(0)) - .await - .unwrap(); - let result2 = api - .ots_get_transaction_by_sender_and_nonce(sender.to_alloy(), rU256::from(1)) - .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.unwrap().hash, receipt1.transaction_hash.to_alloy()); - assert_eq!(result2.unwrap().hash, receipt2.transaction_hash.to_alloy()); + assert_eq!(result1, receipt1.transaction_hash); + assert_eq!(result2, receipt2.transaction_hash); } #[tokio::test(flavor = "multi_thread")] -async fn can_call_ots_get_contract_creator() { +async fn ots_get_contract_creator() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); - api.mine_one().await; - - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let sender = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let provider = handle.http_provider(); + let sender = handle.dev_accounts().next().unwrap(); - let mut deploy_tx = MulticallContract::deploy(Arc::clone(&client), ()).unwrap().deployer.tx; - deploy_tx.set_nonce(0); - - let pending_contract_address = get_contract_address(sender, deploy_tx.nonce().unwrap()); - - let receipt = client.send_transaction(deploy_tx, None).await.unwrap().await.unwrap().unwrap(); + let receipt = MulticallContract::deploy_builder(&provider) + .send() + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + let contract_address = receipt.contract_address.unwrap(); - let creator = - api.ots_get_contract_creator(pending_contract_address.to_alloy()).await.unwrap().unwrap(); + let creator = api.ots_get_contract_creator(contract_address).await.unwrap().unwrap(); - assert_eq!(creator.creator, sender.to_alloy()); - assert_eq!(creator.hash, receipt.transaction_hash.to_alloy()); + assert_eq!(creator.creator, sender); + assert_eq!(creator.hash, receipt.transaction_hash); } diff --git a/crates/anvil/tests/it/proof.rs b/crates/anvil/tests/it/proof.rs new file mode 100644 index 000000000..757d36082 --- /dev/null +++ b/crates/anvil/tests/it/proof.rs @@ -0,0 +1,133 @@ +//! tests for `eth_getProof` + +use alloy_primitives::{address, fixed_bytes, Address, Bytes, B256, U256}; +use anvil::{eth::EthApi, spawn, NodeConfig}; +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!("2031f89b3ea8014eb51a78c316e42af3e0d7695f"), + U256::from(45000000000000000000_u128), + ) + .await + .unwrap(); + api.anvil_set_balance(address!("33f0fc440b8477fcfbe9d0bf8649e7dea9baedb2"), U256::from(1)) + .await + .unwrap(); + api.anvil_set_balance( + address!("62b0dd4aab2b1a0a04e279e2b828791a10755528"), + U256::from(1100000000000000000_u128), + ) + .await + .unwrap(); + api.anvil_set_balance( + address!("1ed9b1dd266b607ee278726d324b855a093394a6"), + U256::from(120000000000000000_u128), + ) + .await + .unwrap(); + + verify_account_proof(&api, address!("2031f89b3ea8014eb51a78c316e42af3e0d7695f"), [ + "0xe48200a7a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", + "0xf87180a04fb9bab4bb88c062f32452b7c94c8f64d07b5851d44a39f1e32ba4b1829fdbfb8080808080a0b61eeb2eb82808b73c4ad14140a2836689f4ab8445d69dd40554eaf1fce34bc080808080808080a0dea230ff2026e65de419288183a340125b04b8405cc61627b3b4137e2260a1e880", + "0xf8719f31355ec1c8f7e26bb3ccbcb0b75d870d15846c0b98e5cc452db46c37faea40b84ff84d80890270801d946c940000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + ]).await; + + verify_account_proof(&api, address!("33f0fc440b8477fcfbe9d0bf8649e7dea9baedb2"), [ + "0xe48200a7a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", + "0xf87180a04fb9bab4bb88c062f32452b7c94c8f64d07b5851d44a39f1e32ba4b1829fdbfb8080808080a0b61eeb2eb82808b73c4ad14140a2836689f4ab8445d69dd40554eaf1fce34bc080808080808080a0dea230ff2026e65de419288183a340125b04b8405cc61627b3b4137e2260a1e880", + "0xe48200d3a0ef957210bca5b9b402d614eb8408c88cfbf4913eb6ab83ca233c8b8f0e626b54", + "0xf851808080a02743a5addaf4cf9b8c0c073e1eaa555deaaf8c41cb2b41958e88624fa45c2d908080808080a0bfbf6937911dfb88113fecdaa6bde822e4e99dae62489fcf61a91cb2f36793d680808080808080", + "0xf8679e207781e762f3577784bab7491fcc43e291ce5a356b9bc517ac52eed3a37ab846f8448001a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + ]).await; + + verify_account_proof(&api, address!("62b0dd4aab2b1a0a04e279e2b828791a10755528"), [ + "0xe48200a7a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", + "0xf87180a04fb9bab4bb88c062f32452b7c94c8f64d07b5851d44a39f1e32ba4b1829fdbfb8080808080a0b61eeb2eb82808b73c4ad14140a2836689f4ab8445d69dd40554eaf1fce34bc080808080808080a0dea230ff2026e65de419288183a340125b04b8405cc61627b3b4137e2260a1e880", + "0xf8709f3936599f93b769acf90c7178fd2ddcac1b5b4bc9949ee5a04b7e0823c2446eb84ef84c80880f43fc2c04ee0000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + ]).await; + + verify_account_proof(&api, address!("1ed9b1dd266b607ee278726d324b855a093394a6"), [ + "0xe48200a7a040f916999be583c572cc4dd369ec53b0a99f7de95f13880cf203d98f935ed1b3", + "0xf87180a04fb9bab4bb88c062f32452b7c94c8f64d07b5851d44a39f1e32ba4b1829fdbfb8080808080a0b61eeb2eb82808b73c4ad14140a2836689f4ab8445d69dd40554eaf1fce34bc080808080808080a0dea230ff2026e65de419288183a340125b04b8405cc61627b3b4137e2260a1e880", + "0xe48200d3a0ef957210bca5b9b402d614eb8408c88cfbf4913eb6ab83ca233c8b8f0e626b54", + "0xf851808080a02743a5addaf4cf9b8c0c073e1eaa555deaaf8c41cb2b41958e88624fa45c2d908080808080a0bfbf6937911dfb88113fecdaa6bde822e4e99dae62489fcf61a91cb2f36793d680808080808080", + "0xf86f9e207a32b8ab5eb4b043c65b1f00c93f517bc8883c5cd31baf8e8a279475e3b84ef84c808801aa535d3d0c0000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + ]).await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_storage_proof() { + let target = address!("1ed9b1dd266b607ee278726d324b855a093394a6"); + + 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:?}")); + } +} diff --git a/crates/anvil/tests/it/proof/eip1186.rs b/crates/anvil/tests/it/proof/eip1186.rs deleted file mode 100644 index c83cdf4f8..000000000 --- a/crates/anvil/tests/it/proof/eip1186.rs +++ /dev/null @@ -1,297 +0,0 @@ -/// Taken from https://github.com/paritytech/trie/blob/aa3168d6de01793e71ebd906d3a82ae4b363db59/trie-eip1186/src/eip1186.rs -use hash_db::Hasher; -use trie_db::{ - node::{decode_hash, Node, NodeHandle, Value}, - CError, NibbleSlice, NodeCodec, TrieHash, TrieLayout, -}; - -/// Errors that may occur during proof verification. Most of the errors types simply indicate that -/// the proof is invalid with respect to the statement being verified, and the exact error type can -/// be used for debugging. -#[derive(Debug, PartialEq, Eq)] -pub enum VerifyError<'a, HO, CE> { - /// The proof does not contain any value for the given key - /// the error carries the nibbles left after traversing the trie - NonExistingValue(NibbleSlice<'a>), - /// The proof contains a value for the given key - /// while we were expecting to find a non-existence proof - ExistingValue(Vec), - /// The proof indicates that the trie contains a different value. - /// the error carries the value contained in the trie - ValueMismatch(Vec), - /// The proof is missing trie nodes required to verify. - IncompleteProof, - /// The node hash computed from the proof is not matching. - HashMismatch(HO), - /// One of the proof nodes could not be decoded. - DecodeError(CE), - /// Error in converting a plain hash into a HO - HashDecodeError(&'a [u8]), -} - -#[cfg(feature = "std")] -impl<'a, HO: std::fmt::Debug, CE: std::error::Error> std::fmt::Display for VerifyError<'a, HO, CE> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { - match self { - VerifyError::NonExistingValue(key) => { - write!(f, "Key does not exist in trie: reaming key={:?}", key) - } - VerifyError::ExistingValue(value) => { - write!(f, "trie contains a value for given key value={:?}", value) - } - VerifyError::ValueMismatch(key) => { - write!(f, "Expected value was not found in the trie: key={:?}", key) - } - VerifyError::IncompleteProof => write!(f, "Proof is incomplete -- expected more nodes"), - VerifyError::HashMismatch(hash) => write!(f, "hash mismatch found: hash={:?}", hash), - VerifyError::DecodeError(err) => write!(f, "Unable to decode proof node: {}", err), - VerifyError::HashDecodeError(plain_hash) => { - write!(f, "Unable to decode hash value plain_hash: {:?}", plain_hash) - } - } - } -} - -#[cfg(feature = "std")] -impl<'a, HO: std::fmt::Debug, CE: std::error::Error + 'static> std::error::Error - for VerifyError<'a, HO, CE> -{ - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - VerifyError::DecodeError(err) => Some(err), - _ => None, - } - } -} - -/// Verify a compact proof for key-value pairs in a trie given a root hash. -pub fn verify_proof<'a, L>( - root: &::Out, - proof: &'a [Vec], - raw_key: &'a [u8], - expected_value: Option<&[u8]>, -) -> Result<(), VerifyError<'a, TrieHash, CError>> -where - L: TrieLayout, -{ - if proof.is_empty() { - return Err(VerifyError::IncompleteProof) - } - let key = NibbleSlice::new(raw_key); - process_node::(Some(root), &proof[0], key, expected_value, &proof[1..]) -} - -fn process_node<'a, L>( - expected_node_hash: Option<&::Out>, - encoded_node: &'a [u8], - key: NibbleSlice<'a>, - expected_value: Option<&[u8]>, - proof: &'a [Vec], -) -> Result<(), VerifyError<'a, TrieHash, CError>> -where - L: TrieLayout, -{ - if let Some(value) = expected_value { - if encoded_node == value { - return Ok(()) - } - } - if let Some(expected) = expected_node_hash { - let calculated_node_hash = ::hash(encoded_node); - if calculated_node_hash != *expected { - return Err(VerifyError::HashMismatch(calculated_node_hash)) - } - } - let node = ::decode(encoded_node).map_err(VerifyError::DecodeError)?; - match node { - Node::Empty => process_empty::(key, expected_value, proof), - Node::Leaf(nib, data) => process_leaf::(nib, data, key, expected_value, proof), - Node::Extension(nib, handle) => { - process_extension::(&nib, handle, key, expected_value, proof) - } - Node::Branch(children, maybe_data) => { - process_branch::(children, maybe_data, key, expected_value, proof) - } - Node::NibbledBranch(nib, children, maybe_data) => { - process_nibbledbranch::(nib, children, maybe_data, key, expected_value, proof) - } - } -} - -fn process_empty<'a, L>( - key: NibbleSlice<'a>, - expected_value: Option<&[u8]>, - _: &[Vec], -) -> Result<(), VerifyError<'a, TrieHash, CError>> -where - L: TrieLayout, -{ - if expected_value.is_none() { - Ok(()) - } else { - Err(VerifyError::NonExistingValue(key)) - } -} - -fn process_leaf<'a, L>( - nib: NibbleSlice, - data: Value<'a>, - key: NibbleSlice<'a>, - expected_value: Option<&[u8]>, - proof: &'a [Vec], -) -> Result<(), VerifyError<'a, TrieHash, CError>> -where - L: TrieLayout, -{ - if key != nib && expected_value.is_none() { - return Ok(()) - } else if key != nib { - return Err(VerifyError::NonExistingValue(key)) - } - match_value::(Some(data), key, expected_value, proof) -} -fn process_extension<'a, L>( - nib: &NibbleSlice, - handle: NodeHandle<'a>, - mut key: NibbleSlice<'a>, - expected_value: Option<&[u8]>, - proof: &'a [Vec], -) -> Result<(), VerifyError<'a, TrieHash, CError>> -where - L: TrieLayout, -{ - if !key.starts_with(nib) && expected_value.is_none() { - return Ok(()) - } else if !key.starts_with(nib) { - return Err(VerifyError::NonExistingValue(key)) - } - key.advance(nib.len()); - - match handle { - NodeHandle::Inline(encoded_node) => { - process_node::(None, encoded_node, key, expected_value, proof) - } - NodeHandle::Hash(plain_hash) => { - let new_root = decode_hash::(plain_hash) - .ok_or_else(|| VerifyError::HashDecodeError(plain_hash))?; - process_node::(Some(&new_root), &proof[0], key, expected_value, &proof[1..]) - } - } -} - -fn process_nibbledbranch<'a, L>( - nib: NibbleSlice, - children: [Option>; 16], - maybe_data: Option>, - mut key: NibbleSlice<'a>, - expected_value: Option<&[u8]>, - proof: &'a [Vec], -) -> Result<(), VerifyError<'a, TrieHash, CError>> -where - L: TrieLayout, -{ - if !key.starts_with(&nib) && expected_value.is_none() { - return Ok(()) - } else if !key.starts_with(&nib) && expected_value.is_some() { - return Err(VerifyError::NonExistingValue(key)) - } - key.advance(nib.len()); - - if key.is_empty() { - match_value::(maybe_data, key, expected_value, proof) - } else { - match_children::(children, key, expected_value, proof) - } -} - -fn process_branch<'a, L>( - children: [Option>; 16], - maybe_data: Option>, - key: NibbleSlice<'a>, - expected_value: Option<&[u8]>, - proof: &'a [Vec], -) -> Result<(), VerifyError<'a, TrieHash, CError>> -where - L: TrieLayout, -{ - if key.is_empty() { - match_value::(maybe_data, key, expected_value, proof) - } else { - match_children::(children, key, expected_value, proof) - } -} -fn match_children<'a, L>( - children: [Option>; 16], - mut key: NibbleSlice<'a>, - expected_value: Option<&[u8]>, - proof: &'a [Vec], -) -> Result<(), VerifyError<'a, TrieHash, CError>> -where - L: TrieLayout, -{ - match children.get(key.at(0) as usize) { - Some(Some(NodeHandle::Hash(hash))) => { - if proof.is_empty() { - Err(VerifyError::IncompleteProof) - } else { - key.advance(1); - let new_root = decode_hash::(hash) - .ok_or_else(|| VerifyError::HashDecodeError(hash))?; - process_node::(Some(&new_root), &proof[0], key, expected_value, &proof[1..]) - } - } - Some(Some(NodeHandle::Inline(encoded_node))) => { - key.advance(1); - process_node::(None, encoded_node, key, expected_value, proof) - } - Some(None) => { - if expected_value.is_none() { - Ok(()) - } else { - Err(VerifyError::NonExistingValue(key)) - } - } - None => panic!("key index is out of range in children array"), - } -} - -fn match_value<'a, L>( - maybe_data: Option>, - key: NibbleSlice<'a>, - expected_value: Option<&[u8]>, - proof: &'a [Vec], -) -> Result<(), VerifyError<'a, TrieHash, CError>> -where - L: TrieLayout, -{ - match (maybe_data, proof.first(), expected_value) { - (None, _, None) => Ok(()), - (None, _, Some(_)) => Err(VerifyError::NonExistingValue(key)), - (Some(Value::Inline(inline_data)), _, Some(value)) => { - if inline_data == value { - Ok(()) - } else { - Err(VerifyError::ValueMismatch(inline_data.to_vec())) - } - } - (Some(Value::Inline(inline_data)), _, None) => { - Err(VerifyError::ExistingValue(inline_data.to_vec())) - } - (Some(Value::Node(plain_hash, _)), Some(next_proof_item), Some(value)) => { - let value_hash = L::Hash::hash(value); - let node_hash = decode_hash::(plain_hash) - .ok_or_else(|| VerifyError::HashDecodeError(plain_hash))?; - if node_hash != value_hash { - Err(VerifyError::HashMismatch(node_hash)) - } else if next_proof_item != value { - Err(VerifyError::ValueMismatch(next_proof_item.to_vec())) - } else { - Ok(()) - } - } - (Some(Value::Node(_, _)), None, _) => Err(VerifyError::IncompleteProof), - (Some(Value::Node(_, _)), Some(proof_item), None) => { - Err(VerifyError::ExistingValue(proof_item.to_vec())) - } - } -} diff --git a/crates/anvil/tests/it/proof/mod.rs b/crates/anvil/tests/it/proof/mod.rs deleted file mode 100644 index 85e8c630f..000000000 --- a/crates/anvil/tests/it/proof/mod.rs +++ /dev/null @@ -1,76 +0,0 @@ -//! tests for `eth_getProof` - -use crate::proof::eip1186::verify_proof; -use alloy_primitives::{keccak256, Address, B256, U256}; -use alloy_rlp::Decodable; -use alloy_rpc_types::EIP1186AccountProofResponse; -use anvil::{spawn, NodeConfig}; -use anvil_core::eth::{proof::BasicAccount, trie::ExtensionLayout}; -use foundry_evm::revm::primitives::KECCAK_EMPTY; - -mod eip1186; - -#[tokio::test(flavor = "multi_thread")] -async fn can_get_proof() { - let (api, _handle) = spawn(NodeConfig::test()).await; - - let acc: Address = "0xaaaf5374fce5edbc8e2a8697c15331677e6ebaaa".parse().unwrap(); - - let key = U256::ZERO; - let value = U256::from(1); - - api.anvil_set_storage_at(acc, key, B256::from(value)).await.unwrap(); - - let proof: EIP1186AccountProofResponse = - api.get_proof(acc, vec![B256::from(key)], None).await.unwrap(); - - let account = BasicAccount { - nonce: U256::from(0), - balance: U256::from(0), - storage_root: proof.storage_hash, - code_hash: KECCAK_EMPTY, - }; - - let rlp_account = alloy_rlp::encode(&account); - - let root: B256 = api.state_root().await.unwrap(); - let acc_proof: Vec> = proof - .account_proof - .into_iter() - .map(|node| Vec::::decode(&mut &node[..]).unwrap()) - .collect(); - - verify_proof::( - &root.0, - &acc_proof, - &keccak256(acc.as_slice())[..], - Some(rlp_account.as_ref()), - ) - .unwrap(); - - assert_eq!(proof.storage_proof.len(), 1); - let expected_value = alloy_rlp::encode(value); - let proof = proof.storage_proof[0].clone(); - let storage_proof: Vec> = - proof.proof.into_iter().map(|node| Vec::::decode(&mut &node[..]).unwrap()).collect(); - let key = B256::from(keccak256(proof.key.0 .0)); - verify_proof::( - &account.storage_root.0, - &storage_proof, - key.as_slice(), - Some(expected_value.as_ref()), - ) - .unwrap(); -} - -#[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:?}")); - } -} diff --git a/crates/anvil/tests/it/pubsub.rs b/crates/anvil/tests/it/pubsub.rs index a8dd6d3d7..fec22ca41 100644 --- a/crates/anvil/tests/it/pubsub.rs +++ b/crates/anvil/tests/it/pubsub.rs @@ -1,252 +1,248 @@ //! tests for subscriptions -use crate::utils::{ethers_http_provider, ethers_ws_provider}; +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::{spawn, NodeConfig}; -use ethers::{ - contract::abigen, - middleware::SignerMiddleware, - prelude::{Middleware, Ws}, - providers::{JsonRpcClient, PubsubClient}, - signers::Signer, - types::{Address, Block, Filter, TransactionRequest, TxHash, ValueOrArray, U256}, -}; -use foundry_common::types::{ToAlloy, ToEthers}; use futures::StreamExt; -use std::sync::Arc; #[tokio::test(flavor = "multi_thread")] async fn test_sub_new_heads() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_ws_provider(&handle.ws_endpoint()); + 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.take(3).collect::>().await; - let block_numbers = blocks.into_iter().map(|b| b.number.unwrap().as_u64()).collect::>(); + let blocks = blocks.into_stream().take(3).collect::>().await; + let block_numbers = blocks.into_iter().map(|b| b.header.number.unwrap()).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() { - abigen!(EmitLogs, "test-data/emit_logs.json"); - let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_ws_provider(&handle.ws_endpoint()); - - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let wallet = handle.dev_wallets().next().unwrap(); + let provider = connect_pubsub(&handle.ws_endpoint()).await; let msg = "First Message".to_string(); - let contract = - EmitLogs::deploy(Arc::clone(&client), msg.clone()).unwrap().legacy().send().await.unwrap(); + 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.get_value().call().await.unwrap(); - assert_eq!(val, msg); + let val = contract.getValue().call().await.unwrap(); + assert_eq!(val._0, msg); // subscribe to events from the contract - let filter = Filter::new().address(ValueOrArray::Value(contract.address())); - let mut logs_sub = client.subscribe_logs(&filter).await.unwrap(); + 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 - .set_value("Next Message".to_string()) - .legacy() + .setValue("Next Message".to_string()) .send() .await .unwrap() + .get_receipt() .await - .unwrap() .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.logs[0], log); + assert_eq!(receipt.inner.logs()[0], log); } #[tokio::test(flavor = "multi_thread")] async fn test_sub_logs() { - abigen!(EmitLogs, "test-data/emit_logs.json"); - let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_ws_provider(&handle.ws_endpoint()); - - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let wallet = handle.dev_wallets().next().unwrap(); + let provider = connect_pubsub(&handle.ws_endpoint()).await; let msg = "First Message".to_string(); - let contract = - EmitLogs::deploy(Arc::clone(&client), msg.clone()).unwrap().send().await.unwrap(); + 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.get_value().call().await.unwrap(); - assert_eq!(val, msg); + let val = contract.getValue().call().await.unwrap(); + assert_eq!(val._0, msg); // subscribe to events from the contract - let filter = Filter::new().address(ValueOrArray::Value(contract.address())); - let mut logs_sub = client.subscribe_logs(&filter).await.unwrap(); + 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 - .set_value("Next Message".to_string()) + .setValue("Next Message".to_string()) .send() .await .unwrap() + .get_receipt() .await - .unwrap() .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.logs[0], log); + assert_eq!(receipt.inner.logs()[0], log); } #[tokio::test(flavor = "multi_thread")] async fn test_sub_logs_impersonated() { - abigen!(EmitLogs, "test-data/emit_logs.json"); - let (api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_ws_provider(&handle.ws_endpoint()); + 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.to_alloy(), funding.to_alloy()).await.unwrap(); - api.anvil_impersonate_account(impersonate.to_alloy()).await.unwrap(); - - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + 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(Arc::clone(&client), msg.clone()).unwrap().send().await.unwrap(); + let contract = EmitLogs::deploy(provider.clone(), msg.clone()).await.unwrap(); - let _val = contract.get_value().call().await.unwrap(); + let _val = contract.getValue().call().await.unwrap(); // subscribe to events from the impersonated account - let filter = Filter::new().address(ValueOrArray::Value(contract.address())); - let mut logs_sub = client.subscribe_logs(&filter).await.unwrap(); + 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.set_value("Next Message".to_string()).tx.data().cloned().unwrap(); + let data = contract.setValue("Next Message".to_string()); + let data = data.calldata().clone(); - let tx = TransactionRequest::new().from(impersonate).to(contract.address()).data(data); + let tx = + TransactionRequest::default().from(impersonate).to(*contract.address()).with_input(data); - let provider = ethers_http_provider(&handle.http_endpoint()); + let tx = WithOtherFields::new(tx); + let provider = handle.http_provider(); - let receipt = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + 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.logs[0], log); + 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() { - abigen!(EmitLogs, "test-data/emit_logs.json"); - let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + let wallet = handle.dev_wallets().next().unwrap(); + let provider = + connect_pubsub_with_wallet(&handle.ws_endpoint(), EthereumWallet::from(wallet.clone())) + .await; - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); let from = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); let msg = "First Message".to_string(); - let contract = - EmitLogs::deploy(Arc::clone(&client), msg.clone()).unwrap().legacy().send().await.unwrap(); - let filter = contract.value_changed_filter(); - let mut stream = filter.stream().await.unwrap(); + // 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 - .set_value("Next Message".to_string()) - .legacy() + .setValue("Next Message".to_string()) .send() .await .unwrap() + .get_receipt() .await - .unwrap() .unwrap(); + let mut log = stream.into_stream(); // get the emitted event - let log = stream.next().await.unwrap().unwrap(); - assert_eq!( - log, - ValueChangedFilter { - author: from, - old_value: "First Message".to_string(), - new_value: "Next Message".to_string(), - }, - ); + 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() { - abigen!(EmitLogs, "test-data/emit_logs.json"); - let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + let wallet = handle.dev_wallets().next().unwrap(); + let provider = + connect_pubsub_with_wallet(&handle.ws_endpoint(), EthereumWallet::from(wallet.clone())) + .await; - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); let from = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); let msg = "First Message".to_string(); - let contract = - EmitLogs::deploy(Arc::clone(&client), msg.clone()).unwrap().send().await.unwrap(); - let filter = contract.value_changed_filter(); - let mut stream = filter.stream().await.unwrap(); + 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 - .set_value("Next Message".to_string()) + .setValue("Next Message".to_string()) .send() .await .unwrap() + .get_receipt() .await - .unwrap() .unwrap(); + let mut log = stream.into_stream(); // get the emitted event - let log = stream.next().await.unwrap().unwrap(); - assert_eq!( - log, - ValueChangedFilter { - author: from, - old_value: "First Message".to_string(), - new_value: "Next Message".to_string(), - }, - ); + 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 ws = Ws::connect(handle.ws_endpoint()).await.unwrap(); - - // Subscribing requires sending the sub request and then subscribing to - // the returned sub_id - let sub_id: U256 = ws.request("eth_subscribe", ["newHeads"]).await.unwrap(); - let mut stream = ws.subscribe(sub_id).unwrap(); - - let mut blocks = Vec::new(); - for _ in 0..3 { - let item = stream.next().await.unwrap(); - let block: Block = serde_json::from_str(item.get()).unwrap(); - blocks.push(block.number.unwrap_or_default().as_u64()); - } + let provider = connect_pubsub(&handle.ws_endpoint()).await; + let sub_id: U256 = 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.unwrap()) + .collect::>(); assert_eq!(blocks, vec![1, 2, 3]) } @@ -255,21 +251,21 @@ async fn test_subscriptions() { async fn test_sub_new_heads_fast() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_ws_provider(&handle.ws_endpoint()); + 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().header.number.unwrap(); + block_numbers.push(block_number); + } - let num = 1_000u64; - let mine_api = api.clone(); - tokio::task::spawn(async move { - for _ in 0..num { - mine_api.mine_one().await; - } - }); - - // collect all the blocks - let blocks = blocks.take(num as usize).collect::>().await; - let block_numbers = blocks.into_iter().map(|b| b.number.unwrap().as_u64()).collect::>(); + println!("Collected {} blocks", block_numbers.len()); let numbers = (1..=num).collect::>(); assert_eq!(block_numbers, numbers); diff --git a/crates/anvil/tests/it/revert.rs b/crates/anvil/tests/it/revert.rs index 45da5eb2e..55762fd0f 100644 --- a/crates/anvil/tests/it/revert.rs +++ b/crates/anvil/tests/it/revert.rs @@ -1,225 +1,126 @@ -use crate::abi::VENDING_MACHINE_CONTRACT; +use crate::abi::VendingMachine; +use alloy_network::TransactionBuilder; +use alloy_primitives::{bytes, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::TransactionRequest; +use alloy_serde::WithOtherFields; +use alloy_sol_types::sol; use anvil::{spawn, NodeConfig}; -use ethers::{ - contract::{ContractFactory, ContractInstance}, - middleware::SignerMiddleware, - types::U256, - utils::WEI_IN_ETHER, -}; -use ethers_solc::{project_util::TempProject, Artifact}; -use std::sync::Arc; #[tokio::test(flavor = "multi_thread")] async fn test_deploy_reverting() { - let prj = TempProject::dapptools().unwrap(); - prj.add_source( - "Contract", - r#" -pragma solidity 0.8.13; -contract Contract { - constructor() { - require(false, ""); - } -} -"#, - ) - .unwrap(); - - let mut compiled = prj.compile().unwrap(); - assert!(!compiled.has_compiler_errors()); - let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); - let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider(); - - let wallet = handle.dev_wallets().next().unwrap(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - - let factory = ContractFactory::new(abi.unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await; - assert!(contract.is_err()); - - // should catch the revert during estimation which results in an err - let err = contract.unwrap_err(); - assert!(err.to_string().contains("execution reverted")); + 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).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() { - let prj = TempProject::dapptools().unwrap(); - prj.add_source( - "Contract", - r#" -pragma solidity 0.8.13; -contract Contract { - address owner; - constructor() public { - owner = msg.sender; - } - modifier onlyOwner() { - require(msg.sender == owner, "!authorized"); - _; - } - function getSecret() public onlyOwner view returns(uint256 secret) { - return 123; - } -} -"#, - ) - .unwrap(); - - let mut compiled = prj.compile().unwrap(); - assert!(!compiled.has_compiler_errors()); - let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); + 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.ws_provider(); - let wallets = handle.dev_wallets().collect::>(); - let client = Arc::new(SignerMiddleware::new(provider, wallets[0].clone())); - - // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); - - let contract = ContractInstance::new( - contract.address(), - abi.unwrap(), - SignerMiddleware::new(handle.http_provider(), wallets[1].clone()), - ); + let provider = handle.http_provider(); - let resp = contract.method::<_, U256>("getSecret", ()).unwrap().call().await; + let contract = Contract::deploy(&provider).await.unwrap(); - let err = resp.unwrap_err(); - let msg = err.to_string(); - assert!(msg.contains("execution reverted: !authorized")); + 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 prj = TempProject::dapptools().unwrap(); - prj.add_source("VendingMachine", VENDING_MACHINE_CONTRACT).unwrap(); - - let mut compiled = prj.compile().unwrap(); - assert!(!compiled.has_compiler_errors()); - let contract = compiled.remove_first("VendingMachine").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); - let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = handle.ws_provider(); - let wallets = handle.dev_wallets().collect::>(); - let client = Arc::new(SignerMiddleware::new(provider, wallets[0].clone())); - - // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); - - let contract = ContractInstance::new( - contract.address(), - abi.unwrap(), - SignerMiddleware::new(handle.http_provider(), wallets[1].clone()), - ); + let sender = handle.dev_accounts().next().unwrap(); + let provider = handle.http_provider(); - for fun in ["buyRevert", "buyRequire"] { - let resp = contract.method::<_, ()>(fun, U256::zero()).unwrap().call().await; - resp.unwrap(); + let contract = VendingMachine::deploy(&provider).await.unwrap(); - let ten = WEI_IN_ETHER.saturating_mul(10u64.into()); - let call = contract.method::<_, ()>(fun, ten).unwrap().value(ten); - - let resp = call.clone().call().await; - let err = resp.unwrap_err().to_string(); - assert!(err.contains("execution reverted: Not enough Ether provided.")); - assert!(err.contains("code: 3")); - } + 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() { - let prj = TempProject::dapptools().unwrap(); - prj.add_source( - "Contract", - r#" -pragma solidity 0.8.13; -contract Contract { - uint256 public number; - - function setNumber(uint256 num) public { - require(num != 0, "RevertStringFooBar"); - number = num; - } -} -"#, - ) - .unwrap(); - - let mut compiled = prj.compile().unwrap(); - assert!(!compiled.has_compiler_errors()); - let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); + 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.ws_provider(); - let wallets = handle.dev_wallets().collect::>(); - let client = Arc::new(SignerMiddleware::new(provider, wallets[0].clone())); - - // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); - - let contract = ContractInstance::new( - contract.address(), - abi.unwrap(), - SignerMiddleware::new(handle.http_provider(), wallets[1].clone()), - ); - let call = contract.method::<_, ()>("setNumber", U256::zero()).unwrap(); - let resp = call.send().await; + let provider = handle.http_provider(); + + let contract = Contract::deploy(&provider).await.unwrap(); - let err = resp.unwrap_err(); - let msg = err.to_string(); - assert!(msg.contains("execution reverted: RevertStringFooBar")); + 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() { - let prj = TempProject::dapptools().unwrap(); - prj.add_source( - "Contract", - r#" -pragma solidity 0.8.13; -contract Contract { - uint256 public number; - error AddressRevert(address); - - function revertAddress() public { - revert AddressRevert(address(1)); - } -} -"#, - ) - .unwrap(); - - let mut compiled = prj.compile().unwrap(); - assert!(!compiled.has_compiler_errors()); - let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); + 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 provider = handle.ws_provider(); - let wallets = handle.dev_wallets().collect::>(); - let client = Arc::new(SignerMiddleware::new(provider, wallets[0].clone())); - - // deploy successfully - let factory = - ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), Arc::clone(&client)); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); - - let call = contract.method::<_, ()>("revertAddress", ()).unwrap().gas(150000); - - let resp = call.call().await; + let contract = Contract::deploy(&provider).await.unwrap(); - let _ = resp.unwrap_err(); + let err = contract.revertAddress().call().await.unwrap_err(); + let s = err.to_string(); + assert!(s.contains("execution reverted"), "{s:?}"); } diff --git a/crates/anvil/tests/it/sign.rs b/crates/anvil/tests/it/sign.rs index 79b8efbf1..0ff56f364 100644 --- a/crates/anvil/tests/it/sign.rs +++ b/crates/anvil/tests/it/sign.rs @@ -1,12 +1,12 @@ -use crate::utils::ethers_http_provider; +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::{spawn, NodeConfig}; -use ethers::{ - prelude::{Middleware, SignerMiddleware}, - signers::Signer, - types::{Address, Chain, TransactionRequest}, -}; -use foundry_common::types::ToEthers; #[tokio::test(flavor = "multi_thread")] async fn can_sign_typed_data() { @@ -284,29 +284,51 @@ async fn can_sign_typed_data_os() { } #[tokio::test(flavor = "multi_thread")] -async fn rejects_different_chain_id() { - let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); +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(); - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let client = SignerMiddleware::new(provider, wallet.with_chain_id(Chain::Mainnet)); + assert_eq!(signed_tx, "0x02f866827a690a65648252089470997970c51812dc3a010c7d01b50e0d17dc79c88203e980c001a0e4de88aefcf87ccb04466e60de66a83192e46aa26177d5ea35efbfd43fd0ecdca00e3148e0e8e0b9a6f9b329efd6e30c4a461920f3a27497be3dbefaba996601da"); +} - let tx = TransactionRequest::new().to(Address::random()).value(100u64); +#[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 res = client.send_transaction(tx, None).await; + 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("signed for another chain"), "{}", err.to_string()); + 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().to_ethers(); - let wallet = wallet.with_chain_id(99u64); - let provider = ethers_http_provider(&handle.http_endpoint()); - let client = SignerMiddleware::new(provider, wallet); - let tx = TransactionRequest::new().to(Address::random()).value(100u64); - let res = client.send_transaction(tx, None).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(); } diff --git a/crates/anvil/tests/it/traces.rs b/crates/anvil/tests/it/traces.rs index e7d42051b..1c4927300 100644 --- a/crates/anvil/tests/it/traces.rs +++ b/crates/anvil/tests/it/traces.rs @@ -1,45 +1,43 @@ -use crate::{ - fork::fork_config, - utils::{ethers_http_provider, ethers_ws_provider}, +use crate::{fork::fork_config, utils::http_provider_with_signer}; +use alloy_network::{EthereumWallet, TransactionBuilder}; +use alloy_primitives::{hex, Address, Bytes, U256}; +use alloy_provider::{ + ext::{DebugApi, TraceApi}, + Provider, }; -use alloy_primitives::U256; -use anvil::{spawn, NodeConfig}; -use ethers::{ - contract::ContractInstance, - prelude::{ - Action, ContractFactory, GethTrace, GethTraceFrame, Middleware, Signer, SignerMiddleware, - TransactionRequest, - }, - types::{ActionType, Address, GethDebugTracingCallOptions, Trace}, - utils::hex, +use alloy_rpc_types::{BlockNumberOrTag, TransactionRequest}; +use alloy_rpc_types_trace::{ + geth::{GethDebugTracingCallOptions, GethTrace}, + parity::{Action, LocalizedTransactionTrace}, }; -use ethers_solc::{project_util::TempProject, Artifact}; -use foundry_common::types::{ToAlloy, ToEthers}; -use std::sync::Arc; +use alloy_serde::WithOtherFields; +use alloy_sol_types::sol; +use anvil::{spawn, Hardfork, NodeConfig}; #[tokio::test(flavor = "multi_thread")] async fn test_get_transfer_parity_traces() { let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_ws_provider(&handle.ws_endpoint()); + let provider = handle.ws_provider(); - let accounts = handle.dev_wallets().collect::>().to_ethers(); + 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::new().to(to).value(amount.to_ethers()).from(from); + 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, None).await.unwrap().await.unwrap().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].action { + match traces[0].trace.action { Action::Call(ref call) => { assert_eq!(call.from, from); assert_eq!(call.to, to); - assert_eq!(call.value, amount.to_ethers()); + assert_eq!(call.value, amount); } _ => unreachable!("unexpected action"), } @@ -51,111 +49,88 @@ async fn test_get_transfer_parity_traces() { 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 prj = TempProject::dapptools().unwrap(); - prj.add_source( - "Contract", - r" -pragma solidity 0.8.13; -contract Contract { - address payable private owner; - constructor() public { - owner = payable(msg.sender); - } - function goodbye() public { - selfdestruct(owner); - } + let (_api, handle) = spawn(NodeConfig::test().with_hardfork(Some(Hardfork::Shanghai))).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()); } -", - ) - .unwrap(); - let mut compiled = prj.compile().unwrap(); - assert!(!compiled.has_compiler_errors()); - let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); +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 provider = ethers_ws_provider(&handle.ws_endpoint()); - let wallets = handle.dev_wallets().collect::>().to_ethers(); - let client = Arc::new(SignerMiddleware::new(provider, wallets[0].clone())); - - // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); - - let contract = ContractInstance::new( - contract.address(), - abi.unwrap(), - SignerMiddleware::new(ethers_http_provider(&handle.http_endpoint()), wallets[1].clone()), - ); - let call = contract.method::<_, ()>("goodbye", ()).unwrap(); - let tx = call.send().await.unwrap().await.unwrap().unwrap(); - - let traces = ethers_http_provider(&handle.http_endpoint()) - .trace_transaction(tx.transaction_hash) + 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(); - assert!(!traces.is_empty()); - assert_eq!(traces[1].action_type, ActionType::Suicide); -} -#[tokio::test(flavor = "multi_thread")] -async fn test_transfer_debug_trace_call() { - let prj = TempProject::dapptools().unwrap(); - prj.add_source( - "Contract", - r" -pragma solidity 0.8.13; -contract Contract { - address payable private owner; - constructor() public { - owner = payable(msg.sender); - } - function goodbye() public { - selfdestruct(owner); - } -} -", - ) - .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 mut compiled = prj.compile().unwrap(); - assert!(!compiled.has_compiler_errors()); - let contract = compiled.remove_first("Contract").unwrap(); - let (abi, bytecode, _) = contract.into_contract_bytecode().into_parts(); + let call = contract.goodbye().from(wallets[1].address()); + let calldata = call.calldata().to_owned(); - let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_ws_provider(&handle.ws_endpoint()); - let wallets = handle.dev_wallets().collect::>().to_ethers(); - let client = Arc::new(SignerMiddleware::new(provider, wallets[0].clone())); - - // deploy successfully - let factory = ContractFactory::new(abi.clone().unwrap(), bytecode.unwrap(), client); - let contract = factory.deploy(()).unwrap().send().await.unwrap(); - - let contract = ContractInstance::new( - contract.address(), - abi.unwrap(), - SignerMiddleware::new(ethers_http_provider(&handle.http_endpoint()), wallets[1].clone()), - ); - let call = contract.method::<_, ()>("goodbye", ()).unwrap(); - - let traces = ethers_http_provider(&handle.http_endpoint()) - .debug_trace_call(call.tx, None, GethDebugTracingCallOptions::default()) + let tx = TransactionRequest::default() + .from(wallets[1].address()) + .to(*contract.address()) + .with_input(calldata); + + let traces = handle + .http_provider() + .debug_trace_call(tx, BlockNumberOrTag::Latest, GethDebugTracingCallOptions::default()) .await .unwrap(); + match traces { - GethTrace::Known(traces) => match traces { - GethTraceFrame::Default(traces) => { - assert!(!traces.failed); - } - _ => { - unreachable!() - } - }, - GethTrace::Unknown(_) => { + GethTrace::Default(default_frame) => { + assert!(!default_frame.failed); + } + _ => { unreachable!() } } @@ -165,21 +140,26 @@ contract Contract { #[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 = ethers_http_provider(&handle.http_endpoint()); + 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::new().to(to).from(from).data(input).gas(300_000); + let tx = TransactionRequest::default() + .to(to) + .from(from) + .with_input::(input.into()) + .with_gas_limit(300_000); - api.anvil_impersonate_account(from.to_alloy()).await.unwrap(); + let tx = WithOtherFields::new(tx); + api.anvil_impersonate_account(from).await.unwrap(); - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().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].action { + match traces[0].trace.action { Action::Call(ref call) => { assert_eq!(call.from, from); assert_eq!(call.to, to); @@ -337,13 +317,13 @@ async fn test_trace_address_fork() { } ]); - let expected_traces: Vec = serde_json::from_value(json).unwrap(); + 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_address, b.trace_address); - assert_eq!(a.subtraces, b.subtraces); - match (a.action, b.action) { + 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); @@ -358,23 +338,29 @@ async fn test_trace_address_fork() { #[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 = ethers_http_provider(&handle.http_endpoint()); + 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::new().to(to).from(from).data(input).gas(350_000); + let tx = TransactionRequest::default() + .to(to) + .from(from) + .with_input::(input.into()) + .with_gas_limit(350_000); - api.anvil_impersonate_account(from.to_alloy()).await.unwrap(); + let tx = WithOtherFields::new(tx); + api.anvil_impersonate_account(from).await.unwrap(); - let tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); - assert_eq!(tx.status, Some(1u64.into())); + 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].action { + match traces[0].trace.action { Action::Call(ref call) => { assert_eq!(call.from, from); assert_eq!(call.to, to); @@ -595,13 +581,13 @@ async fn test_trace_address_fork2() { } ]); - let expected_traces: Vec = serde_json::from_value(json).unwrap(); + 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_address, b.trace_address); - assert_eq!(a.subtraces, b.subtraces); - match (a.action, b.action) { + 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); diff --git a/crates/anvil/tests/it/transaction.rs b/crates/anvil/tests/it/transaction.rs index 18a74e1a9..10b996854 100644 --- a/crates/anvil/tests/it/transaction.rs +++ b/crates/anvil/tests/it/transaction.rs @@ -1,138 +1,140 @@ use crate::{ - abi::*, - utils::{ethers_http_provider, ethers_ws_provider}, + abi::{Greeter, MulticallContract, SimpleStorage}, + utils::{connect_pubsub, http_provider_with_signer}, }; -use alloy_primitives::U256 as rU256; +use alloy_network::{EthereumWallet, TransactionBuilder}; +use alloy_primitives::{Address, Bytes, FixedBytes, U256}; +use alloy_provider::Provider; use alloy_rpc_types::{ - request::TransactionRequest as AlloyTransactionRequest, state::{AccountOverride, StateOverride}, - BlockNumberOrTag, + AccessList, AccessListItem, BlockId, BlockNumberOrTag, BlockTransactions, TransactionRequest, }; -use alloy_signer::Signer as AlloySigner; +use alloy_serde::WithOtherFields; use anvil::{spawn, Hardfork, NodeConfig}; -use ethers::{ - abi::ethereum_types::BigEndianHash, - prelude::{ - signer::SignerMiddlewareError, BlockId, Middleware, Signer, SignerMiddleware, - TransactionRequest, - }, - types::{ - transaction::eip2930::{AccessList, AccessListItem}, - Address, BlockNumber, Transaction, TransactionReceipt, H256, U256, - }, -}; -use foundry_common::types::{to_call_request_from_tx_request, ToAlloy, ToEthers}; +use eyre::Ok; use futures::{future::join_all, FutureExt, StreamExt}; -use std::{collections::HashSet, sync::Arc, time::Duration}; +use std::{collections::HashSet, 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 = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); - let accounts = handle.dev_wallets().collect::>().to_ethers(); + let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); - assert!(nonce.is_zero()); + let nonce = provider.get_transaction_count(from).await.unwrap(); + assert!(nonce == 0); - let balance_before = provider.get_balance(to, None).await.unwrap(); + let balance_before = provider.get_balance(to).await.unwrap(); - let amount = handle.genesis_balance().checked_div(rU256::from(2u64)).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::new().to(to).value(amount.to_ethers()).from(from); - + 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, None).await.unwrap().await.unwrap().unwrap(); + let tx = provider.send_transaction(tx).await.unwrap(); + + let tx = tx.get_receipt().await.unwrap(); - assert_eq!(tx.block_number, Some(1u64.into())); - assert_eq!(tx.transaction_index, 0u64.into()); + assert_eq!(tx.block_number, Some(1)); + assert_eq!(tx.transaction_index, Some(0)); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); + let nonce = provider.get_transaction_count(from).await.unwrap(); - assert_eq!(nonce, 1u64.into()); + assert_eq!(nonce, 1); - let to_balance = provider.get_balance(to, None).await.unwrap(); + let to_balance = provider.get_balance(to).await.unwrap(); - assert_eq!(balance_before.saturating_add(amount.to_ethers()), to_balance); + 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 = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); // disable automine api.anvil_set_auto_mine(false).await.unwrap(); - let accounts = handle.dev_wallets().collect::>().to_ethers(); + let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); - let amount = handle.genesis_balance().checked_div(rU256::from(2u64)).unwrap(); + 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 tx = - TransactionRequest::new().to(to).from(from).value(amount.to_ethers()).gas_price(gas_price); - let tx_lower = provider.send_transaction(tx, None).await.unwrap(); + 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 tx = TransactionRequest::new() - .to(from) - .from(to) - .value(amount.to_ethers()) - .gas_price(gas_price + 1); - let tx_higher = provider.send_transaction(tx, None).await.unwrap(); + 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(BlockNumber::Latest).await.unwrap().unwrap(); - let lower_price = tx_lower.await.unwrap().unwrap().transaction_hash; - let higher_price = tx_higher.await.unwrap().unwrap().transaction_hash; - assert_eq!(block.transactions, vec![higher_price, lower_price]) + let block = provider.get_block(BlockId::latest(), false.into()).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 = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); - let accounts = handle.dev_wallets().collect::>().to_ethers(); + let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); - let amount = handle.genesis_balance().checked_div(rU256::from(3u64)).unwrap(); + 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 = TransactionRequest::new().to(to).value(amount.to_ethers()).from(from); + let tx = WithOtherFields::new(tx); // send the transaction with higher nonce than on chain - let higher_pending_tx = - provider.send_transaction(tx.clone().nonce(nonce + 1u64), None).await.unwrap(); + 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.nonce(nonce), None).await.unwrap().await.unwrap().unwrap(); + 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.await.unwrap().unwrap(); + let higher_tx = higher_pending_tx.get_receipt().await.unwrap(); // Awaits endlessly here due to alloy/#389 - let block = provider.get_block(1u64).await.unwrap().unwrap(); + let block = provider.get_block(1.into(), false.into()).await.unwrap().unwrap(); assert_eq!(2, block.transactions.len()); - assert_eq!(vec![tx.transaction_hash, higher_tx.transaction_hash], block.transactions); + assert_eq!( + BlockTransactions::Hashes(vec![tx.transaction_hash, higher_tx.transaction_hash]), + block.transactions + ); } #[tokio::test(flavor = "multi_thread")] @@ -142,72 +144,109 @@ async fn can_replace_transaction() { // disable auto mining api.anvil_set_auto_mine(false).await.unwrap(); - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); - let accounts = handle.dev_wallets().collect::>().to_ethers(); + let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); + 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(rU256::from(3u64)).unwrap(); + let amount = handle.genesis_balance().checked_div(U256::from(3u64)).unwrap(); - let tx = TransactionRequest::new().to(to).value(amount.to_ethers()).from(from).nonce(nonce); + 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().gas_price(gas_price), None).await.unwrap(); + 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.gas_price(gas_price + 1u64), None).await.unwrap(); + 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; - // lower priced transaction was replaced - let lower_priced_receipt = lower_priced_pending_tx.await.unwrap(); - assert!(lower_priced_receipt.is_none()); + let block = provider.get_block(1.into(), false.into()).await.unwrap().unwrap(); - let higher_priced_receipt = higher_priced_pending_tx.await.unwrap().unwrap(); + assert_eq!(block.transactions.len(), 1); + assert_eq!(BlockTransactions::Hashes(vec![higher_tx_hash]), block.transactions); - // ensure that only the replacement tx was mined - let block = provider.get_block(1u64).await.unwrap().unwrap(); - assert_eq!(1, block.transactions.len()); - assert_eq!(vec![higher_priced_receipt.transaction_hash], block.transactions); + // FIXME: Unable to get receipt despite hotfix in https://github.com/alloy-rs/alloy/pull/614 + + // lower priced transaction was replaced + // let _lower_priced_receipt = lower_priced_pending_tx.get_receipt().await.unwrap(); + // let higher_priced_receipt = higher_priced_pending_tx.get_receipt().await.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_reject_too_high_gas_limits() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); - let accounts = handle.dev_wallets().collect::>().to_ethers(); + let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); - let gas_limit = api.gas_limit(); - let amount = handle.genesis_balance().checked_div(rU256::from(3u64)).unwrap(); + 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 tx = TransactionRequest::new().to(to).value(amount.to_ethers()).from(from); + let mut tx = WithOtherFields::new(tx); // send transaction with the exact gas limit - let pending = provider.send_transaction(tx.clone().gas(gas_limit.to_ethers()), None).await; + let pending = provider.send_transaction(tx.clone()).await.unwrap(); + + let pending_receipt = pending.get_receipt().await; + assert!(pending_receipt.is_ok()); - pending.unwrap(); + tx.set_gas_limit(gas_limit + 1); // send transaction with higher gas limit - let pending = - provider.send_transaction(tx.clone().gas(gas_limit.to_ethers() + 1u64), None).await; + 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.to_alloy(), U256::MAX.to_alloy()).await.unwrap(); + api.anvil_set_balance(from, U256::MAX).await.unwrap(); - let pending = provider.send_transaction(tx.gas(gas_limit.to_ethers()), None).await; - pending.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 (api, 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 = 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 * 3); + + // 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")] @@ -217,61 +256,65 @@ async fn can_reject_underpriced_replacement() { // disable auto mining api.anvil_set_auto_mine(false).await.unwrap(); - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); - let accounts = handle.dev_wallets().collect::>().to_ethers(); + let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); + 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(rU256::from(3u64)).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 tx = TransactionRequest::new().to(to).value(amount.to_ethers()).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().gas_price(gas_price + 1u64), None).await.unwrap(); + 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.gas_price(gas_price), None).await; + 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.await.unwrap().unwrap(); + 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(1u64).await.unwrap().unwrap(); + let block = provider.get_block(1.into(), false.into()).await.unwrap().unwrap(); assert_eq!(1, block.transactions.len()); - assert_eq!(vec![higher_priced_receipt.transaction_hash], block.transactions); + 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 provider = ethers_http_provider(&handle.http_endpoint()); + let wallet = handle.dev_wallets().next().unwrap(); - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let signer: EthereumWallet = wallet.clone().into(); - let greeter_contract = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .legacy() - .send() - .await - .unwrap(); + let alloy_provider = http_provider_with_signer(&handle.http_endpoint(), signer); - let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); + let alloy_greeter_addr = + Greeter::deploy_builder(alloy_provider.clone(), "Hello World!".to_string()) + // .legacy() unimplemented! in alloy + .deploy() + .await + .unwrap(); - let greeter_contract = - Greeter::deploy(client, "Hello World!".to_string()).unwrap().send().await.unwrap(); + let alloy_greeter = Greeter::new(alloy_greeter_addr, alloy_provider); - let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); + let greeting = alloy_greeter.greet().call().await.unwrap(); + + assert_eq!("Hello World!", greeting._0); } #[tokio::test(flavor = "multi_thread")] @@ -285,285 +328,309 @@ async fn can_deploy_and_mine_manually() { // can mine in manual mode api.evm_mine(None).await.unwrap(); - let provider = ethers_http_provider(&handle.http_endpoint()); + 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 wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let tx = TransactionRequest::default().from(from).with_input(greeter_calldata.to_owned()); - let tx = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()).unwrap().deployer.tx; + let tx = WithOtherFields::new(tx); - let tx = client.send_transaction(tx, None).await.unwrap(); + let tx = provider.send_transaction(tx).await.unwrap(); // mine block with tx manually api.evm_mine(None).await.unwrap(); - let receipt = tx.await.unwrap().unwrap(); + let receipt = tx.get_receipt().await.unwrap(); let address = receipt.contract_address.unwrap(); - let greeter_contract = Greeter::new(address, Arc::clone(&client)); + let greeter_contract = Greeter::new(address, provider); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); + assert_eq!("Hello World!", greeting._0); - let set_greeting = greeter_contract.set_greeting("Another Message".to_string()); + 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.await.unwrap(); + let _tx = tx.get_receipt().await.unwrap(); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Another Message", greeting); + assert_eq!("Another Message", greeting._0); } #[tokio::test(flavor = "multi_thread")] async fn can_mine_automatically() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); // disable auto mine api.anvil_set_auto_mine(false).await.unwrap(); - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let wallet = handle.dev_wallets().next().unwrap(); - let tx = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()).unwrap().deployer.tx; - let sent_tx = client.send_transaction(tx, None).await.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.await.unwrap().unwrap(); - assert_eq!(receipt.status.unwrap().as_u64(), 1u64); + 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 = ethers_http_provider(&handle.http_endpoint()); + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let wallet = handle.dev_wallets().next().unwrap(); - let greeter_contract = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .send() + 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); + assert_eq!("Hello World!", greeting._0); - let block = client.get_block_number().await.unwrap(); + let block_number = provider.get_block_number().await.unwrap(); - greeter_contract - .set_greeting("Another Message".to_string()) + 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); + assert_eq!("Another Message", greeting._0); + + // min + api.mine_one().await; // returns previous state let greeting = - greeter_contract.greet().block(BlockId::Number(block.into())).call().await.unwrap(); - assert_eq!("Hello World!", greeting); + greeter_contract.greet().block(BlockId::Number(block_number.into())).call().await.unwrap(); + assert_eq!("Hello World!", greeting._0); } #[tokio::test(flavor = "multi_thread")] async fn can_deploy_greeter_ws() { let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_ws_provider(&handle.ws_endpoint()); + let provider = handle.ws_provider(); - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let wallet = handle.dev_wallets().next().unwrap(); - let greeter_contract = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .legacy() - .send() + let greeter_addr = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()) + .from(wallet.address()) + // .legacy() unimplemented! in alloy + .deploy() .await .unwrap(); - let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); - - let greeter_contract = - Greeter::deploy(client, "Hello World!".to_string()).unwrap().send().await.unwrap(); + let greeter_contract = Greeter::new(greeter_addr, provider.clone()); let greeting = greeter_contract.greet().call().await.unwrap(); - assert_eq!("Hello World!", greeting); + assert_eq!("Hello World!", greeting._0); } #[tokio::test(flavor = "multi_thread")] async fn can_deploy_get_code() { let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_ws_provider(&handle.ws_endpoint()); + let provider = handle.ws_provider(); - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let wallet = handle.dev_wallets().next().unwrap(); - let greeter_contract = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .legacy() - .send() + let greeter_addr = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()) + .from(wallet.address()) + .deploy() .await .unwrap(); - let code = client.get_code(greeter_contract.address(), None).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 = ethers_http_provider(&handle.http_endpoint()); - - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let provider = handle.http_provider(); - let contract = - MulticallContract::deploy(Arc::clone(&client), ()).unwrap().send().await.unwrap(); + let contract = MulticallContract::deploy(provider.clone()).await.unwrap(); - let timestamp = contract.get_current_block_timestamp().call().await.unwrap(); + let timestamp = contract.getCurrentBlockTimestamp().call().await.unwrap().timestamp; - assert!(timestamp > U256::one()); + assert!(timestamp > U256::from(1)); let latest_block = api.block_by_number(alloy_rpc_types::BlockNumberOrTag::Latest).await.unwrap().unwrap(); - let timestamp = contract.get_current_block_timestamp().call().await.unwrap(); - assert_eq!(timestamp, latest_block.header.timestamp.to_ethers()); + let timestamp = contract.getCurrentBlockTimestamp().call().await.unwrap().timestamp; + assert_eq!(timestamp.to::(), latest_block.header.timestamp); // repeat call same result - let timestamp = contract.get_current_block_timestamp().call().await.unwrap(); - assert_eq!(timestamp, latest_block.header.timestamp.to_ethers()); + let timestamp = contract.getCurrentBlockTimestamp().call().await.unwrap().timestamp; + assert_eq!(timestamp.to::(), latest_block.header.timestamp); // mock timestamp - let next_timestamp = timestamp.as_u64() + 1337; + let next_timestamp = timestamp.to::() + 1337; api.evm_set_next_block_timestamp(next_timestamp).unwrap(); - let timestamp = - contract.get_current_block_timestamp().block(BlockNumber::Pending).call().await.unwrap(); - assert_eq!(timestamp, next_timestamp.into()); + let timestamp = contract + .getCurrentBlockTimestamp() + .block(BlockId::pending()) + .call() + .await + .unwrap() + .timestamp; + assert_eq!(timestamp, U256::from(next_timestamp)); // repeat call same result - let timestamp = - contract.get_current_block_timestamp().block(BlockNumber::Pending).call().await.unwrap(); - assert_eq!(timestamp, next_timestamp.into()); + let timestamp = contract + .getCurrentBlockTimestamp() + .block(BlockId::pending()) + .call() + .await + .unwrap() + .timestamp; + 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 = ethers_http_provider(&handle.http_endpoint()); + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + 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::deploy(Arc::clone(&client), "initial value".to_string()) - .unwrap() - .send() - .await - .unwrap(); + let contract = SimpleStorage::new(contract_addr, provider.clone()); - let deployed_block = client.get_block_number().await.unwrap(); + let deployed_block = provider.get_block_number().await.unwrap(); - // assert initial state - let value = contract.method::<_, String>("getValue", ()).unwrap().call().await.unwrap(); - assert_eq!(value, "initial value"); + let value = contract.getValue().call().await.unwrap(); + assert_eq!(value._0, "initial value"); - // make a call with `client` - let _tx_hash = contract - .method::<_, H256>("setValue", "hi".to_owned()) - .unwrap() - .send() - .await - .unwrap() - .await - .unwrap() - .unwrap(); + 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.method::<_, String>("getValue", ()).unwrap().call().await.unwrap(); - assert_eq!(value, "hi"); + let value = contract.getValue().call().await.unwrap(); + assert_eq!(value._0, "hi"); // assert previous value - let value = contract - .method::<_, String>("getValue", ()) - .unwrap() - .block(BlockId::Number(deployed_block.into())) - .call() - .await - .unwrap(); - assert_eq!(value, "initial value"); + let value = + contract.getValue().block(BlockId::Number(deployed_block.into())).call().await.unwrap(); + assert_eq!(value._0, "initial value"); - let hash = client.get_block(1).await.unwrap().unwrap().hash.unwrap(); - let value = contract - .method::<_, String>("getValue", ()) - .unwrap() - .block(BlockId::Hash(hash)) - .call() + let hash = provider + .get_block(BlockId::Number(1.into()), false.into()) .await + .unwrap() + .unwrap() + .header + .hash .unwrap(); - assert_eq!(value, "initial value"); + let value = contract.getValue().block(BlockId::Hash(hash.into())).call().await.unwrap(); + assert_eq!(value._0, "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 = ethers_ws_provider(&handle.ws_endpoint()); + let provider = handle.ws_provider(); - let accounts = handle.dev_wallets().collect::>().to_ethers(); + let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); - let nonce = provider.get_transaction_count(from, None).await.unwrap(); + let nonce = provider.get_transaction_count(from).await.unwrap(); // explicitly set the nonce - let tx = TransactionRequest::new().to(to).value(100u64).from(from).nonce(nonce).gas(21_000u64); + let tx = TransactionRequest::default() + .to(to) + .value(U256::from(100)) + .from(from) + .nonce(nonce) + .with_gas_limit(21000u128); + + let tx = WithOtherFields::new(tx); + let mut tasks = Vec::new(); for _ in 0..10 { - let provider = provider.clone(); let tx = tx.clone(); - let task = - tokio::task::spawn(async move { provider.send_transaction(tx, None).await?.await }); + 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().unwrap().is_ok()).count(); + 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, None).await.unwrap(), 1u64.into()); + 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 = ethers_ws_provider(&handle.ws_endpoint()); + let provider = handle.ws_provider(); - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); + let wallet = handle.dev_wallets().next().unwrap(); let from = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let nonce = client.get_transaction_count(from, None).await.unwrap(); - // explicitly set the nonce + let nonce = provider.get_transaction_count(from).await.unwrap(); + let mut tasks = Vec::new(); - let mut tx = - Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()).unwrap().deployer.tx; - tx.set_nonce(nonce); - tx.set_gas(300_000u64); + + 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_000u128); + + let tx = WithOtherFields::new(tx); for _ in 0..10 { - let client = Arc::clone(&client); + let provider = provider.clone(); let tx = tx.clone(); let task = tokio::task::spawn(async move { - Ok::<_, SignerMiddlewareError<_, _>>( - client.send_transaction(tx, None).await?.await.unwrap(), - ) + Ok(provider.send_transaction(tx).await?.get_receipt().await.unwrap()) }); tasks.push(task); } @@ -572,51 +639,54 @@ async fn can_handle_multiple_concurrent_deploys_with_same_nonce() { 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!(client.get_transaction_count(from, None).await.unwrap(), 1u64.into()); + 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 = ethers_ws_provider(&handle.ws_endpoint()); + let provider = handle.ws_provider(); - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); + let wallet = handle.dev_wallets().next().unwrap(); let from = wallet.address(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); - let greeter_contract = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .send() - .await - .unwrap(); + let greeter_contract = + Greeter::deploy(provider.clone(), "Hello World!".to_string()).await.unwrap(); + + let nonce = provider.get_transaction_count(from).await.unwrap(); - let nonce = client.get_transaction_count(from, None).await.unwrap(); - // explicitly set the nonce let mut tasks = Vec::new(); - let mut deploy_tx = - Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()).unwrap().deployer.tx; - deploy_tx.set_nonce(nonce); - deploy_tx.set_gas(300_000u64); - let mut set_greeting_tx = greeter_contract.set_greeting("Hello".to_string()).tx; - set_greeting_tx.set_nonce(nonce); - set_greeting_tx.set_gas(300_000u64); + 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_000u128); + 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_000u128); + let set_greeting_tx = WithOtherFields::new(set_greeting_tx); for idx in 0..10 { - let client = Arc::clone(&client); + let provider = provider.clone(); let task = if idx % 2 == 0 { let tx = deploy_tx.clone(); tokio::task::spawn(async move { - Ok::<_, SignerMiddlewareError<_, _>>( - client.send_transaction(tx, None).await?.await.unwrap(), - ) + Ok(provider.send_transaction(tx).await?.get_receipt().await.unwrap()) }) } else { let tx = set_greeting_tx.clone(); tokio::task::spawn(async move { - Ok::<_, SignerMiddlewareError<_, _>>( - client.send_transaction(tx, None).await?.await.unwrap(), - ) + Ok(provider.send_transaction(tx).await?.get_receipt().await.unwrap()) }) }; @@ -627,9 +697,8 @@ async fn can_handle_multiple_concurrent_transactions_with_same_nonce() { 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!(client.get_transaction_count(from, None).await.unwrap(), nonce + 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; @@ -637,36 +706,57 @@ async fn can_get_pending_transaction() { // 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 = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); let from = handle.dev_wallets().next().unwrap().address(); - let tx = TransactionRequest::new().from(from.to_ethers()).value(1337u64).to(Address::random()); - let tx = provider.send_transaction(tx, None).await.unwrap(); + 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(tx.tx_hash()).await.unwrap(); - assert!(pending.is_some()); + let pending = provider.get_transaction_by_hash(*tx.tx_hash()).await; + assert!(pending.is_ok()); api.mine_one().await; - let mined = provider.get_transaction(tx.tx_hash()).await.unwrap().unwrap(); + let mined = provider.get_transaction_by_hash(*tx.tx_hash()).await.unwrap().unwrap(); - assert_eq!(mined.hash, pending.unwrap().hash); + assert_eq!(mined.hash, pending.unwrap().unwrap().hash); } #[tokio::test(flavor = "multi_thread")] -async fn test_first_noce_is_zero() { +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 = ethers_http_provider(&handle.http_endpoint()); + 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 nonce = provider - .get_transaction_count(from.to_ethers(), Some(BlockId::Number(BlockNumber::Pending))) - .await - .unwrap(); + let res1 = api.raw_transaction(*tx.tx_hash()).await; + assert!(res1.is_ok()); - assert_eq!(nonce, U256::zero()); + 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")] @@ -675,8 +765,8 @@ async fn can_handle_different_sender_nonce_calculation() { api.anvil_set_auto_mine(false).await.unwrap(); - let provider = ethers_http_provider(&handle.http_endpoint()); - let accounts = handle.dev_wallets().collect::>().to_ethers(); + let provider = handle.http_provider(); + let accounts = handle.dev_wallets().collect::>(); let from_first = accounts[0].address(); let from_second = accounts[1].address(); @@ -684,23 +774,25 @@ async fn can_handle_different_sender_nonce_calculation() { // 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::new().from(from_first).value(1337u64).to(Address::random()); - let _tx = provider.send_transaction(tx_from_first, None).await.unwrap(); - let nonce_from_first = provider - .get_transaction_count(from_first, Some(BlockId::Number(BlockNumber::Pending))) - .await - .unwrap(); - assert_eq!(nonce_from_first, idx.into()); - - let tx_from_second = - TransactionRequest::new().from(from_second).value(1337u64).to(Address::random()); - let _tx = provider.send_transaction(tx_from_second, None).await.unwrap(); - let nonce_from_second = provider - .get_transaction_count(from_second, Some(BlockId::Number(BlockNumber::Pending))) - .await - .unwrap(); - assert_eq!(nonce_from_second, idx.into()); + 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); } } @@ -710,7 +802,7 @@ async fn includes_pending_tx_for_transaction_count() { api.anvil_set_auto_mine(false).await.unwrap(); - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); let from = handle.dev_wallets().next().unwrap().address(); let tx_count = 10u64; @@ -718,55 +810,49 @@ async fn includes_pending_tx_for_transaction_count() { // send a bunch of tx to the mempool and check nonce is returned correctly for idx in 1..=tx_count { let tx = - TransactionRequest::new().from(from.to_ethers()).value(1337u64).to(Address::random()); - let _tx = provider.send_transaction(tx, None).await.unwrap(); - let nonce = provider - .get_transaction_count(from.to_ethers(), Some(BlockId::Number(BlockNumber::Pending))) - .await - .unwrap(); - assert_eq!(nonce, idx.into()); + 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.to_ethers(), Some(BlockId::Number(BlockNumber::Pending))) - .await - .unwrap(); - assert_eq!(nonce, tx_count.into()); + 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 = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); - let accounts = handle.dev_wallets().collect::>().to_ethers(); + let accounts = handle.dev_wallets().collect::>(); let from = accounts[0].address(); let to = accounts[1].address(); - let amount = handle.genesis_balance().checked_div(rU256::from(2u64)).unwrap(); - let tx = TransactionRequest::new().to(to).value(amount.to_ethers()).from(from); - let _tx = provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + 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, Some(BlockNumber::Number(0.into()).into())) - .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, Some(BlockNumber::Latest.into())).await.unwrap(); + let nonce_post = provider.get_transaction_count(from).await.unwrap(); assert!(nonce_pre < nonce_post); - let balance_pre = - provider.get_balance(from, Some(BlockNumber::Number(0.into()).into())).await.unwrap(); + let balance_pre = provider.get_balance(from).block_id(BlockId::number(0)).await.unwrap(); - let balance_post = provider.get_balance(from, Some(BlockNumber::Latest.into())).await.unwrap(); + let balance_post = provider.get_balance(from).await.unwrap(); assert!(balance_post < balance_pre); - let to_balance = provider.get_balance(to, None).await.unwrap(); - assert_eq!(balance_pre.saturating_add(amount.to_ethers()), to_balance); + let to_balance = provider.get_balance(to).await.unwrap(); + assert_eq!(balance_pre.saturating_add(amount), to_balance); } // @@ -774,18 +860,24 @@ async fn can_get_historic_info() { async fn test_tx_receipt() { let (_api, handle) = spawn(NodeConfig::test()).await; - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let client = - Arc::new(SignerMiddleware::new(ethers_http_provider(&handle.http_endpoint()), wallet)); + let wallet = handle.dev_wallets().next().unwrap(); + let provider = handle.http_provider(); - let tx = TransactionRequest::new().to(Address::random()).value(1337u64); + let tx = TransactionRequest::default().to(Address::random()).value(U256::from(1337)); - let tx = client.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + let tx = WithOtherFields::new(tx); + let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); assert!(tx.to.is_some()); - let tx = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()).unwrap().deployer.tx; + let greeter_deploy = Greeter::deploy_builder(provider.clone(), "Hello World!".to_string()); + let greeter_calldata = greeter_deploy.calldata(); - let tx = client.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap(); + 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://eth.wiki/json-rpc/API#eth_getTransactionReceipt assert!(tx.to.is_none()); @@ -797,32 +889,46 @@ 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 = ethers_http_provider(&handle.http_endpoint()); - let ws_provider = ethers_ws_provider(&handle.ws_endpoint()); + + let provider = handle.http_provider(); + let ws_provider = connect_pubsub(&handle.ws_endpoint()).await; let accounts = provider.get_accounts().await.unwrap(); - let tx = TransactionRequest::new().from(accounts[0]).to(accounts[0]).value(1e18 as u64); + let tx = + TransactionRequest::default().from(accounts[0]).to(accounts[0]).value(U256::from(1e18)); let mut sending = futures::future::join_all( std::iter::repeat(tx.clone()) .take(num_txs) .enumerate() - .map(|(nonce, tx)| tx.nonce(nonce)) + .map(|(nonce, tx)| tx.nonce(nonce as u64)) .map(|tx| async { - provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap() + 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().transactions_unordered(num_txs).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_txs().await.unwrap().transactions_unordered(2).fuse(); + let mut sub_tx_stream = ws_provider + .subscribe_pending_transactions() + .await + .unwrap() + .into_stream() + .take(num_txs) + .fuse(); - let mut sent: Option> = None; - let mut watch_received: Vec = Vec::with_capacity(num_txs); - let mut sub_received: Vec = Vec::with_capacity(num_txs); + 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! { @@ -830,18 +936,24 @@ async fn can_stream_pending_transactions() { sent = Some(txs) }, tx = watch_tx_stream.next() => { - watch_received.push(tx.unwrap().unwrap()); + if let Some(tx) = tx { + watch_received.push(tx); + } }, tx = sub_tx_stream.next() => { - sub_received.push(tx.unwrap().unwrap()); + if let Some(tx) = tx { + sub_received.push(tx); + } }, + complete => unreachable!(), }; + if watch_received.len() == num_txs && sub_received.len() == num_txs { - if let Some(ref sent) = sent { + if 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().map(|tx| tx.hash).collect()); - assert_eq!(sent_txs, sub_received.iter().map(|tx| tx.hash).collect()); + assert_eq!(sent_txs, watch_received.iter().copied().collect()); + assert_eq!(sent_txs, sub_received.iter().copied().collect()); break } } @@ -880,39 +992,51 @@ async fn test_tx_access_list() { // - The sender shouldn't be in the AL let (_api, handle) = spawn(NodeConfig::test()).await; - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let client = - Arc::new(SignerMiddleware::new(ethers_http_provider(&handle.http_endpoint()), wallet)); + let provider = handle.http_provider(); let sender = Address::random(); let other_acc = Address::random(); - let multicall = MulticallContract::deploy(client.clone(), ()).unwrap().send().await.unwrap(); - let simple_storage = - SimpleStorage::deploy(client.clone(), "foo".to_string()).unwrap().send().await.unwrap(); + let multicall = MulticallContract::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_tx = simple_storage.set_value("bar".to_string()).from(sender).tx; - let access_list = client.create_access_list(&set_value_tx, None).await.unwrap(); + 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(), + address: *simple_storage.address(), storage_keys: vec![ - H256::zero(), - H256::from_uint(&(1u64.into())), - "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6" - .parse() - .unwrap(), + 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.get_eth_balance(other_acc).from(sender).tx; - let access_list = client.create_access_list(&call_tx, None).await.unwrap(); + 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![] }]), @@ -920,24 +1044,31 @@ async fn test_tx_access_list() { // 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![Call { - target: simple_storage.address(), - call_data: set_value_tx.data().unwrap().clone(), - }]) + let subcall_tx = multicall.aggregate(vec![MulticallContract::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) - .tx; - let access_list = client.create_access_list(&subcall_tx, None).await.unwrap(); + .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(), + address: *simple_storage.address(), storage_keys: vec![ - H256::zero(), - H256::from_uint(&(1u64.into())), - "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6" - .parse() - .unwrap(), + FixedBytes::ZERO, + FixedBytes::with_last_byte(1), + FixedBytes::from_str( + "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6", + ) + .unwrap(), ], }]), ); @@ -951,46 +1082,50 @@ async fn estimates_gas_on_pending_by_default() { // disable auto mine api.anvil_set_auto_mine(false).await.unwrap(); - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); + let wallet = handle.dev_wallets().next().unwrap(); let sender = wallet.address(); let recipient = Address::random(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let tx = TransactionRequest::default().from(sender).to(recipient).value(U256::from(1e18)); + let tx = WithOtherFields::new(tx); - let tx = TransactionRequest::new().from(sender).to(recipient).value(1e18 as u64); - client.send_transaction(tx, None).await.unwrap(); + let _pending = provider.send_transaction(tx).await.unwrap(); - let tx = - TransactionRequest::new().from(recipient).to(sender).value(1e10 as u64).data(vec![0x42]); - api.estimate_gas(to_call_request_from_tx_request(tx), None, None).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, None).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().to_ethers(); + let wallet = handle.dev_wallets().next().unwrap(); let sender = wallet.address(); let recipient = Address::random(); - let tx = - TransactionRequest::new().from(recipient).to(sender).value(1e10 as u64).data(vec![0x42]); + 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(to_call_request_from_tx_request(tx.clone()), None, None).await; + let error_result = api.estimate_gas(WithOtherFields::new(tx.clone()), None, None).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 + "Error message did not match expected: {error_message}" ); // Setup state override to simulate sufficient funds for the recipient. - let addr = alloy_primitives::Address::from_slice(recipient.as_bytes()); + let addr = recipient; let account_override = AccountOverride { balance: Some(alloy_primitives::U256::from(1e18)), ..Default::default() }; let mut state_override = StateOverride::new(); @@ -998,32 +1133,30 @@ async fn test_estimate_gas() { // Estimate gas with state override implying sufficient funds. let gas_estimate = api - .estimate_gas(to_call_request_from_tx_request(tx), None, Some(state_override)) + .estimate_gas(WithOtherFields::new(tx), None, Some(state_override)) .await .expect("Failed to estimate gas with state override"); // Assert the gas estimate meets the expected minimum. - assert!( - gas_estimate >= alloy_primitives::U256::from(21000), - "Gas estimate is lower than expected minimum" - ); + assert!(gas_estimate >= U256::from(21000), "Gas estimate is lower than expected minimum"); } #[tokio::test(flavor = "multi_thread")] async fn test_reject_gas_too_low() { let (_api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); let account = handle.dev_accounts().next().unwrap(); let gas = 21_000u64 - 1; - let tx = TransactionRequest::new() + let tx = TransactionRequest::default() .to(Address::random()) .value(U256::from(1337u64)) - .from(account.to_ethers()) - .gas(gas); + .from(account) + .with_gas_limit(gas as u128); + let tx = WithOtherFields::new(tx); - let resp = provider.send_transaction(tx, None).await; + let resp = provider.send_transaction(tx).await; let err = resp.unwrap_err().to_string(); assert!(err.contains("intrinsic gas too low")); @@ -1032,52 +1165,51 @@ async fn test_reject_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(U256::from(100_000_000).to_alloy()))).await; - let provider = ethers_http_provider(&handle.http_endpoint()); - - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let (_api, handle) = spawn(NodeConfig::test().with_gas_limit(Some(100_000_000))).await; + let provider = handle.http_provider(); - let greeter_contract = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .send() - .await - .unwrap(); + let greeter_contract = Greeter::deploy(provider, "Hello World!".to_string()).await.unwrap(); - let greeting = greeter_contract.greet().gas(60_000_000u64).call().await.unwrap(); - assert_eq!("Hello World!", greeting); + let greeting = greeter_contract.greet().gas(60_000_000u128).call().await.unwrap(); + assert_eq!("Hello World!", greeting._0); } #[tokio::test(flavor = "multi_thread")] async fn test_reject_eip1559_pre_london() { let (api, handle) = spawn(NodeConfig::test().with_hardfork(Some(Hardfork::Berlin))).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); - let wallet = handle.dev_wallets().next().unwrap().to_ethers(); - let client = Arc::new(SignerMiddleware::new(provider, wallet)); + let gas_limit = api.gas_limit().to::(); + let gas_price = api.gas_price(); - let gas_limit = api.gas_limit(); - let gas_price = api.gas_price().unwrap(); - let unsupported = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .gas(gas_limit.to_ethers()) - .gas_price(gas_price.to_ethers()) - .send() - .await - .unwrap_err() - .to_string(); + 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 = Greeter::deploy(Arc::clone(&client), "Hello World!".to_string()) - .unwrap() - .legacy() - .send() - .await - .unwrap(); + 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); + assert_eq!("Hello World!", greeting._0); } // https://github.com/foundry-rs/foundry/issues/6931 @@ -1088,16 +1220,16 @@ async fn can_mine_multiple_in_block() { // disable auto mine api.anvil_set_auto_mine(false).await.unwrap(); - let tx = AlloyTransactionRequest { + let tx = TransactionRequest { from: Some("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266".parse().unwrap()), ..Default::default() }; // broadcast it via the eth_sendTransaction API - let first = api.send_transaction(tx.clone()).await.unwrap(); - let second = api.send_transaction(tx.clone()).await.unwrap(); + 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(rU256::from(1)), Some(rU256::ZERO)).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(); diff --git a/crates/anvil/tests/it/txpool.rs b/crates/anvil/tests/it/txpool.rs index 3c10a577f..c329b27fa 100644 --- a/crates/anvil/tests/it/txpool.rs +++ b/crates/anvil/tests/it/txpool.rs @@ -1,34 +1,41 @@ //! txpool related tests -use crate::utils::ethers_http_provider; +use alloy_network::TransactionBuilder; +use alloy_primitives::U256; +use alloy_provider::{ext::TxPoolApi, Provider}; +use alloy_rpc_types::TransactionRequest; +use alloy_serde::WithOtherFields; use anvil::{spawn, NodeConfig}; -use ethers::{ - prelude::Middleware, - types::{TransactionRequest, U256}, -}; #[tokio::test(flavor = "multi_thread")] async fn geth_txpool() { let (api, handle) = spawn(NodeConfig::test()).await; - let provider = ethers_http_provider(&handle.http_endpoint()); + let provider = handle.http_provider(); + api.anvil_set_auto_mine(false).await.unwrap(); - let account = provider.get_accounts().await.unwrap()[0]; - let value: u64 = 42; - let gas_price: U256 = 221435145689u64.into(); - let tx = TransactionRequest::new().to(account).from(account).value(value).gas_price(gas_price); + 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 let mut txs = Vec::new(); for _ in 0..10 { - let tx_hash = provider.send_transaction(tx.clone(), None).await.unwrap(); + let tx_hash = provider.send_transaction(tx.clone()).await.unwrap(); txs.push(tx_hash); } // 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.as_u64(), 10); - assert_eq!(status.queued.as_u64(), 0); + assert_eq!(status.pending, 10); + assert_eq!(status.queued, 0); let inspect = provider.txpool_inspect().await.unwrap(); assert!(inspect.queued.is_empty()); @@ -36,8 +43,8 @@ async fn geth_txpool() { 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.into()); - assert_eq!(tx_summary.gas, 21000.into()); + assert_eq!(tx_summary.value, value); + assert_eq!(tx_summary.gas, 21000); assert_eq!(tx_summary.to.unwrap(), account); } diff --git a/crates/anvil/tests/it/utils.rs b/crates/anvil/tests/it/utils.rs index 7fdd90823..368377513 100644 --- a/crates/anvil/tests/it/utils.rs +++ b/crates/anvil/tests/it/utils.rs @@ -1,31 +1,65 @@ -use ethers::{ - addressbook::contract, - types::{Address, Chain}, +use alloy_network::{Ethereum, EthereumWallet}; +use foundry_common::provider::{ + get_http_provider, ProviderBuilder, RetryProvider, RetryProviderWithSigner, }; -use foundry_common::provider::ethers::{ProviderBuilder, RetryProvider}; - -/// Returns a set of various contract addresses -pub fn contract_addresses(chain: Chain) -> Vec
{ - vec![ - contract("dai").unwrap().address(chain).unwrap(), - contract("usdc").unwrap().address(chain).unwrap(), - contract("weth").unwrap().address(chain).unwrap(), - contract("uniswapV3Factory").unwrap().address(chain).unwrap(), - contract("uniswapV3SwapRouter02").unwrap().address(chain).unwrap(), - ] + +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") } -/// Builds an ethers HTTP [RetryProvider] -pub fn ethers_http_provider(http_endpoint: &str) -> RetryProvider { - ProviderBuilder::new(http_endpoint).build().expect("failed to build ethers HTTP provider") +/// Currently required to get around +pub async fn connect_pubsub(conn_str: &str) -> RootProvider { + alloy_provider::ProviderBuilder::new().on_builtin(conn_str).await.unwrap() } -/// Builds an ethers ws [RetryProvider] -pub fn ethers_ws_provider(ws_endpoint: &str) -> RetryProvider { - ProviderBuilder::new(ws_endpoint).build().expect("failed to build ethers HTTP provider") +use alloy_provider::{ + fillers::{ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, WalletFiller}, + Identity, RootProvider, +}; +use alloy_transport::BoxTransport; + +type PubsubSigner = FillProvider< + JoinFill< + JoinFill, NonceFiller>, ChainIdFiller>, + WalletFiller, + >, + RootProvider, + BoxTransport, + Ethereum, +>; + +pub async fn connect_pubsub_with_wallet(conn_str: &str, wallet: EthereumWallet) -> PubsubSigner { + alloy_provider::ProviderBuilder::new() + .with_recommended_fillers() + .wallet(wallet) + .on_builtin(conn_str) + .await + .unwrap() } -/// Builds an ethers ws [RetryProvider] -pub fn ethers_ipc_provider(ipc_endpoint: Option) -> Option { - ProviderBuilder::new(&ipc_endpoint?).build().ok() +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") } diff --git a/crates/anvil/tests/it/wsapi.rs b/crates/anvil/tests/it/wsapi.rs index 7d31bdbca..ebe853a7d 100644 --- a/crates/anvil/tests/it/wsapi.rs +++ b/crates/anvil/tests/it/wsapi.rs @@ -1,15 +1,14 @@ //! general eth api tests with websocket provider -use alloy_providers::provider::TempProvider; +use alloy_primitives::U256; +use alloy_provider::Provider; use anvil::{spawn, NodeConfig}; -use ethers::types::U256; -use foundry_common::types::ToAlloy; #[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().to_alloy()); + assert_eq!(block_num, U256::ZERO); let provider = handle.ws_provider(); @@ -24,7 +23,7 @@ async fn can_dev_get_balance_ws() { let genesis_balance = handle.genesis_balance(); for acc in handle.genesis_accounts() { - let balance = provider.get_balance(acc, None).await.unwrap(); + let balance = provider.get_balance(acc).await.unwrap(); assert_eq!(balance, genesis_balance); } } diff --git a/crates/cast/Cargo.toml b/crates/cast/Cargo.toml index e97ae59c5..525c2c3f7 100644 --- a/crates/cast/Cargo.toml +++ b/crates/cast/Cargo.toml @@ -10,12 +10,19 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [[bin]] name = "cast" path = "bin/main.rs" [build-dependencies] -vergen = { workspace = true, default-features = false, features = ["build", "git", "gitcl"] } +vergen = { workspace = true, default-features = false, features = [ + "build", + "git", + "gitcl", +] } [dependencies] # lib @@ -26,65 +33,70 @@ foundry-config.workspace = true foundry-evm.workspace = true foundry-wallets.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"] } alloy-rlp.workspace = true -alloy-providers.workspace = true -alloy-rpc-types.workspace = true +alloy-rpc-types = { workspace = true, features = ["eth"] } +alloy-serde.workspace = true +alloy-signer-local = { workspace = true, features = ["mnemonic", "keystore"] } alloy-signer.workspace = true - -ethers-core.workspace = true -ethers-providers.workspace = true +alloy-sol-types.workspace = true +alloy-transport.workspace = true chrono.workspace = true evm-disassembler.workspace = true eyre.workspace = true -futures = "0.3" +futures.workspace = true hex.workspace = true rand.workspace = true -rayon = "1" +rayon.workspace = true serde_json.workspace = true serde.workspace = true -# aws -rusoto_core = { version = "0.48", default-features = false } -rusoto_kms = { version = "0.48", default-features = false } +# aws-kms +aws-sdk-kms = { version = "1", default-features = false, optional = true } # bin foundry-cli.workspace = true -ethers-contract.workspace = true -ethers-middleware.workspace = true -ethers-signers.workspace = true -eth-keystore = "0.5" - clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } clap_complete = "4" clap_complete_fig = "4" comfy-table = "7" -dunce = "1" +dunce.workspace = true indicatif = "0.17" itertools.workspace = true regex = { version = "1", default-features = false } rpassword = "7" -semver = "1" -tempfile = "3" -tokio = { version = "1", features = ["macros", "signal"] } +semver.workspace = true +tempfile.workspace = true +tokio = { workspace = true, features = ["macros", "signal"] } tracing.workspace = true -yansi = "0.5" +yansi.workspace = true evmole = "0.3.1" +[target.'cfg(unix)'.dependencies] +tikv-jemallocator = { workspace = true, optional = true } + [dev-dependencies] foundry-test-utils.workspace = true -async-trait = "0.1" +async-trait.workspace = true criterion = "0.5" [features] -default = ["rustls"] +default = ["rustls", "jemalloc"] rustls = ["foundry-cli/rustls", "foundry-wallets/rustls"] -openssl = ["foundry-cli/openssl", "foundry-wallets/openssl"] +openssl = ["foundry-cli/openssl"] asm-keccak = ["alloy-primitives/asm-keccak"] +jemalloc = ["dep:tikv-jemallocator"] +aws-kms = ["foundry-wallets/aws-kms", "dep:aws-sdk-kms"] [[bench]] name = "vanity" diff --git a/crates/cast/bin/cmd/access_list.rs b/crates/cast/bin/cmd/access_list.rs index fa31380ff..2a672bffd 100644 --- a/crates/cast/bin/cmd/access_list.rs +++ b/crates/cast/bin/cmd/access_list.rs @@ -1,114 +1,80 @@ -use cast::{Cast, TxBuilder}; +use crate::tx::CastTxBuilder; +use alloy_primitives::TxKind; +use alloy_rpc_types::BlockId; +use cast::Cast; use clap::Parser; -use ethers_core::types::{BlockId, NameOrAddress}; -use ethers_providers::Middleware; -use eyre::{Result, WrapErr}; +use eyre::Result; use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, utils, }; -use foundry_common::types::ToEthers; -use foundry_config::{Chain, Config}; +use foundry_common::ens::NameOrAddress; +use foundry_config::Config; use std::str::FromStr; /// CLI arguments for `cast access-list`. #[derive(Debug, Parser)] pub struct AccessListArgs { /// The destination of the transaction. - #[clap( + #[arg( value_name = "TO", value_parser = NameOrAddress::from_str )] to: Option, /// The signature of the function to call. - #[clap(value_name = "SIG")] + #[arg(value_name = "SIG")] sig: Option, /// The arguments of the function to call. - #[clap(value_name = "ARGS")] + #[arg(value_name = "ARGS")] args: Vec, - /// The data for the transaction. - #[clap( - long, - value_name = "DATA", - conflicts_with_all = &["sig", "args"] - )] - data: Option, - /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short = 'B')] + #[arg(long, short = 'B')] block: Option, /// Print the access list as JSON. - #[clap(long, short, help_heading = "Display options")] + #[arg(long, short, help_heading = "Display options")] json: bool, - #[clap(flatten)] + #[command(flatten)] tx: TransactionOpts, - #[clap(flatten)] + #[command(flatten)] eth: EthereumOpts, } impl AccessListArgs { pub async fn run(self) -> Result<()> { - let AccessListArgs { to, sig, args, data, tx, eth, block, json: to_json } = self; + let Self { to, sig, args, tx, eth, block, json: to_json } = self; let config = Config::from(ð); let provider = utils::get_provider(&config)?; - let chain = utils::get_chain(config.chain, &provider).await?; let sender = eth.wallet.sender().await; - access_list(&provider, sender.to_ethers(), to, sig, args, data, tx, chain, block, to_json) + let tx_kind = if let Some(to) = to { + TxKind::Call(to.resolve(&provider).await?) + } else { + TxKind::Create + }; + + let (tx, _) = CastTxBuilder::new(&provider, tx, &config) + .await? + .with_tx_kind(tx_kind) + .with_code_sig_and_args(None, sig, args) + .await? + .build_raw(sender) .await?; - Ok(()) - } -} -#[allow(clippy::too_many_arguments)] -async fn access_list, T: Into>( - provider: M, - from: F, - to: Option, - sig: Option, - args: Vec, - data: Option, - tx: TransactionOpts, - chain: Chain, - block: Option, - to_json: bool, -) -> Result<()> -where - M::Error: 'static, -{ - let mut builder = TxBuilder::new(&provider, from, to, chain, tx.legacy).await?; - builder - .gas(tx.gas_limit) - .gas_price(tx.gas_price) - .priority_gas_price(tx.priority_gas_price) - .nonce(tx.nonce); + let cast = Cast::new(&provider); - builder.value(tx.value); + let access_list: String = cast.access_list(&tx, block, to_json).await?; - if let Some(sig) = sig { - builder.set_args(sig.as_str(), args).await?; - } - if let Some(data) = data { - // Note: `sig+args` and `data` are mutually exclusive - builder.set_data(hex::decode(data).wrap_err("Expected hex encoded function data")?); - } - - let builder_output = builder.peek(); - - let cast = Cast::new(&provider); + println!("{access_list}"); - let access_list: String = cast.access_list(builder_output, block, to_json).await?; - - println!("{}", access_list); - - Ok(()) + Ok(()) + } } diff --git a/crates/cast/bin/cmd/bind.rs b/crates/cast/bin/cmd/bind.rs index 2de32e2ff..942c441f6 100644 --- a/crates/cast/bin/cmd/bind.rs +++ b/crates/cast/bin/cmd/bind.rs @@ -1,13 +1,10 @@ use clap::{Parser, ValueHint}; -use ethers_contract::{Abigen, MultiAbigen}; use eyre::Result; -use foundry_block_explorers::{errors::EtherscanError, Client}; use foundry_cli::opts::EtherscanOpts; -use foundry_config::Config; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; -static DEFAULT_CRATE_NAME: &str = "foundry-contracts"; -static DEFAULT_CRATE_VERSION: &str = "0.0.1"; +const DEFAULT_CRATE_NAME: &str = "foundry-contracts"; +const DEFAULT_CRATE_VERSION: &str = "0.0.1"; /// CLI arguments for `cast bind`. #[derive(Clone, Debug, Parser)] @@ -18,7 +15,7 @@ pub struct BindArgs { path_or_address: String, /// Path to where bindings will be stored - #[clap( + #[arg( short, long, value_hint = ValueHint::DirPath, @@ -30,7 +27,7 @@ pub struct BindArgs { /// /// This should be a valid crates.io crate name. However, this is currently not validated by /// this command. - #[clap( + #[arg( long, default_value = DEFAULT_CRATE_NAME, value_name = "NAME" @@ -41,7 +38,7 @@ pub struct BindArgs { /// /// This should be a standard semver version string. However, it is not currently validated by /// this command. - #[clap( + #[arg( long, default_value = DEFAULT_CRATE_VERSION, value_name = "VERSION" @@ -49,59 +46,19 @@ pub struct BindArgs { crate_version: String, /// Generate bindings as separate files. - #[clap(long)] + #[arg(long)] separate_files: bool, - #[clap(flatten)] + #[command(flatten)] etherscan: EtherscanOpts, } impl BindArgs { pub async fn run(self) -> Result<()> { - let path = Path::new(&self.path_or_address); - let multi = if path.exists() { - MultiAbigen::from_json_files(path) - } else { - self.abigen_etherscan().await - }?; - - println!("Generating bindings for {} contracts", multi.len()); - let bindings = multi.build()?; - - let out = self - .output_dir - .clone() - .unwrap_or_else(|| std::env::current_dir().unwrap().join("bindings")); - bindings.write_to_crate(self.crate_name, self.crate_version, out, !self.separate_files)?; - Ok(()) - } - - async fn abigen_etherscan(&self) -> Result { - let config = Config::from(&self.etherscan); - - 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 address = self.path_or_address.parse()?; - let source = match client.contract_source_code(address).await { - Ok(source) => source, - Err(EtherscanError::InvalidApiKey) => { - eyre::bail!("Invalid Etherscan API key. Did you set it correctly? You may be using an API key for another Etherscan API chain (e.g. Etherscan API key for Polygonscan).") - } - Err(EtherscanError::ContractCodeNotVerified(address)) => { - eyre::bail!("Contract source code at {:?} on {} not verified. Maybe you have selected the wrong chain?", address, chain) - } - Err(err) => { - eyre::bail!(err) - } - }; - let abigens = source - .items - .into_iter() - .map(|item| Abigen::new(item.contract_name, item.abi).unwrap()) - .collect::>(); - - Ok(MultiAbigen::from_abigens(abigens)) + Err(eyre::eyre!( + "`cast bind` has been removed.\n\ + Please use `cast etherscan-source` to create a Forge project from an Etherscan source\n\ + and `forge bind` to generate the bindings to it instead." + )) } } diff --git a/crates/cast/bin/cmd/call.rs b/crates/cast/bin/cmd/call.rs index 4851e9cf9..0499f5e7b 100644 --- a/crates/cast/bin/cmd/call.rs +++ b/crates/cast/bin/cmd/call.rs @@ -1,28 +1,24 @@ -use alloy_primitives::U256; -use cast::{Cast, TxBuilder}; +use crate::tx::CastTxBuilder; +use alloy_primitives::{TxKind, U256}; +use alloy_rpc_types::{BlockId, BlockNumberOrTag}; +use cast::{traces::TraceKind, Cast}; use clap::Parser; -use ethers_core::types::{BlockId, NameOrAddress}; -use eyre::{Result, WrapErr}; +use eyre::Result; use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, utils::{self, handle_traces, parse_ether_value, TraceResult}, }; -use foundry_common::{ - runtime_client::RuntimeClient, - types::{ToAlloy, ToEthers}, -}; -use foundry_compilers::EvmVersion; +use foundry_common::ens::NameOrAddress; +use foundry_compilers::artifacts::EvmVersion; use foundry_config::{find_project_root_path, Config}; use foundry_evm::{executors::TracingExecutor, opts::EvmOpts}; use std::str::FromStr; -type Provider = ethers_providers::Provider; - /// CLI arguments for `cast call`. #[derive(Debug, Parser)] pub struct CallArgs { /// The destination of the transaction. - #[clap(value_parser = NameOrAddress::from_str)] + #[arg(value_parser = NameOrAddress::from_str)] to: Option, /// The signature of the function to call. @@ -32,51 +28,51 @@ pub struct CallArgs { args: Vec, /// Data for the transaction. - #[clap( + #[arg( long, conflicts_with_all = &["sig", "args"] )] data: Option, /// Forks the remote rpc, executes the transaction locally and prints a trace - #[clap(long, default_value_t = false)] + #[arg(long, default_value_t = false)] trace: bool, /// Opens an interactive debugger. /// Can only be used with `--trace`. - #[clap(long, requires = "trace")] + #[arg(long, requires = "trace")] debug: bool, /// Labels to apply to the traces; format: `address:label`. /// Can only be used with `--trace`. - #[clap(long, requires = "trace")] + #[arg(long, requires = "trace")] labels: Vec, /// The EVM Version to use. /// Can only be used with `--trace`. - #[clap(long, requires = "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. - #[clap(long, short)] + #[arg(long, short)] block: Option, - #[clap(subcommand)] + #[command(subcommand)] command: Option, - #[clap(flatten)] + #[command(flatten)] tx: TransactionOpts, - #[clap(flatten)] + #[command(flatten)] eth: EthereumOpts, } #[derive(Debug, Parser)] pub enum CallSubcommands { /// ignores the address field and simulates creating a contract - #[clap(name = "--create")] + #[command(name = "--create")] Create { /// Bytecode of contract. code: String, @@ -92,19 +88,18 @@ pub enum CallSubcommands { /// Either specified in wei, or as a string with a unit type. /// /// Examples: 1ether, 10gwei, 0.01ether - #[clap(long, value_parser = parse_ether_value)] + #[arg(long, value_parser = parse_ether_value)] value: Option, }, } impl CallArgs { pub async fn run(self) -> Result<()> { - let CallArgs { + let Self { to, - sig, - args, - data, - tx, + mut sig, + mut args, + mut tx, eth, command, block, @@ -112,138 +107,83 @@ impl CallArgs { evm_version, debug, labels, + data, } = self; - let config = Config::from(ð); + if let Some(data) = data { + sig = Some(data); + } + + let mut config = Config::from(ð); let provider = utils::get_provider(&config)?; - let chain = utils::get_chain(config.chain, &provider).await?; let sender = eth.wallet.sender().await; - let mut builder: TxBuilder<'_, Provider> = - TxBuilder::new(&provider, sender.to_ethers(), to, chain, tx.legacy).await?; - - builder - .gas(tx.gas_limit) - .etherscan_api_key(config.get_etherscan_api_key(Some(chain))) - .gas_price(tx.gas_price) - .priority_gas_price(tx.priority_gas_price) - .nonce(tx.nonce); - - match command { - Some(CallSubcommands::Create { code, sig, args, value }) => { - if trace { - let figment = Config::figment_with_root(find_project_root_path(None).unwrap()) - .merge(eth.rpc); - - let evm_opts = figment.extract::()?; - - let (env, fork, chain) = - TracingExecutor::get_fork_material(&config, evm_opts).await?; - - let mut executor = - foundry_evm::executors::TracingExecutor::new(env, fork, evm_version, debug) - .await; - - let trace = match executor.deploy( - sender, - code.into_bytes().into(), - value.unwrap_or(U256::ZERO), - None, - ) { - Ok(deploy_result) => TraceResult::from(deploy_result), - Err(evm_err) => TraceResult::try_from(evm_err)?, - }; - - handle_traces(trace, &config, chain, labels, debug).await?; - - return Ok(()); - } - - // fill the builder after the conditional so we dont move values - fill_create(&mut builder, value, code, sig, args).await?; - } - _ => { - // fill first here because we need to use the builder in the conditional - fill_tx(&mut builder, tx.value, sig, args, data).await?; - - if trace { - let figment = Config::figment_with_root(find_project_root_path(None).unwrap()) - .merge(eth.rpc); - - let evm_opts = figment.extract::()?; - - let (env, fork, chain) = - TracingExecutor::get_fork_material(&config, evm_opts).await?; - - let mut executor = - foundry_evm::executors::TracingExecutor::new(env, fork, evm_version, debug) - .await; - - let (tx, _) = builder.build(); - - let trace = TraceResult::from(executor.call_raw_committing( - sender, - tx.to_addr().copied().expect("an address to be here").to_alloy(), - tx.data().cloned().unwrap_or_default().to_vec().into(), - tx.value().copied().unwrap_or_default().to_alloy(), - )?); - - handle_traces(trace, &config, chain, labels, debug).await?; + let tx_kind = if let Some(to) = to { + TxKind::Call(to.resolve(&provider).await?) + } else { + TxKind::Create + }; - return Ok(()); - } + 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 builder_output = builder.build(); - println!("{}", Cast::new(provider).call(builder_output, block).await?); - - Ok(()) - } -} - -/// fills the builder from create arg -async fn fill_create( - builder: &mut TxBuilder<'_, Provider>, - value: Option, - code: String, - sig: Option, - args: Vec, -) -> Result<()> { - builder.value(value); + let (tx, func) = CastTxBuilder::new(&provider, tx, &config) + .await? + .with_tx_kind(tx_kind) + .with_code_sig_and_args(code, sig, args) + .await? + .build_raw(sender) + .await?; + + if trace { + let figment = + Config::figment_with_root(find_project_root_path(None).unwrap()).merge(eth.rpc); + let evm_opts = figment.extract::()?; + 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 mut data = hex::decode(code)?; + let (env, fork, chain) = TracingExecutor::get_fork_material(&config, evm_opts).await?; + let mut executor = TracingExecutor::new(env, fork, evm_version, debug); - if let Some(s) = sig { - let (mut sigdata, _func) = builder.create_args(&s, args).await?; - data.append(&mut sigdata); - } + let value = tx.value.unwrap_or_default(); + let input = tx.inner.input.into_input().unwrap_or_default(); - builder.set_data(data); + let trace = match tx_kind { + TxKind::Create => { + let deploy_result = executor.deploy(sender, input, value, None); + TraceResult::try_from(deploy_result)? + } + TxKind::Call(to) => TraceResult::from_raw( + executor.transact_raw(sender, to, input, value)?, + TraceKind::Execution, + ), + }; - Ok(()) -} + handle_traces(trace, &config, chain, labels, debug).await?; -/// fills the builder from args -async fn fill_tx( - builder: &mut TxBuilder<'_, Provider>, - value: Option, - sig: Option, - args: Vec, - data: Option, -) -> Result<()> { - builder.value(value); + return Ok(()); + } - if let Some(sig) = sig { - builder.set_args(sig.as_str(), args).await?; - } + println!("{}", Cast::new(provider).call(&tx, func.as_ref(), block).await?); - if let Some(data) = data { - // Note: `sig+args` and `data` are mutually exclusive - builder.set_data(hex::decode(data).wrap_err("Expected hex encoded function data")?); + Ok(()) } - - Ok(()) } #[cfg(test)] diff --git a/crates/cast/bin/cmd/create2.rs b/crates/cast/bin/cmd/create2.rs index dfe34724d..8c3b12dad 100644 --- a/crates/cast/bin/cmd/create2.rs +++ b/crates/cast/bin/cmd/create2.rs @@ -19,7 +19,7 @@ const DEPLOYER: &str = "0x4e59b44847b379578588920ca78fbf26c0b4956c"; #[derive(Clone, Debug, Parser)] pub struct Create2Args { /// Prefix for the contract address. - #[clap( + #[arg( long, short, required_unless_present_any = &["ends_with", "matching"], @@ -28,19 +28,19 @@ pub struct Create2Args { starts_with: Option, /// Suffix for the contract address. - #[clap(long, short, value_name = "HEX")] + #[arg(long, short, value_name = "HEX")] ends_with: Option, /// Sequence that the address has to match. - #[clap(long, short, value_name = "HEX")] + #[arg(long, short, value_name = "HEX")] matching: Option, /// Case sensitive matching. - #[clap(short, long)] + #[arg(short, long)] case_sensitive: bool, /// Address of the contract deployer. - #[clap( + #[arg( short, long, default_value = DEPLOYER, @@ -49,27 +49,27 @@ pub struct Create2Args { deployer: Address, /// Init code of the contract to be deployed. - #[clap(short, long, value_name = "HEX")] + #[arg(short, long, value_name = "HEX")] init_code: Option, /// Init code hash of the contract to be deployed. - #[clap(alias = "ch", long, value_name = "HASH", required_unless_present = "init_code")] + #[arg(alias = "ch", long, value_name = "HASH", required_unless_present = "init_code")] init_code_hash: Option, /// Number of threads to use. Defaults to and caps at the number of logical cores. - #[clap(short, long)] + #[arg(short, long)] jobs: Option, /// Address of the caller. Used for the first 20 bytes of the salt. - #[clap(long, value_name = "ADDRESS")] + #[arg(long, value_name = "ADDRESS")] caller: Option
, /// The random number generator's seed, used to initialize the salt. - #[clap(long, value_name = "HEX")] + #[arg(long, value_name = "HEX")] seed: Option, /// Don't initialize the salt with a random value, and instead use the default value of 0. - #[clap(long, conflicts_with = "seed")] + #[arg(long, conflicts_with = "seed")] no_random: bool, } @@ -81,7 +81,7 @@ pub struct Create2Output { impl Create2Args { pub fn run(self) -> Result { - let Create2Args { + let Self { starts_with, ends_with, matching, @@ -135,14 +135,12 @@ impl Create2Args { let regex = RegexSetBuilder::new(regexs).case_insensitive(!case_sensitive).build()?; let init_code_hash = if let Some(init_code_hash) = init_code_hash { - let mut hash: [u8; 32] = [0; 32]; - hex::decode_to_slice(init_code_hash, &mut hash)?; - hash.into() + hex::FromHex::from_hex(init_code_hash) } else if let Some(init_code) = init_code { - keccak256(hex::decode(init_code)?) + hex::decode(init_code).map(keccak256) } else { unreachable!(); - }; + }?; let mut n_threads = std::thread::available_parallelism().map_or(1, |n| n.get()); if let Some(jobs) = jobs { @@ -168,7 +166,11 @@ impl Create2Args { rng.fill_bytes(remaining); } - println!("Starting to generate deterministic contract address..."); + println!("Configuration:"); + println!("Init code hash: {init_code_hash}"); + println!("Regex patterns: {:?}", regex.patterns()); + println!(); + 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(); @@ -201,9 +203,9 @@ impl Create2Args { // Calculate the `CREATE2` address. #[allow(clippy::needless_borrows_for_generic_args)] - let addr = deployer.create2(&salt.0, init_code_hash); + let addr = deployer.create2(&salt.0, &init_code_hash); - // Check if the the regex matches the calculated address' checksum. + // Check if the regex matches the calculated address' checksum. let _ = addr.to_checksum_raw(&mut checksum, None); // SAFETY: stripping 2 ASCII bytes ("0x") off of an already valid UTF-8 string // is safe. @@ -211,7 +213,7 @@ impl Create2Args { if regex.matches(s).into_iter().count() == regex_len { // Notify other threads that we found a result. found.store(true, Ordering::Relaxed); - break Some((salt.0, addr)); + break Some((addr, salt.0)); } // Increment the salt for the next iteration. @@ -221,15 +223,11 @@ impl Create2Args { } let results = handles.into_iter().filter_map(|h| h.join().unwrap()).collect::>(); - println!("Successfully found contract address(es) in {:?}", timer.elapsed()); - for (i, (salt, address)) in results.iter().enumerate() { - if i > 0 { - println!("---"); - } - println!("Address: {address}\nSalt: {salt} ({})", U256::from_be_bytes(salt.0)); - } + let (address, salt) = results.into_iter().next().unwrap(); + println!("Successfully found contract address in {:?}", timer.elapsed()); + println!("Address: {address}"); + println!("Salt: {salt} ({})", U256::from_be_bytes(salt.0)); - let (salt, address) = results.into_iter().next().unwrap(); Ok(Create2Output { address, salt }) } } diff --git a/crates/cast/bin/cmd/estimate.rs b/crates/cast/bin/cmd/estimate.rs index 6089f0153..d16cfac0f 100644 --- a/crates/cast/bin/cmd/estimate.rs +++ b/crates/cast/bin/cmd/estimate.rs @@ -1,20 +1,22 @@ -use alloy_primitives::U256; -use cast::{Cast, TxBuilder}; +use crate::tx::CastTxBuilder; +use alloy_primitives::{TxKind, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::BlockId; use clap::Parser; -use ethers_core::types::NameOrAddress; use eyre::Result; use foundry_cli::{ - opts::{EtherscanOpts, RpcOpts}, + opts::{EthereumOpts, TransactionOpts}, utils::{self, parse_ether_value}, }; -use foundry_config::{figment::Figment, Config}; +use foundry_common::ens::NameOrAddress; +use foundry_config::Config; use std::str::FromStr; /// CLI arguments for `cast estimate`. #[derive(Debug, Parser)] pub struct EstimateArgs { /// The destination of the transaction. - #[clap(value_parser = NameOrAddress::from_str)] + #[arg(value_parser = NameOrAddress::from_str)] to: Option, /// The signature of the function to call. @@ -23,38 +25,26 @@ pub struct EstimateArgs { /// The arguments of the function to call. args: Vec, - /// The sender account. - #[clap( - short, - long, - value_parser = NameOrAddress::from_str, - default_value = "0x0000000000000000000000000000000000000000", - env = "ETH_FROM", - )] - from: NameOrAddress, - - /// Ether to send in the transaction. + /// The block height to query at. /// - /// Either specified in wei, or as a string with a unit type: - /// - /// Examples: 1ether, 10gwei, 0.01ether - #[clap(long, value_parser = parse_ether_value)] - value: Option, + /// Can also be the tags earliest, finalized, safe, latest, or pending. + #[arg(long, short = 'B')] + block: Option, - #[clap(flatten)] - rpc: RpcOpts, + #[command(subcommand)] + command: Option, - #[clap(flatten)] - etherscan: EtherscanOpts, + #[command(flatten)] + tx: TransactionOpts, - #[clap(subcommand)] - command: Option, + #[command(flatten)] + eth: EthereumOpts, } #[derive(Debug, Parser)] pub enum EstimateSubcommands { /// Estimate gas cost to deploy a smart contract - #[clap(name = "--create")] + #[command(name = "--create")] Create { /// The bytecode of contract code: String, @@ -70,46 +60,51 @@ pub enum EstimateSubcommands { /// Either specified in wei, or as a string with a unit type: /// /// Examples: 1ether, 10gwei, 0.01ether - #[clap(long, value_parser = parse_ether_value)] + #[arg(long, value_parser = parse_ether_value)] value: Option, }, } impl EstimateArgs { pub async fn run(self) -> Result<()> { - let EstimateArgs { from, to, sig, args, value, rpc, etherscan, command } = self; - - let figment = Figment::from(Config::figment()).merge(etherscan).merge(rpc); - let config = Config::try_from(figment)?; + let Self { to, mut sig, mut args, mut tx, block, eth, command } = self; + let config = Config::from(ð); let provider = utils::get_provider(&config)?; - let chain = utils::get_chain(config.chain, &provider).await?; - let api_key = config.get_etherscan_api_key(Some(chain)); - - let mut builder = TxBuilder::new(&provider, from, to, chain, false).await?; - builder.etherscan_api_key(api_key); + let sender = eth.wallet.sender().await; - match command { - Some(EstimateSubcommands::Create { code, sig, args, value }) => { - builder.value(value); - - let mut data = hex::decode(code)?; - - if let Some(s) = sig { - let (mut sigdata, _func) = builder.create_args(&s, args).await?; - data.append(&mut sigdata); - } + let tx_kind = if let Some(to) = to { + TxKind::Call(to.resolve(&provider).await?) + } else { + TxKind::Create + }; - builder.set_data(data); - } - _ => { - let sig = sig.ok_or_else(|| eyre::eyre!("Function signature must be provided."))?; - builder.value(value).set_args(sig.as_str(), args).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 builder_output = builder.peek(); - let gas = Cast::new(&provider).estimate(builder_output).await?; + let (tx, _) = CastTxBuilder::new(&provider, tx, &config) + .await? + .with_tx_kind(tx_kind) + .with_code_sig_and_args(code, sig, args) + .await? + .build_raw(sender) + .await?; + + let gas = provider.estimate_gas(&tx).block(block.unwrap_or_default()).await?; println!("{gas}"); Ok(()) } @@ -122,6 +117,6 @@ mod tests { #[test] fn parse_estimate_value() { let args: EstimateArgs = EstimateArgs::parse_from(["foundry-cli", "--value", "100"]); - assert!(args.value.is_some()); + assert!(args.tx.value.is_some()); } } diff --git a/crates/cast/bin/cmd/find_block.rs b/crates/cast/bin/cmd/find_block.rs index 381d52c50..e598a2121 100644 --- a/crates/cast/bin/cmd/find_block.rs +++ b/crates/cast/bin/cmd/find_block.rs @@ -1,10 +1,8 @@ -use alloy_primitives::{U256, U64}; +use alloy_provider::Provider; use cast::Cast; use clap::Parser; -use ethers_providers::Middleware; use eyre::Result; use foundry_cli::{opts::RpcOpts, utils}; -use foundry_common::types::{ToAlloy, ToEthers}; use foundry_config::Config; use futures::join; @@ -14,15 +12,15 @@ pub struct FindBlockArgs { /// The UNIX timestamp to search for, in seconds. timestamp: u64, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, } impl FindBlockArgs { pub async fn run(self) -> Result<()> { - let FindBlockArgs { timestamp, rpc } = self; + let Self { timestamp, rpc } = self; - let ts_target = U256::from(timestamp); + let ts_target = timestamp; let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; @@ -30,43 +28,43 @@ impl FindBlockArgs { let cast_provider = Cast::new(provider); let res = join!(cast_provider.timestamp(last_block_num), cast_provider.timestamp(1)); - let ts_block_latest = res.0?; - let ts_block_1 = res.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.to_alloy() + last_block_num } else if ts_block_1 > ts_target { // If the target timestamp is below block 1's timestamp, return that - U64::from(1_u64) + 1 } else { // Otherwise, find the block that is closest to the timestamp - let mut low_block = U64::from(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.to_alloy(); - let mut matching_block: Option = None; + 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 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(U64::from(2_u64)) + .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.to_ethers()).await?; + 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() == U64::from(1_u64) { + } 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.to_ethers()), - cast_provider.timestamp(low_block.to_ethers()) + cast_provider.timestamp(high_block), + cast_provider.timestamp(low_block) ); - let ts_high = res.0.unwrap(); - let ts_low = res.1.unwrap(); + 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; diff --git a/crates/cast/bin/cmd/interface.rs b/crates/cast/bin/cmd/interface.rs index 9d3af767f..a402ea8af 100644 --- a/crates/cast/bin/cmd/interface.rs +++ b/crates/cast/bin/cmd/interface.rs @@ -1,6 +1,9 @@ -use cast::{AbiPath, SimpleCast}; +use alloy_chains::Chain; +use alloy_json_abi::ContractObject; +use alloy_primitives::Address; use clap::Parser; use eyre::{Context, Result}; +use foundry_block_explorers::Client; use foundry_cli::opts::EtherscanOpts; use foundry_common::fs; use foundry_config::Config; @@ -16,17 +19,17 @@ pub struct InterfaceArgs { path_or_address: String, /// The name to use for the generated interface. - #[clap(long, short)] + #[arg(long, short)] name: Option, /// Solidity pragma version. - #[clap(long, short, default_value = "^0.8.4", value_name = "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. - #[clap( + #[arg( short, long, value_hint = clap::ValueHint::FilePath, @@ -35,23 +38,16 @@ pub struct InterfaceArgs { output: Option, /// If specified, the interface will be output as JSON rather than Solidity. - #[clap(long, short)] + #[arg(long, short)] json: bool, - #[clap(flatten)] + #[command(flatten)] etherscan: EtherscanOpts, } impl InterfaceArgs { pub async fn run(self) -> Result<()> { - let InterfaceArgs { - path_or_address, - name, - pragma, - output: output_location, - etherscan, - json, - } = self; + let Self { path_or_address, name, pragma, output: output_location, etherscan, json } = self; let source = if Path::new(&path_or_address).exists() { AbiPath::Local { path: path_or_address, name } } else { @@ -65,7 +61,42 @@ impl InterfaceArgs { } }; - let interfaces = SimpleCast::generate_interface(source).await?; + let items = match source { + AbiPath::Local { path, name } => { + 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()); + vec![(abi, name)] + } + AbiPath::Etherscan { address, chain, api_key } => { + let client = Client::new(chain, api_key)?; + let source = client.contract_source_code(address).await?; + source + .items + .into_iter() + .map(|item| Ok((item.abi()?, item.contract_name))) + .collect::>>()? + } + }; + + let interfaces = items + .into_iter() + .map(|(contract_abi, name)| { + let source = match foundry_cli::utils::abi_to_solidity(&contract_abi, &name) { + Ok(generated_source) => generated_source, + Err(e) => { + warn!("Failed to format interface for {name}: {e}"); + contract_abi.to_sol(&name, None) + } + }; + Ok(InterfaceSource { + json_abi: serde_json::to_string_pretty(&contract_abi)?, + source, + }) + }) + .collect::>>()?; // put it all together let res = if json { @@ -92,3 +123,15 @@ impl InterfaceArgs { Ok(()) } } + +struct InterfaceSource { + json_abi: String, + source: String, +} + +// Local is a path to the directory containing the ABI files +// In case of etherscan, ABI is fetched from the address on the chain +enum AbiPath { + Local { path: String, name: Option }, + Etherscan { address: Address, chain: Chain, api_key: String }, +} diff --git a/crates/cast/bin/cmd/logs.rs b/crates/cast/bin/cmd/logs.rs index 752d90dfe..7d92ab935 100644 --- a/crates/cast/bin/cmd/logs.rs +++ b/crates/cast/bin/cmd/logs.rs @@ -1,18 +1,15 @@ +use alloy_dyn_abi::{DynSolType, DynSolValue, Specifier}; +use alloy_json_abi::Event; +use alloy_network::AnyNetwork; +use alloy_primitives::{Address, B256}; +use alloy_rpc_types::{BlockId, BlockNumberOrTag, Filter, FilterBlockOption, FilterSet, Topic}; use cast::Cast; use clap::Parser; -use ethers_core::{ - abi::{ - token::{LenientTokenizer, StrictTokenizer, Tokenizer}, - Address, Event, HumanReadableParser, ParamType, RawTopicFilter, Token, Topic, TopicFilter, - }, - types::{ - BlockId, BlockNumber, Filter, FilterBlockOption, NameOrAddress, ValueOrArray, H256, U256, - }, -}; -use ethers_providers::Middleware; -use eyre::{Result, WrapErr}; +use eyre::Result; use foundry_cli::{opts::EthereumOpts, utils}; +use foundry_common::ens::NameOrAddress; use foundry_config::Config; +use hex::FromHex; use itertools::Itertools; use std::{io, str::FromStr}; @@ -22,17 +19,17 @@ pub struct LogsArgs { /// The block height to start query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long)] + #[arg(long)] from_block: Option, /// The block height to stop query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long)] + #[arg(long)] to_block: Option, /// The contract address to filter on. - #[clap( + #[arg( long, value_parser = NameOrAddress::from_str )] @@ -40,30 +37,30 @@ pub struct LogsArgs { /// The signature of the event to filter logs by which will be converted to the first topic or /// a topic to filter on. - #[clap(value_name = "SIG_OR_TOPIC")] + #[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. - #[clap(value_name = "TOPICS_OR_ARGS")] + #[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. - #[clap(long)] + #[arg(long)] subscribe: bool, /// Print the logs as JSON.s - #[clap(long, short, help_heading = "Display options")] + #[arg(long, short, help_heading = "Display options")] json: bool, - #[clap(flatten)] + #[command(flatten)] eth: EthereumOpts, } impl LogsArgs { pub async fn run(self) -> Result<()> { - let LogsArgs { + let Self { from_block, to_block, address, @@ -80,29 +77,33 @@ impl LogsArgs { let cast = Cast::new(&provider); let address = match address { - Some(address) => { - let address = match address { - NameOrAddress::Name(name) => provider.resolve_name(&name).await?, - NameOrAddress::Address(address) => address, - }; - Some(address) - } + Some(address) => Some(address.resolve(&provider).await?), None => None, }; - let from_block = cast.convert_block_number(from_block).await?; - let to_block = cast.convert_block_number(to_block).await?; + 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, address, sig_or_topic, topics_or_args)?; if !subscribe { let logs = cast.filter_logs(filter, json).await?; - println!("{}", logs); + 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() + .on_builtin(url.as_ref()) + .await?; + let cast = Cast::new(&provider); let mut stdout = io::stdout(); cast.subscribe(filter, &mut stdout, json).await?; @@ -114,47 +115,36 @@ impl LogsArgs { /// 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, + 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 topic_filter = match sig_or_topic { + let filter = match sig_or_topic { // Try and parse the signature as an event signature - Some(sig_or_topic) => match HumanReadableParser::parse_event(sig_or_topic.as_str()) { + 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 => TopicFilter::default(), + None => Filter::default(), }; - // Convert from TopicFilter to Filter - let topics = - vec![topic_filter.topic0, topic_filter.topic1, topic_filter.topic2, topic_filter.topic3] - .into_iter() - .map(|topic| match topic { - Topic::Any => None, - Topic::This(topic) => Some(ValueOrArray::Value(Some(topic))), - _ => unreachable!(), - }) - .collect::>(); - - let filter = Filter { - block_option, - address: address.map(ValueOrArray::Value), - topics: [topics[0].clone(), topics[1].clone(), topics[2].clone(), topics[3].clone()], - }; + let mut filter = filter.select(block_option); + + if let Some(address) = address { + filter = filter.address(address) + } Ok(filter) } -/// Creates a TopicFilter from the given event signature and arguments. -fn build_filter_event_sig(event: Event, args: Vec) -> Result { +/// 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 @@ -164,128 +154,75 @@ fn build_filter_event_sig(event: Event, args: Vec) -> Result>>()? + .into_iter() .enumerate() .partition(|(_, (_, arg))| !arg.is_empty()); // Only parse the inputs with arguments - let indexed_tokens = parse_params(with_args.iter().map(|(_, p)| *p), true)?; + let indexed_tokens = with_args + .iter() + .map(|(_, (kind, arg))| kind.coerce_str(arg)) + .collect::, _>>()?; // Merge the inputs restoring the original ordering - let mut tokens = with_args + 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) - .collect::>(); + .map(|(_, token)| { + token + .map(|token| Topic::from(B256::from_slice(token.abi_encode().as_slice()))) + .unwrap_or(Topic::default()) + }) + .collect::>(); - tokens.resize(3, None); + topics.resize(3, Topic::default()); - let raw = RawTopicFilter { - topic0: tokens[0].clone().map_or(Topic::Any, Topic::This), - topic1: tokens[1].clone().map_or(Topic::Any, Topic::This), - topic2: tokens[2].clone().map_or(Topic::Any, Topic::This), - }; + let filter = Filter::new() + .event_signature(event.selector()) + .topic1(topics[0].clone()) + .topic2(topics[1].clone()) + .topic3(topics[2].clone()); - // Let filter do the hardwork of converting arguments to topics - Ok(event.filter(raw)?) + Ok(filter) } -/// Creates a TopicFilter from raw topic hashes. -fn build_filter_topics(topics: Vec) -> Result { +/// 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(None) } else { H256::from_str(&topic).map(Some) }) - .collect::, _>>()?; - - topics.resize(4, None); - - Ok(TopicFilter { - topic0: topics[0].map_or(Topic::Any, Topic::This), - topic1: topics[1].map_or(Topic::Any, Topic::This), - topic2: topics[2].map_or(Topic::Any, Topic::This), - topic3: topics[3].map_or(Topic::Any, Topic::This), - }) -} - -fn parse_params<'a, I: IntoIterator>( - params: I, - lenient: bool, -) -> eyre::Result> { - let mut tokens = Vec::new(); - - for (param, value) in params { - let mut token = if lenient { - LenientTokenizer::tokenize(param, value) - } else { - StrictTokenizer::tokenize(param, value) - }; - if token.is_err() && value.starts_with("0x") { - match param { - ParamType::FixedBytes(32) => { - if value.len() < 66 { - let padded_value = [value, &"0".repeat(66 - value.len())].concat(); - token = if lenient { - LenientTokenizer::tokenize(param, &padded_value) - } else { - StrictTokenizer::tokenize(param, &padded_value) - }; - } - } - ParamType::Uint(_) => { - // try again if value is hex - if let Ok(value) = U256::from_str(value).map(|v| v.to_string()) { - token = if lenient { - LenientTokenizer::tokenize(param, &value) - } else { - StrictTokenizer::tokenize(param, &value) - }; - } - } - // TODO: Not sure what to do here. Put the no effect in for now, but that is not - // ideal. We could attempt massage for every value type? - _ => {} + .map(|topic| { + if topic.is_empty() { + Ok(Topic::default()) + } else { + Ok(Topic::from(B256::from_hex(topic.as_str())?)) } - } + }) + .collect::>>>()?; - let token = token.map(sanitize_token).wrap_err_with(|| { - format!("Failed to parse `{value}`, expected value of type: {param}") - })?; - tokens.push(token); - } - Ok(tokens) -} + topics.resize(4, Topic::default()); -pub fn sanitize_token(token: Token) -> Token { - match token { - Token::Array(tokens) => { - let mut sanitized = Vec::with_capacity(tokens.len()); - for token in tokens { - let token = match token { - Token::String(val) => { - let val = match val.as_str() { - // this is supposed to be an empty string - "\"\"" | "''" => String::new(), - _ => val, - }; - Token::String(val) - } - _ => sanitize_token(token), - }; - sanitized.push(token) - } - Token::Array(sanitized) - } - _ => token, - } + 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 ethers_core::types::H160; + use alloy_primitives::{U160, U256}; + use alloy_rpc_types::ValueOrArray; const ADDRESS: &str = "0x4D1A2e2bB4F88F0250f26Ffff098B0b30B26BF38"; const TRANSFER_SIG: &str = "Transfer(address indexed,address indexed,uint256)"; @@ -294,13 +231,13 @@ mod tests { #[test] fn test_build_filter_basic() { - let from_block = Some(BlockNumber::from(1337)); - let to_block = Some(BlockNumber::Latest); + 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: Some(ValueOrArray::Value(address.unwrap())), - topics: [None, None, None, None], + address: ValueOrArray::Value(address.unwrap()).into(), + topics: [vec![].into(), vec![].into(), vec![].into(), vec![].into()], }; let filter = build_filter(from_block, to_block, address, None, vec![]).unwrap(); assert_eq!(filter, expected) @@ -310,8 +247,13 @@ mod tests { fn test_build_filter_sig() { let expected = Filter { block_option: FilterBlockOption::Range { from_block: None, to_block: None }, - address: None, - topics: [Some(H256::from_str(TRANSFER_TOPIC).unwrap().into()), None, None, 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(); @@ -322,8 +264,13 @@ mod tests { fn test_build_filter_mismatch() { let expected = Filter { block_option: FilterBlockOption::Range { from_block: None, to_block: None }, - address: None, - topics: [Some(H256::from_str(TRANSFER_TOPIC).unwrap().into()), None, None, None], + address: vec![].into(), + topics: [ + B256::from_str(TRANSFER_TOPIC).unwrap().into(), + vec![].into(), + vec![].into(), + vec![].into(), + ], }; let filter = build_filter( None, @@ -338,14 +285,16 @@ mod tests { #[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: None, + address: vec![].into(), topics: [ - Some(H256::from_str(TRANSFER_TOPIC).unwrap().into()), - Some(H160::from_str(ADDRESS).unwrap().into()), - None, - None, + B256::from_str(TRANSFER_TOPIC).unwrap().into(), + addr.into(), + vec![].into(), + vec![].into(), ], }; let filter = build_filter( @@ -361,14 +310,16 @@ mod tests { #[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: None, + address: vec![].into(), topics: [ - Some(H256::from_str(TRANSFER_TOPIC).unwrap().into()), - None, - Some(H160::from_str(ADDRESS).unwrap().into()), - None, + vec![B256::from_str(TRANSFER_TOPIC).unwrap()].into(), + vec![].into(), + addr.into(), + vec![].into(), ], }; let filter = build_filter( @@ -386,12 +337,12 @@ mod tests { fn test_build_filter_with_topics() { let expected = Filter { block_option: FilterBlockOption::Range { from_block: None, to_block: None }, - address: None, + address: vec![].into(), topics: [ - Some(H256::from_str(TRANSFER_TOPIC).unwrap().into()), - Some(H256::from_str(TRANSFER_TOPIC).unwrap().into()), - None, - None, + vec![B256::from_str(TRANSFER_TOPIC).unwrap()].into(), + vec![B256::from_str(TRANSFER_TOPIC).unwrap()].into(), + vec![].into(), + vec![].into(), ], }; let filter = build_filter( @@ -410,12 +361,12 @@ mod tests { fn test_build_filter_with_skipped_topic() { let expected = Filter { block_option: FilterBlockOption::Range { from_block: None, to_block: None }, - address: None, + address: vec![].into(), topics: [ - Some(H256::from_str(TRANSFER_TOPIC).unwrap().into()), - None, - Some(H256::from_str(TRANSFER_TOPIC).unwrap().into()), - None, + vec![B256::from_str(TRANSFER_TOPIC).unwrap()].into(), + vec![].into(), + vec![B256::from_str(TRANSFER_TOPIC).unwrap()].into(), + vec![].into(), ], }; let filter = build_filter( @@ -443,7 +394,7 @@ mod tests { .unwrap() .to_string(); - assert_eq!(err, "Failed to parse `1234`, expected value of type: address"); + assert_eq!(err, "parser error:\n1234\n^\nInvalid string length"); } #[test] @@ -453,7 +404,7 @@ mod tests { .unwrap() .to_string(); - assert_eq!(err, "Invalid character 's' at position 1"); + assert_eq!(err, "Odd number of digits"); } #[test] @@ -463,7 +414,7 @@ mod tests { .unwrap() .to_string(); - assert_eq!(err, "Invalid input length"); + assert_eq!(err, "Invalid string length"); } #[test] @@ -479,6 +430,6 @@ mod tests { .unwrap() .to_string(); - assert_eq!(err, "Invalid input length"); + assert_eq!(err, "Invalid string length"); } } diff --git a/crates/cast/bin/cmd/mktx.rs b/crates/cast/bin/cmd/mktx.rs new file mode 100644 index 000000000..db8dbf0fe --- /dev/null +++ b/crates/cast/bin/cmd/mktx.rs @@ -0,0 +1,100 @@ +use crate::tx::{self, CastTxBuilder}; +use alloy_network::{eip2718::Encodable2718, EthereumWallet, TransactionBuilder}; +use alloy_signer::Signer; +use clap::Parser; +use eyre::Result; +use foundry_cli::{ + opts::{EthereumOpts, TransactionOpts}, + utils::{self, get_provider}, +}; +use foundry_common::ens::NameOrAddress; +use foundry_config::Config; +use std::str::FromStr; + +/// 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. + args: Vec, + + #[command(subcommand)] + command: Option, + + #[command(flatten)] + tx: TransactionOpts, + + #[command(flatten)] + eth: EthereumOpts, +} + +#[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. + args: Vec, + }, +} + +impl MakeTxArgs { + pub async fn run(self) -> Result<()> { + let Self { to, mut sig, mut args, command, tx, eth } = self; + + 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 = Config::from(ð); + let provider = utils::get_provider(&config)?; + + let tx_kind = tx::resolve_tx_kind(&provider, &code, &to).await?; + + // Retrieve the signer, and bail 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 provider = get_provider(&config)?; + + let (tx, _) = CastTxBuilder::new(provider, tx, &config) + .await? + .with_tx_kind(tx_kind) + .with_code_sig_and_args(code, sig, args) + .await? + .build(from) + .await?; + + let tx = tx.build(&EthereumWallet::new(signer)).await?; + + let signed_tx = hex::encode(tx.encoded_2718()); + println!("0x{signed_tx}"); + + Ok(()) + } +} diff --git a/crates/cast/bin/cmd/mod.rs b/crates/cast/bin/cmd/mod.rs index bd1c8ddf6..6c9044174 100644 --- a/crates/cast/bin/cmd/mod.rs +++ b/crates/cast/bin/cmd/mod.rs @@ -13,6 +13,7 @@ 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; diff --git a/crates/cast/bin/cmd/rpc.rs b/crates/cast/bin/cmd/rpc.rs index 44275204a..cae5d3386 100644 --- a/crates/cast/bin/cmd/rpc.rs +++ b/crates/cast/bin/cmd/rpc.rs @@ -26,16 +26,16 @@ pub struct RpcArgs { /// /// cast rpc eth_getBlockByNumber '["0x123", false]' --raw /// => {"method": "eth_getBlockByNumber", "params": ["0x123", false] ... } - #[clap(long, short = 'w')] + #[arg(long, short = 'w')] raw: bool, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, } impl RpcArgs { pub async fn run(self) -> Result<()> { - let RpcArgs { raw, method, params, rpc } = self; + let Self { raw, method, params, rpc } = self; let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; diff --git a/crates/cast/bin/cmd/run.rs b/crates/cast/bin/cmd/run.rs index ccdd26480..c830f5ab1 100644 --- a/crates/cast/bin/cmd/run.rs +++ b/crates/cast/bin/cmd/run.rs @@ -1,16 +1,15 @@ use alloy_primitives::U256; -use alloy_providers::provider::TempProvider; +use alloy_provider::Provider; use alloy_rpc_types::BlockTransactions; +use cast::{revm::primitives::EnvWithHandlerCfg, traces::TraceKind}; use clap::Parser; use eyre::{Result, WrapErr}; use foundry_cli::{ - init_progress, opts::RpcOpts, - update_progress, - utils::{handle_traces, TraceResult}, + utils::{handle_traces, init_progress, TraceResult}, }; use foundry_common::{is_known_system_sender, SYSTEM_TRANSACTION_TYPE}; -use foundry_compilers::EvmVersion; +use foundry_compilers::artifacts::EvmVersion; use foundry_config::{find_project_root_path, Config}; use foundry_evm::{ executors::{EvmError, TracingExecutor}, @@ -25,36 +24,36 @@ pub struct RunArgs { tx_hash: String, /// Opens the transaction in the debugger. - #[clap(long, short)] + #[arg(long, short)] debug: bool, /// Print out opcode traces. - #[clap(long, short)] + #[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! - #[clap(long, short)] + #[arg(long, short)] quick: bool, /// Prints the full address of the contract. - #[clap(long, short)] + #[arg(long, short)] verbose: bool, /// Label addresses in the trace. /// /// Example: 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045:vitalik.eth - #[clap(long, short)] + #[arg(long, short)] label: Vec, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, /// The EVM version to use. /// /// Overrides the version specified in the config. - #[clap(long, short)] + #[arg(long, short)] evm_version: Option, /// Sets the number of assumed available compute units per second for this provider @@ -62,7 +61,7 @@ pub struct RunArgs { /// default value: 330 /// /// See also, https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second - #[clap(long, alias = "cups", value_name = "CUPS")] + #[arg(long, alias = "cups", value_name = "CUPS")] pub compute_units_per_second: Option, /// Disables rate limiting for this node's provider. @@ -70,7 +69,7 @@ pub struct RunArgs { /// default value: false /// /// See also, https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second - #[clap(long, value_name = "NO_RATE_LIMITS", visible_alias = "no-rpc-rate-limit")] + #[arg(long, value_name = "NO_RATE_LIMITS", visible_alias = "no-rpc-rate-limit")] pub no_rate_limit: bool, } @@ -89,7 +88,7 @@ impl RunArgs { let compute_units_per_second = if self.no_rate_limit { Some(u64::MAX) } else { self.compute_units_per_second }; - let provider = foundry_common::provider::alloy::ProviderBuilder::new( + let provider = foundry_common::provider::ProviderBuilder::new( &config.get_rpc_url_or_localhost_http()?, ) .compute_units_per_second_opt(compute_units_per_second) @@ -99,49 +98,60 @@ impl RunArgs { let tx = provider .get_transaction_by_hash(tx_hash) .await - .wrap_err_with(|| format!("tx not found: {:?}", tx_hash))?; + .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 is_known_system_sender(tx.from) || - tx.transaction_type.map(|ty| ty.to::()) == Some(SYSTEM_TRANSACTION_TYPE) - { + if 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.hash )); } - let tx_block_number = tx - .block_number - .ok_or_else(|| eyre::eyre!("tx may still be pending: {:?}", tx_hash))? - .to::(); + let tx_block_number = + tx.block_number.ok_or_else(|| eyre::eyre!("tx may still be pending: {:?}", tx_hash))?; + + // fetch the block the transaction was mined in + let block = provider.get_block(tx_block_number.into(), true.into()).await?; // we need to fork off the parent block config.fork_block_number = Some(tx_block_number - 1); let (mut env, fork, chain) = TracingExecutor::get_fork_material(&config, evm_opts).await?; - let mut executor = - TracingExecutor::new(env.clone(), fork, self.evm_version, self.debug).await; + let mut evm_version = self.evm_version; env.block.number = U256::from(tx_block_number); - let block = provider.get_block(tx_block_number.into(), true).await?; - if let Some(ref block) = block { - env.block.timestamp = block.header.timestamp; + if let Some(block) = &block { + env.block.timestamp = U256::from(block.header.timestamp); env.block.coinbase = block.header.miner; env.block.difficulty = block.header.difficulty; env.block.prevrandao = Some(block.header.mix_hash.unwrap_or_default()); - env.block.basefee = block.header.base_fee_per_gas.unwrap_or_default(); - env.block.gas_limit = block.header.gas_limit; + env.block.basefee = U256::from(block.header.base_fee_per_gas.unwrap_or_default()); + env.block.gas_limit = U256::from(block.header.gas_limit); + + // TODO: we need a smarter way to map the block to the corresponding evm_version for + // commonly used chains + if evm_version.is_none() { + // if the block has the excess_blob_gas field, we assume it's a Cancun block + if block.header.excess_blob_gas.is_some() { + evm_version = Some(EvmVersion::Cancun); + } + } } + let mut executor = TracingExecutor::new(env.clone(), fork, evm_version, self.debug); + let mut env = + EnvWithHandlerCfg::new_with_spec_id(Box::new(env.clone()), executor.spec_id()); + // Set the state to the moment right before the transaction if !self.quick { println!("Executing previous transactions from the block."); if let Some(block) = block { - let pb = init_progress!(block.transactions, "tx"); + let pb = init_progress(block.transactions.len() as u64, "tx"); pb.set_position(0); let BlockTransactions::Full(txs) = block.transactions else { @@ -153,10 +163,9 @@ impl RunArgs { // we skip them otherwise this would cause // reverts if is_known_system_sender(tx.from) || - tx.transaction_type.map(|ty| ty.to::()) == - Some(SYSTEM_TRANSACTION_TYPE) + tx.transaction_type == Some(SYSTEM_TRANSACTION_TYPE) { - update_progress!(pb, index); + pb.set_position((index + 1) as u64); continue; } if tx.hash == tx_hash { @@ -167,7 +176,7 @@ impl RunArgs { if let Some(to) = tx.to { trace!(tx=?tx.hash,?to, "executing previous call transaction"); - executor.commit_tx_with_env(env.clone()).wrap_err_with(|| { + executor.transact_with_env(env.clone()).wrap_err_with(|| { format!( "Failed to execute transaction: {:?} in block {}", tx.hash, env.block.number @@ -191,7 +200,7 @@ impl RunArgs { } } - update_progress!(pb, index); + pb.set_position((index + 1) as u64); } } } @@ -204,13 +213,10 @@ impl RunArgs { if let Some(to) = tx.to { trace!(tx=?tx.hash, to=?to, "executing call transaction"); - TraceResult::from(executor.commit_tx_with_env(env)?) + TraceResult::from_raw(executor.transact_with_env(env)?, TraceKind::Execution) } else { trace!(tx=?tx.hash, "executing create transaction"); - match executor.deploy_with_env(env, None) { - Ok(res) => TraceResult::from(res), - Err(err) => TraceResult::try_from(err)?, - } + TraceResult::try_from(executor.deploy_with_env(env, None))? } }; diff --git a/crates/cast/bin/cmd/send.rs b/crates/cast/bin/cmd/send.rs index b68ba5c75..55094346f 100644 --- a/crates/cast/bin/cmd/send.rs +++ b/crates/cast/bin/cmd/send.rs @@ -1,20 +1,20 @@ -use cast::{Cast, TxBuilder}; +use crate::tx::{self, CastTxBuilder}; +use alloy_network::{AnyNetwork, EthereumWallet}; +use alloy_provider::{Provider, ProviderBuilder}; +use alloy_rpc_types::TransactionRequest; +use alloy_serde::WithOtherFields; +use alloy_signer::Signer; +use alloy_transport::Transport; +use cast::Cast; use clap::Parser; -use ethers_core::types::NameOrAddress; -use ethers_middleware::SignerMiddleware; -use ethers_providers::Middleware; -use ethers_signers::Signer; use eyre::Result; use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, utils, }; -use foundry_common::{ - cli_warn, - types::{ToAlloy, ToEthers}, -}; -use foundry_config::{Chain, Config}; -use std::str::FromStr; +use foundry_common::{cli_warn, ens::NameOrAddress}; +use foundry_config::Config; +use std::{path::PathBuf, str::FromStr}; /// CLI arguments for `cast send`. #[derive(Debug, Parser)] @@ -22,7 +22,7 @@ pub struct SendTxArgs { /// The destination of the transaction. /// /// If not provided, you must use cast send --create. - #[clap(value_parser = NameOrAddress::from_str)] + #[arg(value_parser = NameOrAddress::from_str)] to: Option, /// The signature of the function to call. @@ -32,39 +32,39 @@ pub struct SendTxArgs { args: Vec, /// Only print the transaction hash and exit immediately. - #[clap(name = "async", long = "async", alias = "cast-async", env = "CAST_ASYNC")] + #[arg(id = "async", long = "async", alias = "cast-async", env = "CAST_ASYNC")] cast_async: bool, /// The number of confirmations until the receipt is fetched. - #[clap(long, default_value = "1")] - confirmations: usize, + #[arg(long, default_value = "1")] + confirmations: u64, /// Print the transaction receipt as JSON. - #[clap(long, short, help_heading = "Display options")] + #[arg(long, short, help_heading = "Display options")] json: bool, - /// Reuse the latest nonce for the sender account. - #[clap(long, conflicts_with = "nonce")] - resend: bool, - - #[clap(subcommand)] + #[command(subcommand)] command: Option, /// Send via `eth_sendTransaction using the `--from` argument or $ETH_FROM as sender - #[clap(long, requires = "from")] + #[arg(long, requires = "from")] unlocked: bool, - #[clap(flatten)] + #[command(flatten)] tx: TransactionOpts, - #[clap(flatten)] + #[command(flatten)] eth: EthereumOpts, + + /// The path of blob data to be sent. + #[arg(long, value_name = "BLOB_DATA_PATH", conflicts_with = "legacy", requires = "blob")] + path: Option, } #[derive(Debug, Parser)] pub enum SendTxSubcommands { /// Use to deploy raw contract bytecode. - #[clap(name = "--create")] + #[command(name = "--create")] Create { /// The bytecode of the contract to deploy. code: String, @@ -78,44 +78,47 @@ pub enum SendTxSubcommands { } impl SendTxArgs { - pub async fn run(self) -> Result<()> { - let SendTxArgs { + #[allow(unknown_lints, dependency_on_unit_never_type_fallback)] + pub async fn run(self) -> Result<(), eyre::Report> { + let Self { eth, to, - sig, + mut sig, cast_async, mut args, - mut tx, + tx, confirmations, json: to_json, - resend, command, unlocked, + path, } = self; - let mut sig = sig.unwrap_or_default(); + let blob_data = if let Some(path) = path { Some(std::fs::read(path)?) } else { None }; + let code = if let Some(SendTxSubcommands::Create { code, sig: constructor_sig, args: constructor_args, }) = command { - sig = constructor_sig.unwrap_or_default(); + sig = constructor_sig; args = constructor_args; Some(code) } else { None }; - // ensure mandatory fields are provided - if code.is_none() && to.is_none() { - eyre::bail!("Must specify a recipient address or contract code to deploy"); - } - let config = Config::from(ð); let provider = utils::get_provider(&config)?; - let chain = utils::get_chain(config.chain, &provider).await?; - let api_key = config.get_etherscan_api_key(Some(chain)); + let tx_kind = tx::resolve_tx_kind(&provider, &code, &to).await?; + + let builder = CastTxBuilder::new(&provider, tx, &config) + .await? + .with_tx_kind(tx_kind) + .with_code_sig_and_args(code, sig, args) + .await? + .with_blob_data(blob_data)?; // Case 1: // Default to sending via eth_sendTransaction if the --unlocked flag is passed. @@ -124,15 +127,15 @@ impl SendTxArgs { if unlocked { // 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_chainid().await?.as_u64(); + 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 { cli_warn!("Switching to chain {}", config_chain); provider - .request( - "wallet_switchEthereumChain", + .raw_request( + "wallet_switchEthereumChain".into(), [serde_json::json!({ "chainId": format!("0x{:x}", config_chain_id), })], @@ -141,29 +144,9 @@ impl SendTxArgs { } } - if resend { - tx.nonce = Some( - provider - .get_transaction_count(config.sender.to_ethers(), None) - .await? - .to_alloy(), - ); - } + let (tx, _) = builder.build(config.sender).await?; - cast_send( - provider, - config.sender.to_ethers(), - to, - code, - (sig, args), - tx, - chain, - api_key, - cast_async, - confirmations, - to_json, - ) - .await + cast_send(provider, tx, cast_async, confirmations, to_json).await // Case 2: // 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 @@ -173,90 +156,31 @@ impl SendTxArgs { let signer = eth.wallet.signer().await?; let from = signer.address(); - // prevent misconfigured hwlib from sending a transaction that defies - // user-specified --from - if let Some(specified_from) = eth.wallet.from { - if specified_from != from.to_alloy() { - 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." - ) - } - } + tx::validate_from_address(eth.wallet.from, from)?; - if resend { - tx.nonce = Some(provider.get_transaction_count(from, None).await?.to_alloy()); - } + let wallet = EthereumWallet::from(signer); + let provider = ProviderBuilder::<_, _, AnyNetwork>::default() + .wallet(wallet) + .on_provider(&provider); - let provider = SignerMiddleware::new_with_provider_chain(provider, signer).await?; + let (tx, _) = builder.build(from).await?; - cast_send( - provider, - from, - to, - code, - (sig, args), - tx, - chain, - api_key, - cast_async, - confirmations, - to_json, - ) - .await + cast_send(provider, tx, cast_async, confirmations, to_json).await } } } -#[allow(clippy::too_many_arguments)] -async fn cast_send, T: Into>( - provider: M, - from: F, - to: Option, - code: Option, - args: (String, Vec), - tx: TransactionOpts, - chain: Chain, - etherscan_api_key: Option, +async fn cast_send, T: Transport + Clone>( + provider: P, + tx: WithOtherFields, cast_async: bool, - confs: usize, + confs: u64, to_json: bool, -) -> Result<()> -where - M::Error: 'static, -{ - let (sig, params) = args; - let params = if !sig.is_empty() { Some((&sig[..], params)) } else { None }; - let mut builder = TxBuilder::new(&provider, from, to, chain, tx.legacy).await?; - builder - .etherscan_api_key(etherscan_api_key) - .gas(tx.gas_limit) - .gas_price(tx.gas_price) - .priority_gas_price(tx.priority_gas_price) - .value(tx.value) - .nonce(tx.nonce); - - if let Some(code) = code { - let mut data = hex::decode(code)?; - - if let Some((sig, args)) = params { - let (mut sigdata, _) = builder.create_args(sig, args).await?; - data.append(&mut sigdata); - } - - builder.set_data(data); - } else { - builder.args(params).await?; - }; - let builder_output = builder.build(); - +) -> Result<()> { let cast = Cast::new(provider); + let pending_tx = cast.send(tx).await?; - let pending_tx = cast.send(builder_output).await?; - let tx_hash = *pending_tx; + let tx_hash = pending_tx.inner().tx_hash(); if cast_async { println!("{tx_hash:#x}"); diff --git a/crates/cast/bin/cmd/storage.rs b/crates/cast/bin/cmd/storage.rs index f8f01b7ae..117130943 100644 --- a/crates/cast/bin/cmd/storage.rs +++ b/crates/cast/bin/cmd/storage.rs @@ -1,10 +1,12 @@ use crate::opts::parse_slot; -use alloy_primitives::{B256, U256}; +use alloy_network::AnyNetwork; +use alloy_primitives::{Address, B256, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::BlockId; +use alloy_transport::Transport; use cast::Cast; use clap::Parser; use comfy_table::{presets::ASCII_MARKDOWN, Table}; -use ethers_core::types::{BlockId, NameOrAddress}; -use ethers_providers::Middleware; use eyre::Result; use foundry_block_explorers::Client; use foundry_cli::{ @@ -14,11 +16,15 @@ use foundry_cli::{ use foundry_common::{ abi::find_source, compile::{etherscan_project, ProjectCompiler}, - provider::ethers::RetryProvider, - types::{ToAlloy, ToEthers}, + ens::NameOrAddress, }; use foundry_compilers::{ - artifacts::StorageLayout, Artifact, ConfigurableContractArtifact, Project, Solc, + artifacts::{ConfigurableContractArtifact, StorageLayout}, + compilers::{ + solc::{Solc, SolcCompiler}, + Compiler, CompilerSettings, + }, + Artifact, Project, }; use foundry_config::{ figment::{self, value::Dict, Metadata, Profile}, @@ -36,26 +42,26 @@ const MIN_SOLC: Version = Version::new(0, 6, 5); #[derive(Clone, Debug, Parser)] pub struct StorageArgs { /// The contract address. - #[clap(value_parser = NameOrAddress::from_str)] + #[arg(value_parser = NameOrAddress::from_str)] address: NameOrAddress, /// The storage slot number. - #[clap(value_parser = parse_slot)] + #[arg(value_parser = parse_slot)] slot: Option, /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short)] + #[arg(long, short)] block: Option, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, - #[clap(flatten)] + #[command(flatten)] etherscan: EtherscanOpts, - #[clap(flatten)] + #[command(flatten)] build: CoreBuildArgs, } @@ -80,19 +86,20 @@ impl StorageArgs { let config = Config::from(&self); let Self { address, slot, 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) = slot { let cast = Cast::new(provider); - println!("{}", cast.storage(address, slot.to_ethers(), block).await?); + println!("{}", cast.storage(address, slot, block).await?); return Ok(()); } // No slot was provided // Get deployed bytecode at given address - let address_code = provider.get_code(address.clone(), block).await?.to_alloy(); + 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"); } @@ -107,8 +114,7 @@ impl StorageArgs { artifact.get_deployed_bytecode_bytes().is_some_and(|b| *b == address_code) }); if let Some((_, artifact)) = artifact { - return fetch_and_print_storage(provider, address.clone(), block, artifact, true) - .await; + return fetch_and_print_storage(provider, address, block, artifact, true).await; } } @@ -123,11 +129,7 @@ impl StorageArgs { let chain = utils::get_chain(config.chain, &provider).await?; let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); let client = Client::new(chain, api_key)?; - let addr = address - .as_address() - .ok_or_else(|| eyre::eyre!("Could not resolve address"))? - .to_alloy(); - let source = find_source(client, addr).await?; + let source = 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") @@ -142,7 +144,12 @@ impl StorageArgs { let root_path = root.path(); let mut project = etherscan_project(metadata, root_path)?; add_storage_layout_output(&mut project); - project.auto_detect = auto_detect; + + project.compiler = if auto_detect { + SolcCompiler::AutoDetect + } else { + SolcCompiler::Specific(Solc::find_or_install(&version)?) + }; // Compile let mut out = ProjectCompiler::new().quiet(true).compile(&project)?; @@ -155,9 +162,8 @@ impl StorageArgs { if is_storage_layout_empty(&artifact.storage_layout) && auto_detect { // try recompiling with the minimum version eprintln!("The requested contract was compiled with {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_svm_version(MIN_SOLC.to_string())?; - project.solc = solc; - project.auto_detect = false; + let solc = Solc::find_or_install(&MIN_SOLC)?; + project.compiler = SolcCompiler::Specific(solc); if let Ok(output) = ProjectCompiler::new().quiet(true).compile(&project) { out = output; let (_, new_artifact) = out @@ -209,9 +215,9 @@ impl StorageValue { } } -async fn fetch_and_print_storage( - provider: RetryProvider, - address: NameOrAddress, +async fn fetch_and_print_storage, T: Transport + Clone>( + provider: P, + address: Address, block: Option, artifact: &ConfigurableContractArtifact, pretty: bool, @@ -226,18 +232,20 @@ async fn fetch_and_print_storage( } } -async fn fetch_storage_slots( - provider: RetryProvider, - address: NameOrAddress, +async fn fetch_storage_slots, T: Transport + Clone>( + 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.clone(), slot.to_ethers(), block).await?.to_alloy(); + 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 }; + let value = StorageValue { slot, raw_slot_value: raw_slot_value.into() }; Ok(value) }); @@ -278,10 +286,15 @@ fn print_storage(layout: StorageLayout, values: Vec, pretty: bool) Ok(()) } -fn add_storage_layout_output(project: &mut Project) { +fn add_storage_layout_output(project: &mut Project) { project.artifacts.additional_values.storage_layout = true; - let output_selection = project.artifacts.output_selection(); - project.solc_config.settings.push_all(output_selection); + project.settings.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 { diff --git a/crates/cast/bin/cmd/wallet/list.rs b/crates/cast/bin/cmd/wallet/list.rs index b0984d4ba..88b948605 100644 --- a/crates/cast/bin/cmd/wallet/list.rs +++ b/crates/cast/bin/cmd/wallet/list.rs @@ -1,7 +1,7 @@ use clap::Parser; use eyre::Result; -use foundry_common::{fs, types::ToAlloy}; +use foundry_common::fs; use foundry_config::Config; use foundry_wallets::multi_wallet::MultiWalletOptsBuilder; @@ -10,27 +10,27 @@ use foundry_wallets::multi_wallet::MultiWalletOptsBuilder; pub struct ListArgs { /// List all the accounts in the keystore directory. /// Default keystore directory is used if no path provided. - #[clap(long, default_missing_value = "", num_args(0..=1))] + #[arg(long, default_missing_value = "", num_args(0..=1))] dir: Option, /// List accounts from a Ledger hardware wallet. - #[clap(long, short, group = "hw-wallets")] + #[arg(long, short, group = "hw-wallets")] ledger: bool, /// List accounts from a Trezor hardware wallet. - #[clap(long, short, group = "hw-wallets")] + #[arg(long, short, group = "hw-wallets")] trezor: bool, /// List accounts from AWS KMS. - #[clap(long)] + #[arg(long, hide = !cfg!(feature = "aws-kms"))] aws: bool, /// List all configured accounts. - #[clap(long, group = "hw-wallets")] + #[arg(long, group = "hw-wallets")] all: bool, /// Max number of addresses to display from hardware wallets. - #[clap(long, short, default_value = "3", requires = "hw-wallets")] + #[arg(long, short, default_value = "3", requires = "hw-wallets")] max_senders: Option, } @@ -61,7 +61,7 @@ impl ListArgs { .available_senders(self.max_senders.unwrap()) .await? .iter() - .for_each(|sender| println!("{} ({})", sender.to_alloy(), $label)); + .for_each(|sender| println!("{} ({})", sender, $label)); } } Err(e) => { @@ -91,17 +91,17 @@ impl ListArgs { dunce::canonicalize(keystore_path)? }; - // list files within keystore dir - std::fs::read_dir(keystore_dir)?.flatten().for_each(|entry| { - let path = entry.path(); - if path.is_file() && path.extension().is_none() { + // List all files within the keystore directory. + for entry in std::fs::read_dir(keystore_dir)? { + let path = entry?.path(); + if path.is_file() { if let Some(file_name) = path.file_name() { if let Some(name) = file_name.to_str() { - println!("{} (Local)", name); + println!("{name} (Local)"); } } } - }); + } Ok(()) } diff --git a/crates/cast/bin/cmd/wallet/mod.rs b/crates/cast/bin/cmd/wallet/mod.rs index c5c045b75..2b50630c3 100644 --- a/crates/cast/bin/cmd/wallet/mod.rs +++ b/crates/cast/bin/cmd/wallet/mod.rs @@ -1,13 +1,13 @@ -use alloy_primitives::{Address, Signature}; -use alloy_signer::{ - coins_bip39::{English, Mnemonic}, - LocalWallet, MnemonicBuilder, Signer as AlloySigner, +use alloy_dyn_abi::TypedData; +use alloy_primitives::{Address, Signature, B256}; +use alloy_signer::Signer; +use alloy_signer_local::{ + coins_bip39::{English, Entropy, Mnemonic}, + MnemonicBuilder, PrivateKeySigner, }; use clap::Parser; -use ethers_core::types::transaction::eip712::TypedData; -use ethers_signers::Signer; use eyre::{Context, Result}; -use foundry_common::{fs, types::ToAlloy}; +use foundry_common::fs; use foundry_config::Config; use foundry_wallets::{RawWalletOpts, WalletOpts, WalletSigner}; use rand::thread_rng; @@ -25,7 +25,7 @@ use list::ListArgs; #[derive(Debug, Parser)] pub enum WalletSubcommands { /// Create a new random keypair. - #[clap(visible_alias = "n")] + #[command(visible_alias = "n")] New { /// If provided, then keypair will be written to an encrypted JSON keystore. path: Option, @@ -33,53 +33,57 @@ pub enum WalletSubcommands { /// Triggers a hidden password prompt for the JSON keystore. /// /// Deprecated: prompting for a hidden password is now the default. - #[clap(long, short, requires = "path", conflicts_with = "unsafe_password")] + #[arg(long, short, requires = "path", conflicts_with = "unsafe_password")] password: bool, /// Password for the JSON keystore in cleartext. /// /// This is UNSAFE to use and we recommend using the --password. - #[clap(long, requires = "path", env = "CAST_PASSWORD", value_name = "PASSWORD")] + #[arg(long, requires = "path", env = "CAST_PASSWORD", value_name = "PASSWORD")] unsafe_password: Option, /// Number of wallets to generate. - #[clap(long, short, default_value = "1")] + #[arg(long, short, default_value = "1")] number: u32, /// Output generated wallets as JSON. - #[clap(long, short, default_value = "false")] + #[arg(long, short, default_value = "false")] json: bool, }, /// Generates a random BIP39 mnemonic phrase - #[clap(visible_alias = "nm")] + #[command(visible_alias = "nm")] NewMnemonic { /// Number of words for the mnemonic - #[clap(long, short, default_value = "12")] + #[arg(long, short, default_value = "12")] words: usize, /// Number of accounts to display - #[clap(long, short, default_value = "1")] + #[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. - #[clap(visible_alias = "va")] + #[command(visible_alias = "va")] Vanity(VanityArgs), /// Convert a private key to an address. - #[clap(visible_aliases = &["a", "addr"])] + #[command(visible_aliases = &["a", "addr"])] Address { /// If provided, the address will be derived from the specified private key. - #[clap(value_name = "PRIVATE_KEY")] + #[arg(value_name = "PRIVATE_KEY")] private_key_override: Option, - #[clap(flatten)] + #[command(flatten)] wallet: WalletOpts, }, /// Sign a message or typed data. - #[clap(visible_alias = "s")] + #[command(visible_alias = "s")] Sign { /// The message, typed data, or hash to sign. /// @@ -97,23 +101,23 @@ pub enum WalletSubcommands { message: String, /// Treat the message as JSON typed data. - #[clap(long)] + #[arg(long)] data: bool, /// Treat the message as a file containing JSON typed data. Requires `--data`. - #[clap(long, 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. - #[clap(long, conflicts_with = "data")] + #[arg(long, conflicts_with = "data")] no_hash: bool, - #[clap(flatten)] + #[command(flatten)] wallet: WalletOpts, }, /// Verify the signature of a message. - #[clap(visible_alias = "v")] + #[command(visible_alias = "v")] Verify { /// The original message. message: String, @@ -122,35 +126,73 @@ pub enum WalletSubcommands { signature: Signature, /// The address of the message signer. - #[clap(long, short)] + #[arg(long, short)] address: Address, }, + /// Import a private key into an encrypted keystore. - #[clap(visible_alias = "i")] + #[command(visible_alias = "i")] Import { /// The name for the account in the keystore. - #[clap(value_name = "ACCOUNT_NAME")] + #[arg(value_name = "ACCOUNT_NAME")] account_name: String, /// If provided, keystore will be saved here instead of the default keystores directory /// (~/.foundry/keystores) - #[clap(long, short)] + #[arg(long, short)] keystore_dir: Option, - #[clap(flatten)] + /// 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 - #[clap(visible_alias = "ls")] + #[command(visible_alias = "ls")] List(ListArgs), /// Derives private key from mnemonic - #[clap(name = "derive-private-key", visible_aliases = &["--derive-private-key"])] - DerivePrivateKey { mnemonic: String, mnemonic_index: Option }, + #[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 menomonic 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, + + /// Verbose mode, print the address and private key. + #[arg(short = 'v', long)] + verbose: bool, + + #[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, + }, } impl WalletSubcommands { pub async fn run(self) -> Result<()> { match self { - WalletSubcommands::New { path, unsafe_password, number, json, .. } => { + Self::New { path, unsafe_password, number, json, .. } => { let mut rng = thread_rng(); let mut json_values = if json { Some(vec![]) } else { None }; @@ -176,8 +218,12 @@ impl WalletSubcommands { }; for _ in 0..number { - let (wallet, uuid) = - LocalWallet::new_keystore(&path, &mut rng, password.clone(), None)?; + let (wallet, uuid) = PrivateKeySigner::new_keystore( + &path, + &mut rng, + password.clone(), + None, + )?; if let Some(json) = json_values.as_mut() { json.push(json!({ @@ -199,17 +245,20 @@ impl WalletSubcommands { } } else { for _ in 0..number { - let wallet = LocalWallet::random_with(&mut rng); + let wallet = PrivateKeySigner::random_with(&mut rng); if let Some(json) = json_values.as_mut() { json.push(json!({ "address": wallet.address().to_checksum(None), - "private_key": format!("0x{}", hex::encode(wallet.signer().to_bytes())), + "private_key": format!("0x{}", hex::encode(wallet.credential().to_bytes())), })) } else { println!("Successfully created new keypair."); println!("Address: {}", wallet.address().to_checksum(None)); - println!("Private key: 0x{}", hex::encode(wallet.signer().to_bytes())); + println!( + "Private key: 0x{}", + hex::encode(wallet.credential().to_bytes()) + ); } } @@ -218,9 +267,15 @@ impl WalletSubcommands { } } } - WalletSubcommands::NewMnemonic { words, accounts } => { - let mut rng = thread_rng(); - let phrase = Mnemonic::::new_with_count(&mut rng, words)?.to_phrase(); + Self::NewMnemonic { words, accounts, entropy } => { + let phrase = if let Some(entropy) = entropy { + let entropy = Entropy::from_slice(hex::decode(entropy)?)?; + println!("{}", "Generating mnemonic from provided entropy...".yellow()); + Mnemonic::::new_from_entropy(entropy).to_phrase() + } else { + let mut rng = thread_rng(); + Mnemonic::::new_with_count(&mut rng, words)?.to_phrase() + }; let builder = MnemonicBuilder::::default().phrase(phrase.as_str()); let derivation_path = "m/44'/60'/0'/0/"; @@ -230,19 +285,19 @@ impl WalletSubcommands { let wallets = wallets.into_iter().map(|b| b.build()).collect::, _>>()?; - println!("{}", Paint::green("Successfully generated a new mnemonic.")); + println!("{}", "Successfully generated a new mnemonic.".green()); println!("Phrase:\n{phrase}"); println!("\nAccounts:"); for (i, wallet) in wallets.iter().enumerate() { println!("- Account {i}:"); println!("Address: {}", wallet.address()); - println!("Private key: 0x{}\n", hex::encode(wallet.signer().to_bytes())); + println!("Private key: 0x{}\n", hex::encode(wallet.credential().to_bytes())); } } - WalletSubcommands::Vanity(cmd) => { + Self::Vanity(cmd) => { cmd.run()?; } - WalletSubcommands::Address { wallet, private_key_override } => { + Self::Address { wallet, private_key_override } => { let wallet = private_key_override .map(|pk| WalletOpts { raw: RawWalletOpts { private_key: Some(pk), ..Default::default() }, @@ -252,9 +307,9 @@ impl WalletSubcommands { .signer() .await?; let addr = wallet.address(); - println!("{}", addr.to_alloy().to_checksum(None)); + println!("{}", addr.to_checksum(None)); } - WalletSubcommands::Sign { message, data, from_file, no_hash, wallet } => { + 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 { @@ -264,23 +319,23 @@ impl WalletSubcommands { // data is a json string serde_json::from_str(&message)? }; - wallet.sign_typed_data(&typed_data).await? + wallet.sign_dynamic_typed_data(&typed_data).await? } else if no_hash { - wallet.sign_hash(&message.parse()?).await? + wallet.sign_hash(&hex::decode(&message)?[..].try_into()?).await? } else { - wallet.sign_message(Self::hex_str_to_bytes(&message)?).await? + wallet.sign_message(&Self::hex_str_to_bytes(&message)?).await? }; - println!("0x{sig}"); + println!("0x{}", hex::encode(sig.as_bytes())); } - WalletSubcommands::Verify { message, signature, address } => { + Self::Verify { message, signature, address } => { let recovered_address = Self::recover_address_from_message(&message, &signature)?; if address == recovered_address { println!("Validation succeeded. Address {address} signed this message."); } else { - println!("Validation failed. Address {address} did not sign this message."); + eyre::bail!("Validation failed. Address {address} did not sign this message."); } } - WalletSubcommands::Import { account_name, keystore_dir, raw_wallet_options } => { + 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() @@ -315,15 +370,20 @@ flag to set your key via: ) })?; - let private_key = wallet.signer().to_bytes(); - let password = rpassword::prompt_password("Enter password: ")?; + 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(); - eth_keystore::encrypt_key( - &dir, + let (wallet, _) = PrivateKeySigner::encrypt_keystore( + dir, &mut rng, private_key, - &password, + password, Some(&account_name), )?; let address = wallet.address(); @@ -331,23 +391,84 @@ flag to set your key via: "`{}` keystore was saved successfully. Address: {:?}", &account_name, address, ); - println!("{}", Paint::green(success_message)); + println!("{}", success_message.green()); } - WalletSubcommands::List(cmd) => { + Self::List(cmd) => { cmd.run().await?; } - WalletSubcommands::DerivePrivateKey { mnemonic, mnemonic_index } => { - let phrase = Mnemonic::::new_from_phrase(mnemonic.as_str())?.to_phrase(); - let builder = MnemonicBuilder::::default().phrase(phrase.as_str()); - let derivation_path = "m/44'/60'/0'/0/"; - let index = if let Some(i) = mnemonic_index { i } else { 0 }; - let wallet = builder - .clone() - .derivation_path(format!("{derivation_path}{index}"))? - .build()?; - println!("- Account:"); - println!("Address: {}", wallet.address()); - println!("Private key: 0x{}\n", hex::encode(wallet.signer().to_bytes())); + Self::PrivateKey { + wallet, + mnemonic_override, + mnemonic_index_or_derivation_path_override, + verbose, + } => { + 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 verbose { + println!("Address: {}", wallet.address()); + println!( + "Private key: 0x{}", + hex::encode(wallet.credential().to_bytes()) + ); + } else { + 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); + + println!("{}", success_message.green()); } }; @@ -369,11 +490,9 @@ flag to set your key via: #[cfg(test)] mod tests { - use std::str::FromStr; - - use alloy_primitives::address; - use super::*; + use alloy_primitives::address; + use std::str::FromStr; #[test] fn can_parse_wallet_sign_message() { diff --git a/crates/cast/bin/cmd/wallet/vanity.rs b/crates/cast/bin/cmd/wallet/vanity.rs index a466bbe31..1e94b5f9a 100644 --- a/crates/cast/bin/cmd/wallet/vanity.rs +++ b/crates/cast/bin/cmd/wallet/vanity.rs @@ -1,5 +1,6 @@ use alloy_primitives::Address; -use alloy_signer::{k256::ecdsa::SigningKey, utils::secret_key_to_address, LocalWallet, Signer}; +use alloy_signer::{k256::ecdsa::SigningKey, utils::secret_key_to_address}; +use alloy_signer_local::PrivateKeySigner; use clap::{builder::TypedValueParser, Parser}; use eyre::Result; use rayon::iter::{self, ParallelIterator}; @@ -18,7 +19,7 @@ pub type GeneratedWallet = (SigningKey, Address); #[derive(Clone, Debug, Parser)] pub struct VanityArgs { /// Prefix for the vanity address. - #[clap( + #[arg( long, required_unless_present = "ends_with", value_parser = HexAddressValidator, @@ -27,20 +28,20 @@ pub struct VanityArgs { pub starts_with: Option, /// Suffix for the vanity address. - #[clap(long, value_parser = HexAddressValidator, value_name = "HEX")] + #[arg(long, value_parser = HexAddressValidator, value_name = "HEX")] 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. - #[clap(long)] + #[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. - #[clap( + #[arg( long, value_hint = clap::ValueHint::FilePath, value_name = "PATH", @@ -62,16 +63,16 @@ struct Wallets { } impl WalletData { - pub fn new(wallet: &LocalWallet) -> Self { - WalletData { + pub fn new(wallet: &PrivateKeySigner) -> Self { + Self { address: wallet.address().to_checksum(None), - private_key: format!("0x{}", hex::encode(wallet.signer().to_bytes())), + private_key: format!("0x{}", hex::encode(wallet.credential().to_bytes())), } } } impl VanityArgs { - pub fn run(self) -> Result { + 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; @@ -159,7 +160,7 @@ impl VanityArgs { String::new() }, wallet.address().to_checksum(None), - hex::encode(wallet.signer().to_bytes()), + hex::encode(wallet.credential().to_bytes()), ); Ok(wallet) @@ -169,7 +170,7 @@ impl VanityArgs { /// 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: &LocalWallet, path: &Path) -> Result<()> { +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() @@ -184,7 +185,7 @@ fn save_wallet_to_file(wallet: &LocalWallet, path: &Path) -> Result<()> { } /// Generates random wallets until `matcher` matches the wallet address, returning the wallet. -pub fn find_vanity_address(matcher: T) -> Option { +pub fn find_vanity_address(matcher: T) -> Option { wallet_generator().find_any(create_matcher(matcher)).map(|(key, _)| key.into()) } @@ -193,7 +194,7 @@ pub fn find_vanity_address(matcher: T) -> Option pub fn find_vanity_address_with_nonce( matcher: T, nonce: u64, -) -> Option { +) -> Option { wallet_generator().find_any(create_nonce_matcher(matcher, nonce)).map(|(key, _)| key.into()) } diff --git a/crates/cast/bin/main.rs b/crates/cast/bin/main.rs index 93c686cb3..0f6673d4f 100644 --- a/crates/cast/bin/main.rs +++ b/crates/cast/bin/main.rs @@ -2,33 +2,37 @@ extern crate tracing; use alloy_primitives::{keccak256, Address, B256}; -use cast::{Cast, SimpleCast, TxBuilder}; +use alloy_provider::Provider; +use alloy_rpc_types::{BlockId, BlockNumberOrTag::Latest}; +use cast::{Cast, SimpleCast}; use clap::{CommandFactory, Parser}; use clap_complete::generate; -use ethers_core::types::{BlockId, BlockNumber::Latest, NameOrAddress}; -use ethers_providers::{Middleware, Provider}; use eyre::Result; use foundry_cli::{handler, prompt, stdin, utils}; use foundry_common::{ abi::get_event, - fmt::format_tokens, + ens::{namehash, ProviderEnsExt}, + fmt::{format_tokens, format_uint_exp}, fs, - runtime_client::RuntimeClient, selectors::{ decode_calldata, decode_event_topic, decode_function_selector, decode_selectors, import_selectors, parse_signatures, pretty_calldata, ParsedSignatures, SelectorImportData, SelectorType, }, - types::{ToAlloy, ToEthers}, }; use foundry_config::Config; use std::time::Instant; pub mod cmd; pub mod opts; +pub mod tx; use opts::{Cast as Opts, CastSubcommand, ToBaseArgs}; +#[cfg(all(feature = "jemalloc", unix))] +#[global_allocator] +static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; + #[tokio::main] async fn main() -> Result<()> { handler::install(); @@ -64,6 +68,10 @@ async fn main() -> Result<()> { let value = stdin::unwrap(hexdata, false)?; println!("{}", SimpleCast::to_ascii(&value)?); } + CastSubcommand::ToUtf8 { hexdata } => { + let value = stdin::unwrap(hexdata, false)?; + println!("{}", SimpleCast::to_utf8(&value)?); + } CastSubcommand::FromFixedPoint { value, decimals } => { let (value, decimals) = stdin::unwrap2(value, decimals)?; println!("{}", SimpleCast::from_fixed_point(&value, &decimals)?); @@ -209,35 +217,16 @@ async fn main() -> Result<()> { CastSubcommand::Balance { block, who, ether, rpc, erc20 } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; + let account_addr = who.resolve(&provider).await?; match erc20 { Some(token) => { - let chain = utils::get_chain(config.chain, &provider).await?; - let mut builder: TxBuilder<'_, Provider> = TxBuilder::new( - &provider, - NameOrAddress::Address(Address::ZERO.to_ethers()), - Some(NameOrAddress::Address(token.to_ethers())), - chain, - true, - ) - .await?; - - let account_addr = match who { - NameOrAddress::Name(ens_name) => provider.resolve_name(&ens_name).await?, - NameOrAddress::Address(addr) => addr, - }; - - builder - .set_args( - "balanceOf(address) returns (uint256)", - vec![format!("{account_addr:#x}")], - ) - .await?; - let builder_output = builder.build(); - println!("{}", Cast::new(provider).call(builder_output, block).await?); + let balance = + Cast::new(&provider).erc20_balance(token, account_addr, block).await?; + println!("{}", format_uint_exp(balance)); } None => { - let value = Cast::new(provider).balance(who, block).await?; + let value = Cast::new(&provider).balance(account_addr, block).await?; if ether { println!("{}", SimpleCast::from_wei(&value.to_string(), "eth")?); } else { @@ -264,10 +253,20 @@ async fn main() -> Result<()> { .await? ); } - CastSubcommand::BlockNumber { rpc } => { + CastSubcommand::BlockNumber { rpc, block } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; - println!("{}", Cast::new(provider).block_number().await?); + let number = match block { + Some(id) => provider + .get_block(id, false.into()) + .await? + .ok_or_else(|| eyre::eyre!("block {id:?} not found"))? + .header + .number + .ok_or_else(|| eyre::eyre!("block {id:?} has no block number"))?, + None => Cast::new(provider).block_number().await?, + }; + println!("{number}"); } CastSubcommand::Chain { rpc } => { let config = Config::from(&rpc); @@ -282,16 +281,18 @@ async fn main() -> Result<()> { CastSubcommand::Client { rpc } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; - println!("{}", provider.client_version().await?); + println!("{}", provider.get_client_version().await?); } CastSubcommand::Code { block, who, disassemble, rpc } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; + let who = who.resolve(&provider).await?; println!("{}", Cast::new(provider).code(who, block, disassemble).await?); } CastSubcommand::Codesize { block, who, rpc } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; + let who = who.resolve(&provider).await?; println!("{}", Cast::new(provider).codesize(who, block).await?); } CastSubcommand::ComputeAddress { address, nonce, rpc } => { @@ -299,7 +300,7 @@ async fn main() -> Result<()> { let provider = utils::get_provider(&config)?; let address: Address = stdin::unwrap_line(address)?.parse()?; - let computed = Cast::new(&provider).compute_address(address, nonce).await?; + let computed = Cast::new(provider).compute_address(address, nonce).await?; println!("Computed Address: {}", computed.to_checksum(None)); } CastSubcommand::Disassemble { bytecode } => { @@ -318,7 +319,7 @@ async fn main() -> Result<()> { { let resolved = match func_names { Some(v) => v.join("|"), - None => "".to_string(), + None => String::new(), }; println!("{selector}\t{arguments:max_args_len$}\t{resolved}"); } @@ -337,26 +338,36 @@ async fn main() -> Result<()> { CastSubcommand::Index { key_type, key, slot_number } => { 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)?; + println!("{}", foundry_common::erc7201(&id)); + } CastSubcommand::Implementation { block, who, rpc } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; + let who = who.resolve(&provider).await?; println!("{}", Cast::new(provider).implementation(who, block).await?); } CastSubcommand::Admin { block, who, rpc } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; + let who = who.resolve(&provider).await?; println!("{}", Cast::new(provider).admin(who, block).await?); } CastSubcommand::Nonce { block, who, rpc } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; + let who = who.resolve(&provider).await?; println!("{}", Cast::new(provider).nonce(who, block).await?); } CastSubcommand::Proof { address, slots, rpc, block } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; + let address = address.resolve(&provider).await?; let value = provider - .get_proof(address, slots.into_iter().map(|s| s.to_ethers()).collect(), block) + .get_proof(address, slots.into_iter().collect()) + .block_id(block.unwrap_or_default()) .await?; println!("{}", serde_json::to_string(&value)?); } @@ -366,18 +377,18 @@ async fn main() -> Result<()> { // 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 = Config::from(&rpc); let provider = utils::get_provider(&config)?; let cast = Cast::new(&provider); let pending_tx = cast.publish(raw_tx).await?; - let tx_hash = *pending_tx; + let tx_hash = pending_tx.inner().tx_hash(); if cast_async { println!("{tx_hash:#x}"); } else { - let receipt = - pending_tx.await?.ok_or_else(|| eyre::eyre!("tx {tx_hash} not found"))?; + let receipt = pending_tx.get_receipt().await?; println!("{}", serde_json::json!(receipt)); } } @@ -457,19 +468,19 @@ async fn main() -> Result<()> { // ENS CastSubcommand::Namehash { name } => { let name = stdin::unwrap_line(name)?; - println!("{}", SimpleCast::namehash(&name)?); + println!("{}", namehash(&name)); } CastSubcommand::LookupAddress { who, rpc, verify } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; let who = stdin::unwrap_line(who)?; - let name = provider.lookup_address(who.to_ethers()).await?; + let name = provider.lookup_address(&who).await?; if verify { - let address = provider.resolve_name(&name).await?.to_alloy(); + let address = provider.resolve_name(&name).await?; eyre::ensure!( address == who, - "Forward lookup verification failed: got `{name:?}`, expected `{who:?}`" + "Reverse lookup verification failed: got `{address}`, expected `{who}`" ); } println!("{name}"); @@ -481,13 +492,13 @@ async fn main() -> Result<()> { let who = stdin::unwrap_line(who)?; let address = provider.resolve_name(&who).await?; if verify { - let name = provider.lookup_address(address).await?; - assert_eq!( - name, who, - "forward lookup verification failed. got {name}, expected {who}" + let name = provider.lookup_address(&address).await?; + eyre::ensure!( + name == who, + "Forward lookup verification failed: got `{name}`, expected `{who}`" ); } - println!("{}", address.to_alloy().to_checksum(None)); + println!("{address}"); } // Misc @@ -519,18 +530,21 @@ async fn main() -> Result<()> { CastSubcommand::RightShift { value, bits, base_in, base_out } => { println!("{}", SimpleCast::right_shift(&value, &bits, base_in.as_deref(), &base_out)?); } - CastSubcommand::EtherscanSource { address, directory, etherscan } => { + CastSubcommand::EtherscanSource { address, directory, etherscan, flatten } => { let config = Config::from(ðerscan); let chain = config.chain.unwrap_or_default(); let api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); - match directory { - Some(dir) => { + match (directory, flatten) { + (Some(dir), false) => { SimpleCast::expand_etherscan_source_to_directory(chain, address, api_key, dir) .await? } - None => { + (None, false) => { println!("{}", SimpleCast::etherscan_source(chain, address, api_key).await?); } + (dir, true) => { + SimpleCast::etherscan_source_flatten(chain, address, api_key, dir).await?; + } } } CastSubcommand::Create2(cmd) => { @@ -549,14 +563,7 @@ async fn main() -> Result<()> { CastSubcommand::Logs(cmd) => cmd.run().await?, CastSubcommand::DecodeTransaction { tx } => { let tx = stdin::unwrap_line(tx)?; - let (tx, sig) = SimpleCast::decode_raw_transaction(&tx)?; - - // Serialize tx, sig and constructed a merged json string - let mut tx = serde_json::to_value(&tx)?; - let tx_map = tx.as_object_mut().unwrap(); - serde_json::to_value(sig)?.as_object().unwrap().iter().for_each(|(k, v)| { - tx_map.entry(k).or_insert(v.clone()); - }); + let tx = SimpleCast::decode_raw_transaction(&tx)?; println!("{}", serde_json::to_string_pretty(&tx)?); } diff --git a/crates/cast/bin/opts.rs b/crates/cast/bin/opts.rs index 9cce33494..decd60374 100644 --- a/crates/cast/bin/opts.rs +++ b/crates/cast/bin/opts.rs @@ -1,13 +1,15 @@ use crate::cmd::{ access_list::AccessListArgs, bind::BindArgs, call::CallArgs, create2::Create2Args, estimate::EstimateArgs, find_block::FindBlockArgs, interface::InterfaceArgs, logs::LogsArgs, - rpc::RpcArgs, run::RunArgs, send::SendTxArgs, storage::StorageArgs, wallet::WalletSubcommands, + mktx::MakeTxArgs, rpc::RpcArgs, run::RunArgs, send::SendTxArgs, storage::StorageArgs, + wallet::WalletSubcommands, }; use alloy_primitives::{Address, B256, U256}; +use alloy_rpc_types::BlockId; use clap::{Parser, Subcommand, ValueHint}; -use ethers_core::types::{BlockId, NameOrAddress}; use eyre::Result; use foundry_cli::opts::{EtherscanOpts, RpcOpts}; +use foundry_common::ens::NameOrAddress; use std::{path::PathBuf, str::FromStr}; const VERSION_MESSAGE: &str = concat!( @@ -21,53 +23,53 @@ const VERSION_MESSAGE: &str = concat!( /// Perform Ethereum RPC calls from the comfort of your command line. #[derive(Parser)] -#[clap( +#[command( name = "cast", version = VERSION_MESSAGE, after_help = "Find more information in the book: http://book.getfoundry.sh/reference/cast/cast.html", next_display_order = None, )] pub struct Cast { - #[clap(subcommand)] + #[command(subcommand)] pub cmd: CastSubcommand, } #[derive(Subcommand)] pub enum CastSubcommand { /// Prints the maximum value of the given integer type. - #[clap(visible_aliases = &["--max-int", "maxi"])] + #[command(visible_aliases = &["--max-int", "maxi"])] MaxInt { /// The integer type to get the maximum value of. - #[clap(default_value = "int256")] + #[arg(default_value = "int256")] r#type: String, }, /// Prints the minimum value of the given integer type. - #[clap(visible_aliases = &["--min-int", "mini"])] + #[command(visible_aliases = &["--min-int", "mini"])] MinInt { /// The integer type to get the minimum value of. - #[clap(default_value = "int256")] + #[arg(default_value = "int256")] r#type: String, }, /// Prints the maximum value of the given integer type. - #[clap(visible_aliases = &["--max-uint", "maxu"])] + #[command(visible_aliases = &["--max-uint", "maxu"])] MaxUint { /// The unsigned integer type to get the maximum value of. - #[clap(default_value = "uint256")] + #[arg(default_value = "uint256")] r#type: String, }, /// Prints the zero address. - #[clap(visible_aliases = &["--address-zero", "az"])] + #[command(visible_aliases = &["--address-zero", "az"])] AddressZero, /// Prints the zero hash. - #[clap(visible_aliases = &["--hash-zero", "hz"])] + #[command(visible_aliases = &["--hash-zero", "hz"])] HashZero, /// Convert UTF8 text to hex. - #[clap( + #[command( visible_aliases = &[ "--from-ascii", "--from-utf8", @@ -81,14 +83,14 @@ pub enum CastSubcommand { }, /// Concatenate hex strings. - #[clap(visible_aliases = &["--concat-hex", "ch"])] + #[command(visible_aliases = &["--concat-hex", "ch"])] ConcatHex { /// The data to concatenate. data: Vec, }, /// Convert binary data into hex data. - #[clap(visible_aliases = &["--from-bin", "from-binx", "fb"])] + #[command(visible_aliases = &["--from-bin", "from-binx", "fb"])] FromBin, /// Normalize the input to lowercase, 0x-prefixed hex. @@ -98,14 +100,14 @@ pub enum CastSubcommand { /// - 0x prefixed hex, concatenated with a ':' /// - an absolute path to file /// - @tag, where the tag is defined in an environment variable - #[clap(visible_aliases = &["--to-hexdata", "thd", "2hd"])] + #[command(visible_aliases = &["--to-hexdata", "thd", "2hd"])] ToHexdata { /// The input to normalize. input: Option, }, /// Convert an address to a checksummed format (EIP-55). - #[clap( + #[command( visible_aliases = &["--to-checksum-address", "--to-checksum", "to-checksum", @@ -118,57 +120,64 @@ pub enum CastSubcommand { }, /// Convert hex data to an ASCII string. - #[clap(visible_aliases = &["--to-ascii", "tas", "2as"])] + #[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. - #[clap(visible_aliases = &["--from-fix", "ff"])] + #[command(visible_aliases = &["--from-fix", "ff"])] FromFixedPoint { /// The number of decimals to use. decimals: Option, /// The value to convert. - #[clap(allow_hyphen_values = true)] + #[arg(allow_hyphen_values = true)] value: Option, }, /// Right-pads hex data to 32 bytes. - #[clap(visible_aliases = &["--to-bytes32", "tb", "2b"])] + #[command(visible_aliases = &["--to-bytes32", "tb", "2b"])] ToBytes32 { /// The hex data to convert. bytes: Option, }, /// Convert an integer into a fixed point number. - #[clap(visible_aliases = &["--to-fix", "tf", "2f"])] + #[command(visible_aliases = &["--to-fix", "tf", "2f"])] ToFixedPoint { /// The number of decimals to use. decimals: Option, /// The value to convert. - #[clap(allow_hyphen_values = true)] + #[arg(allow_hyphen_values = true)] value: Option, }, /// Convert a number to a hex-encoded uint256. - #[clap(name = "to-uint256", visible_aliases = &["--to-uint256", "tu", "2u"])] + #[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. - #[clap(name = "to-int256", visible_aliases = &["--to-int256", "ti", "2i"])] + #[command(name = "to-int256", visible_aliases = &["--to-int256", "ti", "2i"])] ToInt256 { /// The value to convert. value: Option, }, /// Perform a left shifting operation - #[clap(name = "shl")] + #[command(name = "shl")] LeftShift { /// The value to shift. value: String, @@ -177,16 +186,16 @@ pub enum CastSubcommand { bits: String, /// The input base. - #[clap(long)] + #[arg(long)] base_in: Option, /// The output base. - #[clap(long, default_value = "16")] + #[arg(long, default_value = "16")] base_out: String, }, /// Perform a right shifting operation - #[clap(name = "shr")] + #[command(name = "shr")] RightShift { /// The value to shift. value: String, @@ -195,11 +204,11 @@ pub enum CastSubcommand { bits: String, /// The input base, - #[clap(long)] + #[arg(long)] base_in: Option, /// The output base, - #[clap(long, default_value = "16")] + #[arg(long, default_value = "16")] base_out: String, }, @@ -211,46 +220,46 @@ pub enum CastSubcommand { /// - 1ether /// - 1 gwei /// - 1gwei ether - #[clap(visible_aliases = &["--to-unit", "tun", "2un"])] + #[command(visible_aliases = &["--to-unit", "tun", "2un"])] ToUnit { /// The value to convert. value: Option, /// The unit to convert to (ether, gwei, wei). - #[clap(default_value = "wei")] + #[arg(default_value = "wei")] unit: String, }, /// Convert an ETH amount to wei. /// /// Consider using --to-unit. - #[clap(visible_aliases = &["--to-wei", "tw", "2w"])] + #[command(visible_aliases = &["--to-wei", "tw", "2w"])] ToWei { /// The value to convert. - #[clap(allow_hyphen_values = true)] + #[arg(allow_hyphen_values = true)] value: Option, /// The unit to convert from (ether, gwei, wei). - #[clap(default_value = "eth")] + #[arg(default_value = "eth")] unit: String, }, /// Convert wei into an ETH amount. /// /// Consider using --to-unit. - #[clap(visible_aliases = &["--from-wei", "fw"])] + #[command(visible_aliases = &["--from-wei", "fw"])] FromWei { /// The value to convert. - #[clap(allow_hyphen_values = true)] + #[arg(allow_hyphen_values = true)] value: Option, /// The unit to convert from (ether, gwei, wei). - #[clap(default_value = "eth")] + #[arg(default_value = "eth")] unit: String, }, - /// RLP encodes hex data, or an array of hex data - #[clap(visible_aliases = &["--to-rlp"])] + /// RLP encodes hex data, or an array of hex data. + #[command(visible_aliases = &["--to-rlp"])] ToRlp { /// The value to convert. value: Option, @@ -259,22 +268,22 @@ pub enum CastSubcommand { /// Decodes RLP encoded data. /// /// Input must be hexadecimal. - #[clap(visible_aliases = &["--from-rlp"])] + #[command(visible_aliases = &["--from-rlp"])] FromRlp { /// The value to convert. value: Option, }, /// Converts a number of one base to another - #[clap(visible_aliases = &["--to-hex", "th", "2h"])] + #[command(visible_aliases = &["--to-hex", "th", "2h"])] ToHex(ToBaseArgs), /// Converts a number of one base to decimal - #[clap(visible_aliases = &["--to-dec", "td", "2d"])] + #[command(visible_aliases = &["--to-dec", "td", "2d"])] ToDec(ToBaseArgs), /// Converts a number of one base to another - #[clap( + #[command( visible_aliases = &["--to-base", "--to-radix", "to-radix", @@ -282,21 +291,21 @@ pub enum CastSubcommand { "2r"] )] ToBase { - #[clap(flatten)] + #[command(flatten)] base: ToBaseArgs, /// The output base. - #[clap(value_name = "BASE")] + #[arg(value_name = "BASE")] base_out: Option, }, /// Create an access list for a transaction. - #[clap(visible_aliases = &["ac", "acl"])] + #[command(visible_aliases = &["ac", "acl"])] AccessList(AccessListArgs), /// Get logs by signature or topic. - #[clap(visible_alias = "l")] + #[command(visible_alias = "l")] Logs(LogsArgs), /// Get information about a block. - #[clap(visible_alias = "bl")] + #[command(visible_alias = "bl")] Block { /// The block height to query at. /// @@ -304,89 +313,95 @@ pub enum CastSubcommand { block: Option, /// If specified, only get the given field of the block. - #[clap(long, short)] + #[arg(long, short)] field: Option, - #[clap(long, env = "CAST_FULL_BLOCK")] + #[arg(long, env = "CAST_FULL_BLOCK")] full: bool, /// Print the block as JSON. - #[clap(long, short, help_heading = "Display options")] + #[arg(long, short, help_heading = "Display options")] json: bool, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Get the latest block number. - #[clap(visible_alias = "bn")] + #[command(visible_alias = "bn")] BlockNumber { - #[clap(flatten)] + /// 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. - #[clap(visible_alias = "c")] + #[command(visible_alias = "c")] Call(CallArgs), /// ABI-encode a function with arguments. - #[clap(name = "calldata", visible_alias = "cd")] + #[command(name = "calldata", visible_alias = "cd")] CalldataEncode { /// The function signature in the format `()()` sig: String, /// The arguments to encode. - #[clap(allow_hyphen_values = true)] + #[arg(allow_hyphen_values = true)] args: Vec, }, /// Get the symbolic name of the current chain. Chain { - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Get the Ethereum chain ID. - #[clap(visible_aliases = &["ci", "cid"])] + #[command(visible_aliases = &["ci", "cid"])] ChainId { - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Get the current client version. - #[clap(visible_alias = "cl")] + #[command(visible_alias = "cl")] Client { - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Compute the contract address from a given nonce and deployer address. - #[clap(visible_alias = "ca")] + #[command(visible_alias = "ca")] ComputeAddress { /// The deployer address. address: Option, /// The nonce of the deployer address. - #[clap(long)] + #[arg(long)] nonce: Option, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Disassembles hex encoded bytecode into individual / human readable opcodes - #[clap(visible_alias = "da")] + #[command(visible_alias = "da")] Disassemble { /// The hex encoded bytecode. bytecode: String, }, + /// Build and sign a transaction. + #[command(name = "mktx", visible_alias = "m")] + MakeTx(MakeTxArgs), + /// Calculate the ENS namehash of a name. - #[clap(visible_aliases = &["na", "nh"])] + #[command(visible_aliases = &["na", "nh"])] Namehash { name: Option }, /// Get information about a transaction. - #[clap(visible_alias = "t")] + #[command(visible_alias = "t")] Tx { /// The transaction hash. tx_hash: String, @@ -396,19 +411,19 @@ pub enum CastSubcommand { field: Option, /// Print the raw RLP encoded transaction. - #[clap(long, conflicts_with = "field")] + #[arg(long, conflicts_with = "field")] raw: bool, /// Print as JSON. - #[clap(long, short, help_heading = "Display options")] + #[arg(long, short, help_heading = "Display options")] json: bool, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Get the transaction receipt for a transaction. - #[clap(visible_alias = "re")] + #[command(visible_alias = "re")] Receipt { /// The transaction hash. tx_hash: String, @@ -417,48 +432,48 @@ pub enum CastSubcommand { field: Option, /// The number of confirmations until the receipt is fetched - #[clap(long, default_value = "1")] - confirmations: usize, + #[arg(long, default_value = "1")] + confirmations: u64, /// Exit immediately if the transaction was not found. - #[clap(long = "async", env = "CAST_ASYNC", name = "async", alias = "cast-async")] + #[arg(id = "async", long = "async", env = "CAST_ASYNC", alias = "cast-async")] cast_async: bool, /// Print as JSON. - #[clap(long, short, help_heading = "Display options")] + #[arg(long, short, help_heading = "Display options")] json: bool, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Sign and publish a transaction. - #[clap(name = "send", visible_alias = "s")] + #[command(name = "send", visible_alias = "s")] SendTx(SendTxArgs), /// Publish a raw transaction to the network. - #[clap(name = "publish", visible_alias = "p")] + #[command(name = "publish", visible_alias = "p")] PublishTx { /// The raw transaction raw_tx: String, /// Only print the transaction hash and exit immediately. - #[clap(long = "async", env = "CAST_ASYNC", name = "async", alias = "cast-async")] + #[arg(id = "async", long = "async", env = "CAST_ASYNC", alias = "cast-async")] cast_async: bool, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Estimate the gas cost of a transaction. - #[clap(visible_alias = "e")] + #[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 - #[clap(visible_aliases = &["--calldata-decode","cdd"])] + #[command(visible_aliases = &["--calldata-decode","cdd"])] CalldataDecode { /// The function signature in the format `()()`. sig: String, @@ -472,7 +487,7 @@ pub enum CastSubcommand { /// Defaults to decoding output data. To decode input data pass --input. /// /// When passing `--input`, function selector must NOT be prefixed in `calldata` string - #[clap(name = "abi-decode", visible_aliases = &["ad", "--abi-decode"])] + #[command(name = "abi-decode", visible_aliases = &["ad", "--abi-decode"])] AbiDecode { /// The function signature in the format `()()`. sig: String, @@ -481,27 +496,27 @@ pub enum CastSubcommand { calldata: String, /// Whether to decode the input or output data. - #[clap(long, short, help_heading = "Decode input data instead of 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. - #[clap(visible_alias = "ae")] + #[command(visible_alias = "ae")] AbiEncode { /// The function signature. sig: String, /// Whether to use packed encoding. - #[clap(long)] + #[arg(long)] packed: bool, /// The arguments of the function. - #[clap(allow_hyphen_values = true)] + #[arg(allow_hyphen_values = true)] args: Vec, }, /// Compute the storage slot for an entry in a mapping. - #[clap(visible_alias = "in")] + #[command(visible_alias = "in")] Index { /// The mapping key type. key_type: String, @@ -513,59 +528,69 @@ pub enum CastSubcommand { 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 account - #[clap(visible_alias = "impl")] + #[command(visible_alias = "impl")] Implementation { /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short = 'B')] + #[arg(long, short = 'B')] block: Option, /// The address to get the nonce for. - #[clap(value_parser = NameOrAddress::from_str)] + #[arg(value_parser = NameOrAddress::from_str)] who: NameOrAddress, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Fetch the EIP-1967 admin account - #[clap(visible_alias = "adm")] + #[command(visible_alias = "adm")] Admin { /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short = 'B')] + #[arg(long, short = 'B')] block: Option, /// The address to get the nonce for. - #[clap(value_parser = NameOrAddress::from_str)] + #[arg(value_parser = NameOrAddress::from_str)] who: NameOrAddress, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Get the function signatures for the given selector from https://openchain.xyz. - #[clap(name = "4byte", visible_aliases = &["4", "4b"])] + #[command(name = "4byte", visible_aliases = &["4", "4b"])] FourByte { /// The function selector. selector: Option, }, /// Decode ABI-encoded calldata using https://openchain.xyz. - #[clap(name = "4byte-decode", visible_aliases = &["4d", "4bd"])] + #[command(name = "4byte-decode", visible_aliases = &["4d", "4bd"])] FourByteDecode { /// The ABI-encoded calldata. calldata: Option, }, /// Get the event signature for a given topic 0 from https://openchain.xyz. - #[clap(name = "4byte-event", visible_aliases = &["4e", "4be", "topic0-event", "t0e"])] + #[command(name = "4byte-event", visible_aliases = &["4e", "4be", "topic0-event", "t0e"])] FourByteEvent { /// Topic 0 - #[clap(value_name = "TOPIC_0")] + #[arg(value_name = "TOPIC_0")] topic: Option, }, @@ -576,7 +601,7 @@ pub enum CastSubcommand { /// - "function transfer(address,uint256)" /// - "function transfer(address,uint256)" "event Transfer(address,address,uint256)" /// - "./out/Contract.sol/Contract.json" - #[clap(visible_aliases = &["ups"])] + #[command(visible_aliases = &["ups"])] UploadSignature { /// The signatures to upload. /// @@ -588,228 +613,232 @@ pub enum CastSubcommand { /// Pretty print calldata. /// /// Tries to decode the calldata using https://openchain.xyz unless --offline is passed. - #[clap(visible_alias = "pc")] + #[command(visible_alias = "pc")] PrettyCalldata { /// The calldata. calldata: Option, /// Skip the https://openchain.xyz lookup. - #[clap(long, short)] + #[arg(long, short)] offline: bool, }, /// Get the timestamp of a block. - #[clap(visible_alias = "a")] + #[command(visible_alias = "a")] Age { /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. block: Option, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Get the balance of an account in wei. - #[clap(visible_alias = "b")] + #[command(visible_alias = "b")] Balance { /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short = 'B')] + #[arg(long, short = 'B')] block: Option, /// The account to query. - #[clap(value_parser = NameOrAddress::from_str)] + #[arg(value_parser = NameOrAddress::from_str)] who: NameOrAddress, /// Format the balance in ether. - #[clap(long, short)] + #[arg(long, short)] ether: bool, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, /// erc20 address to query, with the method `balanceOf(address) return (uint256)`, alias /// with '--erc721' - #[clap(long, alias = "erc721")] + #[arg(long, alias = "erc721")] erc20: Option
, }, /// Get the basefee of a block. - #[clap(visible_aliases = &["ba", "fee", "basefee"])] + #[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, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Get the runtime bytecode of a contract. - #[clap(visible_alias = "co")] + #[command(visible_alias = "co")] Code { /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short = 'B')] + #[arg(long, short = 'B')] block: Option, /// The contract address. - #[clap(value_parser = NameOrAddress::from_str)] + #[arg(value_parser = NameOrAddress::from_str)] who: NameOrAddress, /// Disassemble bytecodes into individual opcodes. - #[clap(long, short)] + #[arg(long, short)] disassemble: bool, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Get the runtime bytecode size of a contract. - #[clap(visible_alias = "cs")] + #[command(visible_alias = "cs")] Codesize { /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short = 'B')] + #[arg(long, short = 'B')] block: Option, /// The contract address. - #[clap(value_parser = NameOrAddress::from_str)] + #[arg(value_parser = NameOrAddress::from_str)] who: NameOrAddress, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Get the current gas price. - #[clap(visible_alias = "g")] + #[command(visible_alias = "g")] GasPrice { - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Generate event signatures from event string. - #[clap(visible_alias = "se")] + #[command(visible_alias = "se")] SigEvent { /// The event string. event_string: Option, }, /// Hash arbitrary data using Keccak-256. - #[clap(visible_alias = "k")] + #[command(visible_aliases = &["k", "keccak256"])] Keccak { /// The data to hash. data: Option, }, /// Perform an ENS lookup. - #[clap(visible_alias = "rn")] + #[command(visible_alias = "rn")] ResolveName { /// The name to lookup. who: Option, /// Perform a reverse lookup to verify that the name is correct. - #[clap(long, short)] + #[arg(long, short)] verify: bool, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Perform an ENS reverse lookup. - #[clap(visible_alias = "la")] + #[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. - #[clap(long, short)] + #[arg(long, short)] verify: bool, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Get the raw value of a contract's storage slot. - #[clap(visible_alias = "st")] + #[command(visible_alias = "st")] Storage(StorageArgs), /// Generate a storage proof for a given storage slot. - #[clap(visible_alias = "pr")] + #[command(visible_alias = "pr")] Proof { /// The contract address. - #[clap(value_parser = NameOrAddress::from_str)] + #[arg(value_parser = NameOrAddress::from_str)] address: NameOrAddress, /// The storage slot numbers (hex or decimal). - #[clap(value_parser = parse_slot)] + #[arg(value_parser = parse_slot)] slots: Vec, /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short = 'B')] + #[arg(long, short = 'B')] block: Option, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Get the nonce for an account. - #[clap(visible_alias = "n")] + #[command(visible_alias = "n")] Nonce { /// The block height to query at. /// /// Can also be the tags earliest, finalized, safe, latest, or pending. - #[clap(long, short = 'B')] + #[arg(long, short = 'B')] block: Option, /// The address to get the nonce for. - #[clap(value_parser = NameOrAddress::from_str)] + #[arg(value_parser = NameOrAddress::from_str)] who: NameOrAddress, - #[clap(flatten)] + #[command(flatten)] rpc: RpcOpts, }, /// Get the source code of a contract from Etherscan. - #[clap(visible_aliases = &["et", "src"])] + #[command(visible_aliases = &["et", "src"])] EtherscanSource { /// The contract's address. address: String, - /// The output directory to expand source tree into. - #[clap(short, value_hint = ValueHint::DirPath)] + /// 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, - #[clap(flatten)] + #[command(flatten)] etherscan: EtherscanOpts, }, /// Wallet management utilities. - #[clap(visible_alias = "w")] + #[command(visible_alias = "w")] Wallet { - #[clap(subcommand)] + #[command(subcommand)] command: WalletSubcommands, }, /// Generate a Solidity interface from a given ABI. /// /// Currently does not support ABI encoder v2. - #[clap(visible_alias = "i")] + #[command(visible_alias = "i")] Interface(InterfaceArgs), /// Generate a rust binding from a given ABI. - #[clap(visible_alias = "bi")] + #[command(visible_alias = "bi")] Bind(BindArgs), /// Get the selector for a function. - #[clap(visible_alias = "si")] + #[command(visible_alias = "si")] Sig { /// The function signature, e.g. transfer(address,uint256). sig: Option, @@ -819,64 +848,64 @@ pub enum CastSubcommand { }, /// Generate a deterministic contract address using CREATE2. - #[clap(visible_alias = "c2")] + #[command(visible_alias = "c2")] Create2(Create2Args), /// Get the block number closest to the provided timestamp. - #[clap(visible_alias = "f")] + #[command(visible_alias = "f")] FindBlock(FindBlockArgs), /// Generate shell completions script. - #[clap(visible_alias = "com")] + #[command(visible_alias = "com")] Completions { - #[clap(value_enum)] + #[arg(value_enum)] shell: clap_complete::Shell, }, /// Generate Fig autocompletion spec. - #[clap(visible_alias = "fig")] + #[command(visible_alias = "fig")] GenerateFigSpec, /// Runs a published transaction in a local environment and prints the trace. - #[clap(visible_alias = "r")] + #[command(visible_alias = "r")] Run(RunArgs), /// Perform a raw JSON-RPC request. - #[clap(visible_alias = "rp")] + #[command(visible_alias = "rp")] Rpc(RpcArgs), /// Formats a string into bytes32 encoding. - #[clap(name = "format-bytes32-string", visible_aliases = &["--format-bytes32-string"])] + #[command(name = "format-bytes32-string", visible_aliases = &["--format-bytes32-string"])] FormatBytes32String { /// The string to format. string: Option, }, /// Parses a string from bytes32 encoding. - #[clap(name = "parse-bytes32-string", visible_aliases = &["--parse-bytes32-string"])] + #[command(name = "parse-bytes32-string", visible_aliases = &["--parse-bytes32-string"])] ParseBytes32String { /// The string to parse. bytes: Option, }, - #[clap(name = "parse-bytes32-address", visible_aliases = &["--parse-bytes32-address"])] - #[clap(about = "Parses a checksummed address from bytes32 encoding.")] + #[command(name = "parse-bytes32-address", visible_aliases = &["--parse-bytes32-address"])] + #[command(about = "Parses a checksummed address from bytes32 encoding.")] ParseBytes32Address { - #[clap(value_name = "BYTES")] + #[arg(value_name = "BYTES")] bytes: Option, }, /// Decodes a raw signed EIP 2718 typed transaction - #[clap(visible_alias = "dt")] + #[command(visible_alias = "dt")] DecodeTransaction { tx: Option }, /// Extracts function selectors and arguments from bytecode - #[clap(visible_alias = "sel")] + #[command(visible_alias = "sel")] Selectors { /// The hex encoded bytecode. bytecode: String, /// Resolve the function signatures for the extracted selectors using https://openchain.xyz - #[clap(long, short)] + #[arg(long, short)] resolve: bool, }, } @@ -885,11 +914,11 @@ pub enum CastSubcommand { #[derive(Debug, Parser)] pub struct ToBaseArgs { /// The value to convert. - #[clap(allow_hyphen_values = true)] + #[arg(allow_hyphen_values = true)] pub value: Option, /// The input base. - #[clap(long, short = 'i')] + #[arg(long, short = 'i')] pub base_in: Option, } @@ -901,9 +930,9 @@ pub fn parse_slot(s: &str) -> Result { #[cfg(test)] mod tests { use super::*; + use alloy_rpc_types::{BlockNumberOrTag, RpcBlockHash}; use cast::SimpleCast; use clap::CommandFactory; - use ethers_core::types::BlockNumber; #[test] fn verify_cli() { @@ -992,30 +1021,34 @@ mod tests { let test_cases = [ TestCase { input: "0".to_string(), - expect: BlockId::Number(BlockNumber::Number(0u64.into())), + expect: BlockId::Number(BlockNumberOrTag::Number(0u64)), }, TestCase { input: "0x56462c47c03df160f66819f0a79ea07def1569f8aac0fe91bb3a081159b61b4a" .to_string(), - expect: BlockId::Hash( + expect: BlockId::Hash(RpcBlockHash::from_hash( "0x56462c47c03df160f66819f0a79ea07def1569f8aac0fe91bb3a081159b61b4a" .parse() .unwrap(), - ), + None, + )), + }, + TestCase { + input: "latest".to_string(), + expect: BlockId::Number(BlockNumberOrTag::Latest), }, - TestCase { input: "latest".to_string(), expect: BlockId::Number(BlockNumber::Latest) }, TestCase { input: "earliest".to_string(), - expect: BlockId::Number(BlockNumber::Earliest), + expect: BlockId::Number(BlockNumberOrTag::Earliest), }, TestCase { input: "pending".to_string(), - expect: BlockId::Number(BlockNumber::Pending), + expect: BlockId::Number(BlockNumberOrTag::Pending), }, - TestCase { input: "safe".to_string(), expect: BlockId::Number(BlockNumber::Safe) }, + TestCase { input: "safe".to_string(), expect: BlockId::Number(BlockNumberOrTag::Safe) }, TestCase { input: "finalized".to_string(), - expect: BlockId::Number(BlockNumber::Finalized), + expect: BlockId::Number(BlockNumberOrTag::Finalized), }, ]; diff --git a/crates/cast/bin/tx.rs b/crates/cast/bin/tx.rs new file mode 100644 index 000000000..c0f1e1a97 --- /dev/null +++ b/crates/cast/bin/tx.rs @@ -0,0 +1,296 @@ +use alloy_consensus::{SidecarBuilder, SimpleCoder}; +use alloy_json_abi::Function; +use alloy_network::{AnyNetwork, TransactionBuilder}; +use alloy_primitives::{Address, TxKind}; +use alloy_provider::Provider; +use alloy_rpc_types::TransactionRequest; +use alloy_serde::WithOtherFields; +use alloy_transport::Transport; +use eyre::Result; +use foundry_cli::{ + opts::TransactionOpts, + utils::{self, parse_function_args}, +}; +use foundry_common::ens::NameOrAddress; +use foundry_config::{Chain, Config}; + +/// 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 { + if 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(()) +} + +/// Ensures the transaction is either a contract deployment or a recipient address is specified +pub async fn resolve_tx_kind, T: Transport + Clone>( + provider: &P, + code: &Option, + to: &Option, +) -> Result { + if code.is_some() { + Ok(TxKind::Create) + } else if let Some(to) = to { + Ok(TxKind::Call(to.resolve(provider).await?)) + } else { + eyre::bail!("Must specify a recipient address or contract code to deploy"); + } +} + +/// Initial state. +#[derive(Debug)] +pub struct InitState; + +/// State with known [TxKind]. +#[derive(Debug)] +pub struct TxKindState { + kind: TxKind, +} + +/// State with known input for the transaction. +#[derive(Debug)] +pub struct InputState { + kind: TxKind, + input: Vec, + func: Option, +} + +/// Builder type constructing [TransactionRequest] from cast send/mktx inputs. +/// +/// It is implemented as a stateful builder with expected state transition of [InitState] -> +/// [TxKindState] -> [InputState]. +#[derive(Debug)] +pub struct CastTxBuilder { + provider: P, + tx: WithOtherFields, + legacy: bool, + blob: bool, + chain: Chain, + etherscan_api_key: Option, + state: S, + _t: std::marker::PhantomData, +} + +impl CastTxBuilder +where + P: Provider, + T: Transport + Clone, +{ + /// 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 = WithOtherFields::::default(); + + let chain = utils::get_chain(config.chain, &provider).await?; + let etherscan_api_key = config.get_etherscan_api_key(Some(chain)); + + if let Some(gas_limit) = tx_opts.gas_limit { + tx.set_gas_limit(gas_limit.to()); + } + + if let Some(value) = tx_opts.value { + tx.set_value(value); + } + + if let Some(gas_price) = tx_opts.gas_price { + if tx_opts.legacy { + tx.set_gas_price(gas_price.to()); + } else { + tx.set_max_fee_per_gas(gas_price.to()); + } + } + + if !tx_opts.legacy { + if let Some(priority_fee) = tx_opts.priority_gas_price { + tx.set_max_priority_fee_per_gas(priority_fee.to()); + } + } + + if let Some(max_blob_fee) = tx_opts.blob_gas_price { + tx.set_max_fee_per_blob_gas(max_blob_fee.to()) + } + + if let Some(nonce) = tx_opts.nonce { + tx.set_nonce(nonce.to()); + } + + Ok(Self { + provider, + tx, + legacy: tx_opts.legacy || chain.is_legacy(), + blob: tx_opts.blob, + chain, + etherscan_api_key, + state: InitState, + _t: std::marker::PhantomData, + }) + } + + /// Sets [TxKind] for this builder and changes state to [TxKindState]. + pub fn with_tx_kind(self, kind: TxKind) -> CastTxBuilder { + CastTxBuilder { + provider: self.provider, + tx: self.tx, + legacy: self.legacy, + blob: self.blob, + chain: self.chain, + etherscan_api_key: self.etherscan_api_key, + state: TxKindState { kind }, + _t: self._t, + } + } +} + +impl CastTxBuilder +where + P: Provider, + T: Transport + Clone, +{ + /// 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.kind.to().cloned(), + 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 + }; + + Ok(CastTxBuilder { + provider: self.provider, + tx: self.tx, + legacy: self.legacy, + blob: self.blob, + chain: self.chain, + etherscan_api_key: self.etherscan_api_key, + state: InputState { kind: self.state.kind, input, func }, + _t: self._t, + }) + } +} + +impl CastTxBuilder +where + P: Provider, + T: Transport + Clone, +{ + /// Builds [TransactionRequest] and fiils missing fields. Returns a transaction which is ready + /// to be broadcasted. + pub async fn build( + self, + from: impl Into, + ) -> Result<(WithOtherFields, Option)> { + self._build(from, true).await + } + + /// Builds [TransactionRequest] without filling missing fields. Used for read-only calls such as + /// eth_call, eth_estimateGas, etc + pub async fn build_raw( + self, + from: impl Into, + ) -> Result<(WithOtherFields, Option)> { + self._build(from, false).await + } + + async fn _build( + mut self, + from: impl Into, + fill: bool, + ) -> Result<(WithOtherFields, Option)> { + let from = from.into().resolve(&self.provider).await?; + + self.tx.set_kind(self.state.kind); + self.tx.set_input(self.state.input); + self.tx.set_from(from); + self.tx.set_chain_id(self.chain.id()); + + if !fill { + return Ok((self.tx, self.state.func)); + } + + if self.legacy && self.tx.gas_price.is_none() { + self.tx.gas_price = Some(self.provider.get_gas_price().await?); + } + + if self.blob && self.tx.max_fee_per_blob_gas.is_none() { + self.tx.max_fee_per_blob_gas = Some(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(None).await?; + + if !self.legacy { + if self.tx.max_fee_per_gas.is_none() { + self.tx.max_fee_per_gas = Some(estimate.max_fee_per_gas); + } + + if self.tx.max_priority_fee_per_gas.is_none() { + self.tx.max_priority_fee_per_gas = Some(estimate.max_priority_fee_per_gas); + } + } + } + + if self.tx.gas.is_none() { + self.tx.gas = Some(self.provider.estimate_gas(&self.tx).await?); + } + + if self.tx.nonce.is_none() { + self.tx.nonce = Some(self.provider.get_transaction_count(from).await?); + } + + Ok((self.tx, self.state.func)) + } +} + +impl CastTxBuilder +where + P: Provider, + T: Transport + Clone, +{ + 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); + let sidecar = coder.build()?; + + self.tx.set_blob_sidecar(sidecar); + self.tx.populate_blob_hashes(); + + Ok(self) + } +} diff --git a/crates/cast/src/base.rs b/crates/cast/src/base.rs index 44f58091e..63ae59c1c 100644 --- a/crates/cast/src/base.rs +++ b/crates/cast/src/base.rs @@ -90,7 +90,7 @@ impl TryFrom for Base { impl From for u32 { fn from(b: Base) -> Self { - b as u32 + b as Self } } @@ -293,7 +293,7 @@ impl From for NumberWithBase { impl From for I256 { fn from(n: NumberWithBase) -> Self { - I256::from_raw(n.number) + Self::from_raw(n.number) } } diff --git a/crates/cast/src/errors.rs b/crates/cast/src/errors.rs index 40a8e9755..3e6c5977b 100644 --- a/crates/cast/src/errors.rs +++ b/crates/cast/src/errors.rs @@ -15,18 +15,18 @@ pub enum FunctionSignatureError { impl fmt::Display for FunctionSignatureError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - FunctionSignatureError::MissingSignature => { + Self::MissingSignature => { writeln!(f, "Function signature must be set") } - FunctionSignatureError::MissingEtherscan { sig } => { + 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}()") } - FunctionSignatureError::UnknownChain(chain) => { + Self::UnknownChain(chain) => { write!(f, "Resolving via etherscan requires a known chain. Unknown chain: {chain}") } - FunctionSignatureError::MissingToAddress => f.write_str("Target address must be set"), + Self::MissingToAddress => f.write_str("Target address must be set"), } } } diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index ca6eb83be..4934d8dd3 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -1,117 +1,133 @@ +#![doc = include_str!("../README.md")] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +use alloy_consensus::TxEnvelope; use alloy_dyn_abi::{DynSolType, DynSolValue, FunctionExt}; -use alloy_json_abi::ContractObject; +use alloy_json_abi::Function; +use alloy_network::AnyNetwork; use alloy_primitives::{ utils::{keccak256, ParseUnits, Unit}, - Address, Bytes, B256, I256, U256, + Address, Keccak256, TxHash, TxKind, B256, I256, U256, +}; +use alloy_provider::{ + network::eip2718::{Decodable2718, Encodable2718}, + PendingTransactionBuilder, Provider, }; use alloy_rlp::Decodable; +use alloy_rpc_types::{BlockId, BlockNumberOrTag, Filter, TransactionRequest}; +use alloy_serde::WithOtherFields; +use alloy_sol_types::sol; +use alloy_transport::Transport; use base::{Base, NumberWithBase, ToBase}; -use chrono::NaiveDateTime; -use ethers_core::{ - types::{ - transaction::eip2718::TypedTransaction, BlockId, BlockNumber, Filter, NameOrAddress, - Signature, H160, H256, U64, - }, - utils::rlp, -}; -use ethers_providers::{Middleware, PendingTransaction, PubsubClient}; +use chrono::DateTime; use evm_disassembler::{disassemble_bytes, disassemble_str, format_operations}; use eyre::{Context, ContextCompat, Result}; use foundry_block_explorers::Client; use foundry_common::{ abi::{encode_function_args, get_func}, + compile::etherscan_project, fmt::*, - types::{ToAlloy, ToEthers}, - TransactionReceiptWithRevertReason, + fs, TransactionReceiptWithRevertReason, }; +use foundry_compilers::flatten::Flattener; use foundry_config::Chain; use futures::{future::Either, FutureExt, StreamExt}; use rayon::prelude::*; use std::{ + borrow::Cow, io, + marker::PhantomData, path::PathBuf, str::FromStr, sync::atomic::{AtomicBool, Ordering}, }; use tokio::signal::ctrl_c; -use tx::{TxBuilderOutput, TxBuilderPeekOutput}; use foundry_common::abi::encode_function_args_packed; pub use foundry_evm::*; -pub use rusoto_core::{ - credential::ChainProvider as AwsChainProvider, region::Region as AwsRegion, - request::HttpClient as AwsHttpClient, Client as AwsClient, -}; -pub use rusoto_kms::KmsClient; -pub use tx::TxBuilder; pub mod base; pub mod errors; mod rlp_converter; -mod tx; use rlp_converter::Item; // TODO: CastContract with common contract initializers? Same for CastProviders? -pub struct Cast { - provider: M, +sol! { + #[sol(rpc)] + interface IERC20 { + #[derive(Debug)] + function balanceOf(address owner) external view returns (uint256); + } +} + +pub struct Cast { + provider: P, + transport: PhantomData, } -impl Cast +impl Cast where - M::Error: 'static, + T: Transport + Clone, + P: Provider, { /// Creates a new Cast instance from the provided client /// /// # Example /// /// ``` + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_providers::{Http, Provider}; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// # Ok(()) /// # } /// ``` - pub fn new(provider: M) -> Self { - Self { provider } + pub fn new(provider: P) -> Self { + Self { provider, transport: PhantomData } } /// Makes a read-only call to the specified address /// /// # Example /// - /// ```ignore - /// use cast::{Cast, TxBuilder}; - /// use ethers_core::types::Address; - /// use ethers_providers::{Http, Provider}; + /// ``` + /// use alloy_primitives::{Address, U256, Bytes}; + /// use alloy_rpc_types::{TransactionRequest}; + /// use alloy_serde::WithOtherFields; + /// use cast::Cast; + /// 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 = Provider::::try_from("http://localhost:8545")?; + /// let alloy_provider = ProviderBuilder::<_,_, AnyNetwork>::default().on_builtin("http://localhost:8545").await?;; /// let to = Address::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?; - /// let sig = "function greeting(uint256 i) public returns (string)"; - /// let args = vec!["5".to_owned()]; - /// let mut builder = - /// TxBuilder::new(&provider, Address::zero(), Some(to), Chain::Mainnet, false).await?; - /// builder.set_args(sig, args).await?; - /// let builder_output = builder.build(); - /// let cast = Cast::new(provider); - /// let data = cast.call(builder_output, None).await?; + /// 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(alloy_provider); + /// let data = cast.call(&tx, None, None).await?; /// println!("{}", data); /// # Ok(()) /// # } /// ``` pub async fn call<'a>( &self, - builder_output: TxBuilderOutput, + req: &WithOtherFields, + func: Option<&Function>, block: Option, ) -> Result { - let (tx, func) = builder_output; - let res = self.provider.call(&tx, block).await?; + let res = self.provider.call(req).block(block.unwrap_or_default()).await?; let mut decoded = vec![]; @@ -123,12 +139,21 @@ where // ensure the address is a contract if res.is_empty() { // check that the recipient is a contract that can be called - if let Some(NameOrAddress::Address(addr)) = tx.to() { - if let Ok(code) = self.provider.get_code(*addr, block).await { + if let Some(TxKind::Call(addr)) = req.to { + if let Ok(code) = self + .provider + .get_code_at(addr) + .block_id(block.unwrap_or_default()) + .await + { if code.is_empty() { eyre::bail!("contract {addr:?} does not have any code") } } + } else if Some(TxKind::Create) == req.to { + eyre::bail!("tx req is a contract deployment"); + } else { + eyre::bail!("recipient is None"); } } return Err(err).wrap_err( @@ -151,42 +176,47 @@ where /// /// # Example /// - /// ```ignore - /// use cast::{Cast, TxBuilder}; - /// use ethers_core::types::Address; - /// use ethers_providers::{Http, Provider}; + /// ``` + /// 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 = Provider::::try_from("http://localhost:8545")?; + /// let provider = ProviderBuilder::<_,_, AnyNetwork>::default().on_builtin("http://localhost:8545").await?;; /// let to = Address::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?; - /// let sig = "greeting(uint256)(string)"; - /// let args = vec!["5".to_owned()]; - /// let mut builder = - /// TxBuilder::new(&provider, Address::zero(), Some(to), Chain::Mainnet, false).await?; - /// builder.set_args(sig, args).await?; - /// let builder_output = builder.peek(); + /// 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(builder_output, None, false).await?; + /// let access_list = cast.access_list(&tx, None, false).await?; /// println!("{}", access_list); /// # Ok(()) /// # } /// ``` pub async fn access_list( &self, - builder_output: TxBuilderPeekOutput<'_>, + req: &WithOtherFields, block: Option, to_json: bool, ) -> Result { - let (tx, _) = builder_output; - let access_list = self.provider.create_access_list(tx, block).await?; + let access_list = + self.provider.create_access_list(req).block_id(block.unwrap_or_default()).await?; let res = if to_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_alloy().to_checksum(None))); + 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 { @@ -200,177 +230,134 @@ where Ok(res) } - pub async fn balance + Send + Sync>( - &self, - who: T, - block: Option, - ) -> Result { - Ok(self.provider.get_balance(who, block).await?.to_alloy()) + pub async fn balance(&self, who: Address, block: Option) -> Result { + Ok(self.provider.get_balance(who).block_id(block.unwrap_or_default()).await?) } /// Sends a transaction to the specified address /// /// # Example /// - /// ```ignore - /// use cast::{Cast, TxBuilder}; - /// use ethers_core::types::{Address, U256}; - /// use ethers_providers::{Http, Provider}; + /// ``` + /// use cast::{Cast}; + /// 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 = Provider::::try_from("http://localhost:8545")?; - /// let from = "vitalik.eth"; - /// let to = eAddress::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?; - /// let sig = "greet(string)()"; - /// let args = vec!["hello".to_owned()]; + /// let provider = ProviderBuilder::<_,_, AnyNetwork>::default().on_builtin("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 mut builder = TxBuilder::new(&provider, from, Some(to), Chain::Mainnet, false).await?; - /// builder.set_args(sig, args).await?.set_gas(gas).set_value(value).set_nonce(nonce); - /// let builder_output = builder.build(); + /// let tx = TransactionRequest::default().to(to).input(bytes.into()).from(from); + /// let tx = WithOtherFields::new(tx); /// let cast = Cast::new(provider); - /// let data = cast.send(builder_output).await?; - /// println!("{}", *data); + /// let data = cast.send(tx).await?; + /// println!("{:#?}", data); /// # Ok(()) /// # } /// ``` - pub async fn send<'a>( + pub async fn send( &self, - builder_output: TxBuilderOutput, - ) -> Result> { - let (tx, _) = builder_output; - let res = self.provider.send_transaction(tx, None).await?; + tx: WithOtherFields, + ) -> Result> { + let res = self.provider.send_transaction(tx).await?; - Ok::<_, eyre::Error>(res) + Ok(res) } /// Publishes a raw transaction to the network /// /// # Example /// - /// ```ignore + /// ``` + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_providers::{Http, Provider}; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("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, mut raw_tx: String) -> Result> { + pub async fn publish( + &self, + mut raw_tx: String, + ) -> Result> { raw_tx = match raw_tx.strip_prefix("0x") { Some(s) => s.to_string(), None => raw_tx, }; - let tx = Bytes::from(hex::decode(raw_tx)?); - let res = self.provider.send_raw_transaction(tx.0.into()).await?; + let tx = hex::decode(raw_tx)?; + let res = self.provider.send_raw_transaction(&tx).await?; - Ok::<_, eyre::Error>(res) + Ok(res) } - /// Estimates the gas cost of a transaction - /// /// # Example /// - /// ```ignore - /// use alloy_primitives::U256; - /// use cast::{Cast, TxBuilder}; - /// use ethers_core::types::Address; - /// use ethers_providers::{Http, Provider}; - /// use std::str::FromStr; - /// - /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; - /// let from = Address::from_str("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")?; - /// let to = Address::from_str("0xB3C95ff08316fb2F2e3E52Ee82F8e7b605Aa1304")?; - /// let sig = "greet(string)()"; - /// let args = vec!["5".to_owned()]; - /// let value = U256::from_str("1").unwrap(); - /// let mut builder = TxBuilder::new(&provider, from, Some(to), Chain::Mainnet, false).await?; - /// builder.set_value(value).set_args(sig, args).await?; - /// let builder_output = builder.peek(); - /// let cast = Cast::new(&provider); - /// let data = cast.estimate(builder_output).await?; - /// println!("{}", data); - /// # Ok(()) - /// # } /// ``` - pub async fn estimate(&self, builder_output: TxBuilderPeekOutput<'_>) -> Result { - let (tx, _) = builder_output; - - let res = self.provider.estimate_gas(tx, None).await?; - - Ok::<_, eyre::Error>(res.to_alloy()) - } - - /// # Example - /// - /// ```ignore + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_providers::{Http, Provider}; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let block = cast.block(5, true, None, false).await?; /// println!("{}", block); /// # Ok(()) /// # } /// ``` - pub async fn block>( + pub async fn block>( &self, - block: T, + block: B, full: bool, field: Option, to_json: bool, ) -> Result { let block = block.into(); - let block = if full { - let block = self - .provider - .get_block_with_txs(block) - .await? - .ok_or_else(|| eyre::eyre!("block {:?} not found", block))?; - if let Some(ref field) = field { - get_pretty_block_attr(&block, field) - .unwrap_or_else(|| format!("{field} is not a valid block field")) - } else if to_json { - serde_json::to_value(&block).unwrap().to_string() - } else { - block.pretty() + if let Some(ref field) = field { + if field == "transactions" && !full { + eyre::bail!("use --full to view transactions") } + } + + let block = self + .provider + .get_block(block, full.into()) + .await? + .ok_or_else(|| eyre::eyre!("block {:?} not found", block))?; + + let block = if let Some(ref field) = field { + get_pretty_block_attr(&block, field) + .unwrap_or_else(|| format!("{field} is not a valid block field")) + } else if to_json { + serde_json::to_value(&block).unwrap().to_string() } else { - let block = self - .provider - .get_block(block) - .await? - .ok_or_else(|| eyre::eyre!("block {:?} not found", block))?; - - if let Some(ref field) = field { - if field == "transactions" { - "use --full to view transactions".to_string() - } else { - get_pretty_block_attr(&block, field) - .unwrap_or_else(|| format!("{field} is not a valid block field")) - } - } else if to_json { - serde_json::to_value(&block).unwrap().to_string() - } else { - block.pretty() - } + block.pretty() }; Ok(block) } - async fn block_field_as_num>(&self, block: T, field: String) -> Result { + async fn block_field_as_num>(&self, block: B, field: String) -> Result { let block = block.into(); - let block_field = Cast::block( + let block_field = Self::block( self, block, false, @@ -388,24 +375,23 @@ where Ok(ret) } - pub async fn base_fee>(&self, block: T) -> Result { - Cast::block_field_as_num(self, block, String::from("baseFeePerGas")).await + 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: T) -> Result { + pub async fn age>(&self, block: B) -> Result { let timestamp_str = - Cast::block_field_as_num(self, block, String::from("timestamp")).await?.to_string(); - let datetime = - NaiveDateTime::from_timestamp_opt(timestamp_str.parse::().unwrap(), 0).unwrap(); + 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: T) -> Result { - Cast::block_field_as_num(self, block, "timestamp".to_string()).await + 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 = Cast::block( + let genesis_hash = Self::block( self, 0, false, @@ -417,7 +403,7 @@ where Ok(match &genesis_hash[..] { "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3" => { - match &(Cast::block(self, 1920000, false, Some("hash".to_string()), false).await?)[..] + match &(Self::block(self, 1920000, false, Some("hash".to_string()), false).await?)[..] { "0x94365e3a8c0b35089c1d1195081fe7489b528a84b22199c916180db8b28ade7f" => { "etclive" @@ -456,7 +442,7 @@ where "0x6d3c66c5357ec91d5c43af47e234a939b22557cbb552dc45bebbceeed90fbe34" => "bsctest", "0x0d21840abff46b96c84b2ac9e10e4f5cdaeb5693cb665db62a2f3b02d2d57b5b" => "bsc", "0x31ced5b9beb7f8782b014660da0cb18cc409f121f408186886e1ca3e8eeca96b" => { - match &(Cast::block(self, 1, false, Some(String::from("hash")), false).await?)[..] { + match &(Self::block(self, 1, false, Some(String::from("hash")), false).await?)[..] { "0x738639479dc82d199365626f90caa82f7eafcfe9ed354b456fb3d294597ceb53" => { "avalanche-fuji" } @@ -467,28 +453,29 @@ where }) } - pub async fn chain_id(&self) -> Result { - Ok(self.provider.get_chainid().await?.to_alloy()) + pub async fn chain_id(&self) -> Result { + Ok(self.provider.get_chain_id().await?) } - pub async fn block_number(&self) -> Result { + 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?.to_alloy()) + pub async fn gas_price(&self) -> Result { + Ok(self.provider.get_gas_price().await?) } /// # Example /// - /// ```ignore + /// ``` + /// use alloy_primitives::Address; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_core::types::Address; - /// use ethers_providers::{Http, Provider}; /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("0x7eD52863829AB99354F3a0503A622e82AcD5F7d3")?; /// let nonce = cast.nonce(addr, None).await?; @@ -496,24 +483,21 @@ where /// # Ok(()) /// # } /// ``` - pub async fn nonce + Send + Sync>( - &self, - who: T, - block: Option, - ) -> Result { - Ok(self.provider.get_transaction_count(who, block).await?.to_alloy().to()) + 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 /// - /// ```ignore + /// ``` + /// use alloy_primitives::Address; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_core::types::Address; - /// use ethers_providers::{Http, Provider}; /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("0x7eD52863829AB99354F3a0503A622e82AcD5F7d3")?; /// let implementation = cast.implementation(addr, None).await?; @@ -521,28 +505,29 @@ where /// # Ok(()) /// # } /// ``` - pub async fn implementation + Send + Sync>( - &self, - who: T, - block: Option, - ) -> Result { + pub async fn implementation(&self, who: Address, block: Option) -> Result { let slot = - H256::from_str("0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc")?; - let value = self.provider.get_storage_at(who, slot, block).await?; - let addr: H160 = value.into(); + 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 /// - /// ```ignore + /// ``` + /// use alloy_primitives::Address; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_core::types::Address; - /// use ethers_providers::{Http, Provider}; /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("0x7eD52863829AB99354F3a0503A622e82AcD5F7d3")?; /// let admin = cast.admin(addr, None).await?; @@ -550,28 +535,29 @@ where /// # Ok(()) /// # } /// ``` - pub async fn admin + Send + Sync>( - &self, - who: T, - block: Option, - ) -> Result { + pub async fn admin(&self, who: Address, block: Option) -> Result { let slot = - H256::from_str("0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103")?; - let value = self.provider.get_storage_at(who, slot, block).await?; - let addr: H160 = value.into(); + 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 /// - /// ```ignore + /// ``` /// use alloy_primitives::{Address, U256}; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_providers::{Http, Provider}; /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("7eD52863829AB99354F3a0503A622e82AcD5F7d3")?; /// let computed_address = cast.compute_address(addr, None).await?; @@ -580,21 +566,21 @@ where /// # } /// ``` pub async fn compute_address(&self, address: Address, nonce: Option) -> Result
{ - let unpacked = - if let Some(n) = nonce { n } else { self.nonce(address.to_ethers(), None).await? }; + let unpacked = if let Some(n) = nonce { n } else { self.nonce(address, None).await? }; Ok(address.create(unpacked)) } /// # Example /// - /// ```ignore + /// ``` + /// use alloy_primitives::Address; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_core::types::Address; - /// use ethers_providers::{Http, Provider}; /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("0x00000000219ab540356cbb839cbe05303d7705fa")?; /// let code = cast.code(addr, None, false).await?; @@ -602,30 +588,35 @@ where /// # Ok(()) /// # } /// ``` - pub async fn code + Send + Sync>( + pub async fn code( &self, - who: T, + who: Address, block: Option, disassemble: bool, ) -> Result { if disassemble { - let code = self.provider.get_code(who, block).await?.to_vec(); + let code = + self.provider.get_code_at(who).block_id(block.unwrap_or_default()).await?.to_vec(); Ok(format_operations(disassemble_bytes(code)?)?) } else { - Ok(format!("{}", self.provider.get_code(who, block).await?)) + Ok(format!( + "{}", + self.provider.get_code_at(who).block_id(block.unwrap_or_default()).await? + )) } } /// Example /// - /// ```ignore + /// ``` + /// use alloy_primitives::Address; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_core::types::Address; - /// use ethers_providers::{Http, Provider}; /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("0x00000000219ab540356cbb839cbe05303d7705fa")?; /// let codesize = cast.codesize(addr, None).await?; @@ -633,23 +624,21 @@ where /// # Ok(()) /// # } /// ``` - pub async fn codesize + Send + Sync>( - &self, - who: T, - block: Option, - ) -> Result { - let code = self.provider.get_code(who, block).await?.to_vec(); + 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(format!("{}", code.len())) } /// # Example /// - /// ```ignore + /// ``` + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_providers::{Http, Provider}; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let tx_hash = "0xf8d1713ea15a81482958fb7ddf884baee8d3bcc478c5f2f604e008dc788ee4fc"; /// let tx = cast.transaction(tx_hash.to_string(), None, false, false).await?; @@ -664,15 +653,15 @@ where raw: bool, to_json: bool, ) -> Result { - let tx_hash = H256::from_str(&tx_hash).wrap_err("invalid tx hash")?; + let tx_hash = TxHash::from_str(&tx_hash).wrap_err("invalid tx hash")?; let tx = self .provider - .get_transaction(tx_hash) + .get_transaction_by_hash(tx_hash) .await? .ok_or_else(|| eyre::eyre!("tx not found: {:?}", tx_hash))?; Ok(if raw { - format!("0x{}", hex::encode(tx.rlp())) + format!("0x{}", hex::encode(TxEnvelope::try_from(tx.inner)?.encoded_2718())) } else if let Some(field) = field { get_pretty_tx_attr(&tx, field.as_str()) .ok_or_else(|| eyre::eyre!("invalid tx field: {}", field.to_string()))? @@ -686,12 +675,13 @@ where /// # Example /// - /// ```ignore + /// ``` + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_providers::{Http, Provider}; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let tx_hash = "0xf8d1713ea15a81482958fb7ddf884baee8d3bcc478c5f2f604e008dc788ee4fc"; /// let receipt = cast.receipt(tx_hash.to_string(), None, 1, false, false).await?; @@ -703,11 +693,11 @@ where &self, tx_hash: String, field: Option, - confs: usize, + confs: u64, cast_async: bool, to_json: bool, ) -> Result { - let tx_hash = H256::from_str(&tx_hash).wrap_err("invalid tx hash")?; + let tx_hash = TxHash::from_str(&tx_hash).wrap_err("invalid tx hash")?; let mut receipt: TransactionReceiptWithRevertReason = match self.provider.get_transaction_receipt(tx_hash).await? { @@ -718,13 +708,10 @@ where if cast_async { eyre::bail!("tx not found: {:?}", tx_hash) } else { - let tx = PendingTransaction::new(tx_hash, self.provider.provider()); - tx.confirmations(confs).await?.ok_or_else(|| { - eyre::eyre!( - "tx not found, might have been dropped from mempool: {:?}", - tx_hash - ) - })? + PendingTransactionBuilder::new(self.provider.root(), tx_hash) + .with_required_confirmations(confs) + .get_receipt() + .await? } } } @@ -748,12 +735,13 @@ where /// /// # Example /// - /// ```ignore + /// ``` + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_providers::{Http, Provider}; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let result = cast /// .rpc("eth_getBalance", &["0xc94770007dda54cF92009BFF0dE90c06F603a09f", "latest"]) @@ -762,11 +750,14 @@ where /// # Ok(()) /// # } /// ``` - pub async fn rpc(&self, method: &str, params: T) -> Result + pub async fn rpc(&self, method: &str, params: V) -> Result where - T: std::fmt::Debug + serde::Serialize + Send + Sync, + V: alloy_json_rpc::RpcParam, { - let res = self.provider.provider().request::(method, params).await?; + let res = self + .provider + .raw_request::(Cow::Owned(method.to_string()), params) + .await?; Ok(serde_json::to_string(&res)?) } @@ -774,29 +765,38 @@ where /// /// # Example /// - /// ```ignore + /// ``` + /// use alloy_primitives::{Address, B256}; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; /// use cast::Cast; - /// use ethers_core::types::{Address, H256}; - /// use ethers_providers::{Http, Provider}; /// use std::str::FromStr; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// let addr = Address::from_str("0x00000000006c3852cbEf3e08E8dF289169EdE581")?; - /// let slot = H256::zero(); + /// let slot = B256::ZERO; /// let storage = cast.storage(addr, slot, None).await?; /// println!("{}", storage); /// # Ok(()) /// # } /// ``` - pub async fn storage + Send + Sync>( + pub async fn storage( &self, - from: T, - slot: H256, + from: Address, + slot: B256, block: Option, ) -> Result { - Ok(format!("{:?}", self.provider.get_storage_at(from, slot, block).await?)) + 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, to_json: bool) -> Result { @@ -826,23 +826,27 @@ where /// /// # Example /// - /// ```ignore + /// ``` + /// use alloy_primitives::fixed_bytes; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; + /// use alloy_rpc_types::{BlockId, BlockNumberOrTag}; /// use cast::Cast; - /// use ethers_core::types::{BlockId, BlockNumber}; - /// use ethers_providers::{Http, Provider}; - /// use std::convert::TryFrom; + /// use std::{convert::TryFrom, str::FromStr}; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::::try_from("http://localhost:8545")?; + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; /// let cast = Cast::new(provider); /// - /// let block_number = - /// cast.convert_block_number(Some(BlockId::Number(BlockNumber::from(5)))).await?; - /// assert_eq!(block_number, Some(BlockNumber::from(5))); + /// 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("0x1234".parse().unwrap()))).await?; - /// assert_eq!(block_number, Some(BlockNumber::from(1234))); + /// 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); @@ -852,13 +856,14 @@ where pub async fn convert_block_number( &self, block: Option, - ) -> Result, eyre::Error> { + ) -> 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(hash).await?; - Ok(block.map(|block| block.number.unwrap()).map(BlockNumber::from)) + let block = + self.provider.get_block_by_hash(hash.block_hash, false.into()).await?; + Ok(block.map(|block| block.header.number.unwrap()).map(BlockNumberOrTag::from)) } }, None => Ok(None), @@ -869,14 +874,17 @@ where /// /// # Example /// - /// ```ignore + /// ``` + /// use alloy_primitives::Address; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; + /// use alloy_rpc_types::Filter; + /// use alloy_transport::BoxTransport; /// use cast::Cast; - /// use ethers_core::{abi::Address, types::Filter}; - /// use ethers_providers::{Provider, Ws}; /// use std::{io, str::FromStr}; /// /// # async fn foo() -> eyre::Result<()> { - /// let provider = Provider::new(Ws::connect("wss://localhost:8545").await?); + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("wss://localhost:8545").await?; /// let cast = Cast::new(provider); /// /// let filter = @@ -891,16 +899,13 @@ where filter: Filter, output: &mut dyn io::Write, to_json: bool, - ) -> Result<()> - where - ::Provider: PubsubClient, - { + ) -> Result<()> { // Initialize the subscription stream for logs - let mut subscription = self.provider.subscribe_logs(&filter).await?; + 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?) + Some(self.provider.subscribe_blocks().await?.into_stream()) } else { None }; @@ -923,7 +928,7 @@ where Either::Right(futures::future::pending()) } => { if let (Some(block), Some(to_block)) = (block, to_block_number) { - if block.number.map_or(false, |bn| bn > to_block) { + if block.header.number.map_or(false, |bn| bn > to_block) { break; } } @@ -936,12 +941,12 @@ where } first = false; let log_str = serde_json::to_string(&log).unwrap(); - write!(output, "{}", log_str)?; + 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)?; + writeln!(output, "{log_str}")?; } }, // Break on cancel signal, to allow for closing JSON bracket @@ -959,19 +964,20 @@ where Ok(()) } -} - -pub struct InterfaceSource { - pub name: String, - pub json_abi: String, - pub source: String, -} -// Local is a path to the directory containing the ABI files -// In case of etherscan, ABI is fetched from the address on the chain -pub enum AbiPath { - Local { path: String, name: Option }, - Etherscan { address: Address, chain: Chain, api_key: String }, + pub async fn erc20_balance( + &self, + token: Address, + owner: Address, + block: Option, + ) -> Result { + Ok(IERC20::new(token, &self.provider) + .balanceOf(owner) + .block(block.unwrap_or_default()) + .call() + .await? + ._0) + } } pub struct SimpleCast; @@ -982,8 +988,8 @@ impl SimpleCast { /// # Example /// /// ``` + /// use alloy_primitives::{I256, U256}; /// use cast::SimpleCast; - /// use ethers_core::types::{I256, U256}; /// /// assert_eq!(SimpleCast::max_int("uint256")?, U256::MAX.to_string()); /// assert_eq!(SimpleCast::max_int("int256")?, I256::MAX.to_string()); @@ -999,8 +1005,8 @@ impl SimpleCast { /// # Example /// /// ``` + /// use alloy_primitives::{I256, U256}; /// use cast::SimpleCast; - /// use ethers_core::types::{I256, U256}; /// /// assert_eq!(SimpleCast::min_int("uint256")?, "0"); /// assert_eq!(SimpleCast::min_int("int256")?, I256::MIN.to_string()); @@ -1055,6 +1061,24 @@ impl SimpleCast { 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 @@ -1077,8 +1101,8 @@ impl SimpleCast { /// Converts fixed point number into specified number of decimals /// ``` + /// use alloy_primitives::U256; /// use cast::SimpleCast as Cast; - /// use ethers_core::types::U256; /// /// assert_eq!(Cast::from_fixed_point("10", "0")?, "10"); /// assert_eq!(Cast::from_fixed_point("1.0", "1")?, "10"); @@ -1102,8 +1126,8 @@ impl SimpleCast { /// # Example /// /// ``` + /// use alloy_primitives::U256; /// use cast::SimpleCast as Cast; - /// use ethers_core::types::U256; /// /// assert_eq!(Cast::to_fixed_point("10", "0")?, "10."); /// assert_eq!(Cast::to_fixed_point("10", "1")?, "1.0"); @@ -1606,66 +1630,20 @@ impl SimpleCast { Ok(hex::encode_prefixed(calldata)) } - /// Generates an interface in solidity from either a local file ABI or a verified contract on - /// Etherscan. It returns a vector of [`InterfaceSource`] structs that contain the source of the - /// interface and their name. - /// ```ignore - /// use cast::{AbiPath, SimpleCast as Cast}; - /// # async fn foo() -> eyre::Result<()> { - /// let path = - /// AbiPath::Local { path: "utils/testdata/interfaceTestABI.json".to_owned(), name: None }; - /// let interfaces = Cast::generate_interface(path).await?; - /// println!("interface {} {{\n {}\n}}", interfaces[0].name, interfaces[0].source); - /// # Ok(()) - /// # } - /// ``` - pub async fn generate_interface(address_or_path: AbiPath) -> Result> { - let (contract_abis, contract_names) = match address_or_path { - AbiPath::Local { path, name } => { - 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}"))?; - (vec![abi], vec![name.unwrap_or_else(|| "Interface".to_owned())]) - } - AbiPath::Etherscan { address, chain, api_key } => { - let client = Client::new(chain, api_key)?; - let source = client.contract_source_code(address).await?; - let names = source - .items - .iter() - .map(|item| item.contract_name.clone()) - .collect::>(); - - let abis = source.abis()?; - - (abis, names) - } - }; - contract_abis - .iter() - .zip(contract_names) - .map(|(contract_abi, name)| { - let source = foundry_cli::utils::abi_to_solidity(contract_abi, &name)?; - Ok(InterfaceSource { - name, - json_abi: serde_json::to_string_pretty(contract_abi)?, - source, - }) - }) - .collect::>>() - } - - /// Prints the slot number for the specified mapping type and input data - /// Uses abi_encode to pad the data to 32 bytes. - /// For value types v, slot number of v is keccak256(concat(h(v) , p)) where h is the padding - /// function and p is slot number of the mapping. + /// Prints the slot number for the specified mapping type and input data. + /// + /// For value types `v`, slot number of `v` is `keccak256(concat(h(v), p))` where `h` is the + /// padding function for `v`'s type, and `p` is slot number of the mapping. + /// + /// 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" @@ -1674,60 +1652,48 @@ impl SimpleCast { /// Cast::index("uint256", "42", "6").unwrap().as_str(), /// "0xfc808b0f31a1e6b9cf25ff6289feae9b51017b392cc8e25620a94a38dcdafcc1" /// ); - /// # Ok::<_, eyre::Report>(()) - /// ``` - pub fn index(from_type: &str, from_value: &str, slot_number: &str) -> Result { - let sig = format!("x({from_type},uint256)"); - let encoded = Self::abi_encode(&sig, &[from_value, slot_number])?; - let location: String = Self::keccak(&encoded)?; - Ok(location) - } - - /// Converts ENS names to their namehash representation - /// [Namehash reference](https://docs.ens.domains/contract-api-reference/name-processing#hashing-names) - /// [namehash-rust reference](https://github.com/InstateDev/namehash-rust/blob/master/src/lib.rs) - /// - /// # Example - /// - /// ``` - /// use cast::SimpleCast as Cast; /// + /// // Strings and byte arrays. /// assert_eq!( - /// Cast::namehash("")?, - /// "0x0000000000000000000000000000000000000000000000000000000000000000" - /// ); - /// assert_eq!( - /// Cast::namehash("eth")?, - /// "0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae" - /// ); - /// assert_eq!( - /// Cast::namehash("foo.eth")?, - /// "0xde9b09fd7c5f901e23a3f19fecc54828e9c848539801e86591bd9801b019f84f" - /// ); - /// assert_eq!( - /// Cast::namehash("sub.foo.eth")?, - /// "0x500d86f9e663479e5aaa6e99276e55fc139c597211ee47d17e1e92da16a83402" + /// Cast::index("string", "hello", "1").unwrap().as_str(), + /// "0x8404bb4d805e9ca2bd5dd5c43a107e935c8ec393caa7851b353b3192cd5379ae" /// ); /// # Ok::<_, eyre::Report>(()) /// ``` - pub fn namehash(ens: &str) -> Result { - let mut node = vec![0u8; 32]; - - if !ens.is_empty() { - let ens_lower = ens.to_lowercase(); - let mut labels: Vec<&str> = ens_lower.split('.').collect(); - labels.reverse(); - - for label in labels { - let mut label_hash = keccak256(label.as_bytes()); - node.append(&mut label_hash.to_vec()); - - label_hash = keccak256(node.as_slice()); - node = label_hash.to_vec(); + pub fn index(from_type: &str, from_value: &str, slot_number: &str) -> Result { + let mut hasher = Keccak256::new(); + + let v_ty = DynSolType::parse(from_type).wrap_err("Could not parse type")?; + let v = v_ty.coerce_str(from_value).wrap_err("Could not parse value")?; + match v_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(v.as_word().unwrap()), + + // For strings and byte arrays, `h(k)` is just the unpadded data. + DynSolType::String | DynSolType::Bytes => hasher.update(v.as_packed_seq().unwrap()), + + DynSolType::Array(..) | + DynSolType::FixedArray(..) | + DynSolType::Tuple(..) | + DynSolType::CustomStruct { .. } => { + eyre::bail!("Type `{v_ty}` is not supported as a mapping key") } } - Ok(hex::encode_prefixed(node)) + 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 @@ -1883,11 +1849,42 @@ impl SimpleCast { 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: String, + output_path: Option, + ) -> Result<()> { + let client = Client::new(chain, etherscan_api_key)?; + 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 = Flattener::new(project, &target_path)?.flatten(); + + if let Some(path) = output_path { + fs::create_dir_all(path.parent().unwrap())?; + fs::write(&path, flattened)?; + println!("Flattened file written at {}", path.display()); + } else { + println!("{flattened}"); + } + + Ok(()) + } + /// Disassembles hex encoded bytecode into individual / human readable opcodes /// /// # Example /// - /// ```ignore + /// ``` /// use cast::SimpleCast as Cast; /// /// # async fn foo() -> eyre::Result<()> { @@ -1934,7 +1931,7 @@ impl SimpleCast { let mut nonce = nonce_start; while nonce < u32::MAX && !found.load(Ordering::Relaxed) { - let input = format!("{}{}({}", name, nonce, params); + let input = format!("{name}{nonce}({params}"); let hash = keccak256(input.as_bytes()); let selector = &hash[..4]; @@ -1983,13 +1980,13 @@ impl SimpleCast { /// ``` /// use cast::SimpleCast as Cast; /// - /// let tx = "0x02f8f582a86a82058d8459682f008508351050808303fd84948e42f2f4101563bf679975178e880fd87d3efd4e80b884659ac74b00000000000000000000000080f0c1c49891dcfdd40b6e0f960f84e6042bcb6f000000000000000000000000b97ef9ef8734c71904d8002f8b6bc66dd9c48a6e00000000000000000000000000000000000000000000000000000000007ff4e20000000000000000000000000000000000000000000000000000000000000064c001a05d429597befe2835396206781b199122f2e8297327ed4a05483339e7a8b2022aa04c23a7f70fb29dda1b4ee342fb10a625e9b8ddc6a603fb4e170d4f6f37700cb8"; - /// let (tx, sig) = Cast::decode_raw_transaction(&tx)?; + /// let tx = "0x02f8f582a86a82058d8459682f008508351050808303fd84948e42f2f4101563bf679975178e880fd87d3efd4e80b884659ac74b00000000000000000000000080f0c1c49891dcfdd40b6e0f960f84e6042bcb6f000000000000000000000000b97ef9ef8734c71904d8002f8b6bc66dd9c48a6e00000000000000000000000000000000000000000000000000000000007ff4e20000000000000000000000000000000000000000000000000000000000000064c001a05d429597befe2835396206781b199122f2e8297327ed4a05483339e7a8b2022aa04c23a7f70fb29dda1b4ee342fb10a625e9b8ddc6a603fb4e170d4f6f37700cb8"; + /// let tx_envelope = Cast::decode_raw_transaction(&tx)?; /// # Ok::<(), eyre::Report>(()) - pub fn decode_raw_transaction(tx: &str) -> Result<(TypedTransaction, Signature)> { + pub fn decode_raw_transaction(tx: &str) -> Result { let tx_hex = hex::decode(strip_0x(tx))?; - let tx_rlp = rlp::Rlp::new(tx_hex.as_slice()); - Ok(TypedTransaction::decode_signed(&tx_rlp)?) + let tx = TxEnvelope::decode_2718(&mut tx_hex.as_slice())?; + Ok(tx) } } diff --git a/crates/cast/src/rlp_converter.rs b/crates/cast/src/rlp_converter.rs index d0e94301e..cab852ab3 100644 --- a/crates/cast/src/rlp_converter.rs +++ b/crates/cast/src/rlp_converter.rs @@ -2,9 +2,10 @@ use alloy_rlp::{Buf, Decodable, Encodable, Header}; 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] +/// 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), @@ -14,8 +15,8 @@ pub enum Item { impl Encodable for Item { fn encode(&self, out: &mut dyn alloy_rlp::BufMut) { match self { - Item::Array(arr) => arr.encode(out), - Item::Data(data) => <[u8]>::encode(data, out), + Self::Array(arr) => arr.encode(out), + Self::Data(data) => <[u8]>::encode(data, out), } } } @@ -31,11 +32,11 @@ impl Decodable for Item { let view = &mut d; let mut v = Vec::new(); while !view.is_empty() { - v.push(Item::decode(view)?); + v.push(Self::decode(view)?); } - Ok(Item::Array(v)) + Ok(Self::Array(v)) } else { - Ok(Item::Data(d.to_vec())) + Ok(Self::Data(d.to_vec())) }; buf.advance(h.payload_length); r @@ -43,16 +44,16 @@ impl Decodable for Item { } impl Item { - pub(crate) fn value_to_item(value: &Value) -> eyre::Result { + pub(crate) fn value_to_item(value: &Value) -> eyre::Result { return match value { - Value::Null => Ok(Item::Data(vec![])), + Value::Null => Ok(Self::Data(vec![])), Value::Bool(_) => { eyre::bail!("RLP input should not contain booleans") } // If a value is passed without quotes we cast it to string - Value::Number(n) => Ok(Item::value_to_item(&Value::String(n.to_string()))?), - Value::String(s) => Ok(Item::Data(hex::decode(s).expect("Could not decode hex"))), - Value::Array(values) => values.iter().map(Item::value_to_item).collect(), + Value::Number(n) => Ok(Self::value_to_item(&Value::String(n.to_string()))?), + Value::String(s) => Ok(Self::Data(hex::decode(s).expect("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") } @@ -60,9 +61,9 @@ impl Item { } } -impl FromIterator for Item { - fn from_iter>(iter: T) -> Self { - Item::Array(Vec::from_iter(iter)) +impl FromIterator for Item { + fn from_iter>(iter: T) -> Self { + Self::Array(Vec::from_iter(iter)) } } @@ -70,10 +71,10 @@ impl FromIterator for Item { impl fmt::Display for Item { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result { match self { - Item::Data(dat) => { + Self::Data(dat) => { write!(f, "\"0x{}\"", hex::encode(dat))?; } - Item::Array(items) => { + Self::Array(items) => { f.write_str("[")?; for (i, item) in items.iter().enumerate() { if i > 0 { diff --git a/crates/cast/src/tx.rs b/crates/cast/src/tx.rs deleted file mode 100644 index 1bfd0c40f..000000000 --- a/crates/cast/src/tx.rs +++ /dev/null @@ -1,402 +0,0 @@ -use crate::errors::FunctionSignatureError; -use alloy_json_abi::Function; -use alloy_primitives::{Address, U256}; -use ethers_core::types::{ - transaction::eip2718::TypedTransaction, Eip1559TransactionRequest, NameOrAddress, - TransactionRequest, -}; -use ethers_providers::Middleware; -use eyre::{eyre, Result}; -use foundry_common::{ - abi::{encode_function_args, get_func, get_func_etherscan}, - types::{ToAlloy, ToEthers}, -}; -use foundry_config::Chain; -use futures::future::join_all; - -pub type TxBuilderOutput = (TypedTransaction, Option); -pub type TxBuilderPeekOutput<'a> = (&'a TypedTransaction, &'a Option); - -/// Transaction builder -/// -/// # Examples -/// -/// ``` -/// # async fn foo() -> eyre::Result<()> { -/// # use alloy_primitives::U256; -/// # use cast::TxBuilder; -/// # use foundry_config::NamedChain; -/// let provider = ethers_providers::test_provider::MAINNET.provider(); -/// let mut builder = -/// TxBuilder::new(&provider, "a.eth", Some("b.eth"), NamedChain::Mainnet, false).await?; -/// builder.gas(Some(U256::from(1))); -/// let (tx, _) = builder.build(); -/// # Ok(()) -/// # } -/// ``` -pub struct TxBuilder<'a, M: Middleware> { - to: Option
, - chain: Chain, - tx: TypedTransaction, - func: Option, - etherscan_api_key: Option, - provider: &'a M, -} - -impl<'a, M: Middleware> TxBuilder<'a, M> { - /// Create a new TxBuilder - /// `provider` - provider to use - /// `from` - 'from' field. Could be an ENS name - /// `to` - `to`. Could be a ENS - /// `chain` - chain to construct the tx for - /// `legacy` - use type 1 transaction - pub async fn new, T: Into>( - provider: &'a M, - from: F, - to: Option, - chain: impl Into, - legacy: bool, - ) -> Result> { - let chain = chain.into(); - let from_addr = resolve_ens(provider, from).await?; - - let mut tx: TypedTransaction = if chain.is_legacy() || legacy { - TransactionRequest::new().from(from_addr.to_ethers()).chain_id(chain.id()).into() - } else { - Eip1559TransactionRequest::new().from(from_addr.to_ethers()).chain_id(chain.id()).into() - }; - - let to_addr = if let Some(to) = to { - let addr = resolve_ens(provider, to).await?; - tx.set_to(addr.to_ethers()); - Some(addr) - } else { - None - }; - Ok(Self { to: to_addr, chain, tx, func: None, etherscan_api_key: None, provider }) - } - - /// Set gas for tx - pub fn set_gas(&mut self, v: U256) -> &mut Self { - self.tx.set_gas(v.to_ethers()); - self - } - - /// Set gas for tx, if `v` is not None - pub fn gas(&mut self, v: Option) -> &mut Self { - if let Some(value) = v { - self.set_gas(value); - } - self - } - - /// Set gas price - pub fn set_gas_price(&mut self, v: U256) -> &mut Self { - self.tx.set_gas_price(v.to_ethers()); - self - } - - /// Set gas price, if `v` is not None - pub fn gas_price(&mut self, v: Option) -> &mut Self { - if let Some(value) = v { - self.set_gas_price(value); - } - self - } - - /// Set priority gas price - pub fn set_priority_gas_price(&mut self, v: U256) -> &mut Self { - if let TypedTransaction::Eip1559(tx) = &mut self.tx { - tx.max_priority_fee_per_gas = Some(v.to_ethers()) - } - self - } - - /// Set priority gas price, if `v` is not None - pub fn priority_gas_price(&mut self, v: Option) -> &mut Self { - if let Some(value) = v { - self.set_priority_gas_price(value); - } - self - } - - /// Set value - pub fn set_value(&mut self, v: U256) -> &mut Self { - self.tx.set_value(v.to_ethers()); - self - } - - /// Set value, if `v` is not None - pub fn value(&mut self, v: Option) -> &mut Self { - if let Some(value) = v { - self.set_value(value); - } - self - } - - /// Set nonce - pub fn set_nonce(&mut self, v: U256) -> &mut Self { - self.tx.set_nonce(v.to_ethers()); - self - } - - /// Set nonce, if `v` is not None - pub fn nonce(&mut self, v: Option) -> &mut Self { - if let Some(value) = v { - self.set_nonce(value); - } - self - } - - /// Set etherscan API key. Used to look up function signature buy name - pub fn set_etherscan_api_key(&mut self, v: String) -> &mut Self { - self.etherscan_api_key = Some(v); - self - } - - /// Set etherscan API key, if `v` is not None - pub fn etherscan_api_key(&mut self, v: Option) -> &mut Self { - if let Some(value) = v { - self.set_etherscan_api_key(value); - } - self - } - - pub fn set_data(&mut self, v: Vec) -> &mut Self { - self.tx.set_data(v.into()); - self - } - - pub async fn create_args( - &mut self, - sig: &str, - args: Vec, - ) -> Result<(Vec, Function)> { - if sig.trim().is_empty() { - return Err(FunctionSignatureError::MissingSignature.into()) - } - - let args = resolve_name_args(&args, self.provider).await; - - let func = if sig.contains('(') { - // a regular function signature with parentheses - get_func(sig)? - } else if sig.starts_with("0x") { - // if only calldata is provided, returning a dummy function - get_func("x()")? - } else { - get_func_etherscan( - sig, - self.to.ok_or(FunctionSignatureError::MissingToAddress)?, - &args, - self.chain, - self.etherscan_api_key.as_ref().ok_or_else(|| { - FunctionSignatureError::MissingEtherscan { sig: sig.to_string() } - })?, - ) - .await? - }; - - if sig.starts_with("0x") { - Ok((hex::decode(sig)?, func)) - } else { - Ok((encode_function_args(&func, &args)?, func)) - } - } - - /// Set function arguments - /// `sig` can be: - /// * a fragment (`do(uint32,string)`) - /// * selector + abi-encoded calldata - /// (`0xcdba2fd40000000000000000000000000000000000000000000000000000000000007a69`) - /// * only function name (`do`) - in this case, etherscan lookup is performed on `tx.to`'s - /// contract - pub async fn set_args( - &mut self, - sig: &str, - args: Vec, - ) -> Result<&mut TxBuilder<'a, M>> { - let (data, func) = self.create_args(sig, args).await?; - self.tx.set_data(data.into()); - self.func = Some(func); - Ok(self) - } - - /// Set function arguments, if `value` is not None - pub async fn args( - &mut self, - value: Option<(&str, Vec)>, - ) -> Result<&mut TxBuilder<'a, M>> { - if let Some((sig, args)) = value { - return self.set_args(sig, args).await - } - Ok(self) - } - - /// Consuming build: returns typed transaction and optional function call - pub fn build(self) -> TxBuilderOutput { - (self.tx, self.func) - } - - /// Non-consuming build: peek into the tx content - pub fn peek(&self) -> TxBuilderPeekOutput { - (&self.tx, &self.func) - } -} - -async fn resolve_ens>( - provider: &M, - addr: T, -) -> Result
{ - let from_addr = match addr.into() { - NameOrAddress::Name(ref ens_name) => provider.resolve_name(ens_name).await, - NameOrAddress::Address(addr) => Ok(addr), - } - .map_err(|x| eyre!("Failed to resolve ENS name: {x}"))?; - Ok(from_addr.to_alloy()) -} - -async fn resolve_name_args(args: &[String], provider: &M) -> Vec { - join_all(args.iter().map(|arg| async { - if arg.contains('.') { - let addr = provider.resolve_name(arg).await; - match addr { - Ok(addr) => format!("{addr:?}"), - Err(_) => arg.to_string(), - } - } else { - arg.to_string() - } - })) - .await -} - -#[cfg(test)] -mod tests { - use crate::TxBuilder; - use alloy_primitives::{Address, U256}; - use async_trait::async_trait; - use ethers_core::types::{transaction::eip2718::TypedTransaction, NameOrAddress, H160}; - use ethers_providers::{JsonRpcClient, Middleware, ProviderError}; - use foundry_common::types::ToEthers; - use foundry_config::NamedChain; - use serde::{de::DeserializeOwned, Serialize}; - use std::str::FromStr; - - const ADDR_1: &str = "0000000000000000000000000000000000000001"; - const ADDR_2: &str = "0000000000000000000000000000000000000002"; - - #[derive(Debug)] - struct MyProvider {} - - #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] - #[cfg_attr(not(target_arch = "wasm32"), async_trait)] - impl JsonRpcClient for MyProvider { - type Error = ProviderError; - - async fn request( - &self, - _method: &str, - _params: T, - ) -> Result { - Err(ProviderError::CustomError("There is no request".to_string())) - } - } - #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] - #[cfg_attr(not(target_arch = "wasm32"), async_trait)] - impl Middleware for MyProvider { - type Error = ProviderError; - type Provider = MyProvider; - type Inner = MyProvider; - - fn inner(&self) -> &Self::Inner { - self - } - - async fn resolve_name(&self, ens_name: &str) -> Result { - match ens_name { - "a.eth" => Ok(H160::from_str(ADDR_1).unwrap()), - "b.eth" => Ok(H160::from_str(ADDR_2).unwrap()), - _ => unreachable!("don't know how to resolve {ens_name}"), - } - } - } - #[tokio::test(flavor = "multi_thread")] - async fn builder_new_non_legacy() -> eyre::Result<()> { - let provider = MyProvider {}; - let builder = - TxBuilder::new(&provider, "a.eth", Some("b.eth"), NamedChain::Mainnet, false).await?; - let (tx, args) = builder.build(); - assert_eq!(*tx.from().unwrap(), Address::from_str(ADDR_1).unwrap().to_ethers()); - assert_eq!( - *tx.to().unwrap(), - NameOrAddress::Address(Address::from_str(ADDR_2).unwrap().to_ethers()) - ); - assert_eq!(args, None); - - match tx { - TypedTransaction::Eip1559(_) => {} - _ => { - panic!("Wrong tx type"); - } - } - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn builder_new_legacy() -> eyre::Result<()> { - let provider = MyProvider {}; - let builder = - TxBuilder::new(&provider, "a.eth", Some("b.eth"), NamedChain::Mainnet, true).await?; - // don't check anything other than the tx type - the rest is covered in the non-legacy case - let (tx, _) = builder.build(); - match tx { - TypedTransaction::Legacy(_) => {} - _ => { - panic!("Wrong tx type"); - } - } - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn builder_fields() -> eyre::Result<()> { - let provider = MyProvider {}; - let mut builder = - TxBuilder::new(&provider, "a.eth", Some("b.eth"), NamedChain::Mainnet, false) - .await - .unwrap(); - builder - .gas(Some(U256::from(12u32))) - .gas_price(Some(U256::from(34u32))) - .value(Some(U256::from(56u32))) - .nonce(Some(U256::from(78u32))); - - builder.etherscan_api_key(Some(String::from("what a lovely day"))); // not testing for this :-/ - let (tx, _) = builder.build(); - - assert_eq!(tx.gas().unwrap().as_u32(), 12); - assert_eq!(tx.gas_price().unwrap().as_u32(), 34); - assert_eq!(tx.value().unwrap().as_u32(), 56); - assert_eq!(tx.nonce().unwrap().as_u32(), 78); - assert_eq!(tx.chain_id().unwrap().as_u32(), 1); - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn builder_args() -> eyre::Result<()> { - let provider = MyProvider {}; - let mut builder = - TxBuilder::new(&provider, "a.eth", Some("b.eth"), NamedChain::Mainnet, false) - .await - .unwrap(); - builder.args(Some(("what_a_day(int)", vec![String::from("31337")]))).await?; - let (_, function_maybe) = builder.build(); - - assert_ne!(function_maybe, None); - let function = function_maybe.unwrap(); - assert_eq!(function.name, String::from("what_a_day")); - // could test function.inputs() but that should be covered by utils's unit test - Ok(()) - } -} diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 91c8b6109..7d7e33881 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -1,8 +1,12 @@ //! Contains various tests for checking cast commands -use foundry_common::rpc::{next_http_rpc_endpoint, next_ws_rpc_endpoint}; -use foundry_test_utils::{casttest, util::OutputExt}; -use std::{fs, io::Write, path::Path}; +use alloy_primitives::{address, b256, Address, B256}; +use foundry_test_utils::{ + casttest, + rpc::{next_http_rpc_endpoint, next_ws_rpc_endpoint}, + util::OutputExt, +}; +use std::{fs, io::Write, path::Path, str::FromStr}; // tests `--help` is printed to std out casttest!(print_help, |_prj, cmd| { @@ -73,15 +77,22 @@ casttest!(wallet_address_keystore_with_password_file, |_prj, cmd| { // tests that `cast wallet sign message` outputs the expected signature casttest!(wallet_sign_message_utf8_data, |_prj, cmd| { - cmd.args([ - "wallet", - "sign", - "--private-key", - "0x0000000000000000000000000000000000000000000000000000000000000001", - "test", - ]); + let pk = "0x0000000000000000000000000000000000000000000000000000000000000001"; + let address = "0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf"; + let msg = "test"; + let expected = "0xfe28833983d6faa0715c7e8c3873c725ddab6fa5bf84d40e780676e463e6bea20fc6aea97dc273a98eb26b0914e224c8dd5c615ceaab69ddddcf9b0ae3de0e371c"; + + cmd.args(["wallet", "sign", "--private-key", pk, msg]); let output = cmd.stdout_lossy(); - assert_eq!(output.trim(), "0xfe28833983d6faa0715c7e8c3873c725ddab6fa5bf84d40e780676e463e6bea20fc6aea97dc273a98eb26b0914e224c8dd5c615ceaab69ddddcf9b0ae3de0e371c"); + assert_eq!(output.trim(), expected); + + // Success. + cmd.cast_fuse() + .args(["wallet", "verify", "-a", address, msg, expected]) + .assert_non_empty_stdout(); + + // Fail. + cmd.cast_fuse().args(["wallet", "verify", "-a", address, "other msg", expected]).assert_err(); }); // tests that `cast wallet sign message` outputs the expected signature, given a 0x-prefixed data @@ -152,6 +163,106 @@ casttest!(wallet_list_local_accounts, |prj, cmd| { assert_eq!(list_output.matches('\n').count(), 10); }); +// tests that `cast wallet new-mnemonic --entropy` outputs the expected mnemonic +casttest!(wallet_mnemonic_from_entropy, |_prj, cmd| { + cmd.args(["wallet", "new-mnemonic", "--entropy", "0xdf9bf37e6fcdf9bf37e6fcdf9bf37e3c"]); + let output = cmd.stdout_lossy(); + assert!(output.contains("test test test test test test test test test test test junk")); +}); + +// 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", + ]); + let output = cmd.stdout_lossy(); + assert_eq!(output.trim(), "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", + ]); + let output = cmd.stdout_lossy(); + assert_eq!(output.trim(), "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"); +}); + +// 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", + ]); + let output = cmd.stdout_lossy(); + assert_eq!(output.trim(), "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!("ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"); + + // import private key + cmd.cast_fuse().args([ + "wallet", + "import", + account_name, + "--private-key", + &test_private_key.to_string(), + "-k", + "keystore", + "--unsafe-password", + "test", + ]); + + cmd.assert_non_empty_stdout(); + + // 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.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 estimate` is working correctly. casttest!(estimate_function_gas, |_prj, cmd| { let eth_rpc_url = next_http_rpc_endpoint(); @@ -514,6 +625,89 @@ casttest!(logs_sig_2, |_prj, cmd| { ); }); +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", + ]); + let output = cmd.stdout_lossy(); + assert_eq!( + output.trim(), + "0x02f86b0180843b9aca008502540be4008252089400000000000000000000000000000000000000016480c001a070d55e79ed3ac9fc8f51e78eb91fd054720d943d66633f2eb1bc960f0126b0eca052eda05a792680de3181e49bab4093541f75b49d1ecbe443077b3660c836016a" + ); +}); + +// ensure recipient or code is required +casttest!(mktx_requires_to, |_prj, cmd| { + cmd.args([ + "mktx", + "--private-key", + "0x0000000000000000000000000000000000000000000000000000000000000001", + ]); + let output = cmd.stderr_lossy(); + assert_eq!( + output.trim(), + "Error: \nMust 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", + ]); + let output = cmd.stderr_lossy(); + assert!( + output.contains("The specified sender via CLI/env vars does not match the sender configured via\nthe hardware wallet's HD Path.") + ); +}); + +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", + ]); + let output = cmd.stdout_lossy(); + assert_eq!( + output.trim(), + "0x02f86b0180843b9aca008502540be4008252089400000000000000000000000000000000000000018080c001a0cce9a61187b5d18a89ecd27ec675e3b3f10d37f165627ef89a15a7fe76395ce8a07537f5bffb358ffbef22cda84b1c92f7211723f9e09ae037e81686805d3e5505" + ); +}); + // tests that the raw encoded transaction is returned casttest!(tx_raw, |_prj, cmd| { let rpc = next_http_rpc_endpoint(); @@ -674,3 +868,108 @@ casttest!(balance, |_prj, cmd| { assert_ne!(usdt_result, "0x0000000000000000000000000000000000000000000000000000000000000000"); assert_eq!(alias_result, usdt_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.args(["interface"]).arg(&path); + let output = cmd.stdout_lossy(); + + let s = r#"// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.4; + +interface Interface { + type SpendAssetsHandleType is uint8; + + 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 ( + SpendAssetsHandleType spendAssetsHandleType_, + address[] memory spendAssets_, + uint256[] memory spendAssetAmounts_, + address[] memory incomingAssets_, + uint256[] memory minIncomingAssetAmounts_ + ); + function redeem(address _vaultProxy, bytes memory, bytes memory _assetData) external; +}"#; + assert_eq!(output.trim(), s); +}); + +const ENS_NAME: &str = "emo.eth"; +const ENS_NAMEHASH: B256 = + b256!("0a21aaf2f6414aa664deb341d1114351fdb023cad07bf53b28e57c26db681910"); +const ENS_ADDRESS: Address = address!("28679A1a632125fbBf7A68d850E50623194A709E"); + +casttest!(ens_namehash, |_prj, cmd| { + cmd.args(["namehash", ENS_NAME]); + let out = cmd.stdout_lossy().trim().parse::(); + assert_eq!(out, Ok(ENS_NAMEHASH)); +}); + +casttest!(ens_lookup, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + cmd.args(["lookup-address", &ENS_ADDRESS.to_string(), "--rpc-url", ð_rpc_url, "--verify"]); + let out = cmd.stdout_lossy(); + assert_eq!(out.trim(), ENS_NAME); +}); + +casttest!(ens_resolve, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + cmd.args(["resolve-name", ENS_NAME, "--rpc-url", ð_rpc_url, "--verify"]); + let out = cmd.stdout_lossy().trim().parse::
(); + assert_eq!(out, Ok(ENS_ADDRESS)); +}); + +casttest!(ens_resolve_no_dot_eth, |_prj, cmd| { + let eth_rpc_url = next_http_rpc_endpoint(); + let name = ENS_NAME.strip_suffix(".eth").unwrap(); + cmd.args(["resolve-name", name, "--rpc-url", ð_rpc_url, "--verify"]); + let (_out, err) = cmd.unchecked_output_lossy(); + assert!(err.contains("not found"), "{err:?}"); +}); + +casttest!(index7201, |_prj, cmd| { + let tests = + [("example.main", "0x183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab500")]; + for (id, expected) in tests { + cmd.cast_fuse(); + assert_eq!(cmd.args(["index-erc7201", id]).stdout_lossy().trim(), expected); + } +}); + +casttest!(index7201_unknown_formula_id, |_prj, cmd| { + cmd.args(["index-7201", "test", "--formula-id", "unknown"]).assert_err(); +}); + +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()]).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"]).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", + ]) + .stdout_lossy(); + assert_eq!(s.trim().parse::().unwrap(), 1, "{s}") +}); diff --git a/crates/cast/tests/fixtures/ERC20Artifact.json b/crates/cast/tests/fixtures/ERC20Artifact.json index 4f4fd6e61..508464c24 100644 --- a/crates/cast/tests/fixtures/ERC20Artifact.json +++ b/crates/cast/tests/fixtures/ERC20Artifact.json @@ -1,385 +1 @@ -{ - "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" - } -} +{"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"}} \ No newline at end of file diff --git a/crates/cast/tests/fixtures/interface.json b/crates/cast/tests/fixtures/interface.json new file mode 100644 index 000000000..26163abee --- /dev/null +++ b/crates/cast/tests/fixtures/interface.json @@ -0,0 +1 @@ +[{"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"}] \ No newline at end of file diff --git a/crates/cast/tests/fixtures/sign_typed_data.json b/crates/cast/tests/fixtures/sign_typed_data.json index a6002810a..8dc45f2e7 100644 --- a/crates/cast/tests/fixtures/sign_typed_data.json +++ b/crates/cast/tests/fixtures/sign_typed_data.json @@ -1,38 +1 @@ -{ - "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!" - } -} \ No newline at end of file +{"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!"}} \ No newline at end of file diff --git a/crates/cheatcodes/Cargo.toml b/crates/cheatcodes/Cargo.toml index 8e60ec9e5..c58113039 100644 --- a/crates/cheatcodes/Cargo.toml +++ b/crates/cheatcodes/Cargo.toml @@ -11,6 +11,9 @@ homepage.workspace = true repository.workspace = true exclude.workspace = true +[lints] +workspace = true + [dependencies] foundry-cheatcodes-spec.workspace = true foundry-cheatcodes-common.workspace = true @@ -22,16 +25,21 @@ foundry-wallets.workspace = true foundry-zksync-core.workspace = true foundry-zksync-compiler.workspace = true +zksync_types.workspace = true + alloy-dyn-abi.workspace = true alloy-json-abi.workspace = true alloy-primitives.workspace = true alloy-genesis.workspace = true alloy-sol-types.workspace = true -alloy-providers.workspace = true +alloy-provider.workspace = true alloy-rpc-types.workspace = true -alloy-signer = { workspace = true, features = ["mnemonic", "keystore"] } -parking_lot = "0.12" - +alloy-signer.workspace = true +alloy-signer-local = { workspace = true, features = [ + "mnemonic-all-languages", + "keystore", +] } +parking_lot.workspace = true eyre.workspace = true hex.workspace = true @@ -40,11 +48,13 @@ jsonpath_lib.workspace = true revm.workspace = true serde_json.workspace = true base64.workspace = true +toml = { workspace = true, features = ["preserve_order"] } tracing.workspace = true k256.workspace = true -walkdir = "2" +walkdir.workspace = true p256 = "0.13.2" -thiserror = "1" - -# zk -zksync_types.workspace = true \ No newline at end of file +thiserror.workspace = true +semver.workspace = true +rustc-hash.workspace = true +dialoguer = "0.11.0" +rand = "0.8" diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 05fe56ddf..c977d7e3b 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -83,6 +83,48 @@ "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." + } + ] } ], "structs": [ @@ -415,6 +457,37 @@ "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." + } + ] } ], "cheatcodes": [ @@ -2898,6 +2971,46 @@ "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": "breakpoint_0", @@ -2941,7 +3054,7 @@ { "func": { "id": "broadcast_0", - "description": "Using the address that calls the test contract, has the next call (at this call depth only)\ncreate a transaction that can later be signed and sent onchain.", + "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": "", @@ -3558,6 +3671,26 @@ "status": "stable", "safety": "unsafe" }, + { + "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", @@ -3718,6 +3851,26 @@ "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", @@ -4578,6 +4731,46 @@ "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", @@ -4621,7 +4814,7 @@ { "func": { "id": "getCode", - "description": "Gets the creation bytecode from an artifact file. Takes in the relative path to the json file.", + "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", @@ -4641,7 +4834,7 @@ { "func": { "id": "getDeployedCode", - "description": "Gets the deployed bytecode from an artifact file. Takes in the relative path to the json file.", + "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", @@ -4798,6 +4991,46 @@ "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": "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", @@ -4861,7 +5094,7 @@ { "func": { "id": "keyExists", - "description": "Checks if `key` exists in a JSON object.", + "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", @@ -4875,6 +5108,46 @@ ] }, "group": "json", + "status": "deprecated", + "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" }, @@ -4898,6 +5171,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "lastCallGas", + "description": "Gets the gas used in the last call.", + "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", @@ -5540,73 +5833,413 @@ }, { "func": { - "id": "parseUint", - "description": "Parses the given `string` into a `uint256`.", - "declaration": "function parseUint(string calldata stringifiedValue) external pure returns (uint256 parsedValue);", + "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": "parseUint(string)", - "selector": "0xfa91454d", + "signature": "parseTomlAddress(string,string)", + "selector": "0x65e7c844", "selectorBytes": [ - 250, - 145, - 69, - 77 + 101, + 231, + 200, + 68 ] }, - "group": "string", + "group": "toml", "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;", + "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": "", - "signature": "pauseGasMetering()", - "selector": "0xd1a5b36f", + "mutability": "pure", + "signature": "parseTomlAddressArray(string,string)", + "selector": "0x65c428e7", "selectorBytes": [ - 209, - 165, - 179, - 111 + 101, + 196, + 40, + 231 ] }, - "group": "evm", + "group": "toml", "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;", + "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": "", - "signature": "prank(address)", - "selector": "0xca669fa7", + "mutability": "pure", + "signature": "parseTomlBool(string,string)", + "selector": "0xd30dced6", "selectorBytes": [ - 202, - 102, - 159, - 167 + 211, + 13, + 206, + 214 ] }, - "group": "evm", + "group": "toml", "status": "stable", - "safety": "unsafe" + "safety": "safe" }, { "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;", + "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": "", - "signature": "prank(address,address)", - "selector": "0x47e50cce", + "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": "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": "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, @@ -5620,7 +6253,7 @@ }, { "func": { - "id": "prevrandao", + "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", @@ -5638,6 +6271,26 @@ "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", @@ -5658,6 +6311,166 @@ "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": "randomAddress", + "description": "Returns a random `address`.", + "declaration": "function randomAddress() external returns (address);", + "visibility": "external", + "mutability": "", + "signature": "randomAddress()", + "selector": "0xd5bee9f5", + "selectorBytes": [ + 213, + 190, + 233, + 245 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "randomUint_0", + "description": "Returns a random uint256 value.", + "declaration": "function randomUint() external returns (uint256);", + "visibility": "external", + "mutability": "", + "signature": "randomUint()", + "selector": "0x25124730", + "selectorBytes": [ + 37, + 18, + 71, + 48 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "randomUint_1", + "description": "Returns random uin256 value between the provided range (min..=max).", + "declaration": "function randomUint(uint256 min, uint256 max) external returns (uint256);", + "visibility": "external", + "mutability": "", + "signature": "randomUint(uint256,uint256)", + "selector": "0xd61b051b", + "selectorBytes": [ + 214, + 27, + 5, + 27 + ] + }, + "group": "utilities", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "readCallers", @@ -6518,6 +7331,26 @@ "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", @@ -6661,6 +7494,46 @@ { "func": { "id": "sign_1", + "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": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "sign_2", + "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": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "sign_3", "description": "Signs data with a `Wallet`.", "declaration": "function sign(Wallet calldata wallet, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s);", "visibility": "external", @@ -6761,7 +7634,7 @@ { "func": { "id": "startBroadcast_0", - "description": "Using the address that calls the test contract, has all subsequent calls\n(at this call depth only) create transactions that can later be signed and sent onchain.", + "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": "", @@ -7518,6 +8391,26 @@ "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": "zkVm", @@ -7537,6 +8430,26 @@ "group": "testing", "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.", + "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" } ] } \ No newline at end of file diff --git a/crates/cheatcodes/assets/cheatcodes.schema.json b/crates/cheatcodes/assets/cheatcodes.schema.json index 31f9a1922..cd66ecdc4 100644 --- a/crates/cheatcodes/assets/cheatcodes.schema.json +++ b/crates/cheatcodes/assets/cheatcodes.schema.json @@ -298,6 +298,13 @@ "json" ] }, + { + "description": "Utility cheatcodes that deal with parsing values from and converting values to TOML.\n\nExamples: `parseToml`, `writeToml`.\n\nSafety: safe.", + "type": "string", + "enum": [ + "toml" + ] + }, { "description": "Generic, uncategorized utilities.\n\nExamples: `toString`, `parse*`, `serialize*`.\n\nSafety: safe.", "type": "string", diff --git a/crates/cheatcodes/common/src/expect.rs b/crates/cheatcodes/common/src/expect.rs index 743e8ed04..2954c93cb 100644 --- a/crates/cheatcodes/common/src/expect.rs +++ b/crates/cheatcodes/common/src/expect.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use alloy_primitives::{Address, U256}; +use alloy_primitives::{Address, Bytes, U256}; /// Tracks the expected calls per address. /// @@ -11,7 +11,7 @@ use alloy_primitives::{Address, U256}; /// 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, (ExpectedCallData, u64)>>; +pub type ExpectedCallTracker = HashMap>; #[derive(Clone, Debug)] pub struct ExpectedCallData { diff --git a/crates/cheatcodes/spec/Cargo.toml b/crates/cheatcodes/spec/Cargo.toml index ae4ca21e0..738bb62b7 100644 --- a/crates/cheatcodes/spec/Cargo.toml +++ b/crates/cheatcodes/spec/Cargo.toml @@ -11,6 +11,9 @@ homepage.workspace = true repository.workspace = true exclude.workspace = true +[lints] +workspace = true + [dependencies] foundry-macros.workspace = true alloy-sol-types = { workspace = true, features = ["json"] } diff --git a/crates/cheatcodes/spec/src/cheatcode.rs b/crates/cheatcodes/spec/src/cheatcode.rs index b78659410..91573d89e 100644 --- a/crates/cheatcodes/spec/src/cheatcode.rs +++ b/crates/cheatcodes/spec/src/cheatcode.rs @@ -2,7 +2,7 @@ use super::Function; use alloy_sol_types::SolCall; use serde::{Deserialize, Serialize}; -/// Cheatcode definition trait. Implemented by all [`Vm`] functions. +/// 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>; @@ -108,6 +108,12 @@ pub enum Group { /// /// Safety: safe. Json, + /// Utility cheatcodes that deal with parsing values from and converting values to TOML. + /// + /// Examples: `parseToml`, `writeToml`. + /// + /// Safety: safe. + Toml, /// Generic, uncategorized utilities. /// /// Examples: `toString`, `parse*`, `serialize*`. @@ -130,6 +136,7 @@ impl Group { Self::Environment | Self::String | Self::Json | + Self::Toml | Self::Utilities => Some(Safety::Safe), } } @@ -145,6 +152,7 @@ impl Group { Self::Environment => "environment", Self::String => "string", Self::Json => "json", + Self::Toml => "toml", Self::Utilities => "utilities", } } diff --git a/crates/cheatcodes/spec/src/lib.rs b/crates/cheatcodes/spec/src/lib.rs index 10a53e18d..fffc146a9 100644 --- a/crates/cheatcodes/spec/src/lib.rs +++ b/crates/cheatcodes/spec/src/lib.rs @@ -1,5 +1,6 @@ #![doc = include_str!("../README.md")] -#![warn(missing_docs, unreachable_pub, unused_crate_dependencies, rust_2018_idioms)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] use serde::{Deserialize, Serialize}; use std::{borrow::Cow, fmt}; @@ -83,15 +84,17 @@ impl Cheatcodes<'static> { Vm::ChainInfo::STRUCT.clone(), Vm::AccountAccess::STRUCT.clone(), Vm::StorageAccess::STRUCT.clone(), + Vm::Gas::STRUCT.clone(), ]), enums: Cow::Owned(vec![ Vm::CallerMode::ENUM.clone(), Vm::AccountAccessKind::ENUM.clone(), + Vm::ForgeContext::ENUM.clone(), ]), - errors: Vm::VM_ERRORS.iter().map(|&x| x.clone()).collect(), + errors: Vm::VM_ERRORS.iter().copied().cloned().collect(), events: Cow::Borrowed(&[]), - // events: Vm::VM_EVENTS.iter().map(|&x| x.clone()).collect(), - cheatcodes: Vm::CHEATCODES.iter().map(|&x| x.clone()).collect(), + // events: Vm::VM_EVENTS.iter().copied().cloned().collect(), + cheatcodes: Vm::CHEATCODES.iter().copied().cloned().collect(), } } } diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index ac7debdb9..3ddd101a9 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -3,6 +3,7 @@ #![allow(missing_docs)] use super::*; +use crate::Vm::ForgeContext; use alloy_sol_types::sol; use foundry_macros::Cheatcode; @@ -63,6 +64,28 @@ interface Vm { 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. @@ -73,6 +96,20 @@ interface Vm { 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. @@ -169,6 +206,22 @@ interface Vm { uint256 chainId; } + /// 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; + } + /// The result of a `stopAndReturnStateDiff` call. struct AccountAccess { /// The chain and fork the access occurred. @@ -207,22 +260,6 @@ interface Vm { uint64 depth; } - /// 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; - } - // ======== EVM ======== /// Gets the address for a given private key. @@ -249,6 +286,22 @@ interface Vm { #[cheatcode(group = Evm, safety = Safe)] function sign(uint256 privateKey, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); + /// 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 = Evm, safety = Safe)] + function sign(bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); + + /// 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 = Evm, safety = Safe)] + function sign(address signer, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); + /// Signs `digest` with `privateKey` using the secp256r1 curve. #[cheatcode(group = Evm, safety = Safe)] function signP256(uint256 privateKey, bytes32 digest) external pure returns (bytes32 r, bytes32 s); @@ -322,6 +375,23 @@ interface Vm { /// 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)] @@ -349,6 +419,17 @@ interface Vm { #[cheatcode(group = Evm, safety = Safe)] function getBlockTimestamp() external view returns (uint256 timestamp); + /// 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); + // -------- Account State -------- /// Sets an address' balance. @@ -578,6 +659,7 @@ interface Vm { function getRecordedLogs() external returns (Log[] memory logs); // -------- Gas Metering -------- + // It's recommend to use the `noGasMetering` modifier included with forge-std, instead of // using these functions directly. @@ -589,6 +671,12 @@ interface Vm { #[cheatcode(group = Evm, safety = Safe)] function resumeGasMetering() external; + // -------- Gas Measurement -------- + + /// Gets the gas used in the last call. + #[cheatcode(group = Evm, safety = Safe)] + function lastCallGas() external view returns (Gas memory gas); + // ======== Test Assertions and Utilities ======== /// Enables/Disables use ZK-VM usage for transact/call and create instructions. @@ -1389,11 +1477,13 @@ interface Vm { #[cheatcode(group = Filesystem)] function writeLine(string calldata path, string calldata data) external; - /// Gets the creation bytecode from an artifact file. Takes in the relative path to the json file. + /// 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); - /// Gets the deployed bytecode from an artifact file. Takes in the relative path to the json file. + /// 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); @@ -1407,12 +1497,38 @@ interface Vm { #[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 ======== /// 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)] @@ -1557,12 +1673,20 @@ interface Vm { 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 -------- - /// Using the address that calls the test contract, has the next call (at this call depth only) - /// create a transaction that can later be signed and sent onchain. + /// 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; @@ -1576,8 +1700,12 @@ interface Vm { #[cheatcode(group = Scripting)] function broadcast(uint256 privateKey) external; - /// Using the address that calls the test contract, has all subsequent calls - /// (at this call depth only) create transactions that can later be signed and sent onchain. + /// 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; @@ -1652,6 +1780,11 @@ interface Vm { /// 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); // ======== JSON Parsing and Manipulation ======== @@ -1660,9 +1793,13 @@ interface Vm { // 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)] + function keyExists(string calldata json, string calldata key) external view returns (bool); /// Checks if `key` exists in a JSON object. #[cheatcode(group = Json)] - function keyExists(string calldata json, string calldata key) external view returns (bool); + function keyExistsJson(string calldata json, string calldata key) external view returns (bool); /// ABI-encodes a JSON object. #[cheatcode(group = Json)] @@ -1752,6 +1889,11 @@ interface Vm { 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); @@ -1824,6 +1966,98 @@ interface Vm { #[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); + + /// 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. + #[cheatcode(group = Toml)] + function writeToml(string calldata json, string calldata path, string calldata valueKey) external; + // -------- Key Management -------- /// Derives a private key from the name, labels the account with that name, and returns the wallet. @@ -1913,5 +2147,44 @@ interface Vm { /// 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 a random uint256 value. + #[cheatcode(group = Utilities)] + function randomUint() external returns (uint256); + + /// Returns random uin256 value between the provided range (min..=max). + #[cheatcode(group = Utilities)] + function randomUint(uint256 min, uint256 max) external returns (uint256); + + /// Returns a random `address`. + #[cheatcode(group = Utilities)] + function randomAddress() external returns (address); } } + +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, + } + } +} diff --git a/crates/cheatcodes/src/config.rs b/crates/cheatcodes/src/config.rs index 7690ee5fd..5761a1abe 100644 --- a/crates/cheatcodes/src/config.rs +++ b/crates/cheatcodes/src/config.rs @@ -1,7 +1,7 @@ use super::Result; use crate::{script::ScriptWallets, Vm::Rpc}; use alloy_primitives::Address; -use foundry_common::fs::normalize_path; +use foundry_common::{fs::normalize_path, ContractsByArtifact}; use foundry_compilers::{utils::canonicalize, ProjectPathsConfig}; use foundry_config::{ cache::StorageCachingConfig, fs_permissions::FsAccessKind, Config, FsPermissions, @@ -9,9 +9,11 @@ use foundry_config::{ }; use foundry_evm_core::opts::EvmOpts; use foundry_zksync_compiler::DualCompiledContracts; +use semver::Version; use std::{ collections::HashMap, path::{Path, PathBuf}, + time::Duration, }; /// Additional, configurable context the `Cheatcodes` inspector has access to @@ -23,8 +25,12 @@ pub struct CheatsConfig { 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 @@ -41,6 +47,12 @@ pub struct CheatsConfig { pub labels: HashMap, /// Script wallets pub script_wallets: Option, + /// 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, + /// Version of the script/test contract which is currently running. + pub running_version: Option, /// ZKSolc -> Solc Contract codes pub dual_compiled_contracts: DualCompiledContracts, /// Use ZK-VM on startup @@ -52,29 +64,39 @@ impl CheatsConfig { pub fn new( config: &Config, evm_opts: EvmOpts, + available_artifacts: Option, script_wallets: Option, + running_version: Option, dual_compiled_contracts: DualCompiledContracts, use_zk: bool, ) -> Self { - let mut allowed_paths = vec![config.__root.0.clone()]; + let mut allowed_paths = vec![config.root.0.clone()]; allowed_paths.extend(config.libs.clone()); allowed_paths.extend(config.allow_paths.clone()); 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(), - fs_permissions: config.fs_permissions.clone().joined(&config.__root), - root: config.__root.0.clone(), + fs_permissions: config.fs_permissions.clone().joined(config.root.as_ref()), + root: config.root.0.clone(), allowed_paths, evm_opts, labels: config.labels.clone(), script_wallets, + available_artifacts, + running_version, dual_compiled_contracts, use_zk, } @@ -144,13 +166,15 @@ impl CheatsConfig { /// /// 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 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 - /// `scheme` + /// `ws` `scheme` and is not a path to an existing file pub fn rpc_url(&self, url_or_alias: &str) -> Result { match self.rpc_endpoints.get(url_or_alias) { Some(Ok(url)) => Ok(url.clone()), @@ -159,7 +183,12 @@ impl CheatsConfig { err.try_resolve().map_err(Into::into) } None => { - if url_or_alias.starts_with("http") || url_or_alias.starts_with("ws") { + // 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() + { Ok(url_or_alias.into()) } else { Err(fmt_err!("invalid rpc url: {url_or_alias}")) @@ -184,7 +213,9 @@ impl Default for CheatsConfig { 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(), @@ -193,6 +224,8 @@ impl Default for CheatsConfig { evm_opts: Default::default(), labels: Default::default(), script_wallets: None, + available_artifacts: Default::default(), + running_version: Default::default(), dual_compiled_contracts: Default::default(), use_zk: false, } @@ -206,9 +239,11 @@ mod tests { fn config(root: &str, fs_permissions: FsPermissions) -> CheatsConfig { CheatsConfig::new( - &Config { __root: PathBuf::from(root).into(), fs_permissions, ..Default::default() }, + &Config { root: PathBuf::from(root).into(), fs_permissions, ..Default::default() }, Default::default(), None, + None, + None, Default::default(), false, ) diff --git a/crates/cheatcodes/src/env.rs b/crates/cheatcodes/src/env.rs index d6aaea149..bba8069fb 100644 --- a/crates/cheatcodes/src/env.rs +++ b/crates/cheatcodes/src/env.rs @@ -1,10 +1,12 @@ -//! Implementations of [`Environment`](crate::Group::Environment) cheatcodes. +//! Implementations of [`Environment`](spec::Group::Environment) cheatcodes. use crate::{string, Cheatcode, Cheatcodes, Error, Result, Vm::*}; use alloy_dyn_abi::DynSolType; -use alloy_primitives::Bytes; use alloy_sol_types::SolValue; -use std::env; +use std::{env, sync::OnceLock}; + +/// Stores the forge execution context for the duration of the program. +static FORGE_CONTEXT: OnceLock = OnceLock::new(); impl Cheatcode for setEnvCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { @@ -24,6 +26,13 @@ impl Cheatcode for setEnvCall { } } +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; @@ -230,11 +239,24 @@ impl Cheatcode for envOr_12Call { impl Cheatcode for envOr_13Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { name, delim, defaultValue } = self; - let default = defaultValue.iter().map(|vec| vec.clone().into()).collect::>(); + 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))) } diff --git a/crates/cheatcodes/src/error.rs b/crates/cheatcodes/src/error.rs index 66796026d..ec4459d3b 100644 --- a/crates/cheatcodes/src/error.rs +++ b/crates/cheatcodes/src/error.rs @@ -1,12 +1,14 @@ use crate::Vm; use alloy_primitives::{Address, Bytes}; -use alloy_signer::{Error as SignerError, WalletError}; +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::DatabaseError; use foundry_wallets::error::WalletSignerError; use k256::ecdsa::signature::Error as SignatureError; +use revm::primitives::EVMError; use std::{borrow::Cow, fmt}; /// Cheatcode result type. @@ -297,11 +299,18 @@ impl_from!( std::str::Utf8Error, std::string::FromUtf8Error, UnresolvedEnvVarError, - WalletError, + LocalSignerError, SignerError, WalletSignerError, ); +impl From> for Error { + #[inline] + fn from(err: EVMError) -> Self { + Self::display(DatabaseError::from(err)) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index a19fc0d4a..1d530818d 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -1,9 +1,8 @@ -//! Implementations of [`Evm`](crate::Group::Evm) cheatcodes. +//! Implementations of [`Evm`](spec::Group::Evm) cheatcodes. use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Result, Vm::*}; use alloy_genesis::{Genesis, GenesisAccount}; use alloy_primitives::{Address, Bytes, B256, U256}; -use alloy_signer::Signer; use alloy_sol_types::SolValue; use foundry_common::fs::{read_json_file, write_json_file}; use foundry_evm_core::{ @@ -12,9 +11,12 @@ use foundry_evm_core::{ }; use revm::{ primitives::{Account, Bytecode, SpecId, KECCAK_EMPTY}, - EVMData, + InnerEvmContext, +}; +use std::{ + collections::{BTreeMap, HashMap}, + path::Path, }; -use std::{collections::HashMap, path::Path}; mod fork; pub(crate) mod mapping; @@ -45,11 +47,7 @@ impl Cheatcode for getNonce_0Call { let Self { account } = self; if ccx.state.use_zk_vm { - let nonce = foundry_zksync_core::cheatcodes::get_nonce( - *account, - ccx.data.db, - &mut ccx.data.journaled_state, - ); + let nonce = foundry_zksync_core::cheatcodes::get_nonce(*account, ccx.ecx); return Ok(nonce.abi_encode()); } @@ -61,8 +59,8 @@ impl Cheatcode for loadCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { target, slot } = *self; ensure_not_precompile!(&target, ccx); - ccx.data.journaled_state.load_account(target, ccx.data.db)?; - let (val, _) = ccx.data.journaled_state.sload(target, slot.into(), ccx.data.db)?; + ccx.ecx.load_account(target)?; + let (val, _) = ccx.ecx.sload(target, slot.into())?; Ok(val.abi_encode()) } } @@ -75,7 +73,7 @@ impl Cheatcode for loadAllocsCall { ensure!(path.exists(), "allocs file does not exist: {pathToAllocsJson}"); // Let's first assume we're reading a file with only the allocs. - let allocs: HashMap = match read_json_file(path) { + let allocs: BTreeMap = match read_json_file(path) { Ok(allocs) => allocs, Err(_) => { // Let's try and read from a genesis file, and extract allocs. @@ -85,9 +83,9 @@ impl Cheatcode for loadAllocsCall { }; // Then, load the allocs into the database. - ccx.data + ccx.ecx .db - .load_allocs(&allocs, &mut ccx.data.journaled_state) + .load_allocs(&allocs, &mut ccx.ecx.journaled_state) .map(|()| Vec::default()) .map_err(|e| fmt_err!("failed to load allocs: {e}")) } @@ -110,10 +108,10 @@ impl Cheatcode for dumpStateCall { }; let alloc = ccx - .data + .ecx .journaled_state .state() - .into_iter() + .iter_mut() .filter(|(key, val)| !skip(key, val)) .map(|(key, val)| { ( @@ -146,6 +144,20 @@ impl Cheatcode for sign_0Call { } } +impl Cheatcode for sign_1Call { + fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { digest } = self; + super::utils::sign_with_wallet(ccx, None, digest) + } +} + +impl Cheatcode for sign_2Call { + fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { signer, digest } = self; + super::utils::sign_with_wallet(ccx, Some(*signer), digest) + } +} + impl Cheatcode for signP256Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { privateKey, digest } = self; @@ -211,11 +223,24 @@ impl Cheatcode for resumeGasMeteringCall { } } +impl Cheatcode for lastCallGasCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self {} = self; + ensure!(state.last_call_gas.is_some(), "`lastCallGas` is only available after a call"); + Ok(state + .last_call_gas + .as_ref() + // This should never happen, as we ensure `last_call_gas` is `Some` above. + .expect("`lastCallGas` is only available after a call") + .abi_encode()) + } +} + impl Cheatcode for chainIdCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newChainId } = self; ensure!(*newChainId <= U256::from(u64::MAX), "chain ID must be less than 2^64 - 1"); - ccx.data.env.cfg.chain_id = newChainId.to(); + ccx.ecx.env.cfg.chain_id = newChainId.to(); Ok(Default::default()) } } @@ -223,7 +248,7 @@ impl Cheatcode for chainIdCall { impl Cheatcode for coinbaseCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newCoinbase } = self; - ccx.data.env.block.coinbase = *newCoinbase; + ccx.ecx.env.block.coinbase = *newCoinbase; Ok(Default::default()) } } @@ -232,11 +257,11 @@ impl Cheatcode for difficultyCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newDifficulty } = self; ensure!( - ccx.data.env.cfg.spec_id < SpecId::MERGE, + ccx.ecx.spec_id() < 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.data.env.block.difficulty = *newDifficulty; + ccx.ecx.env.block.difficulty = *newDifficulty; Ok(Default::default()) } } @@ -244,38 +269,71 @@ impl Cheatcode for difficultyCall { impl Cheatcode for feeCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newBasefee } = self; - ccx.data.env.block.basefee = *newBasefee; + ccx.ecx.env.block.basefee = *newBasefee; + Ok(Default::default()) + } +} + +impl Cheatcode for prevrandao_0Call { + fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { newPrevrandao } = self; + ensure!( + ccx.ecx.spec_id() >= SpecId::MERGE, + "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \ + see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399" + ); + ccx.ecx.env.block.prevrandao = Some(*newPrevrandao); Ok(Default::default()) } } -impl Cheatcode for prevrandaoCall { +impl Cheatcode for prevrandao_1Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newPrevrandao } = self; ensure!( - ccx.data.env.cfg.spec_id >= SpecId::MERGE, + ccx.ecx.spec_id() >= 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.data.env.block.prevrandao = Some(*newPrevrandao); + ccx.ecx.env.block.prevrandao = Some((*newPrevrandao).into()); + Ok(Default::default()) + } +} + +impl Cheatcode for blobhashesCall { + fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { hashes } = self; + ensure!( + ccx.ecx.spec_id() >= SpecId::CANCUN, + "`blobhash` is not supported before the Cancun hard fork; \ + see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" + ); + ccx.ecx.env.tx.blob_hashes.clone_from(hashes); Ok(Default::default()) } } +impl Cheatcode for getBlobhashesCall { + fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; + ensure!( + ccx.ecx.spec_id() >= SpecId::CANCUN, + "`blobhash` is not supported before the Cancun hard fork; \ + see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" + ); + Ok(ccx.ecx.env.tx.blob_hashes.clone().abi_encode()) + } +} + impl Cheatcode for rollCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newHeight } = self; if ccx.state.use_zk_vm { - foundry_zksync_core::cheatcodes::roll( - *newHeight, - ccx.data.env, - ccx.data.db, - &mut ccx.data.journaled_state, - ); + foundry_zksync_core::cheatcodes::roll(*newHeight, ccx.ecx); return Ok(Default::default()) } - ccx.data.env.block.number = *newHeight; + ccx.ecx.env.block.number = *newHeight; Ok(Default::default()) } } @@ -283,14 +341,14 @@ impl Cheatcode for rollCall { impl Cheatcode for getBlockNumberCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; - Ok(ccx.data.env.block.number.abi_encode()) + Ok(ccx.ecx.env.block.number.abi_encode()) } } impl Cheatcode for txGasPriceCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newGasPrice } = self; - ccx.data.env.tx.gas_price = *newGasPrice; + ccx.ecx.env.tx.gas_price = *newGasPrice; Ok(Default::default()) } } @@ -299,15 +357,10 @@ impl Cheatcode for warpCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newTimestamp } = self; if ccx.state.use_zk_vm { - foundry_zksync_core::cheatcodes::warp( - *newTimestamp, - ccx.data.env, - ccx.data.db, - &mut ccx.data.journaled_state, - ); + foundry_zksync_core::cheatcodes::warp(*newTimestamp, ccx.ecx); return Ok(Default::default()) } - ccx.data.env.block.timestamp = *newTimestamp; + ccx.ecx.env.block.timestamp = *newTimestamp; Ok(Default::default()) } @@ -316,7 +369,27 @@ impl Cheatcode for warpCall { impl Cheatcode for getBlockTimestampCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; - Ok(ccx.data.env.block.timestamp.abi_encode()) + Ok(ccx.ecx.env.block.timestamp.abi_encode()) + } +} + +impl Cheatcode for blobBaseFeeCall { + fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { newBlobBaseFee } = self; + ensure!( + ccx.ecx.spec_id() >= SpecId::CANCUN, + "`blobBaseFee` is not supported before the Cancun hard fork; \ + see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844" + ); + ccx.ecx.env.block.set_blob_excess_gas_and_price((*newBlobBaseFee).to()); + Ok(Default::default()) + } +} + +impl Cheatcode for getBlobBaseFeeCall { + fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; + Ok(ccx.ecx.env.block.get_blob_excess_gas().unwrap_or(0).abi_encode()) } } @@ -324,14 +397,9 @@ impl Cheatcode for dealCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account: address, newBalance: new_balance } = *self; let old_balance = if ccx.state.use_zk_vm { - foundry_zksync_core::cheatcodes::deal( - address, - new_balance, - ccx.data.db, - &mut ccx.data.journaled_state, - ) + foundry_zksync_core::cheatcodes::deal(address, new_balance, ccx.ecx) } else { - let account = journaled_account(ccx.data, address)?; + let account = journaled_account(ccx.ecx, address)?; std::mem::replace(&mut account.info.balance, new_balance) }; @@ -345,20 +413,15 @@ impl Cheatcode for etchCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { target, newRuntimeBytecode } = self; if ccx.state.use_zk_vm { - foundry_zksync_core::cheatcodes::etch( - *target, - newRuntimeBytecode, - ccx.data.db, - &mut ccx.data.journaled_state, - ); + foundry_zksync_core::cheatcodes::etch(*target, newRuntimeBytecode, ccx.ecx); return Ok(Default::default()); } ensure_not_precompile!(target, ccx); - ccx.data.journaled_state.load_account(*target, ccx.data.db)?; - let bytecode = Bytecode::new_raw(Bytes::copy_from_slice(newRuntimeBytecode)).to_checked(); - ccx.data.journaled_state.set_code(*target, bytecode); + ccx.ecx.load_account(*target)?; + let bytecode = Bytecode::new_raw(Bytes::copy_from_slice(newRuntimeBytecode)); + ccx.ecx.journaled_state.set_code(*target, bytecode); Ok(Default::default()) } } @@ -367,16 +430,11 @@ impl Cheatcode for resetNonceCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; if ccx.state.use_zk_vm { - foundry_zksync_core::cheatcodes::set_nonce( - *account, - U256::ZERO, - ccx.data.db, - &mut ccx.data.journaled_state, - ); + foundry_zksync_core::cheatcodes::set_nonce(*account, U256::ZERO, ccx.ecx); return Ok(Default::default()); } - let account = journaled_account(ccx.data, *account)?; + 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. @@ -393,16 +451,11 @@ impl Cheatcode for setNonceCall { let Self { account, newNonce } = *self; if ccx.state.use_zk_vm { - foundry_zksync_core::cheatcodes::set_nonce( - account, - U256::from(newNonce), - ccx.data.db, - &mut ccx.data.journaled_state, - ); + foundry_zksync_core::cheatcodes::set_nonce(account, U256::from(newNonce), ccx.ecx); return Ok(Default::default()); } - let account = journaled_account(ccx.data, account)?; + let account = journaled_account(ccx.ecx, account)?; // nonce must increment only let current = account.info.nonce; ensure!( @@ -420,16 +473,11 @@ impl Cheatcode for setNonceUnsafeCall { let Self { account, newNonce } = *self; if ccx.state.use_zk_vm { - foundry_zksync_core::cheatcodes::set_nonce( - account, - U256::from(newNonce), - ccx.data.db, - &mut ccx.data.journaled_state, - ); + foundry_zksync_core::cheatcodes::set_nonce(account, U256::from(newNonce), ccx.ecx); return Ok(Default::default()); } - let account = journaled_account(ccx.data, account)?; + let account = journaled_account(ccx.ecx, account)?; account.info.nonce = newNonce; Ok(Default::default()) } @@ -440,8 +488,8 @@ impl Cheatcode for storeCall { let Self { target, slot, value } = *self; ensure_not_precompile!(&target, ccx); // ensure the account is touched - let _ = journaled_account(ccx.data, target)?; - ccx.data.journaled_state.sstore(target, slot.into(), value.into(), ccx.data.db)?; + let _ = journaled_account(ccx.ecx, target)?; + ccx.ecx.sstore(target, slot.into(), value.into())?; Ok(Default::default()) } } @@ -449,7 +497,7 @@ impl Cheatcode for storeCall { impl Cheatcode for coolCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { target } = self; - if let Some(account) = ccx.data.journaled_state.state.get_mut(target) { + if let Some(account) = ccx.ecx.journaled_state.state.get_mut(target) { account.unmark_touch(); account.storage.clear(); } @@ -460,28 +508,28 @@ impl Cheatcode for coolCall { impl Cheatcode for readCallersCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; - read_callers(ccx.state, &ccx.data.env.tx.caller) + read_callers(ccx.state, &ccx.ecx.env.tx.caller) } } impl Cheatcode for snapshotCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; - Ok(ccx.data.db.snapshot(&ccx.data.journaled_state, ccx.data.env).abi_encode()) + Ok(ccx.ecx.db.snapshot(&ccx.ecx.journaled_state, &ccx.ecx.env).abi_encode()) } } impl Cheatcode for revertToCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; - let result = if let Some(journaled_state) = ccx.data.db.revert( + let result = if let Some(journaled_state) = ccx.ecx.db.revert( *snapshotId, - &ccx.data.journaled_state, - ccx.data.env, + &ccx.ecx.journaled_state, + &mut ccx.ecx.env, RevertSnapshotAction::RevertKeep, ) { // we reset the evm's journaled_state to the state of the snapshot previous state - ccx.data.journaled_state = journaled_state; + ccx.ecx.journaled_state = journaled_state; true } else { false @@ -493,14 +541,14 @@ impl Cheatcode for revertToCall { impl Cheatcode for revertToAndDeleteCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; - let result = if let Some(journaled_state) = ccx.data.db.revert( + let result = if let Some(journaled_state) = ccx.ecx.db.revert( *snapshotId, - &ccx.data.journaled_state, - ccx.data.env, + &ccx.ecx.journaled_state, + &mut ccx.ecx.env, RevertSnapshotAction::RevertRemove, ) { // we reset the evm's journaled_state to the state of the snapshot previous state - ccx.data.journaled_state = journaled_state; + ccx.ecx.journaled_state = journaled_state; true } else { false @@ -512,14 +560,14 @@ impl Cheatcode for revertToAndDeleteCall { impl Cheatcode for deleteSnapshotCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; - let result = ccx.data.db.delete_snapshot(*snapshotId); + let result = ccx.ecx.db.delete_snapshot(*snapshotId); Ok(result.abi_encode()) } } impl Cheatcode for deleteSnapshotsCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; - ccx.data.db.delete_snapshots(); + ccx.ecx.db.delete_snapshots(); Ok(Default::default()) } } @@ -540,8 +588,7 @@ impl Cheatcode for stopAndReturnStateDiffCall { } pub(super) fn get_nonce(ccx: &mut CheatsCtxt, address: &Address) -> Result { - super::script::correct_sender_nonce(ccx)?; - let (account, _) = ccx.data.journaled_state.load_account(*address, ccx.data.db)?; + let (account, _) = ccx.ecx.journaled_state.load_account(*address, &mut ccx.ecx.db)?; Ok(account.info.nonce.abi_encode()) } @@ -594,13 +641,13 @@ fn read_callers(state: &Cheatcodes, default_sender: &Address) -> Result { } /// Ensures the `Account` is loaded and touched. -pub(super) fn journaled_account<'a, DB: DatabaseExt>( - data: &'a mut EVMData<'_, DB>, +pub(super) fn journaled_account( + ecx: &mut InnerEvmContext, addr: Address, -) -> Result<&'a mut Account> { - data.journaled_state.load_account(addr, data.db)?; - data.journaled_state.touch(&addr); - Ok(data.journaled_state.state.get_mut(&addr).expect("account is loaded")) +) -> Result<&mut Account> { + ecx.load_account(addr)?; + ecx.journaled_state.touch(&addr); + Ok(ecx.journaled_state.state.get_mut(&addr).expect("account is loaded")) } /// Consumes recorded account accesses and returns them as an abi encoded diff --git a/crates/cheatcodes/src/evm/fork.rs b/crates/cheatcodes/src/evm/fork.rs index 85b46efd0..8875369c1 100644 --- a/crates/cheatcodes/src/evm/fork.rs +++ b/crates/cheatcodes/src/evm/fork.rs @@ -1,17 +1,15 @@ use crate::{Cheatcode, Cheatcodes, CheatsCtxt, DatabaseExt, Result, Vm::*}; use alloy_primitives::{B256, U256}; -use alloy_providers::provider::TempProvider; +use alloy_provider::Provider; use alloy_rpc_types::Filter; use alloy_sol_types::SolValue; -use eyre::WrapErr; -use foundry_common::{provider::alloy::ProviderBuilder, types::ToEthers}; -use foundry_compilers::utils::RuntimeOrHandle; +use foundry_common::provider::ProviderBuilder; use foundry_evm_core::fork::CreateFork; impl Cheatcode for activeForkCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; - ccx.data + ccx.ecx .db .active_fork_id() .map(|id| id.abi_encode()) @@ -64,7 +62,13 @@ impl Cheatcode for createSelectFork_2Call { impl Cheatcode for rollFork_0Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { blockNumber } = self; - ccx.data.db.roll_fork(None, *blockNumber, ccx.data.env, &mut ccx.data.journaled_state)?; + persist_caller(ccx); + ccx.ecx.db.roll_fork( + None, + (*blockNumber).to(), + &mut ccx.ecx.env, + &mut ccx.ecx.journaled_state, + )?; Ok(Default::default()) } } @@ -72,11 +76,12 @@ impl Cheatcode for rollFork_0Call { impl Cheatcode for rollFork_1Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { txHash } = self; - ccx.data.db.roll_fork_to_transaction( + persist_caller(ccx); + ccx.ecx.db.roll_fork_to_transaction( None, *txHash, - ccx.data.env, - &mut ccx.data.journaled_state, + &mut ccx.ecx.env, + &mut ccx.ecx.journaled_state, )?; Ok(Default::default()) } @@ -85,11 +90,12 @@ impl Cheatcode for rollFork_1Call { impl Cheatcode for rollFork_2Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { forkId, blockNumber } = self; - ccx.data.db.roll_fork( + persist_caller(ccx); + ccx.ecx.db.roll_fork( Some(*forkId), - *blockNumber, - ccx.data.env, - &mut ccx.data.journaled_state, + (*blockNumber).to(), + &mut ccx.ecx.env, + &mut ccx.ecx.journaled_state, )?; Ok(Default::default()) } @@ -98,11 +104,12 @@ impl Cheatcode for rollFork_2Call { impl Cheatcode for rollFork_3Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { forkId, txHash } = self; - ccx.data.db.roll_fork_to_transaction( + persist_caller(ccx); + ccx.ecx.db.roll_fork_to_transaction( Some(*forkId), *txHash, - ccx.data.env, - &mut ccx.data.journaled_state, + &mut ccx.ecx.env, + &mut ccx.ecx.journaled_state, )?; Ok(Default::default()) } @@ -111,14 +118,11 @@ impl Cheatcode for rollFork_3Call { impl Cheatcode for selectForkCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { forkId } = self; + persist_caller(ccx); check_broadcast(ccx.state)?; - // No need to correct since the sender's nonce does not get incremented when selecting a - // fork. - ccx.state.corrected_nonce = true; - - ccx.state.select_fork_vm(ccx.data, *forkId); - ccx.data.db.select_fork(*forkId, ccx.data.env, &mut ccx.data.journaled_state)?; + ccx.state.select_fork_vm(ccx.ecx, *forkId); + ccx.ecx.db.select_fork(*forkId, &mut ccx.ecx.env, &mut ccx.ecx.journaled_state)?; Ok(Default::default()) } @@ -127,11 +131,11 @@ impl Cheatcode for selectForkCall { impl Cheatcode for transact_0Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { txHash } = *self; - ccx.data.db.transact( + ccx.ecx.db.transact( None, txHash, - ccx.data.env, - &mut ccx.data.journaled_state, + &mut ccx.ecx.env, + &mut ccx.ecx.journaled_state, ccx.state, )?; Ok(Default::default()) @@ -141,11 +145,11 @@ impl Cheatcode for transact_0Call { impl Cheatcode for transact_1Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { forkId, txHash } = *self; - ccx.data.db.transact( + ccx.ecx.db.transact( Some(forkId), txHash, - ccx.data.env, - &mut ccx.data.journaled_state, + &mut ccx.ecx.env, + &mut ccx.ecx.journaled_state, ccx.state, )?; Ok(Default::default()) @@ -155,7 +159,7 @@ impl Cheatcode for transact_1Call { impl Cheatcode for allowCheatcodesCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; - ccx.data.db.allow_cheatcode_access(*account); + ccx.ecx.db.allow_cheatcode_access(*account); Ok(Default::default()) } } @@ -163,7 +167,7 @@ impl Cheatcode for allowCheatcodesCall { impl Cheatcode for makePersistent_0Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; - ccx.data.db.add_persistent_account(*account); + ccx.ecx.db.add_persistent_account(*account); Ok(Default::default()) } } @@ -171,8 +175,8 @@ impl Cheatcode for makePersistent_0Call { impl Cheatcode for makePersistent_1Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account0, account1 } = self; - ccx.data.db.add_persistent_account(*account0); - ccx.data.db.add_persistent_account(*account1); + ccx.ecx.db.add_persistent_account(*account0); + ccx.ecx.db.add_persistent_account(*account1); Ok(Default::default()) } } @@ -180,9 +184,9 @@ impl Cheatcode for makePersistent_1Call { impl Cheatcode for makePersistent_2Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account0, account1, account2 } = self; - ccx.data.db.add_persistent_account(*account0); - ccx.data.db.add_persistent_account(*account1); - ccx.data.db.add_persistent_account(*account2); + ccx.ecx.db.add_persistent_account(*account0); + ccx.ecx.db.add_persistent_account(*account1); + ccx.ecx.db.add_persistent_account(*account2); Ok(Default::default()) } } @@ -190,7 +194,7 @@ impl Cheatcode for makePersistent_2Call { impl Cheatcode for makePersistent_3Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { accounts } = self; - ccx.data.db.extend_persistent_accounts(accounts.iter().copied()); + ccx.ecx.db.extend_persistent_accounts(accounts.iter().copied()); Ok(Default::default()) } } @@ -198,7 +202,7 @@ impl Cheatcode for makePersistent_3Call { impl Cheatcode for revokePersistent_0Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; - ccx.data.db.remove_persistent_account(account); + ccx.ecx.db.remove_persistent_account(account); Ok(Default::default()) } } @@ -206,7 +210,7 @@ impl Cheatcode for revokePersistent_0Call { impl Cheatcode for revokePersistent_1Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { accounts } = self; - ccx.data.db.remove_persistent_accounts(accounts.iter().copied()); + ccx.ecx.db.remove_persistent_accounts(accounts.iter().copied()); Ok(Default::default()) } } @@ -214,7 +218,7 @@ impl Cheatcode for revokePersistent_1Call { impl Cheatcode for isPersistentCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; - Ok(ccx.data.db.is_persistent(account).abi_encode()) + Ok(ccx.ecx.db.is_persistent(account).abi_encode()) } } @@ -222,15 +226,14 @@ impl Cheatcode for rpcCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { method, params } = self; let url = - ccx.data.db.active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?; + ccx.ecx.db.active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?; let provider = ProviderBuilder::new(&url).build()?; - let method: &'static str = Box::new(method.clone()).leak(); let params_json: serde_json::Value = serde_json::from_str(params)?; - let result = RuntimeOrHandle::new() - .block_on(provider.raw_request(method, params_json)) - .map_err(|err| fmt_err!("{method:?}: {err}"))?; + let result = + foundry_common::block_on(provider.raw_request(method.clone().into(), params_json)) + .map_err(|err| fmt_err!("{method:?}: {err}"))?; - let result_as_tokens = crate::json::value_to_token(&result) + let result_as_tokens = crate::json::json_value_to_token(&result) .map_err(|err| fmt_err!("failed to parse result: {err}"))?; Ok(result_as_tokens.abi_encode()) @@ -250,40 +253,27 @@ impl Cheatcode for eth_getLogsCall { } let url = - ccx.data.db.active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?; + 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() { - let topic = topic.to_ethers(); - // todo: needed because rust wants to convert FixedBytes<32> to U256 to convert it back - // to FixedBytes<32> and then to Topic for some reason removing the - // From impl in alloy does not fix the situation, and it is not possible to impl - // From> either because of a conflicting impl - match i { - 0 => filter = filter.event_signature(U256::from_be_bytes(topic.to_fixed_bytes())), - 1 => filter = filter.topic1(U256::from_be_bytes(topic.to_fixed_bytes())), - 2 => filter = filter.topic2(U256::from_be_bytes(topic.to_fixed_bytes())), - 3 => filter = filter.topic3(U256::from_be_bytes(topic.to_fixed_bytes())), - _ => unreachable!(), - }; + for (i, &topic) in topics.iter().enumerate() { + filter.topics[i] = topic.into(); } - // todo: handle the errors somehow - let logs = RuntimeOrHandle::new() - .block_on(provider.get_logs(filter)) - .wrap_err("failed to get logs")?; + 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.into_iter().collect(), - data: log.data.0.into(), + 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().to(), + blockNumber: log.block_number.unwrap_or_default(), transactionHash: log.transaction_hash.unwrap_or_default(), - transactionIndex: log.transaction_index.unwrap_or_default().to(), - logIndex: log.log_index.unwrap_or_default(), + transactionIndex: log.transaction_index.unwrap_or_default(), + logIndex: U256::from(log.log_index.unwrap_or_default()), removed: log.removed, }) .collect::>(); @@ -300,13 +290,10 @@ fn create_select_fork( ) -> Result { check_broadcast(ccx.state)?; - // No need to correct since the sender's nonce does not get incremented when selecting a fork. - ccx.state.corrected_nonce = true; - let fork = create_fork_request(ccx, url_or_alias, block)?; - let id = ccx.data.db.create_fork(fork)?; - ccx.state.select_fork_vm(ccx.data, id); - ccx.data.db.select_fork(id, ccx.data.env, &mut ccx.data.journaled_state)?; + let id = ccx.ecx.db.create_fork(fork)?; + ccx.state.select_fork_vm(ccx.ecx, id); + ccx.ecx.db.select_fork(id, &mut ccx.ecx.env, &mut ccx.ecx.journaled_state)?; Ok(id.abi_encode()) } @@ -317,7 +304,7 @@ fn create_fork( block: Option, ) -> Result { let fork = create_fork_request(ccx, url_or_alias, block)?; - let id = ccx.data.db.create_fork(fork)?; + let id = ccx.ecx.db.create_fork(fork)?; Ok(id.abi_encode()) } @@ -329,14 +316,11 @@ fn create_select_fork_at_transaction( ) -> Result { check_broadcast(ccx.state)?; - // No need to correct since the sender's nonce does not get incremented when selecting a fork. - ccx.state.corrected_nonce = true; - let fork = create_fork_request(ccx, url_or_alias, None)?; - let id = ccx.data.db.create_select_fork_at_transaction( + let id = ccx.ecx.db.create_select_fork_at_transaction( fork, - ccx.data.env, - &mut ccx.data.journaled_state, + &mut ccx.ecx.env, + &mut ccx.ecx.journaled_state, *transaction, )?; Ok(id.abi_encode()) @@ -349,7 +333,7 @@ fn create_fork_at_transaction( transaction: &B256, ) -> Result { let fork = create_fork_request(ccx, url_or_alias, None)?; - let id = ccx.data.db.create_fork_at_transaction(fork, *transaction)?; + let id = ccx.ecx.db.create_fork_at_transaction(fork, *transaction)?; Ok(id.abi_encode()) } @@ -359,13 +343,16 @@ fn create_fork_request( url_or_alias: &str, block: Option, ) -> Result { + persist_caller(ccx); + let url = ccx.state.config.rpc_url(url_or_alias)?; let mut evm_opts = ccx.state.config.evm_opts.clone(); evm_opts.fork_block_number = block; let fork = CreateFork { - enable_caching: ccx.state.config.rpc_storage_caching.enable_for_endpoint(&url), + enable_caching: !ccx.state.config.no_storage_caching && + ccx.state.config.rpc_storage_caching.enable_for_endpoint(&url), url, - env: ccx.data.env.clone(), + env: (*ccx.ecx.env).clone(), evm_opts, }; Ok(fork) @@ -379,3 +366,12 @@ fn check_broadcast(state: &Cheatcodes) -> Result<()> { Err(fmt_err!("cannot select forks during a broadcast")) } } + +// 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 +#[inline] +fn persist_caller(ccx: &mut CheatsCtxt) { + ccx.ecx.db.add_persistent_account(ccx.caller); +} diff --git a/crates/cheatcodes/src/evm/mapping.rs b/crates/cheatcodes/src/evm/mapping.rs index f5acc4966..b506d2058 100644 --- a/crates/cheatcodes/src/evm/mapping.rs +++ b/crates/cheatcodes/src/evm/mapping.rs @@ -117,7 +117,7 @@ pub(crate) fn step(mapping_slots: &mut HashMap, interpret match interpreter.current_opcode() { opcode::KECCAK256 => { if interpreter.stack.peek(1) == Ok(U256::from(0x40)) { - let address = interpreter.contract.address; + let address = interpreter.contract.target_address; let offset = interpreter.stack.peek(0).expect("stack size > 1").saturating_to(); let data = interpreter.shared_memory.slice(offset, 0x40); let low = B256::from_slice(&data[..0x20]); @@ -128,7 +128,8 @@ pub(crate) fn step(mapping_slots: &mut HashMap, interpret } } opcode::SSTORE => { - if let Some(mapping_slots) = mapping_slots.get_mut(&interpreter.contract.address) { + if let Some(mapping_slots) = mapping_slots.get_mut(&interpreter.contract.target_address) + { if let Ok(slot) = interpreter.stack.peek(0) { mapping_slots.insert(slot.into()); } diff --git a/crates/cheatcodes/src/evm/mock.rs b/crates/cheatcodes/src/evm/mock.rs index f623f498d..48e3cff0d 100644 --- a/crates/cheatcodes/src/evm/mock.rs +++ b/crates/cheatcodes/src/evm/mock.rs @@ -14,24 +14,21 @@ impl Cheatcode for clearMockedCallsCall { impl Cheatcode for mockCall_0Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, data, returnData } = self; - let (acc, _) = ccx.data.journaled_state.load_account(*callee, ccx.data.db)?; + // TODO: use ecx.load_account + let (acc, _) = ccx.ecx.journaled_state.load_account(*callee, &mut ccx.ecx.db)?; // Etches a single byte onto the account if it is empty to circumvent the `extcodesize` // check Solidity might perform. let empty_bytecode = acc.info.code.as_ref().map_or(true, Bytecode::is_empty); if empty_bytecode { - let code = Bytecode::new_raw(Bytes::copy_from_slice(&foundry_zksync_core::EMPTY_CODE)) - .to_checked(); - ccx.data.journaled_state.set_code(*callee, code.clone()); + let code = revm::interpreter::analysis::to_analysed(Bytecode::new_raw( + Bytes::copy_from_slice(&foundry_zksync_core::EMPTY_CODE), + )); + ccx.ecx.journaled_state.set_code(*callee, code.clone()); } if ccx.state.use_zk_vm { - foundry_zksync_core::cheatcodes::set_mocked_account( - *callee, - ccx.data.db, - &mut ccx.data.journaled_state, - ccx.caller, - ); + foundry_zksync_core::cheatcodes::set_mocked_account(*callee, ccx.ecx, ccx.caller); } mock_call(ccx.state, callee, data, None, returnData, InstructionResult::Return); @@ -42,7 +39,7 @@ impl Cheatcode for mockCall_0Call { impl Cheatcode for mockCall_1Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, msgValue, data, returnData } = self; - ccx.data.journaled_state.load_account(*callee, ccx.data.db)?; + ccx.ecx.load_account(*callee)?; mock_call(ccx.state, callee, data, Some(msgValue), returnData, InstructionResult::Return); Ok(Default::default()) } @@ -68,9 +65,9 @@ impl Cheatcode for mockCallRevert_1Call { fn mock_call( state: &mut Cheatcodes, callee: &Address, - cdata: &Vec, + cdata: &Bytes, value: Option<&U256>, - rdata: &Vec, + rdata: &Bytes, ret_type: InstructionResult, ) { state.mocked_calls.entry(*callee).or_default().insert( diff --git a/crates/cheatcodes/src/evm/prank.rs b/crates/cheatcodes/src/evm/prank.rs index 73269b23d..4e4ef81f7 100644 --- a/crates/cheatcodes/src/evm/prank.rs +++ b/crates/cheatcodes/src/evm/prank.rs @@ -29,16 +29,8 @@ impl Prank { new_origin: Option
, depth: u64, single_call: bool, - ) -> Prank { - Prank { - prank_caller, - prank_origin, - new_caller, - new_origin, - depth, - single_call, - used: false, - } + ) -> Self { + Self { prank_caller, prank_origin, new_caller, new_origin, depth, single_call, used: false } } /// Apply the prank by setting `used` to true iff it is false @@ -47,7 +39,7 @@ impl Prank { if self.used { None } else { - Some(Prank { used: true, ..self.clone() }) + Some(Self { used: true, ..self.clone() }) } } } @@ -96,10 +88,10 @@ fn prank( ) -> Result { let prank = Prank::new( ccx.caller, - ccx.data.env.tx.caller, + ccx.ecx.env.tx.caller, *new_caller, new_origin.copied(), - ccx.data.journaled_state.depth(), + ccx.ecx.journaled_state.depth(), single_call, ); diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs index 3e345db94..e4ee12513 100644 --- a/crates/cheatcodes/src/fs.rs +++ b/crates/cheatcodes/src/fs.rs @@ -1,16 +1,22 @@ -//! Implementations of [`Filesystem`](crate::Group::Filesystem) cheatcodes. +//! Implementations of [`Filesystem`](spec::Group::Filesystem) cheatcodes. +use super::string::parse; use crate::{Cheatcode, Cheatcodes, Result, Vm::*}; +use alloy_dyn_abi::DynSolType; use alloy_json_abi::ContractObject; -use alloy_primitives::U256; +use alloy_primitives::{Bytes, U256}; use alloy_sol_types::SolValue; -use foundry_common::{fs, get_artifact_path}; +use dialoguer::{Input, Password}; +use foundry_common::fs; use foundry_config::fs_permissions::FsAccessKind; +use semver::Version; use std::{ collections::hash_map::Entry, io::{BufRead, BufReader, Write}, - path::Path, + path::{Path, PathBuf}, process::Command, + sync::mpsc, + thread, time::{SystemTime, UNIX_EPOCH}, }; use walkdir::WalkDir; @@ -245,33 +251,143 @@ impl Cheatcode for writeLineCall { impl Cheatcode for getCodeCall { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { artifactPath: path } = self; - let object = read_bytecode(state, path)?; - if let Some(bin) = object.bytecode { - Ok(bin.abi_encode()) - } else { - Err(fmt_err!("No bytecode for contract. Is it abstract or unlinked?")) - } + 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; - let object = read_bytecode(state, path)?; - if let Some(bin) = object.deployed_bytecode { - Ok(bin.abi_encode()) - } else { - Err(fmt_err!("No deployed bytecode for contract. Is it abstract or unlinked?")) - } + Ok(get_artifact_code(state, path, true)?.abi_encode()) } } -/// Reads the bytecode object(s) from the matching artifact -fn read_bytecode(state: &Cheatcodes, path: &str) -> Result { - let path = get_artifact_path(&state.config.paths, path); +/// Returns the path to the json artifact depending on the input +/// +/// 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` +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(|_| fmt_err!("Error parsing version"))?) + } 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 { + if !id.source.ends_with(path) { + return false; + } + } + if let Some(name) = contract_name { + if id_name != name { + return false; + } + } + if let Some(ref version) = version { + if id.version.minor != version.minor || + id.version.major != version.major || + id.version.patch != version.patch + { + return false; + } + } + true + }) + .collect::>(); + + let artifact = match filtered.len() { + 0 => Err(fmt_err!("No matching artifact found")), + 1 => Ok(filtered[0]), + _ => { + // If we know the current script/test contract solc version, try to filter by it + state + .config + .running_version + .as_ref() + .and_then(|version| { + let filtered = filtered + .into_iter() + .filter(|(id, _)| id.version == *version) + .collect::>(); + + (filtered.len() == 1).then_some(filtered[0]) + }) + .ok_or_else(|| fmt_err!("Multiple matching artifacts found")) + } + }?; + + 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?")); + } else { + 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)?; - serde_json::from_str::(&data).map_err(Into::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 { @@ -296,6 +412,41 @@ impl Cheatcode for tryFfiCall { } } +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 @@ -365,16 +516,49 @@ fn ffi(state: &Cheatcodes, input: &[String]) -> Result { }; Ok(FfiResult { exitCode: output.status.code().unwrap_or(69), - stdout: encoded_stdout, - stderr: output.stderr, + 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| { + println!(); + err.to_string().into() + }), + Err(_) => { + println!(); + Err("Prompt timed out".into()) + } + } +} + #[cfg(test)] mod tests { use super::*; use crate::CheatsConfig; - use std::{path::PathBuf, sync::Arc}; + use std::sync::Arc; fn cheats() -> Cheatcodes { let config = CheatsConfig { @@ -391,7 +575,7 @@ mod tests { let cheats = cheats(); let args = ["echo".to_string(), hex::encode(msg)]; let output = ffi(&cheats, &args).unwrap(); - assert_eq!(output.stdout, msg); + assert_eq!(output.stdout, Bytes::from(msg)); } #[test] @@ -400,7 +584,7 @@ mod tests { let cheats = cheats(); let args = ["echo".to_string(), msg.to_string()]; let output = ffi(&cheats, &args).unwrap(); - assert_eq!(output.stdout, msg.as_bytes()); + assert_eq!(output.stdout, Bytes::from(msg.as_bytes())); } #[test] diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index c1e8d3a9d..d511bdf26 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -9,11 +9,10 @@ use crate::{ }, script::{Broadcast, ScriptWallets}, test::expect::{self, ExpectedEmit, ExpectedRevert, ExpectedRevertKind}, - CheatsConfig, CheatsCtxt, Error, Result, + CheatsConfig, CheatsCtxt, DynCheatcode, Error, Result, Vm::{self, AccountAccess}, }; - -use alloy_primitives::{keccak256, Address, Bytes, Log, LogData, B256, U256, U64}; +use alloy_primitives::{keccak256, Address, Bytes, Log, TxKind, B256, U256}; use alloy_rpc_types::request::{TransactionInput, TransactionRequest}; use alloy_sol_types::{SolInterface, SolValue}; use foundry_cheatcodes_common::{ @@ -21,13 +20,16 @@ use foundry_cheatcodes_common::{ mock::{MockCallDataContext, MockCallReturnData}, record::RecordAccess, }; -use foundry_common::{evm::Breakpoints, provider::alloy::RpcUrl}; +use foundry_common::{evm::Breakpoints, SELECTOR_LEN}; +use foundry_config::Config; use foundry_evm_core::{ + abi::Vm::stopExpectSafeMemoryCall, backend::{DatabaseError, DatabaseExt, LocalForkId, RevertDiagnostic}, constants::{ - CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, DEFAULT_CREATE2_DEPLOYER_CODE, + CHEATCODE_ADDRESS, CHEATCODE_CONTRACT_HASH, DEFAULT_CREATE2_DEPLOYER_CODE, HARDHAT_CONSOLE_ADDRESS, }, + InspectorExt, }; use foundry_zksync_compiler::{DualCompiledContract, DualCompiledContracts}; use foundry_zksync_core::{ @@ -37,14 +39,16 @@ use foundry_zksync_core::{ use itertools::Itertools; use revm::{ interpreter::{ - opcode, CallInputs, CallScheme, CreateInputs, Gas, InstructionResult, Interpreter, + opcode, CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, Gas, + InstructionResult, Interpreter, InterpreterAction, InterpreterResult, }, primitives::{ - AccountInfo, BlockEnv, Bytecode, CreateScheme, Env, ExecutionResult, HashMap as rHashMap, - Output, StorageSlot, TransactTo, KECCAK_EMPTY, + AccountInfo, BlockEnv, Bytecode, CreateScheme, Env, EvmStorageSlot, ExecutionResult, + HashMap as rHashMap, Output, TransactTo, KECCAK_EMPTY, }, - EVMData, Inspector, + EvmContext, InnerEvmContext, Inspector, }; +use rustc_hash::FxHashMap; use serde_json::Value; use std::{ collections::{BTreeMap, HashMap, VecDeque}, @@ -98,7 +102,7 @@ impl Context { #[derive(Clone, Debug, Default)] pub struct BroadcastableTransaction { /// The optional RPC URL. - pub rpc: Option, + pub rpc: Option, /// The transaction to broadcast. pub transaction: TransactionRequest, /// ZK-VM factory deps @@ -125,7 +129,7 @@ pub type BroadcastableTransactions = VecDeque; /// 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, Default)] +#[derive(Clone, Debug)] pub struct Cheatcodes { /// The block environment /// @@ -142,9 +146,6 @@ pub struct Cheatcodes { /// Address labels pub labels: HashMap, - /// Remembered private keys - pub script_wallets: Option, - /// Prank information pub prank: Option, @@ -167,6 +168,10 @@ pub struct Cheatcodes { /// Recorded logs pub recorded_logs: Option>, + /// Cache of the amount of gas used in previous call. + /// This is used by the `lastCallGas` cheatcode. + pub last_call_gas: Option, + /// Mocked calls // **Note**: inner must a BTreeMap because of special `Ord` impl for `MockCallDataContext` pub mocked_calls: HashMap>, @@ -177,15 +182,11 @@ pub struct Cheatcodes { pub expected_emits: VecDeque, /// Map of context depths to memory offset ranges that may be written to within the call depth. - pub allowed_mem_writes: HashMap>>, + pub allowed_mem_writes: FxHashMap>>, /// Current broadcasting information pub broadcast: Option, - /// Used to correct the nonce of --sender after the initiating call. For more, check - /// `docs/scripting`. - pub corrected_nonce: bool, - /// Scripting based transactions pub broadcastable_transactions: BroadcastableTransactions, @@ -251,12 +252,18 @@ pub struct Cheatcodes { pub persisted_factory_deps: HashMap>, } +// 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. - #[inline] pub fn new(config: Arc) -> Self { - let labels = config.labels.clone(); - let script_wallets = config.script_wallets.clone(); let mut dual_compiled_contracts = config.dual_compiled_contracts.clone(); // We add the empty bytecode manually so it is correctly translated in zk mode. @@ -271,11 +278,17 @@ impl Cheatcodes { zk_deployed_bytecode: zk_deployed_bytecode.clone(), zk_factory_deps: Default::default(), evm_bytecode_hash: B256::from_slice(&keccak256(&empty_bytes)[..]), - evm_deployed_bytecode: Bytecode::new_raw(empty_bytes.clone()) - .to_checked() - .bytecode - .to_vec(), - evm_bytecode: Bytecode::new_raw(empty_bytes).to_checked().bytecode.to_vec(), + evm_deployed_bytecode: Bytecode::new_raw(empty_bytes.clone()).bytecode().to_vec(), + evm_bytecode: Bytecode::new_raw(empty_bytes.clone()).bytecode().to_vec(), + }); + dual_compiled_contracts.push(DualCompiledContract { + name: String::from("CheatcodeBytecode"), + zk_bytecode_hash, + zk_deployed_bytecode: zk_deployed_bytecode.clone(), + zk_factory_deps: Default::default(), + evm_bytecode_hash: CHEATCODE_CONTRACT_HASH, + evm_deployed_bytecode: Bytecode::new_raw(empty_bytes.clone()).bytecode().to_vec(), + evm_bytecode: Bytecode::new_raw(empty_bytes).bytecode().to_vec(), }); let mut persisted_factory_deps = HashMap::new(); @@ -283,30 +296,77 @@ impl Cheatcodes { let startup_zk = config.use_zk; Self { - config, fs_commit: true, - labels, - script_wallets, + labels: config.labels.clone(), + config, dual_compiled_contracts, startup_zk, - ..Default::default() + block: Default::default(), + gas_price: Default::default(), + prank: Default::default(), + expected_revert: Default::default(), + fork_revert_diagnostic: Default::default(), + accesses: Default::default(), + recorded_account_diffs_stack: Default::default(), + recorded_logs: Default::default(), + last_call_gas: Default::default(), + mocked_calls: Default::default(), + expected_calls: Default::default(), + expected_emits: Default::default(), + allowed_mem_writes: Default::default(), + broadcast: Default::default(), + broadcastable_transactions: Default::default(), + context: Default::default(), + serialized_jsons: Default::default(), + eth_deals: Default::default(), + gas_metering: Default::default(), + gas_metering_create: Default::default(), + mapping_slots: Default::default(), + pc: Default::default(), + breakpoints: Default::default(), + combined_logs: Default::default(), + use_zk_vm: Default::default(), + persisted_factory_deps: Default::default(), } } + /// Returns the configured script wallets. + pub fn script_wallets(&self) -> Option<&ScriptWallets> { + self.config.script_wallets.as_ref() + } + fn apply_cheatcode( &mut self, - data: &mut EVMData<'_, DB>, + ecx: &mut EvmContext, call: &CallInputs, ) -> Result { // decode the cheatcode call - let decoded = Vm::VmCalls::abi_decode(&call.input, false)?; - let caller = call.context.caller; + let decoded = Vm::VmCalls::abi_decode(&call.input, false).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 - data.db.ensure_cheatcode_access_forking_mode(&caller)?; - - apply_dispatch(&decoded, &mut CheatsCtxt { state: self, data, caller }) + ecx.db.ensure_cheatcode_access_forking_mode(&caller)?; + + apply_dispatch( + &decoded, + &mut CheatsCtxt { + state: self, + ecx: &mut ecx.inner, + precompiles: &mut ecx.precompiles, + caller, + }, + ) } /// Determines the address of the contract and marks it as allowed @@ -316,23 +376,23 @@ impl Cheatcodes { /// automatically we need to determine the new address fn allow_cheatcodes_on_create( &self, - data: &mut EVMData<'_, DB>, + ecx: &mut InnerEvmContext, inputs: &CreateInputs, ) -> Address { - let old_nonce = data + let old_nonce = ecx .journaled_state .state .get(&inputs.caller) .map(|acc| acc.info.nonce) .unwrap_or_default(); let created_address = inputs.created_address(old_nonce); - if data.journaled_state.depth > 1 && !data.db.has_cheatcode_access(&inputs.caller) { + if ecx.journaled_state.depth > 1 && !ecx.db.has_cheatcode_access(&inputs.caller) { // we only grant cheat code access for new contracts if the caller also has // cheatcode access and the new contract is created in top most call return created_address; } - data.db.allow_cheatcode_access(created_address); + ecx.db.allow_cheatcode_access(created_address); created_address } @@ -341,7 +401,7 @@ impl Cheatcodes { /// /// 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, data: &mut EVMData<'_, DB>) { + pub fn on_revert(&mut self, ecx: &mut EvmContext) { trace!(deals=?self.eth_deals.len(), "rolling back deals"); // Delay revert clean up until expected revert is handled, if set. @@ -350,7 +410,7 @@ impl Cheatcodes { } // we only want to apply cleanup top level - if data.journaled_state.depth() > 0 { + if ecx.journaled_state.depth() > 0 { return; } @@ -358,7 +418,7 @@ impl Cheatcodes { // 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) = data.journaled_state.state.get_mut(&record.address) { + if let Some(acc) = ecx.journaled_state.state.get_mut(&record.address) { acc.info.balance = record.old_balance; } } @@ -372,7 +432,7 @@ impl Cheatcodes { /// * Translates all persisted addresses pub fn select_fork_vm( &mut self, - data: &mut EVMData<'_, DB>, + data: &mut InnerEvmContext, fork_id: LocalForkId, ) { let fork_info = data.db.get_fork_info(fork_id).expect("failed getting fork info"); @@ -385,7 +445,7 @@ impl Cheatcodes { /// Switch to EVM and translate block info, balances, nonces and deployed codes for persistent /// accounts - pub fn select_evm(&mut self, data: &mut EVMData<'_, DB>) { + pub fn select_evm(&mut self, data: &mut InnerEvmContext) { if !self.use_zk_vm { tracing::info!("already in EVM"); return @@ -407,8 +467,7 @@ impl Cheatcodes { // to not lose it across VMs. let block_info_key = CURRENT_VIRTUAL_BLOCK_INFO_POSITION.to_ru256(); - let (block_info, _) = - data.journaled_state.sload(system_account, block_info_key, data.db).unwrap_or_default(); + let (block_info, _) = data.sload(system_account, block_info_key).unwrap_or_default(); let (block_number, block_timestamp) = unpack_block_info(block_info.to_u256()); data.env.block.number = U256::from(block_number); data.env.block.timestamp = U256::from(block_timestamp); @@ -421,19 +480,14 @@ impl Cheatcodes { let balance_key = storage_key_for_eth_balance(&zk_address).key().to_ru256(); let nonce_key = get_nonce_key(&zk_address).key().to_ru256(); - let (balance, _) = data - .journaled_state - .sload(balance_account, balance_key, data.db) - .unwrap_or_default(); - let (full_nonce, _) = - data.journaled_state.sload(nonce_account, nonce_key, data.db).unwrap_or_default(); + let (balance, _) = data.sload(balance_account, balance_key).unwrap_or_default(); + let (full_nonce, _) = data.sload(nonce_account, nonce_key).unwrap_or_default(); let (tx_nonce, _deployment_nonce) = decompose_full_nonce(full_nonce.to_u256()); let nonce = tx_nonce.as_u64(); let account_code_key = get_code_key(&zk_address).key().to_ru256(); let (code_hash, code) = data - .journaled_state - .sload(account_code_account, account_code_key, data.db) + .sload(account_code_account, account_code_key) .map(|(value, _)| value) .ok() .and_then(|zk_bytecode_hash| { @@ -458,7 +512,7 @@ impl Cheatcodes { tracing::trace!(?address, "ignoring code translation for test contract"); } else { account.info.code_hash = code_hash; - account.info.code = code.clone(); + account.info.code.clone_from(&code); } } } @@ -467,7 +521,7 @@ impl Cheatcodes { /// accounts pub fn select_zk_vm( &mut self, - data: &mut EVMData<'_, DB>, + data: &mut InnerEvmContext, new_env: Option<&Env>, ) { if self.use_zk_vm { @@ -478,18 +532,18 @@ impl Cheatcodes { tracing::info!("switching to ZK-VM"); self.use_zk_vm = true; - let env = new_env.unwrap_or(data.env); + let env = new_env.unwrap_or(data.env.as_ref()); - let mut system_storage: rHashMap = Default::default(); + let mut system_storage: rHashMap = Default::default(); let block_info_key = CURRENT_VIRTUAL_BLOCK_INFO_POSITION.to_ru256(); let block_info = pack_block_info(env.block.number.as_limbs()[0], env.block.timestamp.as_limbs()[0]); - system_storage.insert(block_info_key, StorageSlot::new(block_info.to_ru256())); + system_storage.insert(block_info_key, EvmStorageSlot::new(block_info.to_ru256())); - let mut l2_eth_storage: rHashMap = Default::default(); - let mut nonce_storage: rHashMap = Default::default(); - let mut account_code_storage: rHashMap = Default::default(); - let mut known_codes_storage: rHashMap = Default::default(); + let mut l2_eth_storage: rHashMap = Default::default(); + let mut nonce_storage: rHashMap = Default::default(); + let mut account_code_storage: rHashMap = Default::default(); + let mut known_codes_storage: rHashMap = Default::default(); let mut deployed_codes: HashMap = Default::default(); for address in data.db.persistent_accounts() { @@ -501,21 +555,21 @@ impl Cheatcodes { let balance_key = storage_key_for_eth_balance(&zk_address).key().to_ru256(); let nonce_key = get_nonce_key(&zk_address).key().to_ru256(); - l2_eth_storage.insert(balance_key, StorageSlot::new(info.balance)); + l2_eth_storage.insert(balance_key, EvmStorageSlot::new(info.balance)); // TODO we need to find a proper way to handle deploy nonces instead of replicating let full_nonce = nonces_to_full_nonce(info.nonce.into(), info.nonce.into()); - nonce_storage.insert(nonce_key, StorageSlot::new(full_nonce.to_ru256())); + nonce_storage.insert(nonce_key, EvmStorageSlot::new(full_nonce.to_ru256())); if let Some(contract) = self.dual_compiled_contracts.iter().find(|contract| { info.code_hash != KECCAK_EMPTY && info.code_hash == contract.evm_bytecode_hash }) { account_code_storage.insert( zk_address.to_h256().to_ru256(), - StorageSlot::new(contract.zk_bytecode_hash.to_ru256()), + EvmStorageSlot::new(contract.zk_bytecode_hash.to_ru256()), ); known_codes_storage - .insert(contract.zk_bytecode_hash.to_ru256(), StorageSlot::new(U256::ZERO)); + .insert(contract.zk_bytecode_hash.to_ru256(), EvmStorageSlot::new(U256::ZERO)); let code_hash = B256::from_slice(contract.zk_bytecode_hash.as_bytes()); deployed_codes.insert( @@ -566,7 +620,7 @@ impl Cheatcodes { tracing::trace!(?address, "ignoring code translation for test contract"); } else { account.info.code_hash = info.code_hash; - account.info.code = info.code.clone(); + account.info.code.clone_from(&info.code); } } } @@ -574,26 +628,27 @@ impl Cheatcodes { impl Inspector for Cheatcodes { #[inline] - fn initialize_interp(&mut self, _: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { + fn initialize_interp(&mut self, _: &mut Interpreter, ecx: &mut EvmContext) { // 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() { - data.env.block = block; + ecx.env.block = block; } if let Some(gas_price) = self.gas_price.take() { - data.env.tx.gas_price = gas_price; + ecx.env.tx.gas_price = gas_price; } if self.startup_zk && !self.use_zk_vm { self.startup_zk = false; // We only do this once. - self.select_zk_vm(data, None); + self.select_zk_vm(ecx, None); } } - fn step_end(&mut self, interpreter: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { + #[inline] + fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext) { // ovverride address(x).balance retrieval to make it consistent between EraVM and EVM if self.use_zk_vm { let address = match interpreter.current_opcode() { - opcode::SELFBALANCE => interpreter.contract().address, + opcode::SELFBALANCE => interpreter.contract().target_address, opcode::BALANCE => { if interpreter.stack.is_empty() { interpreter.instruction_result = InstructionResult::StackUnderflow; @@ -606,7 +661,7 @@ impl Inspector for Cheatcodes { }; // Safety: Length is checked above. - let balance = foundry_zksync_core::balance(address, data.db, &mut data.journaled_state); + let balance = foundry_zksync_core::balance(address, ecx); // Skip the current BALANCE instruction since we've already handled it match interpreter.stack.push(balance) { @@ -620,7 +675,8 @@ impl Inspector for Cheatcodes { } } - fn step(&mut self, interpreter: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { + fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext) { + let ecx = &mut ecx.inner; self.pc = interpreter.program_counter(); // reset gas if gas metering is turned off @@ -687,7 +743,7 @@ impl Inspector for Cheatcodes { let key = try_or_continue!(interpreter.stack().peek(0)); storage_accesses .reads - .entry(interpreter.contract().address) + .entry(interpreter.contract().target_address) .or_default() .push(key); } @@ -697,12 +753,12 @@ impl Inspector for Cheatcodes { // An SSTORE does an SLOAD internally storage_accesses .reads - .entry(interpreter.contract().address) + .entry(interpreter.contract().target_address) .or_default() .push(key); storage_accesses .writes - .entry(interpreter.contract().address) + .entry(interpreter.contract().target_address) .or_default() .push(key); } @@ -715,39 +771,38 @@ impl Inspector for Cheatcodes { if interpreter.current_opcode() == opcode::SELFDESTRUCT { let target = try_or_continue!(interpreter.stack().peek(0)); // load balance of this account - let value = if let Ok((account, _)) = - data.journaled_state.load_account(interpreter.contract().address, data.db) + let value = ecx + .balance(interpreter.contract().target_address) + .map(|(b, _)| b) + .unwrap_or(U256::ZERO); + let account = Address::from_word(B256::from(target)); + // get previous balance and initialized status of the target account + // TODO: use load_account_exists + let (initialized, old_balance) = if let Ok((account, _)) = + ecx.journaled_state.load_account(account, &mut ecx.db) { - account.info.balance + (account.info.exists(), account.info.balance) } else { - U256::ZERO + (false, U256::ZERO) }; - let account = Address::from_word(B256::from(target)); - // get previous balance and initialized status of the target account - let (initialized, old_balance) = - if let Ok((account, _)) = data.journaled_state.load_account(account, data.db) { - (account.info.exists(), account.info.balance) - } else { - (false, U256::ZERO) - }; // register access for the target account let access = crate::Vm::AccountAccess { chainInfo: crate::Vm::ChainInfo { - forkId: data.db.active_fork_id().unwrap_or_default(), - chainId: U256::from(data.env.cfg.chain_id), + forkId: ecx.db.active_fork_id().unwrap_or_default(), + chainId: U256::from(ecx.env.cfg.chain_id), }, - accessor: interpreter.contract().address, + accessor: interpreter.contract().target_address, account, kind: crate::Vm::AccountAccessKind::SelfDestruct, initialized, oldBalance: old_balance, newBalance: old_balance + value, value, - data: vec![], + data: Bytes::new(), reverted: false, - deployedCode: vec![], + deployedCode: Bytes::new(), storageAccesses: vec![], - depth: data.journaled_state.depth(), + depth: ecx.journaled_state.depth(), }; // Ensure that we're not selfdestructing a context recording was initiated on if let Some(last) = account_accesses.last_mut() { @@ -761,20 +816,19 @@ impl Inspector for Cheatcodes { match interpreter.current_opcode() { opcode::SLOAD => { let key = try_or_continue!(interpreter.stack().peek(0)); - let address = interpreter.contract().address; + let address = interpreter.contract().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 data.journaled_state.load_account(address, data.db).is_ok() { - if let Ok((previous, _)) = data.journaled_state.sload(address, key, data.db) - { + if ecx.load_account(address).is_ok() { + if let Ok((previous, _)) = ecx.sload(address, key) { present_value = previous; } } let access = crate::Vm::StorageAccess { - account: interpreter.contract().address, + account: interpreter.contract().target_address, slot: key.into(), isWrite: false, previousValue: present_value.into(), @@ -784,19 +838,18 @@ impl Inspector for Cheatcodes { append_storage_access( recorded_account_diffs_stack, access, - data.journaled_state.depth(), + ecx.journaled_state.depth(), ); } opcode::SSTORE => { let key = try_or_continue!(interpreter.stack().peek(0)); let value = try_or_continue!(interpreter.stack().peek(1)); - let address = interpreter.contract().address; + let address = interpreter.contract().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 data.journaled_state.load_account(address, data.db).is_ok() { - if let Ok((previous, _)) = data.journaled_state.sload(address, key, data.db) - { + if ecx.load_account(address).is_ok() { + if let Ok((previous, _)) = ecx.sload(address, key) { previous_value = previous; } } @@ -812,7 +865,7 @@ impl Inspector for Cheatcodes { append_storage_access( recorded_account_diffs_stack, access, - data.journaled_state.depth(), + ecx.journaled_state.depth(), ); } // Record account accesses via the EXT family of opcodes @@ -832,7 +885,8 @@ impl Inspector for Cheatcodes { .peek(0)))); let balance; let initialized; - if let Ok((acc, _)) = data.journaled_state.load_account(address, data.db) { + // TODO: use ecx.load_account + if let Ok((acc, _)) = ecx.journaled_state.load_account(address, &mut ecx.db) { initialized = acc.info.exists(); balance = acc.info.balance; } else { @@ -841,21 +895,21 @@ impl Inspector for Cheatcodes { } let account_access = crate::Vm::AccountAccess { chainInfo: crate::Vm::ChainInfo { - forkId: data.db.active_fork_id().unwrap_or_default(), - chainId: U256::from(data.env.cfg.chain_id), + forkId: ecx.db.active_fork_id().unwrap_or_default(), + chainId: U256::from(ecx.env.cfg.chain_id), }, - accessor: interpreter.contract().address, + accessor: interpreter.contract().target_address, account: address, kind, initialized, oldBalance: balance, newBalance: balance, value: U256::ZERO, - data: vec![], + data: Bytes::new(), reverted: false, - deployedCode: vec![], + deployedCode: Bytes::new(), storageAccesses: vec![], - depth: data.journaled_state.depth(), + depth: ecx.journaled_state.depth(), }; // Record the EXT* call as an account access at the current depth // (future storage accesses will be recorded in a new "Resume" context) @@ -873,7 +927,7 @@ impl Inspector for Cheatcodes { // 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. - if let Some(ranges) = self.allowed_mem_writes.get(&data.journaled_state.depth()) { + if let Some(ranges) = self.allowed_mem_writes.get(&ecx.journaled_state.depth()) { // 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 @@ -898,8 +952,17 @@ impl Inspector for Cheatcodes { 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_continue!(interpreter.stack().peek(1)).to_be_bytes::<32>(); + let selector = stopExpectSafeMemoryCall {}.cheatcode().func.selector_bytes; + if value[0..SELECTOR_LEN] == selector { + return + } + disallowed_mem_write(offset, 32, interpreter, ranges); - interpreter.instruction_result = InstructionResult::Revert; return } } @@ -911,7 +974,6 @@ impl Inspector for Cheatcodes { // unexpectedly mutated. if !ranges.iter().any(|range| range.contains(&offset)) { disallowed_mem_write(offset, 1, interpreter, ranges); - interpreter.instruction_result = InstructionResult::Revert; return } } @@ -931,7 +993,6 @@ impl Inspector for Cheatcodes { range.contains(&offset) && range.contains(&(offset + 31)) }) { disallowed_mem_write(offset, 32, interpreter, ranges); - interpreter.instruction_result = InstructionResult::Revert; return } } @@ -940,11 +1001,48 @@ impl Inspector for Cheatcodes { // OPERATIONS WITH OFFSET AND SIZE ON STACK // //////////////////////////////////////////////////////////////// + opcode::CALL => { + // The destination offset of the operation is the fifth element on the stack. + let dest_offset = try_or_continue!(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_continue!(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_continue!(interpreter.stack().peek(1)).to_be_bytes::<32>().into()); + if to == CHEATCODE_ADDRESS { + let args_offset = try_or_continue!(interpreter.stack().peek(3)).saturating_to::(); + let args_size = try_or_continue!(interpreter.stack().peek(4)).saturating_to::(); + let selector = stopExpectSafeMemoryCall {}.cheatcode().func.selector_bytes; + let memory_word = interpreter.shared_memory.slice(args_offset, args_size); + if memory_word[0..SELECTOR_LEN] == selector { + return + } + } + + disallowed_mem_write(dest_offset, size, interpreter, ranges); + return + } + } + $(opcode::$opcode => { - // The destination offset of the operation is at the top of the stack. + // The destination offset of the operation. let dest_offset = try_or_continue!(interpreter.stack().peek($offset_depth)).saturating_to::(); - // The size of the data that will be copied is the third item on the stack. + // The size of the data that will be copied. let size = try_or_continue!(interpreter.stack().peek($size_depth)).saturating_to::(); // If none of the allowed ranges contain [dest_offset, dest_offset + size), @@ -963,7 +1061,6 @@ impl Inspector for Cheatcodes { // that gives information about the allowed ranges and revert. if fail_cond { disallowed_mem_write(dest_offset, size, interpreter, ranges); - interpreter.instruction_result = InstructionResult::Revert; return } })* @@ -979,7 +1076,6 @@ impl Inspector for Cheatcodes { (CODECOPY, 0, 2, true), (RETURNDATACOPY, 0, 2, true), (EXTCODECOPY, 1, 3, true), - (CALL, 5, 6, true), (CALLCODE, 5, 6, true), (STATICCALL, 4, 5, true), (DELEGATECALL, 4, 5, true), @@ -1002,47 +1098,84 @@ impl Inspector for Cheatcodes { } } - fn log(&mut self, _: &mut EVMData<'_, DB>, address: &Address, topics: &[B256], data: &Bytes) { + fn log(&mut self, _context: &mut EvmContext, log: &Log) { if !self.expected_emits.is_empty() { - expect::handle_expect_emit(self, address, topics, data); + expect::handle_expect_emit(self, log); } // Stores this log if `recordLogs` has been called if let Some(storage_recorded_logs) = &mut self.recorded_logs { storage_recorded_logs.push(Vm::Log { - topics: topics.to_vec(), - data: data.to_vec(), - emitter: *address, + topics: log.data.topics().to_vec(), + data: log.data.data.clone(), + emitter: log.address, }); } self.combined_logs.push(None); } - fn call( - &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CallInputs, - ) -> (InstructionResult, Gas, Bytes) { + fn call(&mut self, ecx: &mut EvmContext, call: &mut CallInputs) -> Option { let gas = Gas::new(call.gas_limit); - if call.contract == CHEATCODE_ADDRESS { - return match self.apply_cheatcode(data, call) { - Ok(retdata) => (InstructionResult::Return, gas, retdata.into()), - Err(err) => (InstructionResult::Revert, gas, err.abi_encode().into()), + // 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 ecx.journaled_state.depth == 0 { + let sender = ecx.env.tx.caller; + if sender != Config::DEFAULT_SENDER { + 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(), + }) + } + }; + let prev = account.info.nonce; + account.info.nonce = prev.saturating_sub(1); + + debug!(target: "cheatcodes", %sender, nonce=account.info.nonce, prev, "corrected nonce"); + } + } + + if call.target_address == CHEATCODE_ADDRESS { + return match self.apply_cheatcode(ecx, call) { + Ok(retdata) => Some(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Return, + output: retdata.into(), + gas, + }, + memory_offset: call.return_memory_offset.clone(), + }), + Err(err) => Some(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: err.abi_encode().into(), + gas, + }, + memory_offset: call.return_memory_offset.clone(), + }), }; } - if call.contract == HARDHAT_CONSOLE_ADDRESS { + if call.bytecode_address == HARDHAT_CONSOLE_ADDRESS { self.combined_logs.push(None); - return (InstructionResult::Continue, gas, Bytes::new()); + return None; } // Handle expected calls // Grab the different calldatas expected. - if let Some(expected_calls_for_target) = self.expected_calls.get_mut(&(call.contract)) { + 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... @@ -1053,7 +1186,7 @@ impl Inspector for Cheatcodes { // The value matches, if provided expected .value - .map_or(true, |value| value == call.transfer.value) && + .map_or(true, |value| Some(value) == call.transfer_value()) && // The gas matches, if provided expected.gas.map_or(true, |gas| gas == call.gas_limit) && // The minimum gas matches, if provided @@ -1065,41 +1198,44 @@ impl Inspector for Cheatcodes { } // Handle mocked calls - if let Some(mocks) = self.mocked_calls.get(&call.contract) { - let ctx = MockCallDataContext { - calldata: call.input.clone(), - value: Some(call.transfer.value), - }; + if let Some(mocks) = self.mocked_calls.get(&call.bytecode_address) { + let ctx = + MockCallDataContext { calldata: call.input.clone(), value: call.transfer_value() }; if let Some(return_data) = mocks.get(&ctx).or_else(|| { mocks .iter() .find(|(mock, _)| { call.input.get(..mock.calldata.len()) == Some(&mock.calldata[..]) && - mock.value.map_or(true, |value| value == call.transfer.value) + mock.value.map_or(true, |value| Some(value) == call.transfer_value()) }) .map(|(_, v)| v) }) { - return (return_data.ret_type, gas, return_data.data.clone()); + return Some(CallOutcome { + result: InterpreterResult { + result: return_data.ret_type, + output: return_data.data.clone(), + gas, + }, + memory_offset: call.return_memory_offset.clone(), + }) } } // Apply our prank if let Some(prank) = &self.prank { - if data.journaled_state.depth() >= prank.depth && - call.context.caller == prank.prank_caller + if ecx.inner.journaled_state.depth() >= prank.depth && call.caller == prank.prank_caller { let mut prank_applied = false; // At the target depth we set `msg.sender` - if data.journaled_state.depth() == prank.depth { - call.context.caller = prank.new_caller; - call.transfer.source = prank.new_caller; + if ecx.inner.journaled_state.depth() == prank.depth { + 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 { - data.env.tx.caller = new_origin; + ecx.inner.env.tx.caller = new_origin; prank_applied = true; } @@ -1118,37 +1254,44 @@ impl Inspector for Cheatcodes { // // We do this because any subsequent contract calls *must* exist on chain and // we only want to grab *this* call, not internal ones - if data.journaled_state.depth() == broadcast.depth && - call.context.caller == broadcast.original_caller + if ecx.inner.journaled_state.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. - data.env.tx.caller = broadcast.new_origin; + ecx.inner.env.tx.caller = broadcast.new_origin; - call.context.caller = broadcast.new_origin; - call.transfer.source = 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) = - data.journaled_state.load_account(broadcast.new_origin, data.db) - { - return (InstructionResult::Revert, gas, Error::encode(err)); + if let Err(err) = ecx.inner.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(), + }) } - let is_fixed_gas_limit = check_if_fixed_gas_limit(data, call.gas_limit); + let is_fixed_gas_limit = check_if_fixed_gas_limit(&ecx.inner, call.gas_limit); - let nonce = foundry_zksync_core::nonce( - broadcast.new_origin, - data.db, - &mut data.journaled_state, - ) as u64; + let account = + ecx.inner.journaled_state.state().get_mut(&broadcast.new_origin).unwrap(); + + let nonce = if self.use_zk_vm { + foundry_zksync_core::nonce(broadcast.new_origin, ecx) as u64 + } else { + account.info.nonce + }; let account = - data.journaled_state.state().get_mut(&broadcast.new_origin).unwrap(); + ecx.inner.journaled_state.state().get_mut(&broadcast.new_origin).unwrap(); let zk_tx = if self.use_zk_vm { // We shouldn't need factory_deps for CALLs @@ -1158,15 +1301,15 @@ impl Inspector for Cheatcodes { }; self.broadcastable_transactions.push_back(BroadcastableTransaction { - rpc: data.db.active_fork_url(), + rpc: ecx.inner.db.active_fork_url(), transaction: TransactionRequest { from: Some(broadcast.new_origin), - to: Some(call.contract), - value: Some(call.transfer.value), + to: Some(TxKind::from(Some(call.target_address))), + value: call.transfer_value(), input: TransactionInput::new(call.input.clone()), - nonce: Some(U64::from(nonce)), + nonce: Some(nonce), gas: if is_fixed_gas_limit { - Some(U256::from(call.gas_limit)) + Some(call.gas_limit as u128) } else { None }, @@ -1184,7 +1327,14 @@ impl Inspector for Cheatcodes { 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 (InstructionResult::Revert, Gas::new(0), Error::encode(msg)); + return Some(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Error::encode(msg), + gas, + }, + memory_offset: call.return_memory_offset.clone(), + }) } } } @@ -1195,14 +1345,17 @@ impl Inspector for Cheatcodes { // nonce, a non-zero KECCAK_EMPTY codehash, or non-empty code let initialized; let old_balance; - if let Ok((acc, _)) = data.journaled_state.load_account(call.contract, data.db) { + // TODO: use ecx.load_account + if let Ok((acc, _)) = + ecx.inner.journaled_state.load_account(call.target_address, &mut ecx.inner.db) + { initialized = acc.info.exists(); old_balance = acc.info.balance; } else { initialized = false; old_balance = U256::ZERO; } - let kind = match call.context.scheme { + let kind = match call.scheme { CallScheme::Call => crate::Vm::AccountAccessKind::Call, CallScheme::CallCode => crate::Vm::AccountAccessKind::CallCode, CallScheme::DelegateCall => crate::Vm::AccountAccessKind::DelegateCall, @@ -1215,29 +1368,29 @@ impl Inspector for Cheatcodes { // as "warm" if the call from which they were accessed is reverted recorded_account_diffs_stack.push(vec![AccountAccess { chainInfo: crate::Vm::ChainInfo { - forkId: data.db.active_fork_id().unwrap_or_default(), - chainId: U256::from(data.env.cfg.chain_id), + forkId: ecx.inner.db.active_fork_id().unwrap_or_default(), + chainId: U256::from(ecx.inner.env.cfg.chain_id), }, - accessor: call.context.caller, - account: call.contract, + accessor: call.caller, + account: call.bytecode_address, kind, initialized, oldBalance: old_balance, newBalance: U256::ZERO, // updated on call_end - value: call.transfer.value, - data: call.input.to_vec(), + value: call.call_value(), + data: call.input.clone(), reverted: false, - deployedCode: vec![], + deployedCode: Bytes::new(), storageAccesses: vec![], // updated on step - depth: data.journaled_state.depth(), + depth: ecx.inner.journaled_state.depth(), }]); } if self.use_zk_vm { - if let TransactTo::Call(test_contract) = data.env.tx.transact_to { - if call.contract == test_contract { - info!("using evm for calls to test contract {:?}", data.env); - return (InstructionResult::Continue, gas, Bytes::new()) + if let TransactTo::Call(test_contract) = ecx.env.tx.transact_to { + if call.bytecode_address == test_contract { + info!("using evm for calls to test contract {:?}", ecx.env); + return None } } @@ -1249,56 +1402,66 @@ impl Inspector for Cheatcodes { accesses: self.accesses.as_mut(), persisted_factory_deps: Some(&mut self.persisted_factory_deps), }; - if let Ok(result) = foundry_zksync_core::vm::call::<_, DatabaseError>( - call, - data.env, - data.db, - &mut data.journaled_state, - ccx, - ) { - self.combined_logs.extend(result.logs.clone().into_iter().map(|log| { - Some(Log { - address: log.address, - data: LogData::new_unchecked(log.topics, log.data), - }) - })); + if let Ok(result) = foundry_zksync_core::vm::call::<_, DatabaseError>(call, ecx, ccx) { + self.combined_logs.extend(result.logs.clone().into_iter().map(Some)); //for each log in cloned logs call handle_expect_emit if !self.expected_emits.is_empty() { for log in result.logs { - expect::handle_expect_emit(self, &log.address, &log.topics, &log.data); + expect::handle_expect_emit(self, &log); } } return match result.execution_result { ExecutionResult::Success { output, .. } => match output { - Output::Call(bytes) => (InstructionResult::Return, gas, bytes), - _ => (InstructionResult::Revert, gas, Bytes::new()), + Output::Call(bytes) => Some(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Return, + output: bytes, + gas, + }, + memory_offset: call.return_memory_offset.clone(), + }), + _ => Some(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Bytes::new(), + gas, + }, + memory_offset: call.return_memory_offset.clone(), + }), }, - ExecutionResult::Revert { output, .. } => { - (InstructionResult::Revert, gas, output) - } - ExecutionResult::Halt { .. } => ( - InstructionResult::Revert, - gas, - Bytes::from_iter(String::from("zk vm halted").as_bytes()), - ), + ExecutionResult::Revert { output, .. } => Some(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output, + gas, + }, + memory_offset: call.return_memory_offset.clone(), + }), + ExecutionResult::Halt { .. } => Some(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Bytes::from_iter(String::from("zk vm halted").as_bytes()), + gas, + }, + memory_offset: call.return_memory_offset.clone(), + }), } } } - (InstructionResult::Continue, gas, Bytes::new()) + None } fn call_end( &mut self, - data: &mut EVMData<'_, DB>, + ecx: &mut EvmContext, call: &CallInputs, - remaining_gas: Gas, - status: InstructionResult, - retdata: Bytes, - ) -> (InstructionResult, Gas, Bytes) { - let cheatcode_call = - call.contract == CHEATCODE_ADDRESS || call.contract == HARDHAT_CONSOLE_ADDRESS; + mut outcome: CallOutcome, + ) -> CallOutcome { + let ecx = &mut ecx.inner; + 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 appplied for cheatcodes in the `call` hook. @@ -1306,8 +1469,8 @@ impl Inspector for Cheatcodes { if !cheatcode_call { // Clean up pranks if let Some(prank) = &self.prank { - if data.journaled_state.depth() == prank.depth { - data.env.tx.caller = prank.prank_origin; + if ecx.journaled_state.depth() == prank.depth { + ecx.env.tx.caller = prank.prank_origin; // Clean single-call prank once we have returned to the original depth if prank.single_call { @@ -1318,8 +1481,8 @@ impl Inspector for Cheatcodes { // Clean up broadcast if let Some(broadcast) = &self.broadcast { - if data.journaled_state.depth() == broadcast.depth { - data.env.tx.caller = broadcast.original_origin; + if ecx.journaled_state.depth() == broadcast.depth { + ecx.env.tx.caller = broadcast.original_origin; // Clean single-call broadcast once we have returned to the original depth if broadcast.single_call { @@ -1331,7 +1494,7 @@ impl Inspector for Cheatcodes { // Handle expected reverts if let Some(expected_revert) = &self.expected_revert { - if data.journaled_state.depth() <= expected_revert.depth { + if ecx.journaled_state.depth() <= expected_revert.depth { let needs_processing: bool = match expected_revert.kind { ExpectedRevertKind::Default => !cheatcode_call, // `pending_processing` == true means that we're in the `call_end` hook for @@ -1346,14 +1509,20 @@ impl Inspector for Cheatcodes { return match expect::handle_expect_revert( false, expected_revert.reason.as_deref(), - status, - retdata, + outcome.result.result, + outcome.result.output.clone(), ) { Err(error) => { - trace!(expected=?expected_revert, ?error, ?status, "Expected revert mismatch"); - (InstructionResult::Revert, remaining_gas, error.abi_encode().into()) + trace!(expected=?expected_revert, ?error, status=?outcome.result.result, "Expected revert mismatch"); + outcome.result.result = InstructionResult::Revert; + outcome.result.output = error.abi_encode().into(); + outcome + } + Ok((_, retdata)) => { + outcome.result.result = InstructionResult::Return; + outcome.result.output = retdata; + outcome } - Ok((_, retdata)) => (InstructionResult::Return, remaining_gas, retdata), }; } @@ -1372,19 +1541,30 @@ impl Inspector for Cheatcodes { // Exit early for calls to cheatcodes as other logic is not relevant for cheatcode // invocations if cheatcode_call { - return (status, remaining_gas, retdata); + return outcome } + // 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.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 data.journaled_state.depth() > 0 { + if ecx.journaled_state.depth() > 0 { let mut last_recorded_depth = recorded_account_diffs_stack.pop().expect("missing CALL account accesses"); // Update the reverted status of all deeper calls if this call reverted, in // accordance with EVM behavior - if status.is_revert() { + if outcome.result.is_revert() { last_recorded_depth.iter_mut().for_each(|element| { element.reverted = true; element @@ -1397,8 +1577,10 @@ impl Inspector for Cheatcodes { // 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. - if call_access.depth == data.journaled_state.depth() { - if let Ok((acc, _)) = data.journaled_state.load_account(call.contract, data.db) + if call_access.depth == ecx.journaled_state.depth() { + // TODO: use ecx.load_account + if let Ok((acc, _)) = + ecx.journaled_state.load_account(call.target_address, &mut ecx.db) { debug_assert!(access_is_call(call_access.kind)); call_access.newBalance = acc.info.balance; @@ -1429,17 +1611,15 @@ impl Inspector for Cheatcodes { let should_check_emits = self .expected_emits .iter() - .any(|expected| expected.depth == data.journaled_state.depth()) && + .any(|expected| expected.depth == ecx.journaled_state.depth()) && // Ignore staticcalls !call.is_static; if should_check_emits { // Not all emits were matched. if self.expected_emits.iter().any(|expected| !expected.found) { - return ( - InstructionResult::Revert, - remaining_gas, - "log != expected log".abi_encode().into(), - ); + outcome.result.result = InstructionResult::Revert; + outcome.result.output = "log != expected log".abi_encode().into(); + return outcome } else { // All emits were found, we're good. // Clear the queue, as we expect the user to declare more events for the next call @@ -1454,33 +1634,34 @@ impl Inspector for Cheatcodes { // if there's a revert and a previous call was diagnosed as fork related revert then we can // return a better error here - if status == InstructionResult::Revert { + if outcome.result.is_revert() { if let Some(err) = diag { - return (status, remaining_gas, Error::encode(err.to_error_msg(&self.labels))); + outcome.result.output = Error::encode(err.to_error_msg(&self.labels)); + return outcome } } // try to diagnose reverts in multi-fork mode where a call is made to an address that does // not exist - if let TransactTo::Call(test_contract) = data.env.tx.transact_to { + if let TransactTo::Call(test_contract) = ecx.env.tx.transact_to { // 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 data.db.is_forked_mode() && - status == InstructionResult::Stop && - call.contract != test_contract + if ecx.db.is_forked_mode() && + outcome.result.result == InstructionResult::Stop && + call.target_address != test_contract { self.fork_revert_diagnostic = - data.db.diagnose_revert(call.contract, &data.journaled_state); + ecx.db.diagnose_revert(call.target_address, &ecx.journaled_state); } } // If the depth is 0, then this is the root call terminating - if data.journaled_state.depth() == 0 { + if ecx.journaled_state.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 status == InstructionResult::Revert { - return (status, remaining_gas, retdata); + if outcome.result.is_revert() { + return outcome; } // If there's not a revert, we can continue on to run the last logic for expect* @@ -1512,7 +1693,7 @@ impl Inspector for Cheatcodes { .into_iter() .flatten() .join(", "); - let but = if status.is_ok() { + let but = if outcome.result.is_ok() { let s = if *actual_count == 1 { "" } else { "s" }; format!("was called {actual_count} time{s}") } else { @@ -1525,7 +1706,10 @@ impl Inspector for Cheatcodes { "expected call to {address} with {expected_values} \ to be called {count} time{s}, but {but}" ); - return (InstructionResult::Revert, remaining_gas, Error::encode(msg)); + outcome.result.result = InstructionResult::Revert; + outcome.result.output = Error::encode(msg); + + return outcome; } } } @@ -1535,69 +1719,74 @@ impl Inspector for Cheatcodes { self.expected_emits.retain(|expected| !expected.found); // If not empty, we got mismatched emits if !self.expected_emits.is_empty() { - let msg = if status.is_ok() { + 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`" }; - return (InstructionResult::Revert, remaining_gas, Error::encode(msg)); + outcome.result.result = InstructionResult::Revert; + outcome.result.output = Error::encode(msg); + return outcome; } } - (status, remaining_gas, retdata) + outcome } fn create( &mut self, - data: &mut EVMData<'_, DB>, + ecx: &mut EvmContext, call: &mut CreateInputs, - ) -> (InstructionResult, Option
, Gas, Bytes) { + ) -> Option { let gas = Gas::new(call.gas_limit); // Apply our prank if let Some(prank) = &self.prank { - if data.journaled_state.depth() >= prank.depth && call.caller == prank.prank_caller { + if ecx.journaled_state.depth() >= prank.depth && call.caller == prank.prank_caller { // At the target depth we set `msg.sender` - if data.journaled_state.depth() == prank.depth { + if ecx.journaled_state.depth() == prank.depth { call.caller = prank.new_caller; } // At the target depth, or deeper, we set `tx.origin` if let Some(new_origin) = prank.new_origin { - data.env.tx.caller = new_origin; + ecx.env.tx.caller = new_origin; } } } // Apply our broadcast if let Some(broadcast) = &self.broadcast { - if data.journaled_state.depth() >= broadcast.depth && + if ecx.journaled_state.depth() >= broadcast.depth && call.caller == broadcast.original_caller { - if let Err(err) = data.journaled_state.load_account(broadcast.new_origin, data.db) { - return (InstructionResult::Revert, None, gas, Error::encode(err)); + if let Err(err) = ecx.load_account(broadcast.new_origin) { + return Some(CreateOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Error::encode(err), + gas, + }, + address: None, + }) } - data.env.tx.caller = broadcast.new_origin; + ecx.env.tx.caller = broadcast.new_origin; - if data.journaled_state.depth() == broadcast.depth { - let (mut bytecode, mut to, mut nonce) = process_broadcast_create( - broadcast.new_origin, - call.init_code.clone(), - data, - call, - ); - let is_fixed_gas_limit = check_if_fixed_gas_limit(data, call.gas_limit); + if ecx.journaled_state.depth() == broadcast.depth { + call.caller = broadcast.new_origin; + let is_fixed_gas_limit = check_if_fixed_gas_limit(ecx, call.gas_limit); + + let account = &ecx.journaled_state.state()[&broadcast.new_origin]; + let mut to = None; + let mut nonce = account.info.nonce; + let mut call_init_code = call.init_code.clone(); let mut zk_tx = if self.use_zk_vm { - to = Some(CONTRACT_DEPLOYER_ADDRESS.to_address()); - nonce = foundry_zksync_core::nonce( - broadcast.new_origin, - data.db, - &mut data.journaled_state, - ) as u64; + to = Some(TxKind::Call(CONTRACT_DEPLOYER_ADDRESS.to_address())); + nonce = foundry_zksync_core::nonce(broadcast.new_origin, ecx) as u64; let contract = self .dual_compiled_contracts .find_by_evm_bytecode(&call.init_code.0) @@ -1615,15 +1804,14 @@ impl Inspector for Cheatcodes { contract.zk_bytecode_hash, constructor_input, ); - bytecode = Bytes::from(create_input); + call_init_code = Bytes::from(create_input); Some(factory_deps) } else { None }; - let rpc = data.db.active_fork_url(); - + let rpc = ecx.db.active_fork_url(); if let Some(factory_deps) = zk_tx { let mut batched = foundry_zksync_core::vm::batch_factory_dependencies(factory_deps); @@ -1636,15 +1824,9 @@ impl Inspector for Cheatcodes { rpc: rpc.clone(), transaction: TransactionRequest { from: Some(broadcast.new_origin), - to: Some(Address::ZERO), + to: Some(TxKind::Call(Address::ZERO)), value: Some(call.value), - input: TransactionInput::default(), - nonce: Some(U64::from(nonce)), - gas: if is_fixed_gas_limit { - Some(U256::from(call.gas_limit)) - } else { - None - }, + nonce: Some(nonce), ..Default::default() }, zk_tx: Some(ZkTransactionMetadata { factory_deps }), @@ -1661,16 +1843,16 @@ impl Inspector for Cheatcodes { from: Some(broadcast.new_origin), to, value: Some(call.value), - input: TransactionInput::new(bytecode), - nonce: Some(U64::from(nonce)), + input: TransactionInput::new(call_init_code), + nonce: Some(nonce), gas: if is_fixed_gas_limit { - Some(U256::from(call.gas_limit)) + Some(call.gas_limit as u128) } else { None }, ..Default::default() }, - zk_tx: zk_tx.map(|factory_deps| ZkTransactionMetadata { factory_deps }), + zk_tx: zk_tx.map(ZkTransactionMetadata::new), }); let kind = match call.scheme { @@ -1682,41 +1864,18 @@ impl Inspector for Cheatcodes { } } - // Apply the Create2 deployer - if self.broadcast.is_some() || self.config.always_use_create_2_factory { - match apply_create2_deployer( - data, - call, - self.prank.as_ref(), - self.broadcast.as_ref(), - self.recorded_account_diffs_stack.as_mut(), - ) { - Ok(_) => {} - Err(err) => return (InstructionResult::Revert, None, gas, Error::encode(err)), - }; - } - // allow cheatcodes from the address of the new contract // Compute the address *after* any possible broadcast updates, so it's based on the updated // call inputs - let address = self.allow_cheatcodes_on_create(data, call); + let address = self.allow_cheatcodes_on_create(ecx, call); // If `recordAccountAccesses` has been called, record the create if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { - // If the create scheme is create2, and the caller is the DEFAULT_CREATE2_DEPLOYER then - // we must add 1 to the depth to account for the call to the create2 factory. - let mut depth = data.journaled_state.depth(); - if let CreateScheme::Create2 { salt: _ } = call.scheme { - if call.caller == DEFAULT_CREATE2_DEPLOYER { - depth += 1; - } - } - // Record the create context as an account access and create a new vector to record all // subsequent account accesses recorded_account_diffs_stack.push(vec![AccountAccess { chainInfo: crate::Vm::ChainInfo { - forkId: data.db.active_fork_id().unwrap_or_default(), - chainId: U256::from(data.env.cfg.chain_id), + forkId: ecx.db.active_fork_id().unwrap_or_default(), + chainId: U256::from(ecx.env.cfg.chain_id), }, accessor: call.caller, account: address, @@ -1725,11 +1884,11 @@ impl Inspector for Cheatcodes { oldBalance: U256::ZERO, // updated on create_end newBalance: U256::ZERO, // updated on create_end value: call.value, - data: call.init_code.to_vec(), + data: call.init_code.clone(), reverted: false, - deployedCode: vec![], // updated on create_end - storageAccesses: vec![], // updated on create_end - depth, + deployedCode: Bytes::new(), // updated on create_end + storageAccesses: vec![], // updated on create_end + depth: ecx.journaled_state.depth(), }]); } @@ -1737,7 +1896,7 @@ impl Inspector for Cheatcodes { info!("running create in zk vm"); if call.init_code.0 == DEFAULT_CREATE2_DEPLOYER_CODE { info!("ignoring DEFAULT_CREATE2_DEPLOYER_CODE for zk"); - return (InstructionResult::Continue, None, gas, Bytes::new()) + return None } let zk_contract = self @@ -1758,61 +1917,72 @@ impl Inspector for Cheatcodes { call, zk_contract, factory_deps, - data.env, - data.db, - &mut data.journaled_state, + ecx, ccx, ) { - self.combined_logs.extend(result.logs.clone().into_iter().map(|log| { - Some(Log { - address: log.address, - data: LogData::new_unchecked(log.topics, log.data), - }) - })); + self.combined_logs.extend(result.logs.clone().into_iter().map(Some)); // for each log in cloned logs call handle_expect_emit if !self.expected_emits.is_empty() { for log in result.logs { - expect::handle_expect_emit(self, &log.address, &log.topics, &log.data); + expect::handle_expect_emit(self, &log); } } return match result.execution_result { ExecutionResult::Success { output, .. } => match output { - Output::Create(bytes, address) => { - (InstructionResult::Return, address, gas, bytes) - } - _ => (InstructionResult::Revert, None, gas, Bytes::new()), + Output::Create(bytes, address) => Some(CreateOutcome { + result: InterpreterResult { + result: InstructionResult::Return, + output: bytes, + gas, + }, + address, + }), + _ => Some(CreateOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Bytes::new(), + gas, + }, + address: None, + }), }, - ExecutionResult::Revert { output, .. } => { - (InstructionResult::Revert, None, gas, output) - } - ExecutionResult::Halt { .. } => ( - InstructionResult::Revert, - None, - gas, - Bytes::from_iter(String::from("zk vm halted").as_bytes()), - ), + ExecutionResult::Revert { output, .. } => Some(CreateOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output, + gas, + }, + address: None, + }), + ExecutionResult::Halt { .. } => Some(CreateOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Bytes::from_iter(String::from("zk vm halted").as_bytes()), + gas, + }, + address: None, + }), } } } - (InstructionResult::Continue, None, gas, Bytes::new()) + None } fn create_end( &mut self, - data: &mut EVMData<'_, DB>, - _: &CreateInputs, - status: InstructionResult, - address: Option
, - remaining_gas: Gas, - retdata: Bytes, - ) -> (InstructionResult, Option
, Gas, Bytes) { + ecx: &mut EvmContext, + _call: &CreateInputs, + mut outcome: CreateOutcome, + ) -> CreateOutcome { + let ecx = &mut ecx.inner; + // Clean up pranks if let Some(prank) = &self.prank { - if data.journaled_state.depth() == prank.depth { - data.env.tx.caller = prank.prank_origin; + if ecx.journaled_state.depth() == prank.depth { + ecx.env.tx.caller = prank.prank_origin; // Clean single-call prank once we have returned to the original depth if prank.single_call { @@ -1823,8 +1993,8 @@ impl Inspector for Cheatcodes { // Clean up broadcasts if let Some(broadcast) = &self.broadcast { - if data.journaled_state.depth() == broadcast.depth { - data.env.tx.caller = broadcast.original_origin; + if ecx.journaled_state.depth() == broadcast.depth { + ecx.env.tx.caller = broadcast.original_origin; // Clean single-call broadcast once we have returned to the original depth if broadcast.single_call { @@ -1835,21 +2005,26 @@ impl Inspector for Cheatcodes { // Handle expected reverts if let Some(expected_revert) = &self.expected_revert { - if data.journaled_state.depth() <= expected_revert.depth && + if ecx.journaled_state.depth() <= expected_revert.depth && matches!(expected_revert.kind, ExpectedRevertKind::Default) { let expected_revert = std::mem::take(&mut self.expected_revert).unwrap(); return match expect::handle_expect_revert( true, expected_revert.reason.as_deref(), - status, - retdata, + outcome.result.result, + outcome.result.output.clone(), ) { Ok((address, retdata)) => { - (InstructionResult::Return, address, remaining_gas, retdata) + outcome.result.result = InstructionResult::Return; + outcome.result.output = retdata; + outcome.address = address; + outcome } Err(err) => { - (InstructionResult::Revert, None, remaining_gas, err.abi_encode().into()) + outcome.result.result = InstructionResult::Revert; + outcome.result.output = err.abi_encode().into(); + outcome } }; } @@ -1859,12 +2034,12 @@ impl Inspector for Cheatcodes { // 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 data.journaled_state.depth() > 0 { + if ecx.journaled_state.depth() > 0 { let mut last_depth = recorded_account_diffs_stack.pop().expect("missing CREATE account accesses"); // Update the reverted status of all deeper calls if this call reverted, in // accordance with EVM behavior - if status.is_revert() { + if outcome.result.is_revert() { last_depth.iter_mut().for_each(|element| { element.reverted = true; element @@ -1878,23 +2053,18 @@ impl Inspector for Cheatcodes { // changes. Depending on what depth the cheat was called at, there // may not be any pending calls to update if execution has // percolated up to a higher depth. - if create_access.depth == data.journaled_state.depth() { + if create_access.depth == ecx.journaled_state.depth() { debug_assert_eq!( create_access.kind as u8, crate::Vm::AccountAccessKind::Create as u8 ); - if let Some(address) = address { + if let Some(address) = outcome.address { if let Ok((created_acc, _)) = - data.journaled_state.load_account(address, data.db) + ecx.journaled_state.load_account(address, &mut ecx.db) { create_access.newBalance = created_acc.info.balance; - create_access.deployedCode = created_acc - .info - .code - .clone() - .unwrap_or_default() - .original_bytes() - .into(); + create_access.deployedCode = + created_acc.info.code.clone().unwrap_or_default().original_bytes(); } } } @@ -1909,150 +2079,73 @@ impl Inspector for Cheatcodes { } } - (status, address, remaining_gas, retdata) + outcome + } +} + +impl InspectorExt for Cheatcodes { + fn should_use_create2_factory( + &mut self, + ecx: &mut EvmContext, + inputs: &mut CreateInputs, + ) -> bool { + if let CreateScheme::Create2 { .. } = inputs.scheme { + let target_depth = if let Some(prank) = &self.prank { + prank.depth + } else if let Some(broadcast) = &self.broadcast { + broadcast.depth + } else { + 1 + }; + + ecx.journaled_state.depth() == target_depth && + (self.broadcast.is_some() || self.config.always_use_create_2_factory) + } else { + 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<'_>, + 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(" ∪ ") - ) - .abi_encode(); - mstore_revert_string(interpreter, &revert_string); -} - -/// Expands memory, stores a revert string, and sets the return range to the revert -/// string's location in memory. -fn mstore_revert_string(interpreter: &mut Interpreter<'_>, bytes: &[u8]) { - let starting_offset = interpreter.shared_memory.len(); - interpreter.shared_memory.resize(starting_offset + bytes.len()); - interpreter.shared_memory.set_data(starting_offset, 0, bytes.len(), bytes); - interpreter.return_offset = starting_offset; - interpreter.return_len = interpreter.shared_memory.len() - starting_offset -} - -/// Applies the default CREATE2 deployer for contract creation. -/// -/// This function is invoked during the contract creation process and updates the caller of the -/// contract creation transaction to be the `DEFAULT_CREATE2_DEPLOYER` if the `CreateScheme` is -/// `Create2` and the current execution depth matches the depth at which the `prank` or `broadcast` -/// was started, or the default depth of 1 if no prank or broadcast is currently active. -/// -/// Returns a `DatabaseError::MissingCreate2Deployer` if the `DEFAULT_CREATE2_DEPLOYER` account is -/// not found or if it does not have any associated bytecode. -fn apply_create2_deployer( - data: &mut EVMData<'_, DB>, - call: &mut CreateInputs, - prank: Option<&Prank>, - broadcast: Option<&Broadcast>, - diffs_stack: Option<&mut Vec>>, -) -> Result<(), DB::Error> { - if let CreateScheme::Create2 { salt } = call.scheme { - let mut base_depth = 1; - if let Some(prank) = &prank { - base_depth = prank.depth; - } else if let Some(broadcast) = &broadcast { - base_depth = broadcast.depth; - } - - // If the create scheme is Create2 and the depth equals the broadcast/prank/default - // depth, then use the default create2 factory as the deployer - if data.journaled_state.depth() == base_depth { - // Record the call to the create2 factory in the state diff - if let Some(recorded_account_diffs_stack) = diffs_stack { - let calldata = [&salt.to_be_bytes::<32>()[..], &call.init_code[..]].concat(); - recorded_account_diffs_stack.push(vec![AccountAccess { - chainInfo: crate::Vm::ChainInfo { - forkId: data.db.active_fork_id().unwrap_or_default(), - chainId: U256::from(data.env.cfg.chain_id), - }, - accessor: call.caller, - account: DEFAULT_CREATE2_DEPLOYER, - kind: crate::Vm::AccountAccessKind::Call, - initialized: true, - oldBalance: U256::ZERO, // updated on create_end - newBalance: U256::ZERO, // updated on create_end - value: call.value, - data: calldata, - reverted: false, - deployedCode: vec![], // updated on create_end - storageAccesses: vec![], // updated on create_end - depth: data.journaled_state.depth(), - }]) - } - - // Sanity checks for our CREATE2 deployer - let info = - &data.journaled_state.load_account(DEFAULT_CREATE2_DEPLOYER, data.db)?.0.info; - match &info.code { - Some(code) if code.is_empty() => return Err(DatabaseError::MissingCreate2Deployer), - None if data.db.code_by_hash(info.code_hash)?.is_empty() => { - return Err(DatabaseError::MissingCreate2Deployer) - } - _ => {} - } - - call.caller = DEFAULT_CREATE2_DEPLOYER; - } - } - Ok(()) -} - -/// Processes the creation of a new contract when broadcasting, preparing the necessary data for the -/// transaction to deploy the contract. -/// -/// Returns the transaction calldata and the target address. -/// -/// If the CreateScheme is Create, then this function returns the input bytecode without -/// modification and no address since it will be filled in later. If the CreateScheme is Create2, -/// then this function returns the calldata for the call to the create2 deployer which must be the -/// salt and init code concatenated. -fn process_broadcast_create( - broadcast_sender: Address, - bytecode: Bytes, - data: &mut EVMData<'_, DB>, - call: &mut CreateInputs, -) -> (Bytes, Option
, u64) { - call.caller = broadcast_sender; - match call.scheme { - CreateScheme::Create => { - (bytecode, None, data.journaled_state.account(broadcast_sender).info.nonce) - } - CreateScheme::Create2 { salt } => { - // We have to increment the nonce of the user address, since this create2 will be done - // by the create2_deployer - let account = data.journaled_state.state().get_mut(&broadcast_sender).unwrap(); - let prev = account.info.nonce; - // Touch account to ensure that incremented nonce is committed - account.mark_touch(); - account.info.nonce += 1; - debug!(target: "cheatcodes", address=%broadcast_sender, nonce=prev+1, prev, "incremented nonce in create2"); - // Proxy deployer requires the data to be `salt ++ init_code` - let calldata = [&salt.to_be_bytes::<32>()[..], &bytecode[..]].concat(); - (calldata.into(), Some(DEFAULT_CREATE2_DEPLOYER), prev) - } - } + ranges.iter().map(|r| format!("(0x{:02X}, 0x{:02X}]", r.start, r.end)).join(" U ") + ); + + interpreter.instruction_result = InstructionResult::Revert; + interpreter.next_action = InterpreterAction::Return { + result: InterpreterResult { + output: Error::encode(revert_string), + gas: interpreter.gas, + result: InstructionResult::Revert, + }, + }; } // Determines if the gas limit on a given call was manually set in the script and should therefore // not be overwritten by later estimations -fn check_if_fixed_gas_limit(data: &EVMData<'_, DB>, call_gas_limit: u64) -> bool { +fn check_if_fixed_gas_limit( + ecx: &InnerEvmContext, + call_gas_limit: u64, +) -> bool { // If the gas limit was not set in the source code it is set to the estimated gas left at the // time of the call, which should be rather close to configured gas limit. // TODO: Find a way to reliably make this determination. // For example by generating it in the compilation or EVM simulation process - U256::from(data.env.tx.gas_limit) > data.env.block.gas_limit && - U256::from(call_gas_limit) <= data.env.block.gas_limit + U256::from(ecx.env.tx.gas_limit) > ecx.env.block.gas_limit && + U256::from(call_gas_limit) <= ecx.env.block.gas_limit // Transfers in forge scripts seem to be estimated at 2300 by revm leading to "Intrinsic // gas too low" failure when simulated on chain && call_gas_limit > 2300 @@ -2118,8 +2211,8 @@ fn append_storage_access( oldBalance: U256::ZERO, newBalance: U256::ZERO, value: U256::ZERO, - data: vec![], - deployedCode: vec![], + data: Bytes::new(), + deployedCode: Bytes::new(), depth: entry.depth, }; last.push(resume_record); diff --git a/crates/cheatcodes/src/json.rs b/crates/cheatcodes/src/json.rs index 758d46160..d3c3e9596 100644 --- a/crates/cheatcodes/src/json.rs +++ b/crates/cheatcodes/src/json.rs @@ -1,4 +1,4 @@ -//! Implementations of [`Json`](crate::Group::Json) cheatcodes. +//! Implementations of [`Json`](spec::Group::Json) cheatcodes. use crate::{string, Cheatcode, Cheatcodes, Result, Vm::*}; use alloy_dyn_abi::{DynSolType, DynSolValue}; @@ -12,10 +12,14 @@ use std::{borrow::Cow, collections::BTreeMap, fmt::Write}; impl Cheatcode for keyExistsCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; - let json = parse_json_str(json)?; - let values = select(&json, key)?; - let exists = !values.is_empty(); - Ok(exists.abi_encode()) + 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) } } @@ -134,16 +138,7 @@ impl Cheatcode for parseJsonBytes32ArrayCall { impl Cheatcode for parseJsonKeysCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { json, key } = self; - 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()) + parse_json_keys(json, key) } } @@ -253,6 +248,14 @@ impl Cheatcode for serializeBytes_1Call { } } +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, Some(valueKey), &hex) + } +} + impl Cheatcode for writeJson_0Call { fn apply(&self, state: &mut Cheatcodes) -> Result { let Self { json, path } = self; @@ -280,14 +283,21 @@ impl Cheatcode for writeJson_1Call { } } -fn parse_json(json: &str, path: &str) -> Result { +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) -> Result { let value = parse_json_str(json)?; let selected = select(&value, path)?; let sol = json_to_sol(&selected)?; Ok(encode(sol)) } -fn parse_json_coerce(json: &str, path: &str, ty: &DynSolType) -> Result { +pub(super) fn parse_json_coerce(json: &str, path: &str, ty: &DynSolType) -> Result { let value = parse_json_str(json)?; let values = select(&value, path)?; ensure!(!values.is_empty(), "no matching value found at {path:?}"); @@ -311,6 +321,19 @@ fn parse_json_coerce(json: &str, path: &str, ty: &DynSolType) -> Result { } } +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}")) } @@ -318,7 +341,7 @@ fn parse_json_str(json: &str) -> Result { fn json_to_sol(json: &[&Value]) -> Result> { let mut sol = Vec::with_capacity(json.len()); for value in json { - sol.push(value_to_token(value)?); + sol.push(json_value_to_token(value)?); } Ok(sol) } @@ -345,7 +368,7 @@ fn encode(values: Vec) -> Vec { /// Canonicalize a json path key to always start from the root of the document. /// Read more about json path syntax: -fn canonicalize_json_path(path: &str) -> Cow<'_, str> { +pub(super) fn canonicalize_json_path(path: &str) -> Cow<'_, str> { if !path.starts_with('$') { format!("${path}").into() } else { @@ -359,12 +382,12 @@ fn canonicalize_json_path(path: &str) -> Cow<'_, str> { /// 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 value_to_token(value: &Value) -> Result { +pub(super) fn json_value_to_token(value: &Value) -> Result { match value { Value::Null => Ok(DynSolValue::FixedBytes(B256::ZERO, 32)), Value::Bool(boolean) => Ok(DynSolValue::Bool(*boolean)), Value::Array(array) => { - array.iter().map(value_to_token).collect::>().map(DynSolValue::Array) + array.iter().map(json_value_to_token).collect::>().map(DynSolValue::Array) } value @ Value::Object(_) => { // See: [#3647](https://github.com/foundry-rs/foundry/pull/3647) @@ -372,7 +395,7 @@ pub(super) fn value_to_token(value: &Value) -> Result { serde_json::from_value(value.clone()).unwrap(); ordered_object .values() - .map(value_to_token) + .map(json_value_to_token) .collect::>() .map(DynSolValue::Tuple) } @@ -400,18 +423,18 @@ pub(super) fn value_to_token(value: &Value) -> Result { // used. let fallback_s = f.to_string(); if let Ok(n) = fallback_s.parse() { - return Ok(DynSolValue::Uint(n, 256)) + return Ok(DynSolValue::Uint(n, 256)); } if let Ok(n) = I256::from_dec_str(&fallback_s) { - return Ok(DynSolValue::Int(n, 256)) + return Ok(DynSolValue::Int(n, 256)); } } if let Ok(n) = s.parse() { - return Ok(DynSolValue::Uint(n, 256)) + return Ok(DynSolValue::Uint(n, 256)); } if let Ok(n) = s.parse() { - return Ok(DynSolValue::Int(n, 256)) + return Ok(DynSolValue::Int(n, 256)); } } } @@ -422,7 +445,7 @@ pub(super) fn value_to_token(value: &Value) -> Result { if let Some(mut val) = string.strip_prefix("0x") { let s; if val.len() % 2 != 0 { - s = format!("0{}", val); + s = format!("0{val}"); val = &s[..]; } let bytes = hex::decode(val)?; diff --git a/crates/cheatcodes/src/lib.rs b/crates/cheatcodes/src/lib.rs index c0fcefb4b..e18621d7c 100644 --- a/crates/cheatcodes/src/lib.rs +++ b/crates/cheatcodes/src/lib.rs @@ -2,7 +2,8 @@ //! //! Foundry cheatcodes implementations. -#![warn(missing_docs, unreachable_pub, unused_crate_dependencies, rust_2018_idioms)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![allow(elided_lifetimes_in_paths)] // Cheats context uses 3 lifetimes #[macro_use] @@ -12,31 +13,44 @@ extern crate tracing; use alloy_primitives::Address; use foundry_evm_core::backend::DatabaseExt; -use revm::EVMData; +use revm::{ContextPrecompiles, InnerEvmContext}; +pub use config::CheatsConfig; +pub use error::{Error, ErrorKind, Result}; +pub use inspector::{BroadcastableTransaction, BroadcastableTransactions, Cheatcodes, Context}; pub use spec::{CheatcodeDef, Vm}; +pub use Vm::ForgeContext; #[macro_use] mod error; -pub use error::{Error, ErrorKind, Result}; -mod config; -pub use config::CheatsConfig; +mod base64; -mod inspector; -pub use inspector::{BroadcastableTransaction, BroadcastableTransactions, Cheatcodes, Context}; +mod config; -mod base64; mod env; +pub use env::set_execution_context; + mod evm; + mod fs; + +mod inspector; + mod json; + mod script; +pub use script::{ScriptWallets, ScriptWalletsInner}; + mod string; + mod test; -mod utils; +// pub use test::expect::ExpectedCallTracker; +pub use foundry_cheatcodes_common::expect::ExpectedCallTracker; + +mod toml; -pub use script::ScriptWallets; +mod utils; /// Cheatcode implementation. pub(crate) trait Cheatcode: CheatcodeDef + DynCheatcode { @@ -58,15 +72,14 @@ pub(crate) trait Cheatcode: CheatcodeDef + DynCheatcode { #[inline] fn apply_traced(&self, ccx: &mut CheatsCtxt) -> Result { - let span = trace_span(self); - let _enter = span.enter(); - trace_call(); + let _span = trace_span_and_call(self); let result = self.apply_full(ccx); trace_return(&result); return result; // Separate and non-generic functions to avoid inline and monomorphization bloat. - fn trace_span(cheat: &dyn DynCheatcode) -> tracing::Span { + #[inline(never)] + fn trace_span_and_call(cheat: &dyn DynCheatcode) -> tracing::span::EnteredSpan { let span = debug_span!(target: "cheatcodes", "apply"); if !span.is_disabled() { if enabled!(tracing::Level::TRACE) { @@ -75,13 +88,12 @@ pub(crate) trait Cheatcode: CheatcodeDef + DynCheatcode { span.record("id", cheat.cheatcode().func.id); } } - span - } - - fn trace_call() { + let entered = span.entered(); trace!(target: "cheatcodes", "applying"); + entered } + #[inline(never)] fn trace_return(result: &Result) { trace!( target: "cheatcodes", @@ -110,18 +122,36 @@ impl DynCheatcode for T { } /// The cheatcode context, used in [`Cheatcode`]. -pub(crate) struct CheatsCtxt<'a, 'b, 'c, DB: DatabaseExt> { +pub(crate) struct CheatsCtxt<'cheats, 'evm, DB: DatabaseExt> { /// The cheatcodes inspector state. - pub(crate) state: &'a mut Cheatcodes, + pub(crate) state: &'cheats mut Cheatcodes, /// The EVM data. - pub(crate) data: &'b mut EVMData<'c, DB>, + pub(crate) ecx: &'evm mut InnerEvmContext, + /// The precompiles context. + pub(crate) precompiles: &'evm mut ContextPrecompiles, /// The original `msg.sender`. pub(crate) caller: Address, } -impl CheatsCtxt<'_, '_, '_, DB> { +impl<'cheats, 'evm, DB: DatabaseExt> std::ops::Deref for CheatsCtxt<'cheats, 'evm, DB> { + type Target = InnerEvmContext; + + #[inline(always)] + fn deref(&self) -> &Self::Target { + self.ecx + } +} + +impl<'cheats, 'evm, DB: DatabaseExt> std::ops::DerefMut for CheatsCtxt<'cheats, 'evm, DB> { + #[inline(always)] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut *self.ecx + } +} + +impl<'cheats, 'evm, DB: DatabaseExt> CheatsCtxt<'cheats, 'evm, DB> { #[inline] pub(crate) fn is_precompile(&self, address: &Address) -> bool { - self.data.precompiles.contains(address) + self.precompiles.contains(address) } } diff --git a/crates/cheatcodes/src/script.rs b/crates/cheatcodes/src/script.rs index e7523e317..1f84e2475 100644 --- a/crates/cheatcodes/src/script.rs +++ b/crates/cheatcodes/src/script.rs @@ -1,9 +1,8 @@ -//! Implementations of [`Scripting`](crate::Group::Scripting) cheatcodes. +//! Implementations of [`Scripting`](spec::Group::Scripting) cheatcodes. use crate::{Cheatcode, CheatsCtxt, DatabaseExt, Result, Vm::*}; -use alloy_primitives::{Address, U256}; -use alloy_signer::{LocalWallet, Signer}; -use foundry_config::Config; +use alloy_primitives::{Address, B256, U256}; +use alloy_signer_local::PrivateKeySigner; use foundry_wallets::{multi_wallet::MultiWallet, WalletSigner}; use parking_lot::Mutex; use std::sync::Arc; @@ -84,7 +83,7 @@ pub struct ScriptWalletsInner { pub provided_sender: Option
, } -/// Clonable wrapper around [ScriptWalletsInner]. +/// Clonable wrapper around [`ScriptWalletsInner`]. #[derive(Debug, Clone)] pub struct ScriptWallets { /// Inner data. @@ -107,9 +106,19 @@ impl ScriptWallets { } /// Locks inner Mutex and adds a signer to the [MultiWallet]. - pub fn add_signer(&self, private_key: impl AsRef<[u8]>) -> Result { - self.inner.lock().multi_wallet.add_signer(WalletSigner::from_private_key(private_key)?); - Ok(Default::default()) + pub fn add_private_key(&self, private_key: &B256) -> Result<()> { + self.add_local_signer(PrivateKeySigner::from_bytes(private_key)?); + Ok(()) + } + + /// 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().cloned().collect()) } } @@ -125,13 +134,10 @@ fn broadcast( ); ensure!(ccx.state.broadcast.is_none(), "a broadcast is active already"); - // TODO probably need to correct nonce in ZK scope - correct_sender_nonce(ccx)?; - let mut new_origin = new_origin.cloned(); if new_origin.is_none() { - if let Some(script_wallets) = &ccx.state.script_wallets { + if let Some(script_wallets) = ccx.state.script_wallets() { let mut script_wallets = script_wallets.inner.lock(); if let Some(provided_sender) = script_wallets.provided_sender { new_origin = Some(provided_sender); @@ -146,10 +152,10 @@ fn broadcast( } let broadcast = Broadcast { - new_origin: new_origin.unwrap_or(ccx.data.env.tx.caller), + new_origin: new_origin.unwrap_or(ccx.ecx.env.tx.caller), original_caller: ccx.caller, - original_origin: ccx.data.env.tx.caller, - depth: ccx.data.journaled_state.depth(), + original_origin: ccx.ecx.env.tx.caller, + depth: ccx.ecx.journaled_state.depth(), single_call, }; debug!(target: "cheatcodes", ?broadcast, "started"); @@ -165,30 +171,14 @@ fn broadcast_key( private_key: &U256, single_call: bool, ) -> Result { - let key = super::utils::parse_private_key(private_key)?; - let new_origin = LocalWallet::from(key.clone()).address(); + let wallet = super::utils::parse_wallet(private_key)?; + let new_origin = wallet.address(); let result = broadcast(ccx, Some(&new_origin), single_call); - if result.is_ok() { - if let Some(script_wallets) = &ccx.state.script_wallets { - script_wallets.add_signer(key.to_bytes())?; + if let Some(script_wallets) = ccx.state.script_wallets() { + script_wallets.add_local_signer(wallet); } } result } - -/// When using `forge script`, the script method is called using the address from `--sender`. -/// That leads to its nonce being incremented by `call_raw`. In a `broadcast` scenario this is -/// undesirable. Therefore, we make sure to fix the sender's nonce **once**. -pub(super) fn correct_sender_nonce(ccx: &mut CheatsCtxt) -> Result<()> { - let sender = ccx.data.env.tx.caller; - if !ccx.state.corrected_nonce && sender != Config::DEFAULT_SENDER { - let account = super::evm::journaled_account(ccx.data, sender)?; - let prev = account.info.nonce; - account.info.nonce = prev.saturating_sub(1); - debug!(target: "cheatcodes", %sender, nonce=account.info.nonce, prev, "corrected nonce"); - ccx.state.corrected_nonce = true; - } - Ok(()) -} diff --git a/crates/cheatcodes/src/string.rs b/crates/cheatcodes/src/string.rs index b7992b945..07e9e89f5 100644 --- a/crates/cheatcodes/src/string.rs +++ b/crates/cheatcodes/src/string.rs @@ -1,7 +1,8 @@ -//! Implementations of [`String`](crate::Group::String) cheatcodes. +//! Implementations of [`String`](spec::Group::String) cheatcodes. use crate::{Cheatcode, Cheatcodes, Result, Vm::*}; use alloy_dyn_abi::{DynSolType, DynSolValue}; +use alloy_primitives::U256; use alloy_sol_types::SolValue; // address @@ -135,6 +136,14 @@ impl Cheatcode for splitCall { } } +// indexOf +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()) + } +} + pub(super) fn parse(s: &str, ty: &DynSolType) -> Result { parse_value(s, ty).map(|v| v.abi_encode()) } diff --git a/crates/cheatcodes/src/test.rs b/crates/cheatcodes/src/test.rs index 69d52126e..7ab8c2232 100644 --- a/crates/cheatcodes/src/test.rs +++ b/crates/cheatcodes/src/test.rs @@ -1,4 +1,4 @@ -//! Implementations of [`Testing`](crate::Group::Testing) cheatcodes. +//! Implementations of [`Testing`](spec::Group::Testing) cheatcodes. use crate::{Cheatcode, Cheatcodes, CheatsCtxt, DatabaseExt, Error, Result, Vm::*}; use alloy_primitives::Address; @@ -14,9 +14,9 @@ impl Cheatcode for zkVmCall { let Self { enable } = *self; if enable { - ccx.state.select_zk_vm(ccx.data, None); + ccx.state.select_zk_vm(ccx.ecx, None); } else { - ccx.state.select_evm(ccx.data); + ccx.state.select_evm(ccx.ecx); } Ok(Default::default()) @@ -37,12 +37,12 @@ impl Cheatcode for zkRegisterContractCall { let new_contract = DualCompiledContract { name: name.clone(), zk_bytecode_hash: zkBytecodeHash.0.into(), - zk_deployed_bytecode: zkDeployedBytecode.clone(), + zk_deployed_bytecode: zkDeployedBytecode.to_vec(), //TODO: add argument to cheatcode zk_factory_deps: vec![], evm_bytecode_hash: *evmBytecodeHash, - evm_deployed_bytecode: evmDeployedBytecode.clone(), - evm_bytecode: evmBytecode.clone(), + evm_deployed_bytecode: evmDeployedBytecode.to_vec(), + evm_bytecode: evmBytecode.to_vec(), }; if let Some(existing) = ccx.state.dual_compiled_contracts.iter().find(|contract| { @@ -120,7 +120,7 @@ impl Cheatcode for skipCall { 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.data.journaled_state.depth() <= 1, "`skip` can only be used at test level"); + ensure!(ccx.ecx.journaled_state.depth() <= 1, "`skip` can only be used at test level"); Err(MAGIC_SKIP.into()) } else { Ok(Default::default()) diff --git a/crates/cheatcodes/src/test/assert.rs b/crates/cheatcodes/src/test/assert.rs index 753a2da87..9f1eee252 100644 --- a/crates/cheatcodes/src/test/assert.rs +++ b/crates/cheatcodes/src/test/assert.rs @@ -925,7 +925,7 @@ impl Cheatcode for assertLeDecimal_3Call { impl Cheatcode for assertApproxEqAbs_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { Ok(uint_assert_approx_eq_abs(self.left, self.right, self.maxDelta) - .map_err(|e| format!("assertion failed: {}", e))?) + .map_err(|e| format!("assertion failed: {e}"))?) } } @@ -939,7 +939,7 @@ impl Cheatcode for assertApproxEqAbs_1Call { impl Cheatcode for assertApproxEqAbs_2Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { Ok(int_assert_approx_eq_abs(self.left, self.right, self.maxDelta) - .map_err(|e| format!("assertion failed: {}", e))?) + .map_err(|e| format!("assertion failed: {e}"))?) } } @@ -981,7 +981,7 @@ impl Cheatcode for assertApproxEqAbsDecimal_3Call { impl Cheatcode for assertApproxEqRel_0Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { Ok(uint_assert_approx_eq_rel(self.left, self.right, self.maxPercentDelta) - .map_err(|e| format!("assertion failed: {}", e))?) + .map_err(|e| format!("assertion failed: {e}"))?) } } @@ -995,7 +995,7 @@ impl Cheatcode for assertApproxEqRel_1Call { impl Cheatcode for assertApproxEqRel_2Call { fn apply(&self, _state: &mut Cheatcodes) -> Result { Ok(int_assert_approx_eq_rel(self.left, self.right, self.maxPercentDelta) - .map_err(|e| format!("assertion failed: {}", e))?) + .map_err(|e| format!("assertion failed: {e}"))?) } } @@ -1122,13 +1122,17 @@ fn uint_assert_approx_eq_rel( right: U256, max_delta: U256, ) -> Result, EqRelAssertionError> { - if right.is_zero() && !left.is_zero() { - return Err(EqRelAssertionError::Failure(Box::new(EqRelAssertionFailure { - left, - right, - max_delta, - real_delta: EqRelDelta::Undefined, - }))) + if right.is_zero() { + if left.is_zero() { + return Ok(Default::default()) + } else { + return Err(EqRelAssertionError::Failure(Box::new(EqRelAssertionFailure { + left, + right, + max_delta, + real_delta: EqRelDelta::Undefined, + }))) + }; } let delta = get_delta_uint(left, right) @@ -1153,13 +1157,17 @@ fn int_assert_approx_eq_rel( right: I256, max_delta: U256, ) -> Result, EqRelAssertionError> { - if right.is_zero() && !left.is_zero() { - return Err(EqRelAssertionError::Failure(Box::new(EqRelAssertionFailure { - left, - right, - max_delta, - real_delta: EqRelDelta::Undefined, - }))) + if right.is_zero() { + if left.is_zero() { + return Ok(Default::default()) + } else { + return Err(EqRelAssertionError::Failure(Box::new(EqRelAssertionFailure { + left, + right, + max_delta, + real_delta: EqRelDelta::Undefined, + }))) + } } let (_, abs_right) = right.into_sign_and_abs(); diff --git a/crates/cheatcodes/src/test/expect.rs b/crates/cheatcodes/src/test/expect.rs index 6a58202c1..8fe5ef4d4 100644 --- a/crates/cheatcodes/src/test/expect.rs +++ b/crates/cheatcodes/src/test/expect.rs @@ -1,5 +1,5 @@ use crate::{Cheatcode, Cheatcodes, CheatsCtxt, DatabaseExt, Result, Vm::*}; -use alloy_primitives::{address, Address, Bytes, LogData as RawLog, B256, U256}; +use alloy_primitives::{address, Address, Bytes, LogData as RawLog, U256}; use alloy_sol_types::{SolError, SolValue}; use foundry_cheatcodes_common::expect::{ExpectedCallData, ExpectedCallType}; use revm::interpreter::{return_ok, InstructionResult}; @@ -165,7 +165,7 @@ impl Cheatcode for expectEmit_0Call { let Self { checkTopic1, checkTopic2, checkTopic3, checkData } = *self; expect_emit( ccx.state, - ccx.data.journaled_state.depth(), + ccx.ecx.journaled_state.depth(), [checkTopic1, checkTopic2, checkTopic3, checkData], None, ) @@ -177,7 +177,7 @@ impl Cheatcode for expectEmit_1Call { let Self { checkTopic1, checkTopic2, checkTopic3, checkData, emitter } = *self; expect_emit( ccx.state, - ccx.data.journaled_state.depth(), + ccx.ecx.journaled_state.depth(), [checkTopic1, checkTopic2, checkTopic3, checkData], Some(emitter), ) @@ -187,69 +187,69 @@ impl Cheatcode for expectEmit_1Call { impl Cheatcode for expectEmit_2Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; - expect_emit(ccx.state, ccx.data.journaled_state.depth(), [true; 4], None) + expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 4], None) } } impl Cheatcode for expectEmit_3Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { emitter } = *self; - expect_emit(ccx.state, ccx.data.journaled_state.depth(), [true; 4], Some(emitter)) + expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 4], Some(emitter)) } } impl Cheatcode for expectRevert_0Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; - expect_revert(ccx.state, None, ccx.data.journaled_state.depth(), false) + expect_revert(ccx.state, None, ccx.ecx.journaled_state.depth(), false) } } impl Cheatcode for expectRevert_1Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { revertData } = self; - expect_revert(ccx.state, Some(revertData.as_ref()), ccx.data.journaled_state.depth(), false) + expect_revert(ccx.state, Some(revertData.as_ref()), ccx.ecx.journaled_state.depth(), false) } } impl Cheatcode for expectRevert_2Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { revertData } = self; - expect_revert(ccx.state, Some(revertData), ccx.data.journaled_state.depth(), false) + expect_revert(ccx.state, Some(revertData), ccx.ecx.journaled_state.depth(), false) } } impl Cheatcode for _expectCheatcodeRevert_0Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { - expect_revert(ccx.state, None, ccx.data.journaled_state.depth(), true) + expect_revert(ccx.state, None, ccx.ecx.journaled_state.depth(), true) } } impl Cheatcode for _expectCheatcodeRevert_1Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { revertData } = self; - expect_revert(ccx.state, Some(revertData.as_ref()), ccx.data.journaled_state.depth(), true) + expect_revert(ccx.state, Some(revertData.as_ref()), ccx.ecx.journaled_state.depth(), true) } } impl Cheatcode for _expectCheatcodeRevert_2Call { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { revertData } = self; - expect_revert(ccx.state, Some(revertData), ccx.data.journaled_state.depth(), true) + expect_revert(ccx.state, Some(revertData), ccx.ecx.journaled_state.depth(), true) } } impl Cheatcode for expectSafeMemoryCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { min, max } = *self; - expect_safe_memory(ccx.state, min, max, ccx.data.journaled_state.depth()) + expect_safe_memory(ccx.state, min, max, ccx.ecx.journaled_state.depth()) } } impl Cheatcode for stopExpectSafeMemoryCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; - ccx.state.allowed_mem_writes.remove(&ccx.data.journaled_state.depth()); + ccx.state.allowed_mem_writes.remove(&ccx.ecx.journaled_state.depth()); Ok(Default::default()) } } @@ -257,7 +257,7 @@ impl Cheatcode for stopExpectSafeMemoryCall { impl Cheatcode for expectSafeMemoryCallCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { min, max } = *self; - expect_safe_memory(ccx.state, min, max, ccx.data.journaled_state.depth() + 1) + expect_safe_memory(ccx.state, min, max, ccx.ecx.journaled_state.depth() + 1) } } @@ -265,24 +265,23 @@ impl Cheatcode for expectSafeMemoryCallCall { /// /// 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. +/// `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. +/// 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. #[allow(clippy::too_many_arguments)] // It is what it is fn expect_call( state: &mut Cheatcodes, target: &Address, - calldata: &Vec, + calldata: &Bytes, value: Option<&U256>, mut gas: Option, mut min_gas: Option, @@ -315,7 +314,7 @@ fn expect_call( "counted expected calls can only bet set once" ); expecteds.insert( - calldata.to_vec(), + calldata.clone(), (ExpectedCallData { value: value.copied(), gas, min_gas, count, call_type }, 0), ); } @@ -362,12 +361,7 @@ fn expect_emit( Ok(Default::default()) } -pub(crate) fn handle_expect_emit( - state: &mut Cheatcodes, - address: &Address, - topics: &[B256], - data: &Bytes, -) { +pub(crate) fn handle_expect_emit(state: &mut Cheatcodes, log: &alloy_primitives::Log) { // 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. @@ -395,20 +389,21 @@ pub(crate) fn handle_expect_emit( let Some(expected) = &event_to_fill_or_check.log else { // Fill the event. - event_to_fill_or_check.log = Some(RawLog::new_unchecked(topics.to_vec(), data.clone())); + event_to_fill_or_check.log = Some(log.data.clone()); state.expected_emits.push_back(event_to_fill_or_check); return }; let expected_topic_0 = expected.topics().first(); - let log_topic_0 = topics.first(); + let log_topic_0 = log.topics().first(); if expected_topic_0 .zip(log_topic_0) - .map_or(false, |(a, b)| a == b && expected.topics().len() == topics.len()) + .map_or(false, |(a, b)| a == b && expected.topics().len() == log.topics().len()) { // Match topics - event_to_fill_or_check.found = topics + event_to_fill_or_check.found = log + .topics() .iter() .skip(1) .enumerate() @@ -417,12 +412,12 @@ pub(crate) fn handle_expect_emit( // Maybe match source address if let Some(addr) = event_to_fill_or_check.address { - event_to_fill_or_check.found &= addr == *address; + event_to_fill_or_check.found &= addr == log.address; } // Maybe match data if event_to_fill_or_check.checks[3] { - event_to_fill_or_check.found &= expected.data == *data; + event_to_fill_or_check.found &= expected.data.as_ref() == log.data.data.as_ref(); } } diff --git a/crates/cheatcodes/src/toml.rs b/crates/cheatcodes/src/toml.rs new file mode 100644 index 000000000..e1827dbef --- /dev/null +++ b/crates/cheatcodes/src/toml.rs @@ -0,0 +1,243 @@ +//! Implementations of [`Toml`](spec::Group::Toml) cheatcodes. + +use crate::{ + json::{ + canonicalize_json_path, check_json_key_exists, parse_json, parse_json_coerce, + parse_json_keys, + }, + Cheatcode, Cheatcodes, Result, + Vm::*, +}; +use alloy_dyn_abi::DynSolType; +use foundry_common::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, "$") + } +} + +impl Cheatcode for parseToml_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { toml, key } = self; + parse_toml(toml, key) + } +} + +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::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::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::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::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::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::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::FixedBytes(32)) + } +} + +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, path, valueKey } = self; + let json = + serde_json::from_str(json).unwrap_or_else(|_| JsonValue::String(json.to_owned())); + + let data_path = state.config.ensure_path_allowed(path, FsAccessKind::Read)?; + let toml_data = fs::read_to_string(data_path)?; + let json_data: JsonValue = + toml::from_str(&toml_data).map_err(|e| fmt_err!("failed parsing TOML: {e}"))?; + let value = + jsonpath_lib::replace_with(json_data, &canonicalize_json_path(valueKey), &mut |_| { + Some(json.clone()) + })?; + + let toml_string = format_json_to_toml(value)?; + 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) -> Result { + parse_json(&toml_to_json_string(toml)?, key) +} + +/// 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. +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) => JsonValue::Number(serde_json::Number::from_f64(f).unwrap()), + 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()), + } +} diff --git a/crates/cheatcodes/src/utils.rs b/crates/cheatcodes/src/utils.rs index 525120d7d..8d4c547df 100644 --- a/crates/cheatcodes/src/utils.rs +++ b/crates/cheatcodes/src/utils.rs @@ -1,15 +1,17 @@ -//! Implementations of [`Utils`](crate::Group::Utils) cheatcodes. +//! Implementations of [`Utilities`](spec::Group::Utilities) cheatcodes. use crate::{Cheatcode, Cheatcodes, CheatsCtxt, DatabaseExt, Result, Vm::*}; -use alloy_primitives::{keccak256, B256, U256}; -use alloy_signer::{ +use alloy_primitives::{keccak256, Address, B256, U256}; +use alloy_signer::{Signer, SignerSync}; +use alloy_signer_local::{ coins_bip39::{ ChineseSimplified, ChineseTraditional, Czech, English, French, Italian, Japanese, Korean, Portuguese, Spanish, Wordlist, }, - LocalWallet, MnemonicBuilder, Signer, SignerSync, + MnemonicBuilder, PrivateKeySigner, }; use alloy_sol_types::SolValue; +use foundry_common::ens::namehash; use foundry_evm_core::constants::DEFAULT_CREATE2_DEPLOYER; use k256::{ ecdsa::SigningKey, @@ -17,6 +19,7 @@ use k256::{ Secp256k1, }; use p256::ecdsa::{signature::hazmat::PrehashSigner, Signature, SigningKey as P256SigningKey}; +use rand::Rng; /// The BIP32 default derivation path prefix. const DEFAULT_DERIVATION_PATH_PREFIX: &str = "m/44'/60'/0'/0/"; @@ -49,7 +52,7 @@ impl Cheatcode for getNonce_1Call { } } -impl Cheatcode for sign_1Call { +impl Cheatcode for sign_3Call { fn apply_full(&self, _: &mut CheatsCtxt) -> Result { let Self { wallet, digest } = self; sign(&wallet.privateKey, digest) @@ -87,10 +90,10 @@ impl Cheatcode for deriveKey_3Call { impl Cheatcode for rememberKeyCall { fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { let Self { privateKey } = self; - let key = parse_private_key(privateKey)?; - let address = LocalWallet::from(key.clone()).address(); - if let Some(script_wallets) = &ccx.state.script_wallets { - script_wallets.add_signer(key.to_bytes())?; + let wallet = parse_wallet(privateKey)?; + let address = wallet.address(); + if let Some(script_wallets) = ccx.state.script_wallets() { + script_wallets.add_local_signer(wallet); } Ok(address.abi_encode()) } @@ -136,6 +139,42 @@ impl Cheatcode for computeCreate2Address_1Call { } } +impl Cheatcode for ensNamehashCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { name } = self; + Ok(namehash(name).abi_encode()) + } +} + +impl Cheatcode for randomUint_0Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self {} = self; + // Use thread_rng to get a random number + let mut rng = rand::thread_rng(); + let random_number: U256 = rng.gen(); + Ok(random_number.abi_encode()) + } +} + +impl Cheatcode for randomUint_1Call { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { min, max } = self; + // Generate random between range min..=max + let mut rng = rand::thread_rng(); + let range = *max - *min + U256::from(1); + let random_number = rng.gen::() % range + *min; + Ok(random_number.abi_encode()) + } +} + +impl Cheatcode for randomAddressCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self {} = self; + let addr = Address::random(); + Ok(addr.abi_encode()) + } +} + /// 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) /// @@ -156,20 +195,50 @@ fn create_wallet(private_key: &U256, label: Option<&str>, state: &mut Cheatcodes .abi_encode()) } +fn encode_vrs(sig: alloy_primitives::Signature) -> Vec { + let v = sig.v().y_parity_byte_non_eip155().unwrap_or(sig.v().y_parity_byte()); + + (U256::from(v), B256::from(sig.r()), B256::from(sig.s())).abi_encode() +} + pub(super) 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(encode_vrs(sig)) +} - let sig = wallet.sign_hash_sync(*digest)?; - let recovered = sig.recover_address_from_prehash(digest)?; - - assert_eq!(recovered, wallet.address()); - - let v = U256::from(sig.v().y_parity_byte_non_eip155().unwrap_or(sig.v().y_parity_byte())); - let r = B256::from(sig.r()); - let s = B256::from(sig.s()); - - Ok((v, r, s).abi_encode()) +pub(super) fn sign_with_wallet( + ccx: &mut CheatsCtxt, + signer: Option
, + digest: &B256, +) -> Result { + let Some(script_wallets) = ccx.state.script_wallets() else { + bail!("no wallets are available"); + }; + + let mut script_wallets = script_wallets.inner.lock(); + let maybe_provided_sender = script_wallets.provided_sender; + let signers = script_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"); + }; + + 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(encode_vrs(sig)) } pub(super) fn sign_p256(private_key: &U256, digest: &B256, _state: &mut Cheatcodes) -> Result { @@ -199,8 +268,8 @@ pub(super) fn parse_private_key(private_key: &U256) -> Result { SigningKey::from_bytes((&bytes).into()).map_err(Into::into) } -pub(super) fn parse_wallet(private_key: &U256) -> Result { - parse_private_key(private_key).map(LocalWallet::from) +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 { @@ -233,7 +302,7 @@ fn derive_key(mnemonic: &str, path: &str, index: u32) -> Result { .phrase(mnemonic) .derivation_path(derive_key_path(path, index))? .build()?; - let private_key = U256::from_be_bytes(wallet.signer().to_bytes().into()); + let private_key = U256::from_be_bytes(wallet.credential().to_bytes().into()); Ok(private_key.abi_encode()) } diff --git a/crates/chisel/Cargo.toml b/crates/chisel/Cargo.toml index 589beaa3e..64e218f97 100644 --- a/crates/chisel/Cargo.toml +++ b/crates/chisel/Cargo.toml @@ -1,6 +1,9 @@ [package] name = "chisel" -authors = ["clabby ", "asnared "] +authors = [ + "clabby ", + "asnared ", +] description = "Fast, utilitarian, and verbose Solidity REPL" version.workspace = true @@ -10,12 +13,19 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [[bin]] name = "chisel" path = "bin/main.rs" [build-dependencies] -vergen = { workspace = true, default-features = false, features = ["build", "git", "gitcl"] } +vergen = { workspace = true, default-features = false, features = [ + "build", + "git", + "gitcl", +] } [dependencies] # forge @@ -28,7 +38,12 @@ foundry-config.workspace = true foundry-evm.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary"] } -alloy-primitives = { workspace = true, features = ["serde", "getrandom", "arbitrary", "rlp"] } +alloy-primitives = { workspace = true, features = [ + "serde", + "getrandom", + "arbitrary", + "rlp", +] } alloy-json-abi.workspace = true alloy-rpc-types.workspace = true @@ -37,30 +52,33 @@ dirs = "5" eyre.workspace = true once_cell = "1.18.0" regex = "1" -reqwest = { version = "0.11", default-features = false } +reqwest.workspace = true revm.workspace = true rustyline = "12" -semver = "1" +semver.workspace = true serde_json.workspace = true serde.workspace = true solang-parser.workspace = true strum = { workspace = true, features = ["derive"] } time = { version = "0.3", features = ["formatting"] } -tokio = { version = "1", features = ["full"] } -yansi = "0.5" +tokio = { workspace = true, features = ["full"] } +yansi.workspace = true tracing.workspace = true +[target.'cfg(unix)'.dependencies] +tikv-jemallocator = { workspace = true, optional = true } + [dev-dependencies] criterion = { version = "0.5", features = ["async_tokio"] } -once_cell = "1" serial_test = "3" tracing-subscriber.workspace = true [features] -default = ["rustls"] +default = ["rustls", "jemalloc"] rustls = ["reqwest/rustls-tls", "reqwest/rustls-tls-native-roots"] openssl = ["foundry-compilers/openssl", "reqwest/default-tls"] asm-keccak = ["alloy-primitives/asm-keccak"] +jemalloc = ["dep:tikv-jemallocator"] [[bench]] name = "session_source" diff --git a/crates/chisel/benches/session_source.rs b/crates/chisel/benches/session_source.rs index 3c3196956..f54944966 100644 --- a/crates/chisel/benches/session_source.rs +++ b/crates/chisel/benches/session_source.rs @@ -1,11 +1,12 @@ use chisel::session_source::{SessionSource, SessionSourceConfig}; use criterion::{criterion_group, Criterion}; -use foundry_compilers::Solc; +use foundry_compilers::solc::Solc; use once_cell::sync::Lazy; +use semver::Version; use std::hint::black_box; use tokio::runtime::Runtime; -static SOLC: Lazy = Lazy::new(|| Solc::find_or_install_svm_version("0.8.19").unwrap()); +static SOLC: Lazy = Lazy::new(|| Solc::find_or_install(&Version::new(0, 8, 19)).unwrap()); /// Benchmark for the `clone_with_new_line` function in [SessionSource] fn clone_with_new_line(c: &mut Criterion) { diff --git a/crates/chisel/bin/main.rs b/crates/chisel/bin/main.rs index 9da885860..d2b3a726f 100644 --- a/crates/chisel/bin/main.rs +++ b/crates/chisel/bin/main.rs @@ -27,6 +27,10 @@ use std::path::PathBuf; use tracing::debug; use yansi::Paint; +#[cfg(all(feature = "jemalloc", unix))] +#[global_allocator] +static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; + // Loads project's figment and merges the build cli arguments into it foundry_config::merge_impl_figment_convert!(Chisel, opts, evm_opts); @@ -41,7 +45,7 @@ const VERSION_MESSAGE: &str = concat!( /// Fast, utilitarian, and verbose Solidity REPL. #[derive(Debug, Parser)] -#[clap(name = "chisel", version = VERSION_MESSAGE)] +#[command(name = "chisel", version = VERSION_MESSAGE)] pub struct Chisel { #[command(subcommand)] pub cmd: Option, @@ -50,21 +54,21 @@ pub struct Chisel { /// /// These files will be evaluated before the top-level of the /// REPL, therefore functioning as a prelude - #[clap(long, help_heading = "REPL options")] + #[arg(long, help_heading = "REPL options")] pub prelude: Option, /// Disable the default `Vm` import. - #[clap(long, help_heading = "REPL options", long_help = format!( + #[arg(long, help_heading = "REPL options", long_help = format!( "Disable the default `Vm` import.\n\n\ The import is disabled by default if the Solc version is less than {}.", chisel::session_source::MIN_VM_VERSION ))] pub no_vm: bool, - #[clap(flatten)] + #[command(flatten)] pub opts: CoreBuildArgs, - #[clap(flatten)] + #[command(flatten)] pub evm_opts: EvmArgs, } @@ -94,11 +98,6 @@ pub enum ChiselSubcommand { async fn main() -> eyre::Result<()> { handler::install(); utils::subscriber(); - #[cfg(windows)] - if !Paint::enable_windows_ascii() { - Paint::disable() - } - utils::load_dotenv(); // Parse command args @@ -161,7 +160,7 @@ async fn main() -> eyre::Result<()> { } Some(ChiselSubcommand::ClearCache) => { match dispatcher.dispatch_command(ChiselCommand::ClearCache, &[]).await { - DispatchResult::CommandSuccess(Some(msg)) => println!("{}", Paint::green(msg)), + DispatchResult::CommandSuccess(Some(msg)) => println!("{}", msg.green()), DispatchResult::CommandFailed(e) => eprintln!("{e}"), _ => panic!("Unexpected result! Please report this bug."), } @@ -183,7 +182,7 @@ async fn main() -> eyre::Result<()> { } // Print welcome header - println!("Welcome to Chisel! Type `{}` to show available commands.", Paint::green("!help")); + println!("Welcome to Chisel! Type `{}` to show available commands.", "!help".green()); // Begin Rustyline loop loop { @@ -246,17 +245,17 @@ async fn dispatch_repl_line(dispatcher: &mut ChiselDispatcher, line: &str) -> bo DispatchResult::Success(msg) | DispatchResult::CommandSuccess(msg) => { debug!(%line, ?msg, "dispatch success"); if let Some(msg) = msg { - println!("{}", Paint::green(msg)); + println!("{}", msg.green()); } }, DispatchResult::UnrecognizedCommand(e) => eprintln!("{e}"), DispatchResult::SolangParserFailed(e) => { - eprintln!("{}", Paint::red("Compilation error")); - eprintln!("{}", Paint::red(format!("{e:?}"))); + eprintln!("{}", "Compilation error".red()); + eprintln!("{}", format!("{e:?}").red()); } - DispatchResult::FileIoError(e) => eprintln!("{}", Paint::red(format!("⚒️ Chisel File IO Error - {e}"))), - DispatchResult::CommandFailed(msg) | DispatchResult::Failure(Some(msg)) => eprintln!("{}", Paint::red(msg)), - DispatchResult::Failure(None) => eprintln!("{}\nPlease Report this bug as a github issue if it persists: https://github.com/foundry-rs/foundry/issues/new/choose", Paint::red("⚒️ Unknown Chisel Error ⚒️")), + DispatchResult::FileIoError(e) => eprintln!("{}", format!("⚒️ Chisel File IO Error - {e}").red()), + DispatchResult::CommandFailed(msg) | DispatchResult::Failure(Some(msg)) => eprintln!("{}", msg.red()), + DispatchResult::Failure(None) => eprintln!("{}\nPlease Report this bug as a github issue if it persists: https://github.com/foundry-rs/foundry/issues/new/choose", "⚒️ Unknown Chisel Error ⚒️".red()), } r.is_error() } @@ -269,19 +268,20 @@ async fn evaluate_prelude( ) -> eyre::Result<()> { let Some(prelude_dir) = maybe_prelude else { return Ok(()) }; if prelude_dir.is_file() { - println!("{} {}", Paint::yellow("Loading prelude source file:"), prelude_dir.display(),); + println!("{} {}", "Loading prelude source file:".yellow(), prelude_dir.display(),); load_prelude_file(dispatcher, prelude_dir).await?; - println!("{}\n", Paint::green("Prelude source file loaded successfully!")); + println!("{}\n", "Prelude source file loaded successfully!".green()); } else { - let prelude_sources = fs::files_with_ext(prelude_dir, "sol"); - let print_success_msg = !prelude_sources.is_empty(); + let prelude_sources = fs::files_with_ext(&prelude_dir, "sol"); + let mut print_success_msg = false; for source_file in prelude_sources { - println!("{} {}", Paint::yellow("Loading prelude source file:"), source_file.display(),); + print_success_msg = true; + println!("{} {}", "Loading prelude source file:".yellow(), source_file.display()); load_prelude_file(dispatcher, source_file).await?; } if print_success_msg { - println!("{}\n", Paint::green("All prelude source files loaded successfully!")); + println!("{}\n", "All prelude source files loaded successfully!".green()); } } Ok(()) diff --git a/crates/chisel/src/cmd.rs b/crates/chisel/src/cmd.rs index 99e4086b3..c13272bc9 100644 --- a/crates/chisel/src/cmd.rs +++ b/crates/chisel/src/cmd.rs @@ -19,10 +19,10 @@ pub enum ChiselCommand { /// Print the generated source contract Source, /// Save the current session to the cache - /// Takes: [session-id] + /// Takes: `` Save, /// Load a previous session from cache - /// Takes: + /// Takes: `` /// /// WARNING: This will overwrite the current session (though the current session will be /// optimistically cached) @@ -45,7 +45,7 @@ pub enum ChiselCommand { /// Export the current REPL session source to a Script file Export, /// Fetch an interface of a verified contract on Etherscan - /// Takes: + /// Takes: ` ` Fetch, /// Executes a shell command Exec, @@ -61,24 +61,24 @@ impl FromStr for ChiselCommand { fn from_str(s: &str) -> Result { match s.to_lowercase().as_ref() { - "help" | "h" => Ok(ChiselCommand::Help), - "quit" | "q" => Ok(ChiselCommand::Quit), - "clear" | "c" => Ok(ChiselCommand::Clear), - "source" | "so" => Ok(ChiselCommand::Source), - "save" | "s" => Ok(ChiselCommand::Save), - "list" | "ls" => Ok(ChiselCommand::ListSessions), - "load" | "l" => Ok(ChiselCommand::Load), - "clearcache" | "cc" => Ok(ChiselCommand::ClearCache), - "fork" | "f" => Ok(ChiselCommand::Fork), - "traces" | "t" => Ok(ChiselCommand::Traces), - "calldata" | "cd" => Ok(ChiselCommand::Calldata), - "memdump" | "md" => Ok(ChiselCommand::MemDump), - "stackdump" | "sd" => Ok(ChiselCommand::StackDump), - "export" | "ex" => Ok(ChiselCommand::Export), - "fetch" | "fe" => Ok(ChiselCommand::Fetch), - "exec" | "e" => Ok(ChiselCommand::Exec), - "rawstack" | "rs" => Ok(ChiselCommand::RawStack), - "edit" => Ok(ChiselCommand::Edit), + "help" | "h" => Ok(Self::Help), + "quit" | "q" => Ok(Self::Quit), + "clear" | "c" => Ok(Self::Clear), + "source" | "so" => Ok(Self::Source), + "save" | "s" => Ok(Self::Save), + "list" | "ls" => Ok(Self::ListSessions), + "load" | "l" => Ok(Self::Load), + "clearcache" | "cc" => Ok(Self::ClearCache), + "fork" | "f" => Ok(Self::Fork), + "traces" | "t" => Ok(Self::Traces), + "calldata" | "cd" => Ok(Self::Calldata), + "memdump" | "md" => Ok(Self::MemDump), + "stackdump" | "sd" => Ok(Self::StackDump), + "export" | "ex" => Ok(Self::Export), + "fetch" | "fe" => Ok(Self::Fetch), + "exec" | "e" => Ok(Self::Exec), + "rawstack" | "rs" => Ok(Self::RawStack), + "edit" => Ok(Self::Edit), _ => Err(ChiselDispatcher::make_error(format!( "Unknown command \"{s}\"! See available commands with `!help`.", )) @@ -103,10 +103,10 @@ pub enum CmdCategory { impl core::fmt::Display for CmdCategory { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let string = match self { - CmdCategory::General => "General", - CmdCategory::Session => "Session", - CmdCategory::Env => "Environment", - CmdCategory::Debug => "Debug", + Self::General => "General", + Self::Session => "Session", + Self::Env => "Environment", + Self::Debug => "Debug", }; f.write_str(string) } diff --git a/crates/chisel/src/dispatcher.rs b/crates/chisel/src/dispatcher.rs index 46086f1dc..5c13c8e01 100644 --- a/crates/chisel/src/dispatcher.rs +++ b/crates/chisel/src/dispatcher.rs @@ -17,7 +17,7 @@ use foundry_config::{Config, RpcEndpoint}; use foundry_evm::{ decode::decode_console_logs, traces::{ - identifier::{EtherscanIdentifier, SignaturesIdentifier}, + identifier::{SignaturesIdentifier, TraceIdentifiers}, render_trace_arena, CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, }, }; @@ -86,11 +86,11 @@ impl DispatchResult { pub fn is_error(&self) -> bool { matches!( self, - DispatchResult::Failure(_) | - DispatchResult::CommandFailed(_) | - DispatchResult::UnrecognizedCommand(_) | - DispatchResult::SolangParserFailed(_) | - DispatchResult::FileIoError(_) + Self::Failure(_) | + Self::CommandFailed(_) | + Self::UnrecognizedCommand(_) | + Self::SolangParserFailed(_) | + Self::FileIoError(_) ) } } @@ -195,7 +195,7 @@ impl ChiselDispatcher { ChiselCommand::iter().map(CmdDescriptor::from).collect::>(); DispatchResult::CommandSuccess(Some(format!( "{}\n{}", - Paint::cyan(format!("{CHISEL_CHAR} Chisel help\n=============")), + format!("{CHISEL_CHAR} Chisel help\n=============").cyan(), CmdCategory::iter() .map(|cat| { // Get commands in the current category @@ -209,13 +209,13 @@ impl ChiselDispatcher { // Format the help menu for the current category format!( "{}\n{}\n", - Paint::magenta(cat), + cat.magenta(), cat_cmds .iter() .map(|(cmds, desc, _)| format!( "\t{} - {}", cmds.iter() - .map(|cmd| format!("!{}", Paint::green(cmd))) + .map(|cmd| format!("!{}", cmd.green())) .collect::>() .join(" | "), desc @@ -274,7 +274,7 @@ impl ChiselDispatcher { if let Err(e) = self.session.write() { return DispatchResult::FileIoError(e.into()) } - println!("{}", Paint::green("Saved current session!")); + println!("{}", "Saved current session!".green()); } // Parse the arguments @@ -304,11 +304,11 @@ impl ChiselDispatcher { ChiselCommand::ListSessions => match ChiselSession::list_sessions() { Ok(sessions) => DispatchResult::CommandSuccess(Some(format!( "{}\n{}", - Paint::cyan(format!("{CHISEL_CHAR} Chisel Sessions")), + format!("{CHISEL_CHAR} Chisel Sessions").cyan(), sessions .iter() .map(|(time, name)| { - format!("{} - {}", Paint::blue(format!("{time:?}")), name) + format!("{} - {}", format!("{time:?}").blue(), name) }) .collect::>() .join("\n") @@ -372,7 +372,7 @@ impl ChiselDispatcher { } // Create success message before moving the fork_url - let success_msg = format!("Set fork URL to {}", Paint::yellow(&fork_url)); + let success_msg = format!("Set fork URL to {}", &fork_url.yellow()); // Update the fork_url inside of the [SessionSourceConfig]'s [EvmOpts] // field @@ -410,12 +410,11 @@ impl ChiselDispatcher { self.source_mut().config.calldata = Some(calldata); DispatchResult::CommandSuccess(Some(format!( "Set calldata to '{}'", - Paint::yellow(arg) + arg.yellow() ))) } Err(e) => DispatchResult::CommandFailed(Self::make_error(format!( - "Invalid calldata: {}", - e + "Invalid calldata: {e}" ))), } } @@ -428,8 +427,8 @@ impl ChiselDispatcher { (0..mem.len()).step_by(32).for_each(|i| { println!( "{}: {}", - Paint::yellow(format!("[0x{:02x}:0x{:02x}]", i, i + 32)), - Paint::cyan(hex::encode_prefixed(&mem[i..i + 32])) + format!("[0x{:02x}:0x{:02x}]", i, i + 32).yellow(), + hex::encode_prefixed(&mem[i..i + 32]).cyan() ); }); } else { @@ -437,8 +436,8 @@ impl ChiselDispatcher { (0..stack.len()).rev().for_each(|i| { println!( "{}: {}", - Paint::yellow(format!("[{}]", stack.len() - i - 1)), - Paint::cyan(format!("0x{:02x}", stack.data()[i])) + format!("[{}]", stack.len() - i - 1).yellow(), + format!("0x{:02x}", stack[i]).cyan() ); }); } @@ -689,7 +688,7 @@ impl ChiselDispatcher { let failed = !res.success; if new_session_source.config.traces || failed { if let Ok(decoder) = - Self::decode_traces(&new_session_source.config, &mut res) + Self::decode_traces(&new_session_source.config, &mut res).await { if let Err(e) = Self::show_traces(&decoder, &mut res).await { return DispatchResult::CommandFailed(e.to_string()) @@ -698,7 +697,7 @@ impl ChiselDispatcher { // Show console logs, if there are any let decoded_logs = decode_console_logs(&res.logs); if !decoded_logs.is_empty() { - println!("{}", Paint::green("Logs:")); + println!("{}", "Logs:".green()); for log in decoded_logs { println!(" {log}"); } @@ -834,7 +833,8 @@ impl ChiselDispatcher { // If traces are enabled or there was an error in execution, show the execution // traces. if new_source.config.traces || failed { - if let Ok(decoder) = Self::decode_traces(&new_source.config, &mut res) { + if let Ok(decoder) = Self::decode_traces(&new_source.config, &mut res).await + { if let Err(e) = Self::show_traces(&decoder, &mut res).await { return DispatchResult::CommandFailed(e.to_string()) }; @@ -842,7 +842,7 @@ impl ChiselDispatcher { // Show console logs, if there are any let decoded_logs = decode_console_logs(&res.logs); if !decoded_logs.is_empty() { - println!("{}", Paint::green("Logs:")); + println!("{}", "Logs:".green()); for log in decoded_logs { println!(" {log}"); } @@ -888,16 +888,11 @@ impl ChiselDispatcher { /// ### Returns /// /// Optionally, a [CallTraceDecoder] - pub fn decode_traces( + pub async fn decode_traces( session_config: &SessionSourceConfig, result: &mut ChiselResult, // known_contracts: &ContractsByArtifact, ) -> eyre::Result { - let mut etherscan_identifier = EtherscanIdentifier::new( - &session_config.foundry_config, - session_config.evm_opts.get_remote_chain_id(), - )?; - let mut decoder = CallTraceDecoderBuilder::new() .with_labels(result.labeled_addresses.clone()) .with_signature_identifier(SignaturesIdentifier::new( @@ -906,9 +901,14 @@ impl ChiselDispatcher { )?) .build(); - for (_, trace) in &mut result.traces { - // decoder.identify(trace, &mut local_identifier); - decoder.identify(trace, &mut etherscan_identifier); + let mut identifier = TraceIdentifiers::new().with_etherscan( + &session_config.foundry_config, + session_config.evm_opts.get_remote_chain_id().await, + )?; + if !identifier.is_empty() { + for (_, trace) in &mut result.traces { + decoder.identify(trace, &mut identifier); + } } Ok(decoder) } @@ -931,7 +931,7 @@ impl ChiselDispatcher { eyre::bail!("Unexpected error: No traces gathered. Please report this as a bug: https://github.com/foundry-rs/foundry/issues/new?assignees=&labels=T-bug&template=BUG-FORM.yml"); } - println!("{}", Paint::green("Traces:")); + println!("{}", "Traces:".green()); for (kind, trace) in &result.traces { // Display all Setup + Execution traces. if matches!(kind, TraceKind::Setup | TraceKind::Execution) { @@ -942,7 +942,7 @@ impl ChiselDispatcher { Ok(()) } - /// Format a type that implements [fmt::Display] as a chisel error string. + /// Format a type that implements [std::fmt::Display] as a chisel error string. /// /// ### Takes /// @@ -952,7 +952,7 @@ impl ChiselDispatcher { /// /// A formatted error [String]. pub fn make_error(msg: T) -> String { - format!("{} {}", Paint::red(format!("{CHISEL_CHAR} Chisel Error:")), Paint::red(msg)) + format!("{} {}", format!("{CHISEL_CHAR} Chisel Error:").red(), msg.red()) } } diff --git a/crates/chisel/src/executor.rs b/crates/chisel/src/executor.rs index 87a851626..b2033af96 100644 --- a/crates/chisel/src/executor.rs +++ b/crates/chisel/src/executor.rs @@ -91,15 +91,15 @@ impl SessionSource { // Map the source location of the final statement of the `run()` function to its // corresponding runtime program counter let final_pc = { - let offset = source_loc.start(); - let length = source_loc.end() - source_loc.start(); + let offset = source_loc.start() as u32; + let length = (source_loc.end() - source_loc.start()) as u32; contract .get_source_map_deployed() .unwrap() .unwrap() .into_iter() .zip(InstructionIter::new(&deployed_bytecode)) - .filter(|(s, _)| s.offset == offset && s.length == length) + .filter(|(s, _)| s.offset() == offset && s.length() == length) .map(|(_, i)| i.pc) .max() .unwrap_or_default() @@ -189,12 +189,12 @@ impl SessionSource { let Some((stack, memory, _)) = &res.state else { // Show traces and logs, if there are any, and return an error - if let Ok(decoder) = ChiselDispatcher::decode_traces(&source.config, &mut res) { + if let Ok(decoder) = ChiselDispatcher::decode_traces(&source.config, &mut res).await { ChiselDispatcher::show_traces(&decoder, &mut res).await?; } let decoded_logs = decode_console_logs(&res.logs); if !decoded_logs.is_empty() { - println!("{}", Paint::green("Logs:")); + println!("{}", "Logs:".green()); for log in decoded_logs { println!(" {log}"); } @@ -236,7 +236,7 @@ impl SessionSource { // the file compiled correctly, thus the last stack item must be the memory offset of // the `bytes memory inspectoor` value - let mut offset = stack.data().last().unwrap().to::(); + let mut offset = stack.last().unwrap().to::(); let mem_offset = &memory[offset..offset + 32]; let len = U256::try_from_be_slice(mem_offset).unwrap().to::(); offset += 32; @@ -279,7 +279,7 @@ impl SessionSource { /// /// ### Takes /// - /// The final statement's program counter for the [ChiselInspector] + /// The final statement's program counter for the ChiselInspector /// /// ### Returns /// @@ -292,10 +292,8 @@ impl SessionSource { let backend = match self.config.backend.take() { Some(backend) => backend, None => { - let backend = Backend::spawn( - self.config.evm_opts.get_fork(&self.config.foundry_config, env.clone()), - ) - .await; + let fork = self.config.evm_opts.get_fork(&self.config.foundry_config, env.clone()); + let backend = Backend::spawn(fork); self.config.backend = Some(backend.clone()); backend } @@ -309,6 +307,8 @@ impl SessionSource { &self.config.foundry_config, self.config.evm_opts.clone(), None, + None, + Some(self.solc.version.clone()), Default::default(), false, ) @@ -325,34 +325,25 @@ impl SessionSource { } } -/// Formats a [Token] into an inspection message -/// -/// ### Takes -/// -/// An owned [Token] -/// -/// ### Returns -/// -/// A formatted [Token] for use in inspection output. -/// -/// TODO: Verbosity option +/// Formats a value into an inspection message +// TODO: Verbosity option fn format_token(token: DynSolValue) -> String { match token { DynSolValue::Address(a) => { - format!("Type: {}\n└ Data: {}", Paint::red("address"), Paint::cyan(a.to_string())) + format!("Type: {}\n└ Data: {}", "address".red(), a.cyan()) } DynSolValue::FixedBytes(b, byte_len) => { format!( "Type: {}\n└ Data: {}", - Paint::red(format!("bytes{byte_len}")), - Paint::cyan(hex::encode_prefixed(b)) + format!("bytes{byte_len}").red(), + hex::encode_prefixed(b).cyan() ) } DynSolValue::Int(i, bit_len) => { format!( "Type: {}\n├ Hex: {}\n├ Hex (full word): {}\n└ Decimal: {}", - Paint::red(format!("int{}", bit_len)), - Paint::cyan(format!( + format!("int{bit_len}").red(), + format!( "0x{}", format!("{i:x}") .char_indices() @@ -360,16 +351,17 @@ fn format_token(token: DynSolValue) -> String { .take(bit_len / 4) .map(|(_, c)| c) .collect::() - )), - Paint::cyan(format!("{i:#x}")), - Paint::cyan(i) + ) + .cyan(), + format!("{i:#x}").cyan(), + i.cyan() ) } DynSolValue::Uint(i, bit_len) => { format!( "Type: {}\n├ Hex: {}\n├ Hex (full word): {}\n└ Decimal: {}", - Paint::red(format!("uint{}", bit_len)), - Paint::cyan(format!( + format!("uint{bit_len}").red(), + format!( "0x{}", format!("{i:x}") .char_indices() @@ -377,50 +369,51 @@ fn format_token(token: DynSolValue) -> String { .take(bit_len / 4) .map(|(_, c)| c) .collect::() - )), - Paint::cyan(format!("{i:#x}")), - Paint::cyan(i) + ) + .cyan(), + format!("{i:#x}").cyan(), + i.cyan() ) } DynSolValue::Bool(b) => { - format!("Type: {}\n└ Value: {}", Paint::red("bool"), Paint::cyan(b)) + format!("Type: {}\n└ Value: {}", "bool".red(), b.cyan()) } DynSolValue::String(_) | DynSolValue::Bytes(_) => { let hex = hex::encode(token.abi_encode()); let s = token.as_str(); format!( "Type: {}\n{}├ Hex (Memory):\n├─ Length ({}): {}\n├─ Contents ({}): {}\n├ Hex (Tuple Encoded):\n├─ Pointer ({}): {}\n├─ Length ({}): {}\n└─ Contents ({}): {}", - Paint::red(if s.is_some() { "string" } else { "dynamic bytes" }), + if s.is_some() { "string" } else { "dynamic bytes" }.red(), if let Some(s) = s { - format!("├ UTF-8: {}\n", Paint::cyan(s)) + format!("├ UTF-8: {}\n", s.cyan()) } else { String::default() }, - Paint::yellow("[0x00:0x20]"), - Paint::cyan(format!("0x{}", &hex[64..128])), - Paint::yellow("[0x20:..]"), - Paint::cyan(format!("0x{}", &hex[128..])), - Paint::yellow("[0x00:0x20]"), - Paint::cyan(format!("0x{}", &hex[..64])), - Paint::yellow("[0x20:0x40]"), - Paint::cyan(format!("0x{}", &hex[64..128])), - Paint::yellow("[0x40:..]"), - Paint::cyan(format!("0x{}", &hex[128..])), + "[0x00:0x20]".yellow(), + format!("0x{}", &hex[64..128]).cyan(), + "[0x20:..]".yellow(), + format!("0x{}", &hex[128..]).cyan(), + "[0x00:0x20]".yellow(), + format!("0x{}", &hex[..64]).cyan(), + "[0x20:0x40]".yellow(), + format!("0x{}", &hex[64..128]).cyan(), + "[0x40:..]".yellow(), + format!("0x{}", &hex[128..]).cyan(), ) } DynSolValue::FixedArray(tokens) | DynSolValue::Array(tokens) => { let mut out = format!( "{}({}) = {}", - Paint::red("array"), - Paint::yellow(format!("{}", tokens.len())), - Paint::red('[') + "array".red(), + format!("{}", tokens.len()).yellow(), + '['.red() ); for token in tokens { out.push_str("\n ├ "); out.push_str(&format_token(token).replace('\n', "\n ")); out.push('\n'); } - out.push_str(&Paint::red(']').to_string()); + out.push_str(&']'.red().to_string()); out } DynSolValue::Tuple(tokens) => { @@ -430,18 +423,14 @@ fn format_token(token: DynSolValue) -> String { .map(|t| t.unwrap_or_default().into_owned()) .collect::>() .join(", "); - let mut out = format!( - "{}({}) = {}", - Paint::red("tuple"), - Paint::yellow(displayed_types), - Paint::red('(') - ); + let mut out = + format!("{}({}) = {}", "tuple".red(), displayed_types.yellow(), '('.red()); for token in tokens { out.push_str("\n ├ "); out.push_str(&format_token(token).replace('\n', "\n ")); out.push('\n'); } - out.push_str(&Paint::red(')').to_string()); + out.push_str(&')'.red().to_string()); out } _ => { @@ -489,7 +478,7 @@ fn format_event_definition(event_definition: &pt::EventDefinition) -> Result Result>() .join(", ") )), - Paint::cyan(event.signature()), - Paint::cyan(event.selector()), + event.signature().cyan(), + event.selector().cyan(), )) } @@ -759,7 +748,7 @@ impl Type { .map(|(returns, _)| map_parameters(returns)) .unwrap_or_default(); Self::Function( - Box::new(Type::Custom(vec!["__fn_type__".to_string()])), + Box::new(Self::Custom(vec!["__fn_type__".to_string()])), params, returns, ) @@ -904,7 +893,7 @@ impl Type { /// Recurses over itself, appending all the idents and function arguments in the order that they /// are found - fn recurse(&self, types: &mut Vec, args: &mut Option>>) { + fn recurse(&self, types: &mut Vec, args: &mut Option>>) { match self { Self::Builtin(ty) => types.push(ty.to_string()), Self::Custom(tys) => types.extend(tys.clone()), @@ -1171,7 +1160,7 @@ impl Type { .function_definitions .get(&function_name.name)?; let return_parameter = contract.as_ref().returns.first()?.to_owned().1?; - Type::ethabi(&return_parameter.ty, Some(intermediate)).map(|p| (contract_expr.unwrap(), p)) + Self::ethabi(&return_parameter.ty, Some(intermediate)).map(|p| (contract_expr.unwrap(), p)) } /// Inverts Int to Uint and viceversa. @@ -1214,12 +1203,9 @@ impl Type { #[inline] fn is_dynamic(&self) -> bool { match self { - Self::Builtin(ty) => match ty { - // TODO: Note, this is not entirely correct. Fixed arrays of non-dynamic types are - // not dynamic, nor are tuples of non-dynamic types. - DynSolType::Bytes | DynSolType::String | DynSolType::Array(_) => true, - _ => false, - }, + // TODO: Note, this is not entirely correct. Fixed arrays of non-dynamic types are + // not dynamic, nor are tuples of non-dynamic types. + Self::Builtin(DynSolType::Bytes | DynSolType::String | DynSolType::Array(_)) => true, Self::Array(_) => true, _ => false, } @@ -1408,7 +1394,8 @@ impl<'a> Iterator for InstructionIter<'a> { #[cfg(test)] mod tests { use super::*; - use foundry_compilers::{error::SolcError, Solc}; + use foundry_compilers::{error::SolcError, solc::Solc}; + use semver::Version; use std::sync::Mutex; #[test] @@ -1692,10 +1679,10 @@ mod tests { for _ in 0..3 { let mut is_preinstalled = PRE_INSTALL_SOLC_LOCK.lock().unwrap(); if !*is_preinstalled { - let solc = Solc::find_or_install_svm_version(version) - .and_then(|solc| solc.version().map(|v| (solc, v))); + let solc = Solc::find_or_install(&version.parse().unwrap()) + .map(|solc| (solc.version.clone(), solc)); match solc { - Ok((solc, v)) => { + Ok((v, solc)) => { // successfully installed eprintln!("found installed Solc v{v} @ {}", solc.solc.display()); break @@ -1704,7 +1691,7 @@ mod tests { // try reinstalling eprintln!("error while trying to re-install Solc v{version}: {e}"); let solc = Solc::blocking_install(&version.parse().unwrap()); - if solc.map_err(SolcError::from).and_then(|solc| solc.version()).is_ok() { + if solc.map_err(SolcError::from).is_ok() { *is_preinstalled = true; break } @@ -1713,7 +1700,7 @@ mod tests { } } - let solc = Solc::find_or_install_svm_version("0.8.19").expect("could not install solc"); + let solc = Solc::find_or_install(&Version::new(0, 8, 19)).expect("could not install solc"); SessionSource::new(solc, Default::default()) } @@ -1785,9 +1772,6 @@ mod tests { } fn init_tracing() { - if std::env::var_os("RUST_LOG").is_none() { - std::env::set_var("RUST_LOG", "debug"); - } let _ = tracing_subscriber::FmtSubscriber::builder() .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .try_init(); diff --git a/crates/chisel/src/lib.rs b/crates/chisel/src/lib.rs index 2004ed0c4..9e7dcc9fb 100644 --- a/crates/chisel/src/lib.rs +++ b/crates/chisel/src/lib.rs @@ -1,33 +1,22 @@ #![doc = include_str!("../README.md")] -#![warn(missing_docs)] -#![warn(unused_extern_crates)] -#![forbid(unsafe_code)] -#![forbid(where_clauses_object_safety)] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -/// REPL input dispatcher module pub mod dispatcher; -/// Builtin Chisel commands pub mod cmd; pub mod history; -/// Chisel Environment Module pub mod session; -/// Chisel Session Source wrapper pub mod session_source; -/// REPL contract runner pub mod runner; -/// REPL contract executor pub mod executor; -/// A Solidity Helper module for rustyline pub mod solidity_helper; -/// Prelude of all chisel modules pub mod prelude { pub use crate::{ cmd::*, dispatcher::*, runner::*, session::*, session_source::*, solidity_helper::*, diff --git a/crates/chisel/src/runner.rs b/crates/chisel/src/runner.rs index dfbc30189..ff4c9695e 100644 --- a/crates/chisel/src/runner.rs +++ b/crates/chisel/src/runner.rs @@ -49,7 +49,7 @@ pub struct ChiselResult { /// Called address pub address: Option
, /// EVM State at the final instruction of the `run()` function - pub state: Option<(revm::interpreter::Stack, Vec, InstructionResult)>, + pub state: Option<(Vec, Vec, InstructionResult)>, } /// ChiselRunner implementation @@ -153,7 +153,7 @@ impl ChiselRunner { match res.exit_reason { InstructionResult::Revert | InstructionResult::OutOfGas | - InstructionResult::OutOfFund => { + InstructionResult::OutOfFunds => { lowest_gas_limit = mid_gas_limit; } _ => { @@ -189,7 +189,7 @@ impl ChiselRunner { if commit { // if explicitly requested we can now commit the call - res = self.executor.call_raw_committing(from, to, calldata, value)?; + res = self.executor.transact_raw(from, to, calldata, value)?; } let RawCallResult { result, reverted, logs, traces, labels, chisel_state, .. } = res; diff --git a/crates/chisel/src/session.rs b/crates/chisel/src/session.rs index 3be34a179..2f293c1cd 100644 --- a/crates/chisel/src/session.rs +++ b/crates/chisel/src/session.rs @@ -205,9 +205,9 @@ impl ChiselSession { /// /// Optionally, an owned instance of the loaded chisel session. pub fn load(id: &str) -> Result { - let cache_dir = ChiselSession::cache_dir()?; + let cache_dir = Self::cache_dir()?; let contents = std::fs::read_to_string(Path::new(&format!("{cache_dir}chisel-{id}.json")))?; - let chisel_env: ChiselSession = serde_json::from_str(&contents)?; + let chisel_env: Self = serde_json::from_str(&contents)?; Ok(chisel_env) } @@ -241,13 +241,13 @@ impl ChiselSession { pub fn latest() -> Result { let last_session = Self::latest_cached_session()?; let last_session_contents = std::fs::read_to_string(Path::new(&last_session))?; - let chisel_env: ChiselSession = serde_json::from_str(&last_session_contents)?; + let chisel_env: Self = serde_json::from_str(&last_session_contents)?; Ok(chisel_env) } } /// Generic helper function that attempts to convert a type that has -/// an [Into] implementation into a formatted date string. +/// an [`Into`] implementation into a formatted date string. fn systemtime_strftime(dt: T, format: &str) -> Result where T: Into, diff --git a/crates/chisel/src/session_source.rs b/crates/chisel/src/session_source.rs index 47660ba09..f83eefeae 100644 --- a/crates/chisel/src/session_source.rs +++ b/crates/chisel/src/session_source.rs @@ -7,8 +7,8 @@ use eyre::Result; use forge_fmt::solang_ext::SafeUnwrap; use foundry_compilers::{ - artifacts::{Source, Sources}, - CompilerInput, CompilerOutput, Solc, + artifacts::{CompilerOutput, Settings, SolcInput, Source, Sources}, + compilers::solc::Solc, }; use foundry_config::{Config, SolcReq}; use foundry_evm::{backend::Backend, opts::EvmOpts}; @@ -94,20 +94,20 @@ impl SessionSourceConfig { let solc_req = if let Some(solc_req) = self.foundry_config.solc.clone() { solc_req } else if let Some(version) = Solc::installed_versions().into_iter().max() { - SolcReq::Version(version.into()) + SolcReq::Version(version) } else { if !self.foundry_config.offline { - print!("{}", Paint::green("No solidity versions installed! ")); + print!("{}", "No solidity versions installed! ".green()); } // use default - SolcReq::Version("0.8.19".parse().unwrap()) + SolcReq::Version(Version::new(0, 8, 19)) }; match solc_req { SolcReq::Version(version) => { // Validate that the requested evm version is supported by the solc version let req_evm_version = self.foundry_config.evm_version; - if let Some(compat_evm_version) = req_evm_version.normalize_version(&version) { + if let Some(compat_evm_version) = req_evm_version.normalize_version_solc(&version) { if req_evm_version > compat_evm_version { eyre::bail!( "The set evm version, {req_evm_version}, is not supported by solc {version}. Upgrade to a newer solc version." @@ -115,26 +115,22 @@ impl SessionSourceConfig { } } - let mut solc = Solc::find_svm_installed_version(version.to_string())?; - - if solc.is_none() { + let solc = if let Some(solc) = Solc::find_svm_installed_version(&version)? { + solc + } else { if self.foundry_config.offline { eyre::bail!("can't install missing solc {version} in offline mode") } - println!( - "{}", - Paint::green(format!("Installing solidity version {version}...")) - ); - Solc::blocking_install(&version)?; - solc = Solc::find_svm_installed_version(version.to_string())?; - } - solc.ok_or_else(|| eyre::eyre!("Failed to install {version}")) + println!("{}", format!("Installing solidity version {version}...").green()); + Solc::blocking_install(&version)? + }; + Ok(solc) } SolcReq::Local(solc) => { if !solc.is_file() { eyre::bail!("`solc` {} does not exist", solc.display()); } - Ok(Solc::new(solc)) + Ok(Solc::new(solc)?) } } } @@ -185,11 +181,9 @@ impl SessionSource { /// A new instance of [SessionSource] #[track_caller] pub fn new(solc: Solc, mut config: SessionSourceConfig) -> Self { - if let Ok(v) = solc.version_short() { - if v < MIN_VM_VERSION && !config.no_vm { - tracing::info!(version=%v, minimum=%MIN_VM_VERSION, "Disabling VM injection"); - config.no_vm = true; - } + if solc.version < MIN_VM_VERSION && !config.no_vm { + tracing::info!(version=%solc.version, minimum=%MIN_VM_VERSION, "Disabling VM injection"); + config.no_vm = true; } Self { @@ -230,7 +224,7 @@ impl SessionSource { /// /// Optionally, a shallow-cloned [SessionSource] with the passed content appended to the /// source code. - pub fn clone_with_new_line(&self, mut content: String) -> Result<(SessionSource, bool)> { + pub fn clone_with_new_line(&self, mut content: String) -> Result<(Self, bool)> { let new_source = self.shallow_clone(); if let Some(parsed) = parse_fragment(new_source.solc, new_source.config, &content) .or_else(|| { @@ -289,32 +283,32 @@ impl SessionSource { /// Clears global code from the source pub fn drain_global_code(&mut self) -> &mut Self { - self.global_code.clear(); + String::clear(&mut self.global_code); self.generated_output = None; self } /// Clears top-level code from the source pub fn drain_top_level_code(&mut self) -> &mut Self { - self.top_level_code.clear(); + String::clear(&mut self.top_level_code); self.generated_output = None; self } /// Clears the "run()" function's code pub fn drain_run(&mut self) -> &mut Self { - self.run_code.clear(); + String::clear(&mut self.run_code); self.generated_output = None; self } - /// Generates and foundry_compilers::CompilerInput from the source + /// Generates and [`SolcInput`] from the source. /// /// ### Returns /// - /// A [CompilerInput] object containing forge-std's `Vm` interface as well as the REPL contract + /// A [`SolcInput`] object containing forge-std's `Vm` interface as well as the REPL contract /// source. - pub fn compiler_input(&self) -> CompilerInput { + pub fn compiler_input(&self) -> SolcInput { let mut sources = Sources::new(); sources.insert(self.file_name.clone(), Source::new(self.to_repl_source())); @@ -325,19 +319,18 @@ impl SessionSource { sources.insert(PathBuf::from("forge-std/Vm.sol"), Source::new(VM_SOURCE)); } + let settings = Settings { + remappings, + evm_version: Some(self.config.foundry_config.evm_version), + ..Default::default() + }; + // we only care about the solidity source, so we can safely unwrap - let mut compiler_input = CompilerInput::with_sources(sources) + SolcInput::resolve_and_build(sources, settings) .into_iter() .next() - .expect("Solidity source not found"); - - // get all remappings from the config - compiler_input.settings.remappings = remappings; - - // We also need to enforce the EVM version that the user has specified. - compiler_input.settings.evm_version = Some(self.config.foundry_config.evm_version); - - compiler_input + .map(|i| i.sanitized(&self.solc.version)) + .expect("Solidity source not found") } /// Compiles the source using [solang_parser] @@ -445,7 +438,7 @@ impl SessionSource { /// /// The [SessionSource] represented as a Forge Script contract. pub fn to_script_source(&self) -> String { - let Version { major, minor, patch, .. } = self.solc.version().unwrap(); + let Version { major, minor, patch, .. } = self.solc.version; let Self { contract_name, global_code, top_level_code, run_code, config, .. } = self; let script_import = @@ -476,7 +469,7 @@ contract {contract_name} is Script {{ /// /// The [SessionSource] represented as a REPL contract. pub fn to_repl_source(&self) -> String { - let Version { major, minor, patch, .. } = self.solc.version().unwrap(); + let Version { major, minor, patch, .. } = self.solc.version; let Self { contract_name, global_code, top_level_code, run_code, config, .. } = self; let (vm_import, vm_constant) = if !config.no_vm { diff --git a/crates/chisel/src/solidity_helper.rs b/crates/chisel/src/solidity_helper.rs index 42641a1f0..84140c820 100644 --- a/crates/chisel/src/solidity_helper.rs +++ b/crates/chisel/src/solidity_helper.rs @@ -19,7 +19,7 @@ use solang_parser::{ pt, }; use std::{borrow::Cow, str::FromStr}; -use yansi::{Color, Paint, Style}; +use yansi::{Color, Style}; /// The default pre-allocation for solang parsed comments const DEFAULT_COMMENTS: usize = 5; @@ -71,7 +71,7 @@ impl SolidityHelper { pt::Comment::DocLine(loc, _) | pt::Comment::DocBlock(loc, _) => loc, }; - (loc.start(), Style::default().dimmed(), loc.end()) + (loc.start(), Style::new().dim(), loc.end()) }); out.extend(comments_iter); @@ -101,8 +101,8 @@ impl SolidityHelper { } /// Highlights a solidity source string - pub fn highlight(input: &str) -> Cow { - if !Paint::is_enabled() { + pub fn highlight(input: &str) -> Cow<'_, str> { + if !yansi::is_enabled() { return Cow::Borrowed(input) } @@ -119,7 +119,7 @@ impl SolidityHelper { // cmd out.push(COMMAND_LEADER); let cmd_res = ChiselCommand::from_str(cmd); - let style = Style::new(if cmd_res.is_ok() { Color::Green } else { Color::Red }); + let style = (if cmd_res.is_ok() { Color::Green } else { Color::Red }).foreground(); Self::paint_unchecked(cmd, style, &mut out); // rest @@ -186,7 +186,7 @@ impl SolidityHelper { } /// Formats `input` with `style` into `out`, without checking `style.wrapping` or - /// `Paint::is_enabled` + /// `yansi::is_enabled` #[inline] fn paint_unchecked(string: &str, style: Style, out: &mut String) { if style == Style::default() { @@ -220,7 +220,7 @@ impl Highlighter for SolidityHelper { prompt: &'p str, _default: bool, ) -> Cow<'b, str> { - if !Paint::is_enabled() { + if !yansi::is_enabled() { return Cow::Borrowed(prompt) } @@ -231,12 +231,16 @@ impl Highlighter for SolidityHelper { let id_end = prompt.find(')').unwrap(); let id_span = 5..id_end; let id = &prompt[id_span.clone()]; - out.replace_range(id_span, &Self::paint_unchecked_owned(id, Color::Yellow.style())); - out.replace_range(1..=2, &Self::paint_unchecked_owned("ID", Color::Cyan.style())); + out.replace_range( + id_span, + &Self::paint_unchecked_owned(id, Color::Yellow.foreground()), + ); + out.replace_range(1..=2, &Self::paint_unchecked_owned("ID", Color::Cyan.foreground())); } if let Some(i) = out.find(PROMPT_ARROW) { - let style = if self.errored { Color::Red.style() } else { Color::Green.style() }; + let style = + if self.errored { Color::Red.foreground() } else { Color::Green.foreground() }; let mut arrow = String::with_capacity(MAX_ANSI_LEN + 4); @@ -252,7 +256,7 @@ impl Highlighter for SolidityHelper { } impl Validator for SolidityHelper { - fn validate(&self, ctx: &mut ValidationContext) -> rustyline::Result { + fn validate(&self, ctx: &mut ValidationContext<'_>) -> rustyline::Result { Ok(Self::validate_closed(ctx.input())) } } @@ -278,7 +282,7 @@ impl<'a> TokenStyle for Token<'a> { fn style(&self) -> Style { use Token::*; match self { - StringLiteral(_, _) => Color::Green.style(), + StringLiteral(_, _) => Color::Green.foreground(), AddressLiteral(_) | HexLiteral(_) | @@ -286,21 +290,21 @@ impl<'a> TokenStyle for Token<'a> { RationalNumber(_, _, _) | HexNumber(_) | True | - False => Color::Yellow.style(), + False => Color::Yellow.foreground(), Memory | Storage | Calldata | Public | Private | Internal | External | Constant | Pure | View | Payable | Anonymous | Indexed | Abstract | Virtual | Override | - Modifier | Immutable | Unchecked => Color::Cyan.style(), + Modifier | Immutable | Unchecked => Color::Cyan.foreground(), Contract | Library | Interface | Function | Pragma | Import | Struct | Event | Enum | Type | Constructor | As | Is | Using | New | Delete | Do | Continue | Break | Throw | Emit | Return | Returns | Revert | For | While | If | Else | Try | Catch | Assembly | Let | Leave | Switch | Case | Default | YulArrow | Arrow => { - Color::Magenta.style() + Color::Magenta.foreground() } Uint(_) | Int(_) | Bytes(_) | Byte | DynamicBytes | Bool | Address | String | - Mapping => Color::Blue.style(), + Mapping => Color::Blue.foreground(), Identifier(_) => Style::default(), diff --git a/crates/chisel/tests/cache.rs b/crates/chisel/tests/cache.rs index c6b9625eb..5f0864bee 100644 --- a/crates/chisel/tests/cache.rs +++ b/crates/chisel/tests/cache.rs @@ -1,6 +1,7 @@ use chisel::session::ChiselSession; -use foundry_compilers::EvmVersion; +use foundry_compilers::artifacts::EvmVersion; use foundry_config::{Config, SolcReq}; +use semver::Version; use serial_test::serial; use std::path::Path; @@ -44,7 +45,7 @@ fn test_write_session() { foundry_config, ..Default::default() }) - .unwrap_or_else(|e| panic!("Failed to create ChiselSession!, {}", e)); + .unwrap_or_else(|e| panic!("Failed to create ChiselSession!, {e}")); // Write the session let cached_session_name = env.write().unwrap(); @@ -72,7 +73,7 @@ fn test_write_session_with_name() { foundry_config, ..Default::default() }) - .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {}", e)); + .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {e}")); env.id = Some(String::from("test")); // Write the session @@ -122,7 +123,7 @@ fn test_list_sessions() { foundry_config, ..Default::default() }) - .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {}", e)); + .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {e}")); env.write().unwrap(); @@ -149,7 +150,7 @@ fn test_load_cache() { foundry_config, ..Default::default() }) - .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {}", e)); + .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {e}")); env.write().unwrap(); // Load the session @@ -177,7 +178,7 @@ fn test_write_same_session_multiple_times() { foundry_config, ..Default::default() }) - .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {}", e)); + .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {e}")); env.write().unwrap(); env.write().unwrap(); env.write().unwrap(); @@ -200,7 +201,7 @@ fn test_load_latest_cache() { foundry_config: foundry_config.clone(), ..Default::default() }) - .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {}", e)); + .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {e}")); env.write().unwrap(); let wait_time = std::time::Duration::from_millis(100); @@ -210,7 +211,7 @@ fn test_load_latest_cache() { foundry_config, ..Default::default() }) - .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {}", e)); + .unwrap_or_else(|e| panic!("Failed to create ChiselSession! {e}")); env2.write().unwrap(); // Load the latest session @@ -231,7 +232,7 @@ fn test_solc_evm_configuration_mismatch() { // Force the solc version to be 0.8.13 which does not support Paris let foundry_config = Config { evm_version: EvmVersion::Paris, - solc: Some(SolcReq::Version("0.8.13".parse().expect("invalid semver"))), + solc: Some(SolcReq::Version(Version::new(0, 8, 13))), ..Default::default() }; diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index aad365bb5..f13dbe5f6 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -9,6 +9,9 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [dependencies] forge-fmt.workspace = true foundry-common.workspace = true @@ -22,30 +25,32 @@ foundry-compilers = { workspace = true, features = ["full"] } alloy-dyn-abi.workspace = true alloy-json-abi.workspace = true alloy-primitives.workspace = true - -ethers-core.workspace = true -ethers-providers.workspace = true +alloy-provider.workspace = true +alloy-transport.workspace = true +alloy-chains.workspace = true clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } color-eyre.workspace = true dotenvy = "0.15" eyre.workspace = true indicatif = "0.17" -once_cell = "1" +once_cell.workspace = true regex = { version = "1", default-features = false } serde.workspace = true strsim = "0.10" strum = { workspace = true, features = ["derive"] } -tokio = { version = "1", features = ["macros"] } +tokio = { workspace = true, features = ["macros"] } tracing-error = "0.2" tracing-subscriber = { workspace = true, features = ["registry", "env-filter", "fmt"] } tracing.workspace = true -yansi = "0.5" +yansi.workspace = true +hex.workspace = true +futures.workspace = true [dev-dependencies] -tempfile = "3.7" +tempfile.workspace = true [features] default = ["rustls"] -rustls = ["ethers-providers/rustls", "foundry-wallets/rustls"] -openssl = ["ethers-providers/openssl", "foundry-compilers/openssl", "foundry-wallets/openssl"] +rustls = ["foundry-wallets/rustls"] +openssl = ["foundry-compilers/openssl"] diff --git a/crates/cli/src/handler.rs b/crates/cli/src/handler.rs index 6666686c5..d6e34f3b8 100644 --- a/crates/cli/src/handler.rs +++ b/crates/cli/src/handler.rs @@ -16,7 +16,7 @@ impl EyreHandler for Handler { return core::fmt::Debug::fmt(error, f) } writeln!(f)?; - write!(f, "{}", Paint::red(error))?; + write!(f, "{}", error.red())?; if let Some(cause) = error.source() { write!(f, "\n\nContext:")?; diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index f0e90a96c..6f5e2f607 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -1,4 +1,9 @@ -#![warn(unused_crate_dependencies)] +//! # foundry-cli +//! +//! Common CLI utilities. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #[macro_use] extern crate tracing; diff --git a/crates/cli/src/opts/build/core.rs b/crates/cli/src/opts/build/core.rs index cdb0dd0f4..41aecb841 100644 --- a/crates/cli/src/opts/build/core.rs +++ b/crates/cli/src/opts/build/core.rs @@ -3,7 +3,10 @@ use crate::{opts::CompilerArgs, utils::LoadConfig}; use clap::{Parser, ValueHint}; use eyre::Result; use foundry_compilers::{ - artifacts::RevertStrings, remappings::Remapping, utils::canonicalized, Project, + artifacts::{remappings::Remapping, RevertStrings}, + compilers::multi::MultiCompiler, + utils::canonicalized, + Project, }; use foundry_config::{ figment::{ @@ -12,6 +15,7 @@ use foundry_config::{ value::{Dict, Map, Value}, Figment, Metadata, Profile, Provider, }, + filter::SkipBuildFilter, providers::remappings::Remappings, Config, }; @@ -19,59 +23,66 @@ use serde::Serialize; use std::path::PathBuf; #[derive(Clone, Debug, Default, Serialize, Parser)] -#[clap(next_help_heading = "Build options")] +#[command(next_help_heading = "Build options")] pub struct CoreBuildArgs { /// Clear the cache and artifacts folder and recompile. - #[clap(long, help_heading = "Cache options")] + #[arg(long, help_heading = "Cache options")] #[serde(skip)] pub force: bool, /// Disable the cache. - #[clap(long)] + #[arg(long)] #[serde(skip)] pub no_cache: bool, /// Set pre-linked libraries. - #[clap(long, help_heading = "Linker options", env = "DAPP_LIBRARIES")] + #[arg(long, help_heading = "Linker options", env = "DAPP_LIBRARIES")] #[serde(skip_serializing_if = "Vec::is_empty")] pub libraries: Vec, /// Ignore solc warnings by error code. - #[clap(long, help_heading = "Compiler options", value_name = "ERROR_CODES")] + #[arg(long, help_heading = "Compiler options", value_name = "ERROR_CODES")] #[serde(skip_serializing_if = "Vec::is_empty")] pub ignored_error_codes: Vec, /// Warnings will trigger a compiler error - #[clap(long, help_heading = "Compiler options")] + #[arg(long, help_heading = "Compiler options")] #[serde(skip)] pub deny_warnings: bool, /// Do not auto-detect the `solc` version. - #[clap(long, help_heading = "Compiler options")] + #[arg(long, help_heading = "Compiler options")] #[serde(skip)] pub no_auto_detect: bool, /// Specify the solc version, or a path to a local solc, to build with. /// /// Valid values are in the format `x.y.z`, `solc:x.y.z` or `path/to/solc`. - #[clap(long = "use", help_heading = "Compiler options", value_name = "SOLC_VERSION")] + #[arg(long = "use", help_heading = "Compiler options", value_name = "SOLC_VERSION")] #[serde(skip)] pub use_solc: Option, /// Do not access the network. /// /// Missing solc versions will not be installed. - #[clap(help_heading = "Compiler options", long)] + #[arg(help_heading = "Compiler options", long)] #[serde(skip)] pub offline: bool, /// Use the Yul intermediate representation compilation pipeline. - #[clap(long, help_heading = "Compiler options")] + #[arg(long, help_heading = "Compiler options")] #[serde(skip)] pub via_ir: bool, + /// Do not append any metadata to the bytecode. + /// + /// This is equivalent to setting `bytecode_hash` to `none` and `cbor_metadata` to `false`. + #[arg(long, help_heading = "Compiler options")] + #[serde(skip)] + pub no_metadata: bool, + /// The path to the contract artifacts folder. - #[clap( + #[arg( long = "out", short, help_heading = "Project options", @@ -85,22 +96,22 @@ pub struct CoreBuildArgs { /// /// Possible values are "default", "strip" (remove), /// "debug" (Solidity-generated revert strings) and "verboseDebug" - #[clap(long, help_heading = "Project options", value_name = "REVERT")] + #[arg(long, help_heading = "Project options", value_name = "REVERT")] #[serde(skip)] pub revert_strings: Option, /// Don't print anything on startup. - #[clap(long, help_heading = "Compiler options")] + #[arg(long, help_heading = "Compiler options")] #[serde(skip)] pub silent: bool, /// Generate build info files. - #[clap(long, help_heading = "Project options")] + #[arg(long, help_heading = "Project options")] #[serde(skip)] pub build_info: bool, /// Output path to directory that build info files will be written to. - #[clap( + #[arg( long, help_heading = "Project options", value_hint = ValueHint::DirPath, @@ -110,11 +121,18 @@ pub struct CoreBuildArgs { #[serde(skip_serializing_if = "Option::is_none")] pub build_info_path: Option, - #[clap(flatten)] + /// Skip building files whose names contain the given filter. + /// + /// `test` and `script` are aliases for `.t.sol` and `.s.sol`. + #[arg(long, num_args(1..))] + #[serde(skip)] + pub skip: Option>, + + #[command(flatten)] #[serde(flatten)] pub compiler: CompilerArgs, - #[clap(flatten)] + #[command(flatten)] #[serde(flatten)] pub project_paths: ProjectPathsArgs, } @@ -123,9 +141,9 @@ impl CoreBuildArgs { /// Returns the `Project` for the current workspace /// /// This loads the `foundry_config::Config` for the current workspace (see - /// [`utils::find_project_root_path`] and merges the cli `BuildArgs` into it before returning - /// [`foundry_config::Config::project()`] - pub fn project(&self) -> Result { + /// `find_project_root_path` and merges the cli `BuildArgs` into it before returning + /// [`foundry_config::Config::project()`]). + pub fn project(&self) -> Result> { let config = self.try_load_config_emit_warnings()?; Ok(config.project()?) } @@ -140,7 +158,7 @@ impl CoreBuildArgs { // Loads project's figment and merges the build cli arguments into it impl<'a> From<&'a CoreBuildArgs> for Figment { fn from(args: &'a CoreBuildArgs) -> Self { - let figment = if let Some(ref config_path) = args.project_paths.config_path { + let mut figment = if let Some(ref config_path) = args.project_paths.config_path { if !config_path.exists() { panic!("error: config-path `{}` does not exist", config_path.display()) } @@ -157,23 +175,33 @@ impl<'a> From<&'a CoreBuildArgs> for Figment { let mut remappings = Remappings::new_with_remappings(args.project_paths.get_remappings()); remappings .extend(figment.extract_inner::>("remappings").unwrap_or_default()); - // override only set values from the CLI for zksync let zksync = args.compiler.zk.apply_overrides(figment.extract_inner("zksync").unwrap_or_default()); - figment.merge(("remappings", remappings.into_inner())).merge(args).merge(("zksync", zksync)) + figment = figment + .merge(("remappings", remappings.into_inner())) + .merge(args) + .merge(("zksync", zksync)); + + if let Some(skip) = &args.skip { + let mut skip = skip.iter().map(|s| s.file_pattern().to_string()).collect::>(); + skip.extend(figment.extract_inner::>("skip").unwrap_or_default()); + figment = figment.merge(("skip", skip)); + }; + + figment } } impl<'a> From<&'a CoreBuildArgs> for Config { fn from(args: &'a CoreBuildArgs) -> Self { let figment: Figment = args.into(); - let mut config = Config::from_provider(figment).sanitized(); + let mut config = Self::from_provider(figment).sanitized(); // if `--config-path` is set we need to adjust the config's root path to the actual root // path for the project, otherwise it will the parent dir of the `--config-path` if args.project_paths.config_path.is_some() { - config.__root = args.project_paths.project_root().into(); + config.root = args.project_paths.project_root().into(); } config } @@ -209,9 +237,15 @@ impl Provider for CoreBuildArgs { dict.insert("via_ir".to_string(), true.into()); } + if self.no_metadata { + dict.insert("bytecode_hash".to_string(), "none".into()); + dict.insert("cbor_metadata".to_string(), false.into()); + } + if self.force { dict.insert("force".to_string(), self.force.into()); } + // we need to ensure no_cache set accordingly if self.no_cache { dict.insert("cache".to_string(), false.into()); diff --git a/crates/cli/src/opts/build/mod.rs b/crates/cli/src/opts/build/mod.rs index af44c99b0..66fcc880b 100644 --- a/crates/cli/src/opts/build/mod.rs +++ b/crates/cli/src/opts/build/mod.rs @@ -1,5 +1,5 @@ use clap::Parser; -use foundry_compilers::{artifacts::output_selection::ContractOutputSelection, EvmVersion}; +use foundry_compilers::artifacts::{output_selection::ContractOutputSelection, EvmVersion}; use serde::Serialize; mod core; @@ -16,25 +16,25 @@ pub use self::zksync::ZkSyncArgs; // // See also `BuildArgs`. #[derive(Clone, Debug, Default, Serialize, Parser)] -#[clap(next_help_heading = "Compiler options")] +#[command(next_help_heading = "Compiler options")] pub struct CompilerArgs { /// Includes the AST as JSON in the compiler output. - #[clap(long, help_heading = "Compiler options")] + #[arg(long, help_heading = "Compiler options")] #[serde(skip)] pub ast: bool, /// The target EVM version. - #[clap(long, value_name = "VERSION")] + #[arg(long, value_name = "VERSION")] #[serde(skip_serializing_if = "Option::is_none")] pub evm_version: Option, /// Activate the Solidity optimizer. - #[clap(long)] + #[arg(long)] #[serde(skip)] pub optimize: bool, /// The number of optimizer runs. - #[clap(long, value_name = "RUNS")] + #[arg(long, value_name = "RUNS")] #[serde(skip_serializing_if = "Option::is_none")] pub optimizer_runs: Option, @@ -42,15 +42,15 @@ pub struct CompilerArgs { /// /// Example keys: evm.assembly, ewasm, ir, irOptimized, metadata /// - /// For a full description, see https://docs.soliditylang.org/en/v0.8.13/using-the-compiler.html#input-description - #[clap(long, num_args(1..), value_name = "SELECTOR")] + /// For a full description, see + #[arg(long, num_args(1..), value_name = "SELECTOR")] #[serde(skip_serializing_if = "Vec::is_empty")] pub extra_output: Vec, /// Extra output to write to separate files. /// /// Valid values: metadata, ir, irOptimized, ewasm, evm.assembly - #[clap(long, num_args(1..), value_name = "SELECTOR")] + #[arg(long, num_args(1..), value_name = "SELECTOR")] #[serde(skip_serializing_if = "Vec::is_empty")] pub extra_output_files: Vec, diff --git a/crates/cli/src/opts/build/paths.rs b/crates/cli/src/opts/build/paths.rs index 29f505ee3..9553eedb0 100644 --- a/crates/cli/src/opts/build/paths.rs +++ b/crates/cli/src/opts/build/paths.rs @@ -1,6 +1,6 @@ use clap::{Parser, ValueHint}; use eyre::Result; -use foundry_compilers::remappings::Remapping; +use foundry_compilers::artifacts::remappings::Remapping; use foundry_config::{ figment, figment::{ @@ -15,60 +15,66 @@ use std::path::PathBuf; /// Common arguments for a project's paths. #[derive(Clone, Debug, Default, Serialize, Parser)] -#[clap(next_help_heading = "Project options")] +#[command(next_help_heading = "Project options")] pub struct ProjectPathsArgs { /// The project's root path. /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] #[serde(skip)] pub root: Option, /// The contracts source directory. - #[clap(long, short = 'C', value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, short = 'C', value_hint = ValueHint::DirPath, value_name = "PATH")] #[serde(rename = "src", skip_serializing_if = "Option::is_none")] pub contracts: Option, /// The project's remappings. - #[clap(long, short = 'R')] + #[arg(long, short = 'R')] #[serde(skip)] pub remappings: Vec, /// The project's remappings from the environment. - #[clap(long, value_name = "ENV")] + #[arg(long, value_name = "ENV")] #[serde(skip)] pub remappings_env: Option, /// The path to the compiler cache. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] #[serde(skip_serializing_if = "Option::is_none")] pub cache_path: Option, /// The path to the library folder. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] #[serde(rename = "libs", skip_serializing_if = "Vec::is_empty")] pub lib_paths: Vec, /// Use the Hardhat-style project layout. /// /// This is the same as using: `--contracts contracts --lib-paths node_modules`. - #[clap(long, conflicts_with = "contracts", visible_alias = "hh")] + #[arg(long, conflicts_with = "contracts", visible_alias = "hh")] #[serde(skip)] pub hardhat: bool, /// Path to the config file. - #[clap(long, value_hint = ValueHint::FilePath, value_name = "FILE")] + #[arg(long, value_hint = ValueHint::FilePath, value_name = "FILE")] #[serde(skip)] pub config_path: Option, } impl ProjectPathsArgs { - /// Returns the root directory to use for configuring the [Project] + /// Returns the root directory to use for configuring the project. /// /// This will be the `--root` argument if provided, otherwise see [find_project_root_path()] + /// + /// # Panics + /// + /// If the project root directory cannot be found: [find_project_root_path()] pub fn project_root(&self) -> PathBuf { - self.root.clone().unwrap_or_else(|| find_project_root_path(None).unwrap()) + self.root + .clone() + .unwrap_or_else(|| find_project_root_path(None).expect("Failed to find project root")) } /// Returns the remappings to add to the config diff --git a/crates/cli/src/opts/dependency.rs b/crates/cli/src/opts/dependency.rs index 5fa1d851f..945386d2b 100644 --- a/crates/cli/src/opts/dependency.rs +++ b/crates/cli/src/opts/dependency.rs @@ -120,7 +120,7 @@ impl FromStr for Dependency { (None, None, None) }; - Ok(Dependency { name: name.or_else(|| alias.clone()).unwrap(), url, tag, alias }) + Ok(Self { name: name.or_else(|| alias.clone()).unwrap(), url, tag, alias }) } } diff --git a/crates/cli/src/opts/ethereum.rs b/crates/cli/src/opts/ethereum.rs index 62cebeaec..c4e8f0887 100644 --- a/crates/cli/src/opts/ethereum.rs +++ b/crates/cli/src/opts/ethereum.rs @@ -18,13 +18,15 @@ const FLASHBOTS_URL: &str = "https://rpc.flashbots.net/fast"; #[derive(Clone, Debug, Default, Parser)] pub struct RpcOpts { /// The RPC endpoint. - #[clap(short = 'r', long = "rpc-url", env = "ETH_RPC_URL")] + #[arg(short = 'r', long = "rpc-url", env = "ETH_RPC_URL")] pub url: Option, - /// Use the Flashbots RPC URL with fast mode (https://rpc.flashbots.net/fast). + /// Use the Flashbots RPC URL with fast mode (). + /// /// This shares the transaction privately with all registered builders. - /// https://docs.flashbots.net/flashbots-protect/quick-start#faster-transactions - #[clap(long)] + /// + /// See: + #[arg(long)] pub flashbots: bool, /// JWT Secret for the RPC endpoint. @@ -36,7 +38,7 @@ pub struct RpcOpts { /// '["0x6bb38c26db65749ab6e472080a3d20a2f35776494e72016d1e339593f21c59bc", /// "0x6bb38c26db65749ab6e472080a3d20a2f35776494e72016d1e339593f21c59bc", /// "0x6bb38c26db65749ab6e472080a3d20a2f35776494e72016d1e339593f21c59bc"]' - #[clap(long, env = "ETH_RPC_JWT_SECRET")] + #[arg(long, env = "ETH_RPC_JWT_SECRET")] pub jwt_secret: Option, } @@ -89,12 +91,12 @@ impl RpcOpts { #[derive(Clone, Debug, Default, Serialize, Parser)] pub struct EtherscanOpts { /// The Etherscan (or equivalent) API key. - #[clap(short = 'e', long = "etherscan-api-key", alias = "api-key", env = "ETHERSCAN_API_KEY")] + #[arg(short = 'e', long = "etherscan-api-key", alias = "api-key", env = "ETHERSCAN_API_KEY")] #[serde(rename = "etherscan_api_key", skip_serializing_if = "Option::is_none")] pub key: Option, /// The chain name or EIP-155 chain ID. - #[clap( + #[arg( short, long, alias = "chain-id", @@ -141,15 +143,15 @@ impl EtherscanOpts { } #[derive(Clone, Debug, Default, Parser)] -#[clap(next_help_heading = "Ethereum options")] +#[command(next_help_heading = "Ethereum options")] pub struct EthereumOpts { - #[clap(flatten)] + #[command(flatten)] pub rpc: RpcOpts, - #[clap(flatten)] + #[command(flatten)] pub etherscan: EtherscanOpts, - #[clap(flatten)] + #[command(flatten)] pub wallet: WalletOpts, } diff --git a/crates/cli/src/opts/transaction.rs b/crates/cli/src/opts/transaction.rs index bda2fb021..d424626c4 100644 --- a/crates/cli/src/opts/transaction.rs +++ b/crates/cli/src/opts/transaction.rs @@ -1,17 +1,17 @@ use crate::utils::parse_ether_value; -use alloy_primitives::U256; +use alloy_primitives::{U256, U64}; use clap::Parser; use serde::Serialize; #[derive(Clone, Debug, Serialize, Parser)] -#[clap(next_help_heading = "Transaction options")] +#[command(next_help_heading = "Transaction options")] pub struct TransactionOpts { /// Gas limit for the transaction. - #[clap(long, env = "ETH_GAS_LIMIT")] + #[arg(long, env = "ETH_GAS_LIMIT")] pub gas_limit: Option, /// Gas price for legacy transactions, or max fee per gas for EIP1559 transactions. - #[clap( + #[arg( long, env = "ETH_GAS_PRICE", value_parser = parse_ether_value, @@ -20,7 +20,7 @@ pub struct TransactionOpts { pub gas_price: Option, /// Max priority fee per gas for EIP1559 transactions. - #[clap( + #[arg( long, env = "ETH_PRIORITY_GAS_PRICE", value_parser = parse_ether_value, @@ -33,18 +33,26 @@ pub struct TransactionOpts { /// /// /// Examples: 1ether, 10gwei, 0.01ether - #[clap(long, value_parser = parse_ether_value)] + #[arg(long, value_parser = parse_ether_value)] pub value: Option, /// Nonce for the transaction. - #[clap(long)] - pub nonce: Option, + #[arg(long)] + pub nonce: Option, /// Send a legacy transaction instead of an EIP1559 transaction. /// /// This is automatically enabled for common networks without EIP1559. - #[clap(long)] + #[arg(long)] pub legacy: bool, + + /// Send a EIP-4844 blob transaction. + #[arg(long, conflicts_with = "legacy")] + pub blob: bool, + + /// Gas price for EIP-4844 blob transaction. + #[arg(long, conflicts_with = "legacy", value_parser = parse_ether_value, env = "ETH_BLOB_GAS_PRICE", value_name = "BLOB_PRICE")] + pub blob_gas_price: Option, } #[cfg(test)] diff --git a/crates/cli/src/utils/abi.rs b/crates/cli/src/utils/abi.rs new file mode 100644 index 000000000..a52bdc158 --- /dev/null +++ b/crates/cli/src/utils/abi.rs @@ -0,0 +1,61 @@ +use alloy_chains::Chain; +use alloy_json_abi::Function; +use alloy_primitives::Address; +use alloy_provider::{network::AnyNetwork, Provider}; +use alloy_transport::Transport; +use eyre::{OptionExt, Result}; +use foundry_common::{ + abi::{encode_function_args, get_func, get_func_etherscan}, + ens::NameOrAddress, +}; +use futures::future::join_all; + +async fn resolve_name_args>( + args: &[String], + provider: &P, +) -> Vec { + join_all(args.iter().map(|arg| async { + if arg.contains('.') { + let addr = NameOrAddress::Name(arg.to_string()).resolve(provider).await; + match addr { + Ok(addr) => addr.to_string(), + Err(_) => arg.to_string(), + } + } else { + arg.to_string() + } + })) + .await +} + +pub async fn parse_function_args>( + sig: &str, + args: Vec, + to: Option
, + chain: Chain, + provider: &P, + etherscan_api_key: Option<&str>, +) -> Result<(Vec, Option)> { + if sig.trim().is_empty() { + eyre::bail!("Function signature or calldata must be provided.") + } + + let args = resolve_name_args(&args, provider).await; + + if let Ok(data) = hex::decode(sig) { + return Ok((data, None)) + } + + let func = if sig.contains('(') { + // a regular function signature with parentheses + get_func(sig)? + } else { + let etherscan_api_key = etherscan_api_key.ok_or_eyre( + "If you wish to fetch function data from EtherScan, please provide an API key.", + )?; + let to = to.ok_or_eyre("A 'to' address must be provided to fetch function data.")?; + get_func_etherscan(sig, to, &args, chain, etherscan_api_key).await? + }; + + Ok((encode_function_args(&func, &args)?, Some(func))) +} diff --git a/crates/cli/src/utils/cmd.rs b/crates/cli/src/utils/cmd.rs index 4d674858e..29377213b 100644 --- a/crates/cli/src/utils/cmd.rs +++ b/crates/cli/src/utils/cmd.rs @@ -3,24 +3,28 @@ use alloy_primitives::Address; use eyre::{Result, WrapErr}; use foundry_common::{cli_warn, fs, TestFunctionExt}; use foundry_compilers::{ - artifacts::{CompactBytecode, CompactDeployedBytecode}, - cache::{CacheEntry, SolFilesCache}, - info::ContractInfo, + artifacts::{CompactBytecode, CompactDeployedBytecode, Settings}, + cache::{CacheEntry, CompilerCache}, utils::read_json_file, + zksync::artifact_output::zk::ZkContractArtifact, Artifact, ProjectCompileOutput, }; use foundry_config::{error::ExtractConfigError, figment::Figment, Chain, Config, NamedChain}; use foundry_debugger::Debugger; use foundry_evm::{ debug::DebugArena, - executors::{DeployResult, EvmError, ExecutionErr, RawCallResult}, + executors::{DeployResult, EvmError, RawCallResult}, opts::EvmOpts, traces::{ identifier::{EtherscanIdentifier, SignaturesIdentifier}, render_trace_arena, CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, Traces, }, }; -use std::{fmt::Write, path::PathBuf, str::FromStr}; +use std::{ + fmt::Write, + path::{Path, PathBuf}, + str::FromStr, +}; use yansi::Paint; /// Given a `Project`'s output, removes the matching ABI, Bytecode and @@ -28,16 +32,17 @@ use yansi::Paint; #[track_caller] pub fn remove_contract( output: &mut ProjectCompileOutput, - info: &ContractInfo, + path: &Path, + name: &str, ) -> Result<(JsonAbi, CompactBytecode, CompactDeployedBytecode)> { - let contract = if let Some(contract) = output.remove_contract(info) { + let contract = if let Some(contract) = output.remove(path.to_string_lossy(), name) { contract } else { - let mut err = format!("could not find artifact: `{}`", info.name); + let mut err = format!("could not find artifact: `{name}`"); if let Some(suggestion) = - super::did_you_mean(&info.name, output.artifacts().map(|(name, _)| name)).pop() + super::did_you_mean(name, output.artifacts().map(|(name, _)| name)).pop() { - if suggestion != info.name { + if suggestion != name { err = format!( r#"{err} @@ -50,27 +55,56 @@ pub fn remove_contract( let abi = contract .get_abi() - .ok_or_else(|| eyre::eyre!("contract {} does not contain abi", info))? + .ok_or_else(|| eyre::eyre!("contract {} does not contain abi", name))? .into_owned(); let bin = contract .get_bytecode() - .ok_or_else(|| eyre::eyre!("contract {} does not contain bytecode", info))? + .ok_or_else(|| eyre::eyre!("contract {} does not contain bytecode", name))? .into_owned(); let runtime = contract .get_deployed_bytecode() - .ok_or_else(|| eyre::eyre!("contract {} does not contain deployed bytecode", info))? + .ok_or_else(|| eyre::eyre!("contract {} does not contain deployed bytecode", name))? .into_owned(); Ok((abi, bin, runtime)) } +/// Given a `Project`'s output, removes the matching ABI, Bytecode and +/// Runtime Bytecode of the given contract. +#[track_caller] +pub fn remove_zk_contract( + output: &mut foundry_compilers::zksync::compile::output::ProjectCompileOutput, + path: &Path, + name: &str, +) -> Result { + let contract = if let Some(contract) = output.remove(path.to_string_lossy(), name) { + contract + } else { + let mut err = format!("could not find artifact: `{name}`"); + if let Some(suggestion) = + super::did_you_mean(name, output.artifacts().map(|(name, _)| name)).pop() + { + if suggestion != name { + err = format!( + r#"{err} + + Did you mean `{suggestion}`?"# + ); + } + } + eyre::bail!(err) + }; + + Ok(contract) +} + /// Helper function for finding a contract by ContractName // TODO: Is there a better / more ergonomic way to get the artifacts given a project and a // contract name? pub fn get_cached_entry_by_name( - cache: &SolFilesCache, + cache: &CompilerCache, name: &str, ) -> Result<(PathBuf, CacheEntry)> { let mut cached_entry = None; @@ -124,7 +158,7 @@ pub fn needs_setup(abi: &JsonAbi) -> bool { if setup_fn.name != "setUp" { println!( "{} Found invalid setup function \"{}\" did you mean \"setUp()\"?", - Paint::yellow("Warning:").bold(), + "Warning:".yellow().bold(), setup_fn.signature() ); } @@ -137,29 +171,20 @@ pub fn eta_key(state: &indicatif::ProgressState, f: &mut dyn Write) { write!(f, "{:.1}s", state.eta().as_secs_f64()).unwrap() } -#[macro_export] -macro_rules! init_progress { - ($local:expr, $label:expr) => {{ - let pb = indicatif::ProgressBar::new($local.len() as u64); - let mut template = - "{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos}/{len} ".to_string(); - template += $label; - template += " ({eta})"; - pb.set_style( - indicatif::ProgressStyle::with_template(&template) - .unwrap() - .with_key("eta", $crate::utils::eta_key) - .progress_chars("#>-"), - ); - pb - }}; -} - -#[macro_export] -macro_rules! update_progress { - ($pb:ident, $index:expr) => { - $pb.set_position(($index + 1) as u64); - }; +pub fn init_progress(len: u64, label: &str) -> indicatif::ProgressBar { + let pb = indicatif::ProgressBar::new(len); + let mut template = + "{prefix}{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos}/{len} " + .to_string(); + write!(template, "{label}").unwrap(); + template += " ({eta})"; + pb.set_style( + indicatif::ProgressStyle::with_template(&template) + .unwrap() + .with_key("eta", crate::utils::eta_key) + .progress_chars("#>-"), + ); + pb } /// True if the network calculates gas costs differently. @@ -199,7 +224,7 @@ pub fn has_batch_support(chain_id: u64) -> bool { /// Helpers for loading configuration. /// /// This is usually implicitly implemented on a "&CmdArgs" struct via impl macros defined in -/// `forge_config` (See [`forge_config::impl_figment_convert`] for more details) and the impl +/// `forge_config` (see [`foundry_config::impl_figment_convert`] for more details) and the impl /// definition on `T: Into + Into` below. /// /// Each function also has an `emit_warnings` form which does the same thing as its counterpart but @@ -272,31 +297,31 @@ where fn load_config_emit_warnings(self) -> Config { let config = self.load_config(); - config.__warnings.iter().for_each(|w| cli_warn!("{w}")); + config.warnings.iter().for_each(|w| cli_warn!("{w}")); config } fn try_load_config_emit_warnings(self) -> Result { let config = self.try_load_config()?; - config.__warnings.iter().for_each(|w| cli_warn!("{w}")); + config.warnings.iter().for_each(|w| cli_warn!("{w}")); Ok(config) } fn load_config_and_evm_opts_emit_warnings(self) -> Result<(Config, EvmOpts)> { let (config, evm_opts) = self.load_config_and_evm_opts()?; - config.__warnings.iter().for_each(|w| cli_warn!("{w}")); + config.warnings.iter().for_each(|w| cli_warn!("{w}")); Ok((config, evm_opts)) } fn load_config_unsanitized_emit_warnings(self) -> Config { let config = self.load_config_unsanitized(); - config.__warnings.iter().for_each(|w| cli_warn!("{w}")); + config.warnings.iter().for_each(|w| cli_warn!("{w}")); config } fn try_load_config_unsanitized_emit_warnings(self) -> Result { let config = self.try_load_config_unsanitized()?; - config.__warnings.iter().for_each(|w| cli_warn!("{w}")); + config.warnings.iter().for_each(|w| cli_warn!("{w}")); Ok(config) } } @@ -321,19 +346,19 @@ pub fn read_constructor_args_file(constructor_args_path: PathBuf) -> Result, + pub debug: Option, pub gas_used: u64, } -impl From for TraceResult { - fn from(result: RawCallResult) -> Self { - let RawCallResult { gas_used, traces, reverted, debug, .. } = result; - +impl TraceResult { + /// Create a new [`TraceResult`] from a [`RawCallResult`]. + pub fn from_raw(raw: RawCallResult, trace_kind: TraceKind) -> Self { + let RawCallResult { gas_used, traces, reverted, debug, .. } = raw; Self { success: !reverted, - traces: vec![(TraceKind::Execution, traces.expect("traces is None"))], - debug: debug.unwrap_or_default(), + traces: traces.map(|arena| vec![(trace_kind, arena)]), + debug, gas_used, } } @@ -341,32 +366,18 @@ impl From for TraceResult { impl From for TraceResult { fn from(result: DeployResult) -> Self { - let DeployResult { gas_used, traces, debug, .. } = result; - - Self { - success: true, - traces: vec![(TraceKind::Execution, traces.expect("traces is None"))], - debug: debug.unwrap_or_default(), - gas_used, - } + Self::from_raw(result.raw, TraceKind::Deployment) } } -impl TryFrom for TraceResult { +impl TryFrom> for TraceResult { type Error = EvmError; - fn try_from(err: EvmError) -> Result { - match err { - EvmError::Execution(err) => { - let ExecutionErr { reverted, gas_used, traces, debug: run_debug, .. } = *err; - Ok(TraceResult { - success: !reverted, - traces: vec![(TraceKind::Execution, traces.expect("traces is None"))], - debug: run_debug.unwrap_or_default(), - gas_used, - }) - } - _ => Err(err), + fn try_from(value: Result) -> Result { + match value { + Ok(result) => Ok(Self::from(result)), + Err(EvmError::Execution(err)) => Ok(Self::from_raw(err.raw, TraceKind::Deployment)), + Err(err) => Err(err), } } } @@ -399,14 +410,20 @@ pub async fn handle_traces( .build(); let mut etherscan_identifier = EtherscanIdentifier::new(config, chain)?; - for (_, trace) in &mut result.traces { - decoder.identify(trace, &mut etherscan_identifier); + if let Some(etherscan_identifier) = &mut etherscan_identifier { + for (_, trace) in result.traces.as_deref_mut().unwrap_or_default() { + decoder.identify(trace, etherscan_identifier); + } } if debug { - let sources = etherscan_identifier.get_compiled_contracts().await?; + let sources = if let Some(etherscan_identifier) = etherscan_identifier { + etherscan_identifier.get_compiled_contracts().await? + } else { + Default::default() + }; let mut debugger = Debugger::builder() - .debug_arena(&result.debug) + .debug_arena(result.debug.as_ref().expect("missing debug arena")) .decoder(&decoder) .sources(sources) .build(); @@ -419,20 +436,18 @@ pub async fn handle_traces( } pub async fn print_traces(result: &mut TraceResult, decoder: &CallTraceDecoder) -> Result<()> { - if result.traces.is_empty() { - panic!("No traces found") - } + let traces = result.traces.as_ref().expect("No traces found"); println!("Traces:"); - for (_, arena) in &result.traces { + for (_, arena) in traces { println!("{}", render_trace_arena(arena, decoder).await?); } println!(); if result.success { - println!("{}", Paint::green("Transaction successfully executed.")); + println!("{}", "Transaction successfully executed.".green()); } else { - println!("{}", Paint::red("Transaction failed.")); + println!("{}", "Transaction failed.".red()); } println!("Gas used: {}", result.gas_used); diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index 99fcceb3e..3a3e65c5e 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -1,21 +1,19 @@ use alloy_json_abi::JsonAbi; -use alloy_primitives::{utils::format_units, U256}; -use ethers_core::types::TransactionReceipt; -use ethers_providers::Middleware; +use alloy_primitives::U256; +use alloy_provider::{network::AnyNetwork, Provider}; +use alloy_transport::Transport; use eyre::{ContextCompat, Result}; -use foundry_common::types::ToAlloy; +use foundry_common::provider::{ProviderBuilder, RetryProvider}; use foundry_config::{Chain, Config}; use std::{ ffi::OsStr, future::Future, - ops::Mul, path::{Path, PathBuf}, process::{Command, Output, Stdio}, time::{Duration, SystemTime, UNIX_EPOCH}, }; use tracing_error::ErrorLayer; use tracing_subscriber::prelude::*; -use yansi::Paint; mod cmd; pub use cmd::*; @@ -23,6 +21,9 @@ pub use cmd::*; mod suggestions; pub use suggestions::*; +mod abi; +pub use abi::*; + // reexport all `foundry_config::utils` #[doc(hidden)] pub use foundry_config::utils::*; @@ -76,28 +77,23 @@ pub fn subscriber() { } pub fn abi_to_solidity(abi: &JsonAbi, name: &str) -> Result { - let s = abi.to_sol(name); + let s = abi.to_sol(name, None); let s = forge_fmt::format(&s)?; Ok(s) } -/// Returns a [RetryProvider](foundry_common::RetryProvider) instantiated using [Config]'s RPC URL -/// and chain. -/// -/// Defaults to `http://localhost:8545` and `Mainnet`. -pub fn get_provider(config: &Config) -> Result { +/// Returns a [RetryProvider] instantiated using [Config]'s +/// RPC +pub fn get_provider(config: &Config) -> Result { get_provider_builder(config)?.build() } -/// Returns a [ProviderBuilder](foundry_common::ProviderBuilder) instantiated using [Config]'s RPC -/// URL and chain. +/// Returns a [ProviderBuilder] instantiated using [Config] values. /// /// Defaults to `http://localhost:8545` and `Mainnet`. -pub fn get_provider_builder( - config: &Config, -) -> Result { +pub fn get_provider_builder(config: &Config) -> Result { let url = config.get_rpc_url_or_localhost_http()?; - let mut builder = foundry_common::provider::ethers::ProviderBuilder::new(url.as_ref()); + let mut builder = ProviderBuilder::new(url.as_ref()); if let Ok(chain) = config.chain.unwrap_or_default().try_into() { builder = builder.chain(chain); @@ -111,14 +107,14 @@ pub fn get_provider_builder( Ok(builder) } -pub async fn get_chain(chain: Option, provider: M) -> Result +pub async fn get_chain(chain: Option, provider: P) -> Result where - M: Middleware, - M::Error: 'static, + P: Provider, + T: Transport + Clone, { match chain { Some(chain) => Ok(chain), - None => Ok(Chain::from_id(provider.get_chainid().await?.as_u64())), + None => Ok(Chain::from_id(provider.get_chain_id().await?)), } } @@ -162,7 +158,6 @@ pub fn now() -> Duration { } /// Runs the `future` in a new [`tokio::runtime::Runtime`] -#[allow(unused)] pub fn block_on(future: F) -> F::Output { let rt = tokio::runtime::Runtime::new().expect("could not start tokio rt"); rt.block_on(future) @@ -209,50 +204,10 @@ pub fn load_dotenv() { }; } -/// Disables terminal colours if either: -/// - Running windows and the terminal does not support colour codes. -/// - Colour has been disabled by some environment variable. -/// - We are running inside a test +/// Sets the default [`yansi`] color output condition. pub fn enable_paint() { - let is_windows = cfg!(windows) && !Paint::enable_windows_ascii(); - let env_colour_disabled = std::env::var("NO_COLOR").is_ok(); - if is_windows || env_colour_disabled { - Paint::disable(); - } -} - -/// Prints parts of the receipt to stdout -pub fn print_receipt(chain: Chain, receipt: &TransactionReceipt) { - let gas_used = receipt.gas_used.unwrap_or_default(); - let gas_price = receipt.effective_gas_price.unwrap_or_default(); - foundry_common::shell::println(format!( - "\n##### {chain}\n{status}Hash: {tx_hash:?}{caddr}\nBlock: {bn}\n{gas}\n", - status = if receipt.status.map_or(true, |s| s.is_zero()) { - "❌ [Failed]" - } else { - "✅ [Success]" - }, - tx_hash = receipt.transaction_hash, - caddr = if let Some(addr) = &receipt.contract_address { - format!("\nContract Address: {}", addr.to_alloy().to_checksum(None)) - } else { - String::new() - }, - bn = receipt.block_number.unwrap_or_default(), - gas = if gas_price.is_zero() { - format!("Gas Used: {gas_used}") - } else { - let paid = format_units(gas_used.mul(gas_price).to_alloy(), 18) - .unwrap_or_else(|_| "N/A".into()); - let gas_price = format_units(gas_price.to_alloy(), 9).unwrap_or_else(|_| "N/A".into()); - format!( - "Paid: {} ETH ({gas_used} gas * {} gwei)", - paid.trim_end_matches('0'), - gas_price.trim_end_matches('0').trim_end_matches('.') - ) - }, - )) - .expect("could not print receipt"); + let enable = yansi::Condition::os_support() && yansi::Condition::tty_and_color_live(); + yansi::whenever(yansi::Condition::cached(enable)); } /// Useful extensions to [`std::process::Command`]. @@ -334,7 +289,7 @@ impl<'a> Git<'a> { #[inline] pub fn from_config(config: &'a Config) -> Self { - Self::new(config.__root.0.as_path()) + Self::new(config.root.0.as_path()) } pub fn root_of(relative_to: &Path) -> Result { diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 733b2e95b..a9d421467 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -9,6 +9,9 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [dependencies] foundry-block-explorers = { workspace = true, features = [ "foundry-compilers", @@ -17,61 +20,74 @@ foundry-zksync-compiler.workspace = true foundry-compilers.workspace = true foundry-config.workspace = true foundry-macros.workspace = true +foundry-linking.workspace = true -ethers-core.workspace = true -ethers-middleware.workspace = true -ethers-providers = { workspace = true, features = [ "ws", "ipc" ] } -ethers-signers.workspace = true - -alloy-dyn-abi = { workspace = true, features = [ "arbitrary", "eip712" ] } +alloy-consensus.workspace = true +alloy-contract.workspace = true +alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } alloy-json-abi.workspace = true +alloy-json-rpc.workspace = true alloy-primitives = { workspace = true, features = [ "serde", "getrandom", "arbitrary", "rlp", ] } -alloy-rpc-types.workspace = true +alloy-provider.workspace = true +alloy-pubsub.workspace = true alloy-rpc-client.workspace = true -alloy-providers.workspace = true -alloy-transport.workspace = true -alloy-signer.workspace = true -alloy-transport-http.workspace = true -alloy-transport-ws.workspace = true +alloy-rpc-types = { workspace = true, features = ["eth"] } +alloy-rpc-types-engine.workspace = true +alloy-serde.workspace = true +alloy-sol-types = { workspace = true, features = ["json"] } +alloy-transport-http = { workspace = true, features = [ + "reqwest", + "reqwest-rustls-tls", +] } alloy-transport-ipc.workspace = true -alloy-json-rpc.workspace = true -alloy-pubsub.workspace = true -alloy-sol-types = { workspace = true, features = [ "json" ] } +alloy-transport-ws.workspace = true +alloy-transport.workspace = true -revm.workspace = true +revm = { workspace = true, features = [ + "std", + "serde", + "memory_limit", + "optional_eip3607", + "optional_block_gas_limit", + "optional_no_base_fee", + "arbitrary", + "optimism", + "c-kzg", +] } tower.workspace = true derive_more.workspace = true itertools.workspace = true -async-trait = "0.1" -clap = { version = "4", features = [ "derive", "env", "unicode", "wrap_help" ] } +async-trait.workspace = true +clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } comfy-table = "7" -dunce = "1" +dunce.workspace = true eyre.workspace = true -glob = "0.3" -globset = "0.4" -hex.workspace = true -once_cell = "1" -rand.workspace = true -reqwest = { version = "0.11", default-features = false } -semver = "1" +once_cell.workspace = true +reqwest.workspace = true +semver.workspace = true serde_json.workspace = true serde.workspace = true -tempfile = "3" -thiserror = "1" -tokio = "1" +thiserror.workspace = true +tokio.workspace = true tracing.workspace = true -url = "2" -walkdir = "2" -yansi = "0.5" +url.workspace = true +walkdir.workspace = true +yansi.workspace = true +rustc-hash.workspace = true +num-format.workspace = true +chrono.workspace = true + +# zksync +globset = "0.4" [dev-dependencies] foundry-macros.workspace = true -pretty_assertions.workspace = true -tokio = { version = "1", features = [ "rt-multi-thread", "macros" ] } +similar-asserts.workspace = true +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } diff --git a/crates/common/src/abi.rs b/crates/common/src/abi.rs index 66a7c9891..6b7615b39 100644 --- a/crates/common/src/abi.rs +++ b/crates/common/src/abi.rs @@ -68,41 +68,6 @@ pub fn abi_decode_calldata( Ok(res) } -/// Helper trait for converting types to Functions. Helpful for allowing the `call` -/// function on the EVM to be generic over `String`, `&str` and `Function`. -pub trait IntoFunction { - /// Consumes self and produces a function - /// - /// # Panics - /// - /// This function does not return a Result, so it is expected that the consumer - /// uses it correctly so that it does not panic. - fn into(self) -> Function; -} - -impl IntoFunction for Function { - fn into(self) -> Function { - self - } -} - -impl IntoFunction for String { - #[track_caller] - fn into(self) -> Function { - IntoFunction::into(self.as_str()) - } -} - -impl<'a> IntoFunction for &'a str { - #[track_caller] - fn into(self) -> Function { - match get_func(self) { - Ok(func) => func, - Err(e) => panic!("could not parse function: {e}"), - } - } -} - /// Given a function signature string, it tries to parse it as a `Function` pub fn get_func(sig: &str) -> Result { Function::parse(sig).wrap_err("could not parse function signature") @@ -194,7 +159,7 @@ pub fn find_source( } /// Helper function to coerce a value to a [DynSolValue] given a type string -fn coerce_value(ty: &str, arg: &str) -> Result { +pub fn coerce_value(ty: &str, arg: &str) -> Result { let ty = DynSolType::parse(ty)?; Ok(DynSolType::coerce_str(&ty, arg)?) } diff --git a/crates/common/src/compile.rs b/crates/common/src/compile.rs index 6c00f8365..df6391bfc 100644 --- a/crates/common/src/compile.rs +++ b/crates/common/src/compile.rs @@ -1,36 +1,41 @@ //! Support for compiling [foundry_compilers::Project] -use crate::{compact_to_contract, glob::GlobMatcher, term::SpinnerReporter, TestFunctionExt}; -use comfy_table::{presets::ASCII_MARKDOWN, Attribute, Cell, Color, Table}; +use crate::{compact_to_contract, term::SpinnerReporter, TestFunctionExt}; +use comfy_table::{presets::ASCII_MARKDOWN, Attribute, Cell, CellAlignment, Color, Table}; use eyre::{Context, Result}; use foundry_block_explorers::contract::Metadata; use foundry_compilers::{ - artifacts::{BytecodeObject, CompactContractBytecode, ContractBytecodeSome}, - remappings::Remapping, + artifacts::{remappings::Remapping, BytecodeObject, ContractBytecodeSome, Libraries, Source}, + compilers::{ + multi::MultiCompilerLanguage, + solc::{Solc, SolcCompiler}, + Compiler, + }, report::{BasicStdoutReporter, NoReporter, Report}, zksync::{ artifact_output::Artifact as ZkArtifact, compile::output::ProjectCompileOutput as ZkProjectCompileOutput, }, - Artifact, ArtifactId, FileFilter, Graph, Project, ProjectCompileOutput, ProjectPathsConfig, - Solc, SolcConfig, + Artifact, Project, ProjectBuilder, ProjectCompileOutput, ProjectPathsConfig, SolcConfig, }; +use foundry_linking::Linker; use foundry_zksync_compiler::libraries::{self, ZkMissingLibrary}; +use num_format::{Locale, ToFormattedString}; +use rustc_hash::FxHashMap; use std::{ collections::{BTreeMap, HashMap, HashSet}, - convert::Infallible, fmt::Display, io::IsTerminal, path::{Path, PathBuf}, - result, - str::FromStr, + sync::Arc, + time::Instant, }; /// Builder type to configure how to compile a project. /// /// This is merely a wrapper for [`Project::compile()`] which also prints to stdout depending on its /// settings. -#[must_use = "this builder does nothing unless you call a `compile*` method"] +#[must_use = "ProjectCompiler does nothing unless you call a `compile*` method"] pub struct ProjectCompiler { /// Whether we are going to verify the contracts after compilation. verify: Option, @@ -47,9 +52,6 @@ pub struct ProjectCompiler { /// Whether to bail on compiler errors. bail: Option, - /// Files to exclude. - filter: Option>, - /// Extra files to include, that are not necessarily in the project's source dir. files: Vec, } @@ -71,7 +73,6 @@ impl ProjectCompiler { print_sizes: None, quiet: Some(crate::shell::verbosity().is_silent()), bail: None, - filter: None, files: Vec::new(), } } @@ -121,13 +122,6 @@ impl ProjectCompiler { self } - /// Sets the filter to use. - #[inline] - pub fn filter(mut self, filter: Box) -> Self { - self.filter = Some(filter); - self - } - /// Sets extra files to include, that are not necessarily in the project's source dir. #[inline] pub fn files(mut self, files: impl IntoIterator) -> Self { @@ -136,7 +130,7 @@ impl ProjectCompiler { } /// Compiles the project. - pub fn compile(mut self, project: &Project) -> Result { + pub fn compile(mut self, project: &Project) -> Result> { // TODO: Avoid process::exit if !project.paths.has_input_files() && self.files.is_empty() { println!("Nothing to compile"); @@ -145,17 +139,17 @@ impl ProjectCompiler { } // Taking is fine since we don't need these in `compile_with`. - let filter = std::mem::take(&mut self.filter); let files = std::mem::take(&mut self.files); self.compile_with(|| { - if !files.is_empty() { - project.compile_files(files) - } else if let Some(filter) = filter { - project.compile_sparse(filter) + let sources = if !files.is_empty() { + Source::read_all(files)? } else { - project.compile() - } - .map_err(Into::into) + project.paths.read_input_files()? + }; + + foundry_compilers::project::ProjectCompiler::with_sources(project, sources)? + .compile() + .map_err(Into::into) }) } @@ -170,27 +164,17 @@ impl ProjectCompiler { /// ProjectCompiler::new().compile_with(|| Ok(prj.compile()?)).unwrap(); /// ``` #[instrument(target = "forge::compile", skip_all)] - fn compile_with(self, f: F) -> Result + fn compile_with(self, f: F) -> Result> where - F: FnOnce() -> Result, + F: FnOnce() -> Result>, { let quiet = self.quiet.unwrap_or(false); let bail = self.bail.unwrap_or(true); - #[allow(clippy::collapsible_else_if)] - let reporter = if quiet { - Report::new(NoReporter::default()) - } else { - if std::io::stdout().is_terminal() { - Report::new(SpinnerReporter::spawn()) - } else { - Report::new(BasicStdoutReporter::default()) - } - }; - let output = foundry_compilers::report::with_scoped(&reporter, || { + let output = with_compilation_reporter(self.quiet.unwrap_or(false), || { tracing::debug!("compiling project"); - let timer = std::time::Instant::now(); + let timer = Instant::now(); let r = f(); let elapsed = timer.elapsed(); @@ -198,9 +182,6 @@ impl ProjectCompiler { r })?; - // need to drop the reporter here, so that the spinner terminates - drop(reporter); - if bail && output.has_compiler_errors() { eyre::bail!("{output}") } @@ -220,7 +201,7 @@ impl ProjectCompiler { } /// If configured, this will print sizes or names - fn handle_output(&self, output: &ProjectCompileOutput) { + fn handle_output(&self, output: &ProjectCompileOutput) { let print_names = self.print_names.unwrap_or(false); let print_sizes = self.print_sizes.unwrap_or(false); @@ -248,7 +229,16 @@ impl ProjectCompiler { } let mut size_report = SizeReport { contracts: BTreeMap::new() }; - let artifacts: BTreeMap<_, _> = output.artifacts().collect(); + + let artifacts: BTreeMap<_, _> = output + .artifact_ids() + .filter(|(id, _)| { + // filter out forge-std specific contracts + !id.source.to_string_lossy().contains("/forge-std/src/") + }) + .map(|(id, artifact)| (id.name, artifact)) + .collect(); + for (name, artifact) in artifacts { let size = deployed_contract_size(artifact).unwrap_or_default(); @@ -276,7 +266,11 @@ impl ProjectCompiler { } /// Compiles the project. - pub fn zksync_compile(self, project: &Project) -> Result { + pub fn zksync_compile( + self, + project: &Project, + maybe_avoid_contracts: Option>, + ) -> Result { // TODO: Avoid process::exit if !project.paths.has_input_files() && self.files.is_empty() { println!("Nothing to compile"); @@ -290,16 +284,26 @@ impl ProjectCompiler { // We need to clone files since we use them in `compile_with` // for filtering artifacts in missing libraries detection let files = self.files.clone(); + + { + Report::new(SpinnerReporter::spawn_with(format!( + "Using zksolc-{}", + project.zksync_zksolc.version()? + ))); + } self.zksync_compile_with(&project.paths.root, || { - if !files.is_empty() { - project.zksync_compile_files(files) - /* TODO: evualuate supporting compiling with filters - } else if let Some(filter) = filter { - project.compile_sparse(filter) - */ - } else { - project.zksync_compile() - } + let files_to_compile = + if !files.is_empty() { files } else { project.paths.input_files() }; + let avoid_contracts = maybe_avoid_contracts.unwrap_or_default(); + let sources = Source::read_all( + files_to_compile + .into_iter() + .filter(|p| !avoid_contracts.iter().any(|c| c.is_match(p))), + )?; + foundry_compilers::zksync::compile::project::ProjectCompiler::with_sources( + project, sources, + )? + .compile() .map_err(Into::into) }) } @@ -320,7 +324,7 @@ impl ProjectCompiler { Report::new(NoReporter::default()) } else { if std::io::stdout().is_terminal() { - Report::new(SpinnerReporter::spawn_with("Compiling (zksync)...")) + Report::new(SpinnerReporter::spawn_with("Compiling (zksync)")) } else { Report::new(BasicStdoutReporter::default()) } @@ -401,8 +405,7 @@ impl ProjectCompiler { let art = output.find(abs_path_str, contract_name).unwrap_or_else(|| { panic!( - "Could not find contract {} at path {} for compilation output", - contract_name, contract_path + "Could not find contract {contract_name} at path {contract_path} for compilation output" ) }); @@ -413,13 +416,19 @@ impl ProjectCompiler { } }) .collect(); + if !missing_libs.is_empty() { libraries::add_dependencies_to_missing_libraries_cache( root_path, missing_libs.as_slice(), ) .expect("Error while adding missing libraries"); - eyre::bail!("Missing libraries detected {:?}\n\nRun the following command in order to deploy the missing libraries:\nforge create --deploy-missing-libraries --private-key --rpc-url --chain --zksync", missing_libs); + let missing_libs_list = missing_libs + .iter() + .map(|ml| format!("{}:{}", ml.contract_path, ml.contract_name)) + .collect::>() + .join(", "); + eyre::bail!("Missing libraries detected: {missing_libs_list}\n\nRun the following command in order to deploy each missing library:\n\nforge create --private-key --rpc-url --chain --zksync\n\nThen pass the library addresses using the --libraries option"); } // print any sizes or names @@ -482,73 +491,128 @@ impl ProjectCompiler { } } -/// Contract source code and bytecode. +#[derive(Clone, Debug)] +pub struct SourceData { + pub source: Arc, + pub language: MultiCompilerLanguage, + pub name: String, +} + +#[derive(Clone, Debug)] +pub struct ArtifactData { + pub bytecode: ContractBytecodeSome, + pub build_id: String, + pub file_id: u32, +} + +/// Contract source code and bytecode data used for debugger. #[derive(Clone, Debug, Default)] pub struct ContractSources { - /// Map over artifacts' contract names -> vector of file IDs - pub ids_by_name: HashMap>, - /// Map over file_id -> (source code, contract) - pub sources_by_id: HashMap, + /// Map over build_id -> file_id -> (source code, language) + pub sources_by_id: HashMap>, + /// Map over contract name -> Vec<(bytecode, build_id, file_id)> + pub artifacts_by_name: HashMap>, } impl ContractSources { /// Collects the contract sources and artifacts from the project compile output. pub fn from_project_output( output: &ProjectCompileOutput, - root: &Path, - ) -> Result { - let mut sources = ContractSources::default(); + root: impl AsRef, + libraries: Option<&Libraries>, + ) -> Result { + let mut sources = Self::default(); + + sources.insert(output, root, libraries)?; + + Ok(sources) + } + + pub fn insert( + &mut self, + output: &ProjectCompileOutput, + root: impl AsRef, + libraries: Option<&Libraries>, + ) -> Result<()> + where + C::Language: Into, + { + let root = root.as_ref(); + let link_data = libraries.map(|libraries| { + let linker = Linker::new(root, output.artifact_ids().collect()); + (linker, libraries) + }); + for (id, artifact) in output.artifact_ids() { if let Some(file_id) = artifact.id { - let abs_path = root.join(&id.path); - let source_code = std::fs::read_to_string(abs_path).wrap_err_with(|| { - format!("failed to read artifact source file for `{}`", id.identifier()) - })?; - let compact = CompactContractBytecode { - abi: artifact.abi.clone(), - bytecode: artifact.bytecode.clone(), - deployed_bytecode: artifact.deployed_bytecode.clone(), + let artifact = if let Some((linker, libraries)) = link_data.as_ref() { + linker.link(&id, libraries)?.into_contract_bytecode() + } else { + artifact.clone().into_contract_bytecode() }; - let contract = compact_to_contract(compact)?; - sources.insert(&id, file_id, source_code, contract); + let bytecode = compact_to_contract(artifact.clone().into_contract_bytecode())?; + + self.artifacts_by_name.entry(id.name.clone()).or_default().push(ArtifactData { + bytecode, + build_id: id.build_id.clone(), + file_id, + }); } else { warn!(id = id.identifier(), "source not found"); } } - Ok(sources) - } - /// Inserts a contract into the sources. - pub fn insert( - &mut self, - artifact_id: &ArtifactId, - file_id: u32, - source: String, - bytecode: ContractBytecodeSome, - ) { - self.ids_by_name.entry(artifact_id.name.clone()).or_default().push(file_id); - self.sources_by_id.insert(file_id, (source, bytecode)); - } + // Not all source files produce artifacts, so we are populating sources by using build + // infos. + let mut files: BTreeMap> = BTreeMap::new(); + for (build_id, build) in output.builds() { + for (source_id, path) in &build.source_id_to_path { + let source_code = if let Some(source) = files.get(path) { + source.clone() + } else { + let source = Source::read(path).wrap_err_with(|| { + format!("failed to read artifact source file for `{}`", path.display()) + })?; + files.insert(path.clone(), source.content.clone()); + source.content + }; + + self.sources_by_id.entry(build_id.clone()).or_default().insert( + *source_id, + SourceData { + source: source_code, + language: build.language.into(), + name: path.strip_prefix(root).unwrap_or(path).to_string_lossy().to_string(), + }, + ); + } + } - /// Returns the source for a contract by file ID. - pub fn get(&self, id: u32) -> Option<&(String, ContractBytecodeSome)> { - self.sources_by_id.get(&id) + Ok(()) } /// Returns all sources for a contract by name. pub fn get_sources( &self, name: &str, - ) -> Option> { - self.ids_by_name - .get(name) - .map(|ids| ids.iter().filter_map(|id| Some((*id, self.sources_by_id.get(id)?)))) + ) -> Option> { + self.artifacts_by_name.get(name).map(|artifacts| { + artifacts.iter().filter_map(|artifact| { + let source = + self.sources_by_id.get(artifact.build_id.as_str())?.get(&artifact.file_id)?; + Some((artifact, source)) + }) + }) } - /// Returns all (name, source) pairs. - pub fn entries(&self) -> impl Iterator { - self.ids_by_name.iter().flat_map(|(name, ids)| { - ids.iter().filter_map(|id| self.sources_by_id.get(id).map(|s| (name.clone(), s))) + /// Returns all (name, bytecode, source) sets. + pub fn entries(&self) -> impl Iterator { + self.artifacts_by_name.iter().flat_map(|(name, artifacts)| { + artifacts.iter().filter_map(|artifact| { + let source = + self.sources_by_id.get(artifact.build_id.as_str())?.get(&artifact.file_id)?; + Some((name.as_str(), artifact, source)) + }) }) } } @@ -586,10 +650,11 @@ impl Display for SizeReport { table.load_preset(ASCII_MARKDOWN); table.set_header([ Cell::new("Contract").add_attribute(Attribute::Bold).fg(Color::Blue), - Cell::new("Size (kB)").add_attribute(Attribute::Bold).fg(Color::Blue), - Cell::new("Margin (kB)").add_attribute(Attribute::Bold).fg(Color::Blue), + Cell::new("Size (B)").add_attribute(Attribute::Bold).fg(Color::Blue), + Cell::new("Margin (B)").add_attribute(Attribute::Bold).fg(Color::Blue), ]); + // filters out non dev contracts (Test or Script) let contracts = self.contracts.iter().filter(|(_, c)| !c.is_dev_contract && c.size > 0); for (name, contract) in contracts { let margin = CONTRACT_SIZE_LIMIT as isize - contract.size as isize; @@ -599,10 +664,15 @@ impl Display for SizeReport { _ => Color::Red, }; + let locale = &Locale::en; table.add_row([ Cell::new(name).fg(color), - Cell::new(contract.size as f64 / 1000.0).fg(color), - Cell::new(margin as f64 / 1000.0).fg(color), + Cell::new(contract.size.to_formatted_string(locale)) + .set_alignment(CellAlignment::Right) + .fg(color), + Cell::new(margin.to_formatted_string(locale)) + .set_alignment(CellAlignment::Right) + .fg(color), ]); } @@ -646,65 +716,19 @@ pub struct ContractInfo { /// If `verify` and it's a standalone script, throw error. Only allowed for projects. /// /// **Note:** this expects the `target_path` to be absolute -pub fn compile_target_with_filter( +pub fn compile_target( target_path: &Path, - project: &Project, + project: &Project, quiet: bool, - verify: bool, - skip: Vec, -) -> Result { - let graph = Graph::resolve(&project.paths)?; - - // Checking if it's a standalone script, or part of a project. - let mut compiler = ProjectCompiler::new().quiet(quiet); - if !skip.is_empty() { - compiler = compiler.filter(Box::new(SkipBuildFilters::new(skip)?)); - } - if !graph.files().contains_key(target_path) { - if verify { - eyre::bail!("You can only verify deployments from inside a project! Make sure it exists with `forge tree`."); - } - compiler = compiler.files([target_path.into()]); - } - compiler.compile(project) -} - -/// Compiles an Etherscan source from metadata by creating a project. -/// Returns the artifact_id, the file_id, and the bytecode -pub async fn compile_from_source( - metadata: &Metadata, -) -> Result<(ArtifactId, u32, ContractBytecodeSome)> { - let root = tempfile::tempdir()?; - let root_path = root.path(); - let project = etherscan_project(metadata, root_path)?; - - let project_output = project.compile()?; - - if project_output.has_compiler_errors() { - eyre::bail!("{project_output}") - } - - let (artifact_id, file_id, contract) = project_output - .into_artifacts() - .find(|(artifact_id, _)| artifact_id.name == metadata.contract_name) - .map(|(aid, art)| { - (aid, art.source_file().expect("no source file").id, art.into_contract_bytecode()) - }) - .ok_or_else(|| { - eyre::eyre!( - "Unable to find bytecode in compiled output for contract: {}", - metadata.contract_name - ) - })?; - let bytecode = compact_to_contract(contract)?; - - root.close()?; - - Ok((artifact_id, file_id, bytecode)) +) -> Result> { + ProjectCompiler::new().quiet(quiet).files([target_path.into()]).compile(project) } /// Creates a [Project] from an Etherscan source. -pub fn etherscan_project(metadata: &Metadata, target_path: impl AsRef) -> Result { +pub fn etherscan_project( + metadata: &Metadata, + target_path: impl AsRef, +) -> Result> { let target_path = dunce::canonicalize(target_path.as_ref())?; let sources_path = target_path.join(&metadata.contract_name); metadata.source_tree().write_to(&target_path)?; @@ -736,115 +760,30 @@ pub fn etherscan_project(metadata: &Metadata, target_path: impl AsRef) -> .build_with_root(sources_path); let v = metadata.compiler_version()?; - let v = format!("{}.{}.{}", v.major, v.minor, v.patch); - let solc = Solc::find_or_install_svm_version(v)?; + let solc = Solc::find_or_install(&v)?; - Ok(Project::builder() - .solc_config(SolcConfig::builder().settings(settings).build()) - .no_auto_detect() + let compiler = SolcCompiler::Specific(solc); + + Ok(ProjectBuilder::::default() + .settings(SolcConfig::builder().settings(settings).build().settings) .paths(paths) - .solc(solc) .ephemeral() .no_artifacts() - .build()?) + .build(compiler)?) } -/// Bundles multiple `SkipBuildFilter` into a single `FileFilter` -#[derive(Clone, Debug)] -pub struct SkipBuildFilters(Vec); - -impl FileFilter for SkipBuildFilters { - /// Only returns a match if _no_ exclusion filter matches - fn is_match(&self, file: &Path) -> bool { - self.0.iter().all(|matcher| is_match_exclude(matcher, file)) - } -} - -impl SkipBuildFilters { - /// Creates a new `SkipBuildFilters` from multiple `SkipBuildFilter`. - pub fn new(matchers: impl IntoIterator) -> Result { - matchers.into_iter().map(|m| m.compile()).collect::>().map(Self) - } -} - -/// A filter that excludes matching contracts from the build -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum SkipBuildFilter { - /// Exclude all `.t.sol` contracts - Tests, - /// Exclude all `.s.sol` contracts - Scripts, - /// Exclude if the file matches - Custom(String), -} - -impl SkipBuildFilter { - fn new(s: &str) -> Self { - match s { - "test" | "tests" => SkipBuildFilter::Tests, - "script" | "scripts" => SkipBuildFilter::Scripts, - s => SkipBuildFilter::Custom(s.to_string()), - } - } - - /// Returns the pattern to match against a file - fn file_pattern(&self) -> &str { - match self { - SkipBuildFilter::Tests => ".t.sol", - SkipBuildFilter::Scripts => ".s.sol", - SkipBuildFilter::Custom(s) => s.as_str(), +/// Configures the reporter and runs the given closure. +pub fn with_compilation_reporter(quiet: bool, f: impl FnOnce() -> O) -> O { + #[allow(clippy::collapsible_else_if)] + let reporter = if quiet { + Report::new(NoReporter::default()) + } else { + if std::io::stdout().is_terminal() { + Report::new(SpinnerReporter::spawn()) + } else { + Report::new(BasicStdoutReporter::default()) } - } - - fn compile(&self) -> Result { - self.file_pattern().parse().map_err(Into::into) - } -} - -impl FromStr for SkipBuildFilter { - type Err = Infallible; - - fn from_str(s: &str) -> result::Result { - Ok(Self::new(s)) - } -} - -/// Matches file only if the filter does not apply. -/// -/// This returns the inverse of `file.name.contains(pattern) || matcher.is_match(file)`. -fn is_match_exclude(matcher: &GlobMatcher, path: &Path) -> bool { - fn is_match(matcher: &GlobMatcher, path: &Path) -> Option { - let file_name = path.file_name()?.to_str()?; - Some(file_name.contains(matcher.as_str()) || matcher.is_match(path)) - } - - !is_match(matcher, path).unwrap_or_default() -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_build_filter() { - let tests = SkipBuildFilter::Tests.compile().unwrap(); - let scripts = SkipBuildFilter::Scripts.compile().unwrap(); - let custom = |s: &str| SkipBuildFilter::Custom(s.to_string()).compile().unwrap(); - - let file = Path::new("A.t.sol"); - assert!(!is_match_exclude(&tests, file)); - assert!(is_match_exclude(&scripts, file)); - assert!(!is_match_exclude(&custom("A.t"), file)); - - let file = Path::new("A.s.sol"); - assert!(is_match_exclude(&tests, file)); - assert!(!is_match_exclude(&scripts, file)); - assert!(!is_match_exclude(&custom("A.s"), file)); - - let file = Path::new("/home/test/Foo.sol"); - assert!(!is_match_exclude(&custom("*/test/**"), file)); + }; - let file = Path::new("/home/script/Contract.sol"); - assert!(!is_match_exclude(&custom("*/script/**"), file)); - } + foundry_compilers::report::with_scoped(&reporter, f) } diff --git a/crates/common/src/console/HardhatConsole.json b/crates/common/src/console/HardhatConsole.json index c1b1b46cf..4013d8753 100644 --- a/crates/common/src/console/HardhatConsole.json +++ b/crates/common/src/console/HardhatConsole.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"}],"name":"logAddress","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"}],"name":"logBool","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"p0","type":"bytes"}],"name":"logBytes","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes1","name":"p0","type":"bytes1"}],"name":"logBytes1","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes10","name":"p0","type":"bytes10"}],"name":"logBytes10","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes11","name":"p0","type":"bytes11"}],"name":"logBytes11","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes12","name":"p0","type":"bytes12"}],"name":"logBytes12","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes13","name":"p0","type":"bytes13"}],"name":"logBytes13","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes14","name":"p0","type":"bytes14"}],"name":"logBytes14","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes15","name":"p0","type":"bytes15"}],"name":"logBytes15","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes16","name":"p0","type":"bytes16"}],"name":"logBytes16","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes17","name":"p0","type":"bytes17"}],"name":"logBytes17","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes18","name":"p0","type":"bytes18"}],"name":"logBytes18","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes19","name":"p0","type":"bytes19"}],"name":"logBytes19","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes2","name":"p0","type":"bytes2"}],"name":"logBytes2","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes20","name":"p0","type":"bytes20"}],"name":"logBytes20","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes21","name":"p0","type":"bytes21"}],"name":"logBytes21","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes22","name":"p0","type":"bytes22"}],"name":"logBytes22","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes23","name":"p0","type":"bytes23"}],"name":"logBytes23","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes24","name":"p0","type":"bytes24"}],"name":"logBytes24","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes25","name":"p0","type":"bytes25"}],"name":"logBytes25","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes26","name":"p0","type":"bytes26"}],"name":"logBytes26","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes27","name":"p0","type":"bytes27"}],"name":"logBytes27","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes28","name":"p0","type":"bytes28"}],"name":"logBytes28","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes29","name":"p0","type":"bytes29"}],"name":"logBytes29","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes3","name":"p0","type":"bytes3"}],"name":"logBytes3","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes30","name":"p0","type":"bytes30"}],"name":"logBytes30","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes31","name":"p0","type":"bytes31"}],"name":"logBytes31","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"p0","type":"bytes32"}],"name":"logBytes32","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"p0","type":"bytes4"}],"name":"logBytes4","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes5","name":"p0","type":"bytes5"}],"name":"logBytes5","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes6","name":"p0","type":"bytes6"}],"name":"logBytes6","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes7","name":"p0","type":"bytes7"}],"name":"logBytes7","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes8","name":"p0","type":"bytes8"}],"name":"logBytes8","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes9","name":"p0","type":"bytes9"}],"name":"logBytes9","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int256","name":"p0","type":"int256"}],"name":"logInt","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"}],"name":"logString","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"}],"name":"logUint","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int256","name":"p0","type":"int256"}],"outputs":[],"stateMutability":"view","type":"function","name":"log"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"int256","name":"p1","type":"int256"}],"outputs":[],"stateMutability":"view","type":"function","name":"log"}] +[{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"}],"name":"logAddress","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"}],"name":"logBool","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"p0","type":"bytes"}],"name":"logBytes","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes1","name":"p0","type":"bytes1"}],"name":"logBytes1","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes10","name":"p0","type":"bytes10"}],"name":"logBytes10","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes11","name":"p0","type":"bytes11"}],"name":"logBytes11","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes12","name":"p0","type":"bytes12"}],"name":"logBytes12","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes13","name":"p0","type":"bytes13"}],"name":"logBytes13","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes14","name":"p0","type":"bytes14"}],"name":"logBytes14","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes15","name":"p0","type":"bytes15"}],"name":"logBytes15","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes16","name":"p0","type":"bytes16"}],"name":"logBytes16","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes17","name":"p0","type":"bytes17"}],"name":"logBytes17","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes18","name":"p0","type":"bytes18"}],"name":"logBytes18","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes19","name":"p0","type":"bytes19"}],"name":"logBytes19","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes2","name":"p0","type":"bytes2"}],"name":"logBytes2","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes20","name":"p0","type":"bytes20"}],"name":"logBytes20","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes21","name":"p0","type":"bytes21"}],"name":"logBytes21","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes22","name":"p0","type":"bytes22"}],"name":"logBytes22","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes23","name":"p0","type":"bytes23"}],"name":"logBytes23","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes24","name":"p0","type":"bytes24"}],"name":"logBytes24","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes25","name":"p0","type":"bytes25"}],"name":"logBytes25","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes26","name":"p0","type":"bytes26"}],"name":"logBytes26","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes27","name":"p0","type":"bytes27"}],"name":"logBytes27","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes28","name":"p0","type":"bytes28"}],"name":"logBytes28","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes29","name":"p0","type":"bytes29"}],"name":"logBytes29","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes3","name":"p0","type":"bytes3"}],"name":"logBytes3","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes30","name":"p0","type":"bytes30"}],"name":"logBytes30","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes31","name":"p0","type":"bytes31"}],"name":"logBytes31","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"p0","type":"bytes32"}],"name":"logBytes32","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"p0","type":"bytes4"}],"name":"logBytes4","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes5","name":"p0","type":"bytes5"}],"name":"logBytes5","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes6","name":"p0","type":"bytes6"}],"name":"logBytes6","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes7","name":"p0","type":"bytes7"}],"name":"logBytes7","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes8","name":"p0","type":"bytes8"}],"name":"logBytes8","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes9","name":"p0","type":"bytes9"}],"name":"logBytes9","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int256","name":"p0","type":"int256"}],"name":"logInt","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"}],"name":"logString","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"}],"name":"logUint","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int256","name":"p0","type":"int256"}],"outputs":[],"stateMutability":"view","type":"function","name":"log"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"int256","name":"p1","type":"int256"}],"outputs":[],"stateMutability":"view","type":"function","name":"log"}] \ No newline at end of file diff --git a/crates/common/src/constants.rs b/crates/common/src/constants.rs index 23ca0abab..0ba0514c2 100644 --- a/crates/common/src/constants.rs +++ b/crates/common/src/constants.rs @@ -38,7 +38,7 @@ pub const ARBITRUM_SENDER: Address = address!("000000000000000000000000000000000 pub const OPTIMISM_SYSTEM_ADDRESS: Address = address!("deaddeaddeaddeaddeaddeaddeaddeaddead0001"); /// Transaction identifier of System transaction types -pub const SYSTEM_TRANSACTION_TYPE: u64 = 126u64; +pub const SYSTEM_TRANSACTION_TYPE: u8 = 126; /// Returns whether the sender is a known L2 system sender that is the first tx in every block. /// diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index a1b251b76..046a07425 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -1,40 +1,242 @@ //! Commonly used contract types and functions. use alloy_json_abi::{Event, Function, JsonAbi}; -use alloy_primitives::{hex, Address, Selector, B256}; +use alloy_primitives::{hex, Address, Bytes, Selector, B256}; use eyre::Result; use foundry_compilers::{ - artifacts::{CompactContractBytecode, ContractBytecodeSome}, - ArtifactId, ProjectPathsConfig, -}; -use std::{ - collections::BTreeMap, - fmt, - ops::{Deref, DerefMut}, - path::PathBuf, + artifacts::{ + BytecodeObject, CompactBytecode, CompactContractBytecode, CompactDeployedBytecode, + ContractBytecodeSome, Offsets, + }, + ArtifactId, }; +use std::{collections::BTreeMap, ops::Deref, str::FromStr, sync::Arc}; -type ArtifactWithContractRef<'a> = (&'a ArtifactId, &'a (JsonAbi, Vec)); +/// Libraries' runtime code always starts with the following instruction: +/// `PUSH20 0x0000000000000000000000000000000000000000` +/// +/// See: +const CALL_PROTECTION_BYTECODE_PREFIX: [u8; 21] = + hex!("730000000000000000000000000000000000000000"); -/// Wrapper type that maps an artifact to a contract ABI and bytecode. -#[derive(Clone, Default)] -pub struct ContractsByArtifact(pub BTreeMap)>); +/// Subset of [CompactBytecode] excluding sourcemaps. +#[allow(missing_docs)] +#[derive(Debug, Clone)] +pub struct BytecodeData { + pub object: Option, + pub link_references: BTreeMap>>, + pub immutable_references: BTreeMap>, +} -impl fmt::Debug for ContractsByArtifact { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_map().entries(self.iter().map(|(k, (v1, v2))| (k, (v1, hex::encode(v2))))).finish() +impl BytecodeData { + fn bytes(&self) -> Option<&Bytes> { + self.object.as_ref().and_then(|b| b.as_bytes()) + } +} + +impl From for BytecodeData { + fn from(bytecode: CompactBytecode) -> Self { + Self { + object: Some(bytecode.object), + link_references: bytecode.link_references, + immutable_references: BTreeMap::new(), + } } } +impl From for BytecodeData { + fn from(bytecode: CompactDeployedBytecode) -> Self { + let (object, link_references) = if let Some(compact) = bytecode.bytecode { + (Some(compact.object), compact.link_references) + } else { + (None, BTreeMap::new()) + }; + Self { object, link_references, immutable_references: bytecode.immutable_references } + } +} + +/// Container for commonly used contract data. +#[derive(Debug)] +pub struct ContractData { + /// Contract name. + pub name: String, + /// Contract ABI. + pub abi: JsonAbi, + /// Contract creation code. + pub bytecode: Option, + /// Contract runtime code. + pub deployed_bytecode: Option, +} + +impl ContractData { + /// Returns reference to bytes of contract creation code, if present. + pub fn bytecode(&self) -> Option<&Bytes> { + self.bytecode.as_ref()?.bytes().filter(|b| !b.is_empty()) + } + + /// Returns reference to bytes of contract deployed code, if present. + pub fn deployed_bytecode(&self) -> Option<&Bytes> { + self.deployed_bytecode.as_ref()?.bytes().filter(|b| !b.is_empty()) + } +} + +type ArtifactWithContractRef<'a> = (&'a ArtifactId, &'a ContractData); + +/// Wrapper type that maps an artifact to a contract ABI and bytecode. +#[derive(Clone, Default, Debug)] +pub struct ContractsByArtifact(Arc>); + impl ContractsByArtifact { + /// Creates a new instance by collecting all artifacts with present bytecode from an iterator. + /// + /// It is recommended to use this method with an output of + /// [foundry_linking::Linker::get_linked_artifacts]. + pub fn new(artifacts: impl IntoIterator) -> Self { + let map = artifacts + .into_iter() + .filter_map(|(id, artifact)| { + let name = id.name.clone(); + let CompactContractBytecode { abi, bytecode, deployed_bytecode } = artifact; + Some(( + id, + ContractData { + name, + abi: abi?, + bytecode: bytecode.map(Into::into), + deployed_bytecode: deployed_bytecode.map(Into::into), + }, + )) + }) + .collect(); + Self(Arc::new(map)) + } + + /// Clears all contracts. + pub fn clear(&mut self) { + *self = Self::default(); + } + /// Finds a contract which has a similar bytecode as `code`. - pub fn find_by_code(&self, code: &[u8]) -> Option { - self.iter().find(|(_, (_, known_code))| bytecode_diff_score(known_code, code) <= 0.1) + pub fn find_by_creation_code(&self, code: &[u8]) -> Option> { + self.iter().find(|(_, contract)| { + if let Some(bytecode) = contract.bytecode() { + bytecode_diff_score(bytecode.as_ref(), code) <= 0.1 + } else { + false + } + }) + } + + /// Finds a contract which has a similar deployed bytecode as `code`. + pub fn find_by_deployed_code(&self, code: &[u8]) -> Option> { + self.iter().find(|(_, contract)| { + if let Some(deployed_bytecode) = contract.deployed_bytecode() { + bytecode_diff_score(deployed_bytecode.as_ref(), code) <= 0.1 + } else { + false + } + }) + } + + /// Finds a contract which deployed bytecode exactly matches the given code. Accounts for link + /// references and immutables. + pub fn find_by_deployed_code_exact(&self, code: &[u8]) -> Option> { + self.iter().find(|(_, contract)| { + let Some(deployed_bytecode) = &contract.deployed_bytecode else { + return false; + }; + let Some(deployed_code) = &deployed_bytecode.object else { + return false; + }; + + let len = match deployed_code { + BytecodeObject::Bytecode(ref bytes) => bytes.len(), + BytecodeObject::Unlinked(ref bytes) => bytes.len() / 2, + }; + + if len != code.len() { + return false; + } + + // Collect ignored offsets by chaining link and immutable references. + let mut ignored = deployed_bytecode + .immutable_references + .values() + .chain(deployed_bytecode.link_references.values().flat_map(|v| v.values())) + .flatten() + .cloned() + .collect::>(); + + // For libraries solidity adds a call protection prefix to the bytecode. We need to + // ignore it as it includes library address determined at runtime. + // See https://docs.soliditylang.org/en/latest/contracts.html#call-protection-for-libraries and + // https://github.com/NomicFoundation/hardhat/blob/af7807cf38842a4f56e7f4b966b806e39631568a/packages/hardhat-verify/src/internal/solc/bytecode.ts#L172 + let has_call_protection = match deployed_code { + BytecodeObject::Bytecode(ref bytes) => { + bytes.starts_with(&CALL_PROTECTION_BYTECODE_PREFIX) + } + BytecodeObject::Unlinked(ref bytes) => { + if let Ok(bytes) = + Bytes::from_str(&bytes[..CALL_PROTECTION_BYTECODE_PREFIX.len() * 2]) + { + bytes.starts_with(&CALL_PROTECTION_BYTECODE_PREFIX) + } else { + false + } + } + }; + + if has_call_protection { + ignored.push(Offsets { start: 1, length: 20 }); + } + + ignored.sort_by_key(|o| o.start); + + let mut left = 0; + for offset in ignored { + let right = offset.start as usize; + + let matched = match deployed_code { + BytecodeObject::Bytecode(ref bytes) => bytes[left..right] == code[left..right], + BytecodeObject::Unlinked(ref bytes) => { + if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..right * 2]) { + bytes == code[left..right] + } else { + false + } + } + }; + + if !matched { + return false; + } + + left = right + offset.length as usize; + } + + if left < code.len() { + match deployed_code { + BytecodeObject::Bytecode(ref bytes) => bytes[left..] == code[left..], + BytecodeObject::Unlinked(ref bytes) => { + if let Ok(bytes) = Bytes::from_str(&bytes[left * 2..]) { + bytes == code[left..] + } else { + false + } + } + } + } else { + true + } + }) } /// Finds a contract which has the same contract name or identifier as `id`. If more than one is /// found, return error. - pub fn find_by_name_or_identifier(&self, id: &str) -> Result> { + pub fn find_by_name_or_identifier( + &self, + id: &str, + ) -> Result>> { let contracts = self .iter() .filter(|(artifact, _)| artifact.name == id || artifact.identifier() == id) @@ -52,14 +254,14 @@ impl ContractsByArtifact { let mut funcs = BTreeMap::new(); let mut events = BTreeMap::new(); let mut errors_abi = JsonAbi::new(); - for (_name, (abi, _code)) in self.iter() { - for func in abi.functions() { + for (_name, contract) in self.iter() { + for func in contract.abi.functions() { funcs.insert(func.selector(), func.clone()); } - for event in abi.events() { + for event in contract.abi.events() { events.insert(event.selector(), event.clone()); } - for error in abi.errors() { + for error in contract.abi.errors() { errors_abi.errors.entry(error.name.clone()).or_default().push(error.clone()); } } @@ -68,19 +270,13 @@ impl ContractsByArtifact { } impl Deref for ContractsByArtifact { - type Target = BTreeMap)>; + type Target = BTreeMap; fn deref(&self) -> &Self::Target { &self.0 } } -impl DerefMut for ContractsByArtifact { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - /// Wrapper type that maps an address to a contract identifier and contract ABI. pub type ContractsByAddress = BTreeMap; @@ -135,23 +331,6 @@ unsafe fn count_different_bytes(a: &[u8], b: &[u8]) -> usize { sum } -/// Flattens the contracts into (`id` -> (`JsonAbi`, `Vec`)) pairs -pub fn flatten_contracts( - contracts: &BTreeMap, - deployed_code: bool, -) -> ContractsByArtifact { - ContractsByArtifact( - contracts - .iter() - .filter_map(|(id, c)| { - let bytecode = - if deployed_code { c.deployed_bytecode.bytes() } else { c.bytecode.bytes() }; - bytecode.cloned().map(|code| (id.clone(), (c.abi.clone(), code.into()))) - }) - .collect(), - ) -} - /// Artifact/Contract identifier can take the following form: /// `:`, the `artifact file name` is the name of the json file of /// the contract's artifact and the contract name is the name of the solidity contract, like @@ -187,19 +366,6 @@ pub fn get_file_name(id: &str) -> &str { id.split(':').next().unwrap_or(id) } -/// Returns the path to the json artifact depending on the input -pub fn get_artifact_path(paths: &ProjectPathsConfig, path: &str) -> PathBuf { - if path.ends_with(".json") { - PathBuf::from(path) - } else { - let parts: Vec<&str> = path.split(':').collect(); - let file = parts[0]; - let contract_name = - if parts.len() == 1 { parts[0].replace(".sol", "") } else { parts[1].to_string() }; - paths.artifacts.join(format!("{file}/{contract_name}.json")) - } -} - /// Helper function to convert CompactContractBytecode ~> ContractBytecodeSome pub fn compact_to_contract(contract: CompactContractBytecode) -> Result { Ok(ContractBytecodeSome { diff --git a/crates/common/src/ens.rs b/crates/common/src/ens.rs new file mode 100644 index 000000000..f84ea84ce --- /dev/null +++ b/crates/common/src/ens.rs @@ -0,0 +1,237 @@ +//! ENS Name resolving utilities. + +#![allow(missing_docs)] + +use self::EnsResolver::EnsResolverInstance; +use alloy_primitives::{address, Address, Keccak256, B256}; +use alloy_provider::{Network, Provider}; +use alloy_sol_types::sol; +use alloy_transport::Transport; +use async_trait::async_trait; +use std::{borrow::Cow, str::FromStr}; + +// ENS Registry and Resolver contracts. +sol! { + /// ENS Registry contract. + #[sol(rpc)] + contract EnsRegistry { + /// Returns the resolver for the specified node. + function resolver(bytes32 node) view returns (address); + } + + /// ENS Resolver interface. + #[sol(rpc)] + contract EnsResolver { + /// Returns the address associated with the specified node. + function addr(bytes32 node) view returns (address); + + /// Returns the name associated with an ENS node, for reverse records. + function name(bytes32 node) view returns (string); + } +} + +/// ENS registry address (`0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e`) +pub const ENS_ADDRESS: Address = address!("00000000000C2E074eC69A0dFb2997BA6C7d2e1e"); + +pub const ENS_REVERSE_REGISTRAR_DOMAIN: &str = "addr.reverse"; + +/// Error type for ENS resolution. +#[derive(Debug, thiserror::Error)] +pub enum EnsError { + /// Failed to get resolver from the ENS registry. + #[error("Failed to get resolver from the ENS registry: {0}")] + Resolver(alloy_contract::Error), + /// Failed to get resolver from the ENS registry. + #[error("ENS resolver not found for name {0:?}")] + ResolverNotFound(String), + /// Failed to lookup ENS name from an address. + #[error("Failed to lookup ENS name from an address: {0}")] + Lookup(alloy_contract::Error), + /// Failed to resolve ENS name to an address. + #[error("Failed to resolve ENS name to an address: {0}")] + Resolve(alloy_contract::Error), +} + +/// ENS name or Ethereum Address. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum NameOrAddress { + /// An ENS Name (format does not get checked) + Name(String), + /// An Ethereum Address + Address(Address), +} + +impl NameOrAddress { + /// Resolves the name to an Ethereum Address. + pub async fn resolve>( + &self, + provider: &P, + ) -> Result { + match self { + Self::Name(name) => provider.resolve_name(name).await, + Self::Address(addr) => Ok(*addr), + } + } +} + +impl From for NameOrAddress { + fn from(name: String) -> Self { + Self::Name(name) + } +} + +impl From<&String> for NameOrAddress { + fn from(name: &String) -> Self { + Self::Name(name.clone()) + } +} + +impl From
for NameOrAddress { + fn from(addr: Address) -> Self { + Self::Address(addr) + } +} + +impl FromStr for NameOrAddress { + type Err =
::Err; + + fn from_str(s: &str) -> Result { + if let Ok(addr) = Address::from_str(s) { + Ok(Self::Address(addr)) + } else { + Ok(Self::Name(s.to_string())) + } + } +} + +/// Extension trait for ENS contract calls. +#[async_trait] +pub trait ProviderEnsExt> { + /// Returns the resolver for the specified node. The `&str` is only used for error messages. + async fn get_resolver( + &self, + node: B256, + error_name: &str, + ) -> Result, EnsError>; + + /// Performs a forward lookup of an ENS name to an address. + async fn resolve_name(&self, name: &str) -> Result { + let node = namehash(name); + let resolver = self.get_resolver(node, name).await?; + let addr = resolver + .addr(node) + .call() + .await + .map_err(EnsError::Resolve) + .inspect_err(|e| eprintln!("{e:?}"))? + ._0; + Ok(addr) + } + + /// Performs a reverse lookup of an address to an ENS name. + async fn lookup_address(&self, address: &Address) -> Result { + let name = reverse_address(address); + let node = namehash(&name); + let resolver = self.get_resolver(node, &name).await?; + let name = resolver.name(node).call().await.map_err(EnsError::Lookup)?._0; + Ok(name) + } +} + +#[async_trait] +impl ProviderEnsExt for P +where + P: Provider, + N: Network, + T: Transport + Clone, +{ + async fn get_resolver( + &self, + node: B256, + error_name: &str, + ) -> Result, EnsError> { + let registry = EnsRegistry::new(ENS_ADDRESS, self); + let address = registry.resolver(node).call().await.map_err(EnsError::Resolver)?._0; + if address == Address::ZERO { + return Err(EnsError::ResolverNotFound(error_name.to_string())); + } + Ok(EnsResolverInstance::new(address, self)) + } +} + +/// Returns the ENS namehash as specified in [EIP-137](https://eips.ethereum.org/EIPS/eip-137) +pub fn namehash(name: &str) -> B256 { + if name.is_empty() { + return B256::ZERO + } + + // Remove the variation selector `U+FE0F` if present. + const VARIATION_SELECTOR: char = '\u{fe0f}'; + let name = if name.contains(VARIATION_SELECTOR) { + Cow::Owned(name.replace(VARIATION_SELECTOR, "")) + } else { + Cow::Borrowed(name) + }; + + // Generate the node starting from the right. + // This buffer is `[node @ [u8; 32], label_hash @ [u8; 32]]`. + let mut buffer = [0u8; 64]; + for label in name.rsplit('.') { + // node = keccak256([node, keccak256(label)]) + + // Hash the label. + let mut label_hasher = Keccak256::new(); + label_hasher.update(label.as_bytes()); + label_hasher.finalize_into(&mut buffer[32..]); + + // Hash both the node and the label hash, writing into the node. + let mut buffer_hasher = Keccak256::new(); + buffer_hasher.update(buffer.as_slice()); + buffer_hasher.finalize_into(&mut buffer[..32]); + } + buffer[..32].try_into().unwrap() +} + +/// Returns the reverse-registrar name of an address. +pub fn reverse_address(addr: &Address) -> String { + format!("{addr:x}.{ENS_REVERSE_REGISTRAR_DOMAIN}") +} + +#[cfg(test)] +mod test { + use super::*; + use alloy_primitives::hex; + + fn assert_hex(hash: B256, val: &str) { + assert_eq!(hash.0[..], hex::decode(val).unwrap()[..]); + } + + #[test] + fn test_namehash() { + for (name, expected) in &[ + ("", "0x0000000000000000000000000000000000000000000000000000000000000000"), + ("eth", "0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae"), + ("foo.eth", "0xde9b09fd7c5f901e23a3f19fecc54828e9c848539801e86591bd9801b019f84f"), + ("alice.eth", "0x787192fc5378cc32aa956ddfdedbf26b24e8d78e40109add0eea2c1a012c3dec"), + ("ret↩️rn.eth", "0x3de5f4c02db61b221e7de7f1c40e29b6e2f07eb48d65bf7e304715cd9ed33b24"), + ] { + assert_hex(namehash(name), expected); + } + } + + #[test] + fn test_reverse_address() { + for (addr, expected) in [ + ( + "0x314159265dd8dbb310642f98f50c066173c1259b", + "314159265dd8dbb310642f98f50c066173c1259b.addr.reverse", + ), + ( + "0x28679A1a632125fbBf7A68d850E50623194A709E", + "28679a1a632125fbbf7a68d850e50623194a709e.addr.reverse", + ), + ] { + assert_eq!(reverse_address(&addr.parse().unwrap()), expected, "{addr}"); + } + } +} diff --git a/crates/common/src/errors/fs.rs b/crates/common/src/errors/fs.rs index b929e7838..9ea84b9ce 100644 --- a/crates/common/src/errors/fs.rs +++ b/crates/common/src/errors/fs.rs @@ -3,36 +3,39 @@ use std::{ path::{Path, PathBuf}, }; -/// Various error variants for `std::fs` operations that serve as an addition to the io::Error which +#[allow(unused_imports)] +use std::fs::{self, File}; + +/// Various error variants for `fs` operations that serve as an addition to the io::Error which /// does not provide any information about the path. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum FsPathError { - /// Provides additional path context for [`std::fs::write`]. + /// Provides additional path context for [`fs::write`]. #[error("failed to write to {path:?}: {source}")] Write { source: io::Error, path: PathBuf }, - /// Provides additional path context for [`std::fs::read`]. + /// Provides additional path context for [`fs::read`]. #[error("failed to read from {path:?}: {source}")] Read { source: io::Error, path: PathBuf }, - /// Provides additional path context for [`std::fs::copy`]. + /// Provides additional path context for [`fs::copy`]. #[error("failed to copy from {from:?} to {to:?}: {source}")] Copy { source: io::Error, from: PathBuf, to: PathBuf }, - /// Provides additional path context for [`std::fs::read_link`]. + /// Provides additional path context for [`fs::read_link`]. #[error("failed to read from {path:?}: {source}")] ReadLink { source: io::Error, path: PathBuf }, /// Provides additional path context for [`File::create`]. #[error("failed to create file {path:?}: {source}")] CreateFile { source: io::Error, path: PathBuf }, - /// Provides additional path context for [`std::fs::remove_file`]. + /// Provides additional path context for [`fs::remove_file`]. #[error("failed to remove file {path:?}: {source}")] RemoveFile { source: io::Error, path: PathBuf }, - /// Provides additional path context for [`std::fs::create_dir`]. + /// Provides additional path context for [`fs::create_dir`]. #[error("failed to create dir {path:?}: {source}")] CreateDir { source: io::Error, path: PathBuf }, - /// Provides additional path context for [`std::fs::remove_dir`]. + /// Provides additional path context for [`fs::remove_dir`]. #[error("failed to remove dir {path:?}: {source}")] RemoveDir { source: io::Error, path: PathBuf }, - /// Provides additional path context for [`std::fs::File::open`]. + /// Provides additional path context for [`File::open`]. #[error("failed to open file {path:?}: {source}")] Open { source: io::Error, path: PathBuf }, /// Provides additional path context for the file whose contents should be parsed as JSON. @@ -44,49 +47,49 @@ pub enum FsPathError { } impl FsPathError { - /// Returns the complementary error variant for [`std::fs::write`]. + /// Returns the complementary error variant for [`fs::write`]. pub fn write(source: io::Error, path: impl Into) -> Self { - FsPathError::Write { source, path: path.into() } + Self::Write { source, path: path.into() } } - /// Returns the complementary error variant for [`std::fs::read`]. + /// Returns the complementary error variant for [`fs::read`]. pub fn read(source: io::Error, path: impl Into) -> Self { - FsPathError::Read { source, path: path.into() } + Self::Read { source, path: path.into() } } - /// Returns the complementary error variant for [`std::fs::copy`]. + /// Returns the complementary error variant for [`fs::copy`]. pub fn copy(source: io::Error, from: impl Into, to: impl Into) -> Self { - FsPathError::Copy { source, from: from.into(), to: to.into() } + Self::Copy { source, from: from.into(), to: to.into() } } - /// Returns the complementary error variant for [`std::fs::read_link`]. + /// Returns the complementary error variant for [`fs::read_link`]. pub fn read_link(source: io::Error, path: impl Into) -> Self { - FsPathError::ReadLink { source, path: path.into() } + Self::ReadLink { source, path: path.into() } } /// Returns the complementary error variant for [`File::create`]. pub fn create_file(source: io::Error, path: impl Into) -> Self { - FsPathError::CreateFile { source, path: path.into() } + Self::CreateFile { source, path: path.into() } } - /// Returns the complementary error variant for [`std::fs::remove_file`]. + /// Returns the complementary error variant for [`fs::remove_file`]. pub fn remove_file(source: io::Error, path: impl Into) -> Self { - FsPathError::RemoveFile { source, path: path.into() } + Self::RemoveFile { source, path: path.into() } } - /// Returns the complementary error variant for [`std::fs::create_dir`]. + /// Returns the complementary error variant for [`fs::create_dir`]. pub fn create_dir(source: io::Error, path: impl Into) -> Self { - FsPathError::CreateDir { source, path: path.into() } + Self::CreateDir { source, path: path.into() } } - /// Returns the complementary error variant for [`std::fs::remove_dir`]. + /// Returns the complementary error variant for [`fs::remove_dir`]. pub fn remove_dir(source: io::Error, path: impl Into) -> Self { - FsPathError::RemoveDir { source, path: path.into() } + Self::RemoveDir { source, path: path.into() } } /// Returns the complementary error variant for [`File::open`]. pub fn open(source: io::Error, path: impl Into) -> Self { - FsPathError::Open { source, path: path.into() } + Self::Open { source, path: path.into() } } } diff --git a/crates/common/src/evm.rs b/crates/common/src/evm.rs index 2230bd760..98355c890 100644 --- a/crates/common/src/evm.rs +++ b/crates/common/src/evm.rs @@ -11,11 +11,11 @@ use foundry_config::{ }, Chain, Config, }; +use rustc_hash::FxHashMap; use serde::Serialize; -use std::collections::HashMap; /// Map keyed by breakpoints char to their location (contract address, pc) -pub type Breakpoints = HashMap; +pub type Breakpoints = FxHashMap; /// `EvmArgs` and `EnvArgs` take the highest precedence in the Config/Figment hierarchy. /// All vars are opt-in, their default values are expected to be set by the @@ -39,33 +39,33 @@ pub type Breakpoints = HashMap; /// # } /// ``` #[derive(Clone, Debug, Default, Serialize, Parser)] -#[clap(next_help_heading = "EVM options", about = None, long_about = None)] // override doc +#[command(next_help_heading = "EVM options", about = None, long_about = None)] // override doc pub struct EvmArgs { /// Fetch state over a remote endpoint instead of starting from an empty state. /// /// If you want to fetch state from a specific block number, see --fork-block-number. - #[clap(long, short, visible_alias = "rpc-url", value_name = "URL")] + #[arg(long, short, visible_alias = "rpc-url", value_name = "URL")] #[serde(rename = "eth_rpc_url", skip_serializing_if = "Option::is_none")] pub fork_url: Option, /// Fetch state from a specific block number over a remote endpoint. /// /// See --fork-url. - #[clap(long, requires = "fork_url", value_name = "BLOCK")] + #[arg(long, requires = "fork_url", value_name = "BLOCK")] #[serde(skip_serializing_if = "Option::is_none")] pub fork_block_number: Option, /// Number of retries. /// /// See --fork-url. - #[clap(long, requires = "fork_url", value_name = "RETRIES")] + #[arg(long, requires = "fork_url", value_name = "RETRIES")] #[serde(skip_serializing_if = "Option::is_none")] pub fork_retries: Option, /// Initial retry backoff on encountering errors. /// /// See --fork-url. - #[clap(long, requires = "fork_url", value_name = "BACKOFF")] + #[arg(long, requires = "fork_url", value_name = "BACKOFF")] #[serde(skip_serializing_if = "Option::is_none")] pub fork_retry_backoff: Option, @@ -76,27 +76,27 @@ pub struct EvmArgs { /// This flag overrides the project's configuration file. /// /// See --fork-url. - #[clap(long)] + #[arg(long)] #[serde(skip)] pub no_storage_caching: bool, /// The initial balance of deployed test contracts. - #[clap(long, value_name = "BALANCE")] + #[arg(long, value_name = "BALANCE")] #[serde(skip_serializing_if = "Option::is_none")] pub initial_balance: Option, /// The address which will be executing tests. - #[clap(long, value_name = "ADDRESS")] + #[arg(long, value_name = "ADDRESS")] #[serde(skip_serializing_if = "Option::is_none")] pub sender: Option
, /// Enable the FFI cheatcode. - #[clap(long)] + #[arg(long)] #[serde(skip)] pub ffi: bool, /// Use the create 2 factory in all cases including tests and non-broadcasting scripts. - #[clap(long)] + #[arg(long)] #[serde(skip)] pub always_use_create_2_factory: bool, @@ -109,7 +109,7 @@ pub struct EvmArgs { /// - 3: Print execution traces for failing tests /// - 4: Print execution traces for all tests, and setup traces for failing tests /// - 5: Print execution and setup traces for all tests - #[clap(long, short, verbatim_doc_comment, action = ArgAction::Count)] + #[arg(long, short, verbatim_doc_comment, action = ArgAction::Count)] #[serde(skip)] pub verbosity: u8, @@ -117,22 +117,15 @@ pub struct EvmArgs { /// /// default value: 330 /// - /// See also --fork-url and https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second - #[clap( - long, - requires = "fork_url", - alias = "cups", - value_name = "CUPS", - help_heading = "Fork config" - )] + /// See also --fork-url and + #[arg(long, alias = "cups", value_name = "CUPS", help_heading = "Fork config")] pub compute_units_per_second: Option, /// Disables rate limiting for this node's provider. /// - /// See also --fork-url and https://docs.alchemy.com/reference/compute-units#what-are-cups-compute-units-per-second - #[clap( + /// See also --fork-url and + #[arg( long, - requires = "fork_url", value_name = "NO_RATE_LIMITS", help_heading = "Fork config", visible_alias = "no-rate-limit" @@ -141,14 +134,14 @@ pub struct EvmArgs { pub no_rpc_rate_limit: bool, /// All ethereum environment related arguments - #[clap(flatten)] + #[command(flatten)] #[serde(flatten)] pub env: EnvArgs, /// Whether to enable isolation of calls. /// In isolation mode all top-level calls are executed as a separate transaction in a separate /// EVM context, enabling more precise gas accounting and transaction state changes. - #[clap(long)] + #[arg(long)] #[serde(skip)] pub isolate: bool, } @@ -202,66 +195,66 @@ impl Provider for EvmArgs { /// Configures the executor environment during tests. #[derive(Clone, Debug, Default, Serialize, Parser)] -#[clap(next_help_heading = "Executor environment config")] +#[command(next_help_heading = "Executor environment config")] pub struct EnvArgs { /// The block gas limit. - #[clap(long, value_name = "GAS_LIMIT")] + #[arg(long, value_name = "GAS_LIMIT")] #[serde(skip_serializing_if = "Option::is_none")] pub gas_limit: Option, /// EIP-170: Contract code size limit in bytes. Useful to increase this because of tests. By /// default, it is 0x6000 (~25kb). - #[clap(long, value_name = "CODE_SIZE")] + #[arg(long, value_name = "CODE_SIZE")] #[serde(skip_serializing_if = "Option::is_none")] pub code_size_limit: Option, /// The chain name or EIP-155 chain ID. - #[clap(long, visible_alias = "chain-id", value_name = "CHAIN")] + #[arg(long, visible_alias = "chain-id", value_name = "CHAIN")] #[serde(rename = "chain_id", skip_serializing_if = "Option::is_none", serialize_with = "id")] pub chain: Option, /// The gas price. - #[clap(long, value_name = "GAS_PRICE")] + #[arg(long, value_name = "GAS_PRICE")] #[serde(skip_serializing_if = "Option::is_none")] pub gas_price: Option, /// The base fee in a block. - #[clap(long, visible_alias = "base-fee", value_name = "FEE")] + #[arg(long, visible_alias = "base-fee", value_name = "FEE")] #[serde(skip_serializing_if = "Option::is_none")] pub block_base_fee_per_gas: Option, /// The transaction origin. - #[clap(long, value_name = "ADDRESS")] + #[arg(long, value_name = "ADDRESS")] #[serde(skip_serializing_if = "Option::is_none")] pub tx_origin: Option
, /// The coinbase of the block. - #[clap(long, value_name = "ADDRESS")] + #[arg(long, value_name = "ADDRESS")] #[serde(skip_serializing_if = "Option::is_none")] pub block_coinbase: Option
, /// The timestamp of the block. - #[clap(long, value_name = "TIMESTAMP")] + #[arg(long, value_name = "TIMESTAMP")] #[serde(skip_serializing_if = "Option::is_none")] pub block_timestamp: Option, /// The block number. - #[clap(long, value_name = "BLOCK")] + #[arg(long, value_name = "BLOCK")] #[serde(skip_serializing_if = "Option::is_none")] pub block_number: Option, /// The block difficulty. - #[clap(long, value_name = "DIFFICULTY")] + #[arg(long, value_name = "DIFFICULTY")] #[serde(skip_serializing_if = "Option::is_none")] pub block_difficulty: Option, /// The block prevrandao value. NOTE: Before merge this field was mix_hash. - #[clap(long, value_name = "PREVRANDAO")] + #[arg(long, value_name = "PREVRANDAO")] #[serde(skip_serializing_if = "Option::is_none")] pub block_prevrandao: Option, /// The block gas limit. - #[clap(long, value_name = "GAS_LIMIT")] + #[arg(long, value_name = "GAS_LIMIT")] #[serde(skip_serializing_if = "Option::is_none")] pub block_gas_limit: Option, @@ -269,9 +262,13 @@ pub struct EnvArgs { /// If this limit is exceeded, a `MemoryLimitOOG` result is thrown. /// /// The default is 128MiB. - #[clap(long, value_name = "MEMORY_LIMIT")] + #[arg(long, value_name = "MEMORY_LIMIT")] #[serde(skip_serializing_if = "Option::is_none")] pub memory_limit: Option, + + /// Whether to disable the block gas limit checks. + #[arg(long, visible_alias = "no-gas-limit")] + pub disable_block_gas_limit: bool, } impl EvmArgs { diff --git a/crates/common/src/fmt/console.rs b/crates/common/src/fmt/console.rs index e4b33c5cd..efade2e43 100644 --- a/crates/common/src/fmt/console.rs +++ b/crates/common/src/fmt/console.rs @@ -47,7 +47,7 @@ impl ConsoleFmt for String { FormatSpec::Number | FormatSpec::Integer | FormatSpec::Exponential | - FormatSpec::Hexadecimal => String::from("NaN"), + FormatSpec::Hexadecimal => Self::from("NaN"), } } } @@ -77,7 +77,7 @@ impl ConsoleFmt for U256 { } FormatSpec::Exponential => { let log = self.pretty().len() - 1; - let exp10 = U256::from(10).pow(U256::from(log)); + let exp10 = Self::from(10).pow(Self::from(log)); let amount = *self; let integer = amount / exp10; let decimal = (amount % exp10).to_string(); @@ -110,7 +110,7 @@ impl ConsoleFmt for I256 { } else { self.pretty().len() - 1 }; - let exp10 = I256::exp10(log); + let exp10 = Self::exp10(log); let integer = (amount / exp10).twos_complement(); let decimal = (amount % exp10).twos_complement().to_string(); let decimal = format!("{decimal:0>log$}").trim_end_matches('0').to_string(); diff --git a/crates/common/src/fmt/dynamic.rs b/crates/common/src/fmt/dynamic.rs index 2e30f2f7e..19330d474 100644 --- a/crates/common/src/fmt/dynamic.rs +++ b/crates/common/src/fmt/dynamic.rs @@ -85,7 +85,7 @@ impl DynValueFormatter { } } -/// Wrapper that implements [`Display`] for a [`DynSolValue`]. +/// Wrapper that implements [`Display`](fmt::Display) for a [`DynSolValue`]. struct DynValueDisplay<'a> { /// The value to display. value: &'a DynSolValue, @@ -94,7 +94,7 @@ struct DynValueDisplay<'a> { } impl<'a> DynValueDisplay<'a> { - /// Creates a new [`Display`] wrapper for the given value. + /// Creates a new [`Display`](fmt::Display) wrapper for the given value. #[inline] fn new(value: &'a DynSolValue, raw: bool) -> Self { Self { value, formatter: DynValueFormatter { raw } } diff --git a/crates/common/src/fmt/mod.rs b/crates/common/src/fmt/mod.rs index fbe1670fc..a43fe7dea 100644 --- a/crates/common/src/fmt/mod.rs +++ b/crates/common/src/fmt/mod.rs @@ -23,7 +23,7 @@ pub use ui::{get_pretty_block_attr, get_pretty_tx_attr, get_pretty_tx_receipt_at /// use alloy_primitives::U256; /// use foundry_common::fmt::format_uint_exp as f; /// -/// # yansi::Paint::disable(); +/// # yansi::disable(); /// assert_eq!(f(U256::from(0)), "0"); /// assert_eq!(f(U256::from(1234)), "1234"); /// assert_eq!(f(U256::from(1234567890)), "1234567890 [1.234e9]"); @@ -36,7 +36,7 @@ pub fn format_uint_exp(num: U256) -> String { } let exp = to_exp_notation(num, 4, true, Sign::Positive); - format!("{num} {}", Paint::default(format!("[{exp}]")).dimmed()) + format!("{num} {}", format!("[{exp}]").dim()) } /// Formats a U256 number to string, adding an exponential notation _hint_. @@ -49,7 +49,7 @@ pub fn format_uint_exp(num: U256) -> String { /// use alloy_primitives::I256; /// use foundry_common::fmt::format_int_exp as f; /// -/// # yansi::Paint::disable(); +/// # yansi::disable(); /// assert_eq!(f(I256::try_from(0).unwrap()), "0"); /// assert_eq!(f(I256::try_from(-1).unwrap()), "-1"); /// assert_eq!(f(I256::try_from(1234).unwrap()), "1234"); @@ -72,5 +72,5 @@ pub fn format_int_exp(num: I256) -> String { } let exp = to_exp_notation(abs, 4, true, sign); - format!("{sign}{abs} {}", Paint::default(format!("[{exp}]")).dimmed()) + format!("{sign}{abs} {}", format!("[{exp}]").dim()) } diff --git a/crates/common/src/fmt/ui.rs b/crates/common/src/fmt/ui.rs index 5549f36ab..0fe8dccd1 100644 --- a/crates/common/src/fmt/ui.rs +++ b/crates/common/src/fmt/ui.rs @@ -1,8 +1,12 @@ //! Helper trait and functions to format Ethereum types. use crate::TransactionReceiptWithRevertReason; +use alloy_consensus::{AnyReceiptEnvelope, Receipt, ReceiptWithBloom, TxType}; use alloy_primitives::*; -use ethers_core::types::{Block, Log, OtherFields, Transaction, TransactionReceipt, TxHash}; +use alloy_rpc_types::{ + AnyTransactionReceipt, Block, BlockTransactions, Log, Transaction, TransactionReceipt, +}; +use alloy_serde::OtherFields; use serde::Deserialize; /// length of the name column for pretty formatting `{:>20}{value}` @@ -23,6 +27,12 @@ pub trait UIfmt { fn pretty(&self) -> String; } +impl UIfmt for &T { + fn pretty(&self) -> String { + (*self).pretty() + } +} + impl UIfmt for Option { fn pretty(&self) -> String { if let Some(ref inner) = self { @@ -33,7 +43,7 @@ impl UIfmt for Option { } } -impl UIfmt for Vec { +impl UIfmt for [T] { fn pretty(&self) -> String { if !self.is_empty() { let mut s = String::with_capacity(self.len() * 64); @@ -59,13 +69,25 @@ impl UIfmt for String { } } +impl UIfmt for u64 { + fn pretty(&self) -> String { + self.to_string() + } +} + +impl UIfmt for u128 { + fn pretty(&self) -> String { + self.to_string() + } +} + impl UIfmt for bool { fn pretty(&self) -> String { self.to_string() } } -impl UIfmt for U256 { +impl UIfmt for Uint { fn pretty(&self) -> String { self.to_string() } @@ -89,6 +111,12 @@ impl UIfmt for Bloom { } } +impl UIfmt for TxType { + fn pretty(&self) -> String { + (*self as u8).to_string() + } +} + impl UIfmt for Vec { fn pretty(&self) -> String { self[..].pretty() @@ -119,32 +147,38 @@ impl UIfmt for [u8] { } } -impl UIfmt for U64 { - fn pretty(&self) -> String { - self.to_string() - } +pub fn pretty_status(status: bool) -> String { + if status { "1 (success)" } else { "0 (failed)" }.to_string() } -impl UIfmt for TransactionReceipt { +impl UIfmt for AnyTransactionReceipt { fn pretty(&self) -> String { let Self { - transaction_hash, - transaction_index, - block_hash, - block_number, - from, - to, - cumulative_gas_used, - gas_used, - contract_address, - logs, - status, - root, - logs_bloom, - transaction_type, - effective_gas_price, + inner: + TransactionReceipt { + transaction_hash, + transaction_index, + block_hash, + block_number, + from, + to, + gas_used, + contract_address, + state_root, + effective_gas_price, + inner: + AnyReceiptEnvelope { + r#type: transaction_type, + inner: + ReceiptWithBloom { + receipt: Receipt { status, cumulative_gas_used, logs }, + logs_bloom, + }, + }, + blob_gas_price, + blob_gas_used, + }, other, - .. } = self; let mut pretty = format!( @@ -162,7 +196,9 @@ root {} status {} transactionHash {} transactionIndex {} -type {}", +type {} +blobGasPrice {} +blobGasUsed {}", block_hash.pretty(), block_number.pretty(), contract_address.pretty(), @@ -170,13 +206,15 @@ type {}", effective_gas_price.pretty(), from.pretty(), gas_used.pretty(), - serde_json::to_string(logs).unwrap(), + serde_json::to_string(&logs).unwrap(), logs_bloom.pretty(), - root.pretty(), - status.pretty(), + state_root.pretty(), + pretty_status(status.coerce_status()), transaction_hash.pretty(), transaction_index.pretty(), - transaction_type.pretty() + transaction_type, + blob_gas_price.pretty(), + blob_gas_used.pretty(), ); if let Some(to) = to { @@ -185,7 +223,7 @@ type {}", // additional captured fields for (key, val) in other.iter() { - pretty.push_str(&format!("\n{} {}", key, val)); + pretty.push_str(&format!("\n{key} {val}")); } pretty @@ -205,40 +243,38 @@ removed: {} topics: {} transactionHash: {} transactionIndex: {}", - self.address.pretty(), + self.address().pretty(), self.block_hash.pretty(), self.block_number.pretty(), - self.data.pretty(), + self.data().data.pretty(), self.log_index.pretty(), self.removed.pretty(), - self.topics.pretty(), + self.topics().pretty(), self.transaction_hash.pretty(), self.transaction_index.pretty(), ) } } -impl UIfmt for Block { +impl UIfmt for Block { fn pretty(&self) -> String { format!( " {} -transactions {}", +transactions: {}", pretty_block_basics(self), self.transactions.pretty() ) } } -impl UIfmt for Block { +impl UIfmt for BlockTransactions { fn pretty(&self) -> String { - format!( - " -{} -transactions: {}", - pretty_block_basics(self), - self.transactions.pretty() - ) + match self { + Self::Hashes(hashes) => hashes.pretty(), + Self::Full(transactions) => transactions.pretty(), + Self::Uncle => String::new(), + } } } @@ -285,12 +321,12 @@ value {}{}", self.gas_price.pretty(), self.hash.pretty(), self.input.pretty(), - self.nonce.pretty(), - to_bytes(self.r).pretty(), - to_bytes(self.s).pretty(), + self.nonce, + self.signature.map(|s| s.r.to_be_bytes_vec()).pretty(), + self.signature.map(|s| s.s.to_be_bytes_vec()).pretty(), self.to.pretty(), self.transaction_index.pretty(), - self.v.pretty(), + self.signature.map(|s| s.v).pretty(), self.value.pretty(), self.other.pretty() ) @@ -333,45 +369,13 @@ impl From for EthValue { impl UIfmt for EthValue { fn pretty(&self) -> String { match self { - EthValue::U64(num) => num.pretty(), - EthValue::U256(num) => num.pretty(), - EthValue::U64Array(arr) => arr.pretty(), - EthValue::U256Array(arr) => arr.pretty(), - EthValue::Other(val) => val.to_string().trim_matches('"').to_string(), - } - } -} - -// TODO: replace these above and remove this module once types are converted -mod temp_ethers { - use super::UIfmt; - use ethers_core::types::{Address, Bloom, Bytes, H256, H64, I256, U256, U64}; - use foundry_common::types::ToAlloy; - - macro_rules! with_alloy { - ($($t:ty),*) => {$( - impl UIfmt for $t { - fn pretty(&self) -> String { - self.to_alloy().pretty() - } - } - )*}; - } - - impl UIfmt for Bytes { - fn pretty(&self) -> String { - self.clone().to_alloy().pretty() + Self::U64(num) => num.pretty(), + Self::U256(num) => num.pretty(), + Self::U64Array(arr) => arr.pretty(), + Self::U256Array(arr) => arr.pretty(), + Self::Other(val) => val.to_string().trim_matches('"').to_string(), } } - - with_alloy!(Address, Bloom, H64, H256, I256, U256, U64); -} - -/// Convert a U256 to bytes -pub fn to_bytes(uint: ethers_core::types::U256) -> [u8; 32] { - let mut buffer: [u8; 32] = [0; 32]; - uint.to_big_endian(&mut buffer); - buffer } /// Returns the `UiFmt::pretty()` formatted attribute of the transactions @@ -384,12 +388,12 @@ pub fn get_pretty_tx_attr(transaction: &Transaction, attr: &str) -> Option Some(transaction.gas_price.pretty()), "hash" => Some(transaction.hash.pretty()), "input" => Some(transaction.input.pretty()), - "nonce" => Some(transaction.nonce.pretty()), - "s" => Some(to_bytes(transaction.s).pretty()), - "r" => Some(to_bytes(transaction.r).pretty()), + "nonce" => Some(transaction.nonce.to_string()), + "s" => transaction.signature.map(|s| B256::from(s.s).pretty()), + "r" => transaction.signature.map(|s| B256::from(s.r).pretty()), "to" => Some(transaction.to.pretty()), "transactionIndex" | "transaction_index" => Some(transaction.transaction_index.pretty()), - "v" => Some(transaction.v.pretty()), + "v" => transaction.signature.map(|s| s.v.pretty()), "value" => Some(transaction.value.pretty()), other => { if let Some(value) = transaction.other.get(other) { @@ -410,49 +414,50 @@ pub fn get_pretty_tx_receipt_attr( "blockNumber" | "block_number" => Some(receipt.receipt.block_number.pretty()), "contractAddress" | "contract_address" => Some(receipt.receipt.contract_address.pretty()), "cumulativeGasUsed" | "cumulative_gas_used" => { - Some(receipt.receipt.cumulative_gas_used.pretty()) + Some(receipt.receipt.inner.inner.inner.receipt.cumulative_gas_used.pretty()) } "effectiveGasPrice" | "effective_gas_price" => { - Some(receipt.receipt.effective_gas_price.pretty()) + Some(receipt.receipt.effective_gas_price.to_string()) + } + "gasUsed" | "gas_used" => Some(receipt.receipt.gas_used.to_string()), + "logs" => Some(receipt.receipt.inner.inner.inner.receipt.logs.as_slice().pretty()), + "logsBloom" | "logs_bloom" => Some(receipt.receipt.inner.inner.inner.logs_bloom.pretty()), + "root" | "stateRoot" | "state_root " => Some(receipt.receipt.state_root.pretty()), + "status" | "statusCode" | "status_code" => { + Some(pretty_status(receipt.receipt.inner.inner.inner.receipt.status.coerce_status())) } - "gasUsed" | "gas_used" => Some(receipt.receipt.gas_used.pretty()), - "logs" => Some(receipt.receipt.logs.pretty()), - "logsBloom" | "logs_bloom" => Some(receipt.receipt.logs_bloom.pretty()), - "root" => Some(receipt.receipt.root.pretty()), - "status" => Some(receipt.receipt.status.pretty()), "transactionHash" | "transaction_hash" => Some(receipt.receipt.transaction_hash.pretty()), "transactionIndex" | "transaction_index" => { Some(receipt.receipt.transaction_index.pretty()) } - "type" | "transaction_type" => Some(receipt.receipt.transaction_type.pretty()), + "type" | "transaction_type" => Some(receipt.receipt.inner.inner.r#type.to_string()), "revertReason" | "revert_reason" => Some(receipt.revert_reason.pretty()), _ => None, } } /// Returns the `UiFmt::pretty()` formatted attribute of the given block -pub fn get_pretty_block_attr(block: &Block, attr: &str) -> Option { +pub fn get_pretty_block_attr(block: &Block, attr: &str) -> Option { match attr { - "baseFeePerGas" | "base_fee_per_gas" => Some(block.base_fee_per_gas.pretty()), - "difficulty" => Some(block.difficulty.pretty()), - "extraData" | "extra_data" => Some(block.extra_data.pretty()), - "gasLimit" | "gas_limit" => Some(block.gas_limit.pretty()), - "gasUsed" | "gas_used" => Some(block.gas_used.pretty()), - "hash" => Some(block.hash.pretty()), - "logsBloom" | "logs_bloom" => Some(block.logs_bloom.pretty()), - "miner" | "author" => Some(block.author.pretty()), - "mixHash" | "mix_hash" => Some(block.mix_hash.pretty()), - "nonce" => Some(block.nonce.pretty()), - "number" => Some(block.number.pretty()), - "parentHash" | "parent_hash" => Some(block.parent_hash.pretty()), - "transactionsRoot" | "transactions_root" => Some(block.transactions_root.pretty()), - "receiptsRoot" | "receipts_root" => Some(block.receipts_root.pretty()), - "sealFields" | "seal_fields" => Some(block.seal_fields.pretty()), - "sha3Uncles" | "sha_3_uncles" => Some(block.uncles_hash.pretty()), + "baseFeePerGas" | "base_fee_per_gas" => Some(block.header.base_fee_per_gas.pretty()), + "difficulty" => Some(block.header.difficulty.pretty()), + "extraData" | "extra_data" => Some(block.header.extra_data.pretty()), + "gasLimit" | "gas_limit" => Some(block.header.gas_limit.pretty()), + "gasUsed" | "gas_used" => Some(block.header.gas_used.pretty()), + "hash" => Some(block.header.hash.pretty()), + "logsBloom" | "logs_bloom" => Some(block.header.logs_bloom.pretty()), + "miner" | "author" => Some(block.header.miner.pretty()), + "mixHash" | "mix_hash" => Some(block.header.mix_hash.pretty()), + "nonce" => Some(block.header.nonce.pretty()), + "number" => Some(block.header.number.pretty()), + "parentHash" | "parent_hash" => Some(block.header.parent_hash.pretty()), + "transactionsRoot" | "transactions_root" => Some(block.header.transactions_root.pretty()), + "receiptsRoot" | "receipts_root" => Some(block.header.receipts_root.pretty()), + "sha3Uncles" | "sha_3_uncles" => Some(block.header.uncles_hash.pretty()), "size" => Some(block.size.pretty()), - "stateRoot" | "state_root" => Some(block.state_root.pretty()), - "timestamp" => Some(block.timestamp.pretty()), - "totalDifficulty" | "total_difficult" => Some(block.total_difficulty.pretty()), + "stateRoot" | "state_root" => Some(block.header.state_root.pretty()), + "timestamp" => Some(block.header.timestamp.pretty()), + "totalDifficulty" | "total_difficult" => Some(block.header.total_difficulty.pretty()), other => { if let Some(value) = block.other.get(other) { let val = EthValue::from(value.clone()); @@ -463,7 +468,7 @@ pub fn get_pretty_block_attr(block: &Block, attr: &str) -> Option(block: &Block) -> String { +fn pretty_block_basics(block: &Block) -> String { format!( " baseFeePerGas {} @@ -480,34 +485,35 @@ number {} parentHash {} transactionsRoot {} receiptsRoot {} -sealFields {} sha3Uncles {} size {} stateRoot {} -timestamp {} +timestamp {} ({}) withdrawalsRoot {} totalDifficulty {}{}", - block.base_fee_per_gas.pretty(), - block.difficulty.pretty(), - block.extra_data.pretty(), - block.gas_limit.pretty(), - block.gas_used.pretty(), - block.hash.pretty(), - block.logs_bloom.pretty(), - block.author.pretty(), - block.mix_hash.pretty(), - block.nonce.pretty(), - block.number.pretty(), - block.parent_hash.pretty(), - block.transactions_root.pretty(), - block.receipts_root.pretty(), - block.seal_fields.pretty(), - block.uncles_hash.pretty(), + block.header.base_fee_per_gas.pretty(), + block.header.difficulty.pretty(), + block.header.extra_data.pretty(), + block.header.gas_limit.pretty(), + block.header.gas_used.pretty(), + block.header.hash.pretty(), + block.header.logs_bloom.pretty(), + block.header.miner.pretty(), + block.header.mix_hash.pretty(), + block.header.nonce.pretty(), + block.header.number.pretty(), + block.header.parent_hash.pretty(), + block.header.transactions_root.pretty(), + block.header.receipts_root.pretty(), + block.header.uncles_hash.pretty(), block.size.pretty(), - block.state_root.pretty(), - block.timestamp.pretty(), - block.withdrawals_root.pretty(), - block.total_difficulty.pretty(), + block.header.state_root.pretty(), + block.header.timestamp.pretty(), + chrono::DateTime::from_timestamp(block.header.timestamp as i64, 0) + .expect("block timestamp in range") + .to_rfc2822(), + block.header.withdrawals_root.pretty(), + block.header.total_difficulty.pretty(), block.other.pretty() ) } @@ -515,7 +521,7 @@ totalDifficulty {}{}", #[cfg(test)] mod tests { use super::*; - use pretty_assertions::assert_eq; + use similar_asserts::assert_eq; use std::str::FromStr; #[test] @@ -594,7 +600,7 @@ txType 0 #[test] fn print_block_w_txs() { let block = r#"{"number":"0x3","hash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","parentHash":"0x689c70c080ca22bc0e681694fa803c1aba16a69c8b6368fed5311d279eb9de90","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x7270c1c4440180f2bd5215809ee3d545df042b67329499e1ab97eb759d31610d","stateRoot":"0x29f32984517a7d25607da485b23cefabfd443751422ca7e603395e1de9bc8a4b","receiptsRoot":"0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","size":"0x3e8","gasLimit":"0x6691b7","gasUsed":"0x5208","timestamp":"0x5ecedbb9","transactions":[{"hash":"0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067","nonce":"0x2","blockHash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","blockNumber":"0x3","transactionIndex":"0x0","from":"0xfdcedc3bfca10ecb0890337fbdd1977aba84807a","to":"0xdca8ce283150ab773bcbeb8d38289bdb5661de1e","value":"0x0","gas":"0x15f90","gasPrice":"0x4a817c800","input":"0x","v":"0x25","r":"0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88","s":"0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e"}],"uncles":[]}"#; - let block: Block = serde_json::from_str(block).unwrap(); + let block: Block = serde_json::from_str(block).unwrap(); let output ="\nblockHash 0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972 blockNumber 3 from 0xFdCeDC3bFca10eCb0890337fbdD1977aba84807a @@ -609,7 +615,11 @@ to 0xdca8ce283150AB773BCbeB8d38289bdB5661dE1e transactionIndex 0 v 37 value 0".to_string(); - let generated = block.transactions[0].pretty(); + let txs = match block.transactions { + BlockTransactions::Full(txs) => txs, + _ => panic!("not full transactions"), + }; + let generated = txs[0].pretty(); assert_eq!(generated.as_str(), output.as_str()); } @@ -657,45 +667,40 @@ value 0".to_string(); #[test] fn test_pretty_tx_attr() { let block = r#"{"number":"0x3","hash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","parentHash":"0x689c70c080ca22bc0e681694fa803c1aba16a69c8b6368fed5311d279eb9de90","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x7270c1c4440180f2bd5215809ee3d545df042b67329499e1ab97eb759d31610d","stateRoot":"0x29f32984517a7d25607da485b23cefabfd443751422ca7e603395e1de9bc8a4b","receiptsRoot":"0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2","miner":"0x0000000000000000000000000000000000000000","difficulty":"0x0","totalDifficulty":"0x0","extraData":"0x","size":"0x3e8","gasLimit":"0x6691b7","gasUsed":"0x5208","timestamp":"0x5ecedbb9","transactions":[{"hash":"0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067","nonce":"0x2","blockHash":"0xda53da08ef6a3cbde84c33e51c04f68c3853b6a3731f10baa2324968eee63972","blockNumber":"0x3","transactionIndex":"0x0","from":"0xfdcedc3bfca10ecb0890337fbdd1977aba84807a","to":"0xdca8ce283150ab773bcbeb8d38289bdb5661de1e","value":"0x0","gas":"0x15f90","gasPrice":"0x4a817c800","input":"0x","v":"0x25","r":"0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88","s":"0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e"}],"uncles":[]}"#; - let block: Block = serde_json::from_str(block).unwrap(); - assert_eq!(None, get_pretty_tx_attr(&block.transactions[0], "")); - assert_eq!( - Some("3".to_string()), - get_pretty_tx_attr(&block.transactions[0], "blockNumber") - ); + let block: Block = serde_json::from_str(block).unwrap(); + let txs = match block.transactions { + BlockTransactions::Full(txes) => txes, + _ => panic!("not full transactions"), + }; + assert_eq!(None, get_pretty_tx_attr(&txs[0], "")); + assert_eq!(Some("3".to_string()), get_pretty_tx_attr(&txs[0], "blockNumber")); assert_eq!( Some("0xFdCeDC3bFca10eCb0890337fbdD1977aba84807a".to_string()), - get_pretty_tx_attr(&block.transactions[0], "from") - ); - assert_eq!(Some("90000".to_string()), get_pretty_tx_attr(&block.transactions[0], "gas")); - assert_eq!( - Some("20000000000".to_string()), - get_pretty_tx_attr(&block.transactions[0], "gasPrice") + get_pretty_tx_attr(&txs[0], "from") ); + assert_eq!(Some("90000".to_string()), get_pretty_tx_attr(&txs[0], "gas")); + assert_eq!(Some("20000000000".to_string()), get_pretty_tx_attr(&txs[0], "gasPrice")); assert_eq!( Some("0xc3c5f700243de37ae986082fd2af88d2a7c2752a0c0f7b9d6ac47c729d45e067".to_string()), - get_pretty_tx_attr(&block.transactions[0], "hash") + get_pretty_tx_attr(&txs[0], "hash") ); - assert_eq!(Some("0x".to_string()), get_pretty_tx_attr(&block.transactions[0], "input")); - assert_eq!(Some("2".to_string()), get_pretty_tx_attr(&block.transactions[0], "nonce")); + assert_eq!(Some("0x".to_string()), get_pretty_tx_attr(&txs[0], "input")); + assert_eq!(Some("2".to_string()), get_pretty_tx_attr(&txs[0], "nonce")); assert_eq!( Some("0x19f2694eb9113656dbea0b925e2e7ceb43df83e601c4116aee9c0dd99130be88".to_string()), - get_pretty_tx_attr(&block.transactions[0], "r") + get_pretty_tx_attr(&txs[0], "r") ); assert_eq!( Some("0x73e5764b324a4f7679d890a198ba658ba1c8cd36983ff9797e10b1b89dbb448e".to_string()), - get_pretty_tx_attr(&block.transactions[0], "s") + get_pretty_tx_attr(&txs[0], "s") ); assert_eq!( Some("0xdca8ce283150AB773BCbeB8d38289bdB5661dE1e".into()), - get_pretty_tx_attr(&block.transactions[0], "to") - ); - assert_eq!( - Some("0".to_string()), - get_pretty_tx_attr(&block.transactions[0], "transactionIndex") + get_pretty_tx_attr(&txs[0], "to") ); - assert_eq!(Some("37".to_string()), get_pretty_tx_attr(&block.transactions[0], "v")); - assert_eq!(Some("0".to_string()), get_pretty_tx_attr(&block.transactions[0], "value")); + assert_eq!(Some("0".to_string()), get_pretty_tx_attr(&txs[0], "transactionIndex")); + assert_eq!(Some("37".to_string()), get_pretty_tx_attr(&txs[0], "v")); + assert_eq!(Some("0".to_string()), get_pretty_tx_attr(&txs[0], "value")); } #[test] @@ -731,7 +736,7 @@ value 0".to_string(); } ); - let block: Block<()> = serde_json::from_value(json).unwrap(); + let block: Block = serde_json::from_value(json).unwrap(); assert_eq!(None, get_pretty_block_attr(&block, "")); assert_eq!(Some("7".to_string()), get_pretty_block_attr(&block, "baseFeePerGas")); diff --git a/crates/common/src/fs.rs b/crates/common/src/fs.rs index 83b98670a..8ee47d2fd 100644 --- a/crates/common/src/fs.rs +++ b/crates/common/src/fs.rs @@ -133,19 +133,18 @@ pub fn normalize_path(path: &Path) -> PathBuf { ret } -/// Returns all files with the given extension under the `root` dir -pub fn files_with_ext(root: impl AsRef, ext: &str) -> Vec { +/// Returns an iterator over all files with the given extension under the `root` dir. +pub fn files_with_ext<'a>(root: &Path, ext: &'a str) -> impl Iterator + 'a { walkdir::WalkDir::new(root) + .sort_by_file_name() .into_iter() .filter_map(walkdir::Result::ok) - .filter(|e| e.file_type().is_file()) - .filter(|e| e.path().extension().map(|e| e == ext).unwrap_or_default()) - .map(|e| e.path().into()) - .collect() + .filter(|e| e.file_type().is_file() && e.path().extension() == Some(ext.as_ref())) + .map(walkdir::DirEntry::into_path) } -/// Returns a list of absolute paths to all the json files under the root -pub fn json_files(root: impl AsRef) -> Vec { +/// Returns an iterator over all JSON files under the `root` dir. +pub fn json_files(root: &Path) -> impl Iterator { files_with_ext(root, "json") } diff --git a/crates/common/src/glob.rs b/crates/common/src/glob.rs deleted file mode 100644 index 070f70367..000000000 --- a/crates/common/src/glob.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! Contains `globset::Glob` wrapper functions used for filtering. - -use std::{ - fmt, - path::{Path, PathBuf}, - str::FromStr, -}; - -/// Expand globs with a root path. -pub fn expand_globs( - root: &Path, - patterns: impl IntoIterator>, -) -> eyre::Result> { - let mut expanded = Vec::new(); - for pattern in patterns { - for paths in glob::glob(&root.join(pattern.as_ref()).display().to_string())? { - expanded.push(paths?); - } - } - Ok(expanded) -} - -/// A `globset::Glob` that creates its `globset::GlobMatcher` when its created, so it doesn't need -/// to be compiled when the filter functions `TestFilter` functions are called. -#[derive(Clone, Debug)] -pub struct GlobMatcher { - /// The compiled glob - pub matcher: globset::GlobMatcher, -} - -impl GlobMatcher { - /// Creates a new `GlobMatcher` from a `globset::Glob`. - pub fn new(glob: globset::Glob) -> Self { - Self { matcher: glob.compile_matcher() } - } - - /// Tests whether the given path matches this pattern or not. - /// - /// The glob `./test/*` won't match absolute paths like `test/Contract.sol`, which is common - /// format here, so we also handle this case here - pub fn is_match(&self, path: &Path) -> bool { - if self.matcher.is_match(path) { - return true; - } - - if !path.starts_with("./") && self.as_str().starts_with("./") { - return self.matcher.is_match(format!("./{}", path.display())); - } - - false - } - - /// Returns the `globset::Glob`. - pub fn glob(&self) -> &globset::Glob { - self.matcher.glob() - } - - /// Returns the `Glob` string used to compile this matcher. - pub fn as_str(&self) -> &str { - self.glob().glob() - } -} - -impl fmt::Display for GlobMatcher { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.glob().fmt(f) - } -} - -impl FromStr for GlobMatcher { - type Err = globset::Error; - - fn from_str(s: &str) -> Result { - s.parse::().map(Self::new) - } -} - -impl From for GlobMatcher { - fn from(glob: globset::Glob) -> Self { - Self::new(glob) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn can_match_glob_paths() { - let matcher: GlobMatcher = "./test/*".parse().unwrap(); - assert!(matcher.is_match(Path::new("test/Contract.sol"))); - assert!(matcher.is_match(Path::new("./test/Contract.sol"))); - } -} diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 20c68e911..979282e97 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -1,7 +1,11 @@ +//! # foundry-common +//! //! Common utilities for building and using foundry's tools. -#![warn(missing_docs, unused_crate_dependencies)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#[allow(unused_extern_crates)] // Used by `ConsoleFmt`. extern crate self as foundry_common; #[macro_use] @@ -13,25 +17,24 @@ pub mod compile; pub mod console; pub mod constants; pub mod contracts; +pub mod ens; pub mod errors; pub mod evm; pub mod fmt; pub mod fs; -pub mod glob; pub mod provider; pub mod retry; -pub mod rpc; -pub mod runtime_client; pub mod selectors; pub mod serde_helpers; pub mod shell; pub mod term; pub mod traits; pub mod transactions; -pub mod types; +mod utils; pub use console::*; pub use constants::*; pub use contracts::*; pub use traits::*; pub use transactions::*; +pub use utils::*; diff --git a/crates/common/src/provider/alloy.rs b/crates/common/src/provider/alloy.rs deleted file mode 100644 index bb5277814..000000000 --- a/crates/common/src/provider/alloy.rs +++ /dev/null @@ -1,318 +0,0 @@ -//! Commonly used helpers to construct `Provider`s - -use crate::{ - provider::runtime_transport::RuntimeTransportBuilder, ALCHEMY_FREE_TIER_CUPS, REQUEST_TIMEOUT, -}; -use alloy_primitives::U256; -use alloy_providers::provider::{Provider, TempProvider}; -use alloy_rpc_client::ClientBuilder; -use alloy_transport::BoxTransport; -use ethers_middleware::gas_oracle::{GasCategory, GasOracle, Polygon}; -use eyre::{Result, WrapErr}; -use foundry_common::types::ToAlloy; -use foundry_config::NamedChain; -use reqwest::Url; -use std::{ - path::{Path, PathBuf}, - time::Duration, -}; -use url::ParseError; - -use super::tower::RetryBackoffLayer; - -/// Helper type alias for a retry provider -pub type RetryProvider = Provider; - -/// Helper type alias for a rpc url -pub type RpcUrl = String; - -/// Constructs a provider with a 100 millisecond interval poll if it's a localhost URL (most likely -/// an anvil or other dev node) and with the default, or 7 second otherwise. -/// -/// See [`try_get_http_provider`] for more details. -/// -/// # Panics -/// -/// Panics if the URL is invalid. -/// -/// # Examples -/// -/// ``` -/// use foundry_common::provider::alloy::get_http_provider; -/// -/// let retry_provider = get_http_provider("http://localhost:8545"); -/// ``` -#[inline] -#[track_caller] -pub fn get_http_provider(builder: impl AsRef) -> RetryProvider { - try_get_http_provider(builder).unwrap() -} - -/// Constructs a provider with a 100 millisecond interval poll if it's a localhost URL (most likely -/// an anvil or other dev node) and with the default, or 7 second otherwise. -#[inline] -pub fn try_get_http_provider(builder: impl AsRef) -> Result { - ProviderBuilder::new(builder.as_ref()).build() -} - -/// Helper type to construct a `RetryProvider` -#[derive(Debug)] -pub struct ProviderBuilder { - // Note: this is a result, so we can easily chain builder calls - url: Result, - chain: NamedChain, - max_retry: u32, - timeout_retry: u32, - initial_backoff: u64, - timeout: Duration, - /// available CUPS - compute_units_per_second: u64, - /// JWT Secret - jwt: Option, - headers: Vec, -} - -// === impl ProviderBuilder === - -impl ProviderBuilder { - /// Creates a new builder instance - pub fn new(url_str: &str) -> Self { - // a copy is needed for the next lines to work - let mut url_str = url_str; - - // invalid url: non-prefixed URL scheme is not allowed, so we prepend the default http - // prefix - let storage; - if url_str.starts_with("localhost:") { - storage = format!("http://{url_str}"); - url_str = storage.as_str(); - } - - let url = Url::parse(url_str) - .or_else(|err| match err { - ParseError::RelativeUrlWithoutBase => { - let path = Path::new(url_str); - - if let Ok(path) = resolve_path(path) { - Url::parse(&format!("file://{}", path.display())) - } else { - Err(err) - } - } - _ => Err(err), - }) - .wrap_err_with(|| format!("invalid provider URL: {url_str:?}")); - - Self { - url, - chain: NamedChain::Mainnet, - max_retry: 8, - timeout_retry: 8, - initial_backoff: 800, - timeout: REQUEST_TIMEOUT, - // alchemy max cpus - compute_units_per_second: ALCHEMY_FREE_TIER_CUPS, - jwt: None, - headers: vec![], - } - } - - /// Enables a request timeout. - /// - /// The timeout is applied from when the request starts connecting until the - /// response body has finished. - /// - /// Default is no timeout. - pub fn timeout(mut self, timeout: Duration) -> Self { - self.timeout = timeout; - self - } - - /// Sets the chain of the node the provider will connect to - pub fn chain(mut self, chain: NamedChain) -> Self { - self.chain = chain; - self - } - - /// How often to retry a failed request - pub fn max_retry(mut self, max_retry: u32) -> Self { - self.max_retry = max_retry; - self - } - - /// How often to retry a failed request. If `None`, defaults to the already-set value. - pub fn maybe_max_retry(mut self, max_retry: Option) -> Self { - self.max_retry = max_retry.unwrap_or(self.max_retry); - self - } - - /// The starting backoff delay to use after the first failed request. If `None`, defaults to - /// the already-set value. - pub fn maybe_initial_backoff(mut self, initial_backoff: Option) -> Self { - self.initial_backoff = initial_backoff.unwrap_or(self.initial_backoff); - self - } - - /// How often to retry a failed request due to connection issues - pub fn timeout_retry(mut self, timeout_retry: u32) -> Self { - self.timeout_retry = timeout_retry; - self - } - - /// The starting backoff delay to use after the first failed request - pub fn initial_backoff(mut self, initial_backoff: u64) -> Self { - self.initial_backoff = initial_backoff; - self - } - - /// Sets the number of assumed available compute units per second - /// - /// See also, - pub fn compute_units_per_second(mut self, compute_units_per_second: u64) -> Self { - self.compute_units_per_second = compute_units_per_second; - self - } - - /// Sets the number of assumed available compute units per second - /// - /// See also, - pub fn compute_units_per_second_opt(mut self, compute_units_per_second: Option) -> Self { - if let Some(cups) = compute_units_per_second { - self.compute_units_per_second = cups; - } - self - } - - /// Sets aggressive `max_retry` and `initial_backoff` values - /// - /// This is only recommend for local dev nodes - pub fn aggressive(self) -> Self { - self.max_retry(100).initial_backoff(100) - } - - /// Sets the JWT secret - pub fn jwt(mut self, jwt: impl Into) -> Self { - self.jwt = Some(jwt.into()); - self - } - - /// Sets http headers - pub fn headers(mut self, headers: Vec) -> Self { - self.headers = headers; - - self - } - - /// Same as [`Self:build()`] but also retrieves the `chainId` in order to derive an appropriate - /// interval. - pub async fn connect(self) -> Result { - let provider = self.build()?; - // todo: port poll interval hint - /*if let Some(blocktime) = provider.get_chainid().await.ok().and_then(|id| { - }) { - provider = provider.interval(blocktime / 2); - }*/ - Ok(provider) - } - - /// Constructs the `RetryProvider` taking all configs into account. - pub fn build(self) -> Result { - let ProviderBuilder { - url, - chain: _, - max_retry, - timeout_retry, - initial_backoff, - timeout, - compute_units_per_second, - jwt, - headers, - } = self; - let url = url?; - - let retry_layer = RetryBackoffLayer::new( - max_retry, - timeout_retry, - initial_backoff, - compute_units_per_second, - ); - let transport = RuntimeTransportBuilder::new(url.clone()) - .with_timeout(timeout) - .with_headers(headers) - .with_jwt(jwt) - .build(); - let client = ClientBuilder::default().layer(retry_layer).transport(transport, false); - - // todo: provider polling interval - Ok(Provider::new_with_client(client.boxed())) - } -} - -/// Estimates EIP1559 fees depending on the chain -/// -/// Uses custom gas oracles for -/// - polygon -/// -/// Fallback is the default [`Provider::estimate_eip1559_fees`] implementation -pub async fn estimate_eip1559_fees( - provider: &P, - chain: Option, -) -> Result<(U256, U256)> { - let chain = if let Some(chain) = chain { - chain - } else { - provider.get_chain_id().await.wrap_err("Failed to get chain id")?.to() - }; - - if let Ok(chain) = NamedChain::try_from(chain) { - // handle chains that deviate from `eth_feeHistory` and have their own oracle - match chain { - NamedChain::Polygon | NamedChain::PolygonMumbai => { - // TODO: phase this out somehow - let chain = match chain { - NamedChain::Polygon => ethers_core::types::Chain::Polygon, - NamedChain::PolygonMumbai => ethers_core::types::Chain::PolygonMumbai, - _ => unreachable!(), - }; - let estimator = Polygon::new(chain)?.category(GasCategory::Standard); - let (a, b) = estimator.estimate_eip1559_fees().await?; - return Ok((a.to_alloy(), b.to_alloy())); - } - _ => {} - } - } - provider.estimate_eip1559_fees(None).await.wrap_err("Failed fetch EIP1559 fees") -} - -#[cfg(not(windows))] -fn resolve_path(path: &Path) -> Result { - if path.is_absolute() { - Ok(path.to_path_buf()) - } else { - std::env::current_dir().map(|d| d.join(path)).map_err(drop) - } -} - -#[cfg(windows)] -fn resolve_path(path: &Path) -> Result { - if let Some(s) = path.to_str() { - if s.starts_with(r"\\.\pipe\") { - return Ok(path.to_path_buf()); - } - } - Err(()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn can_auto_correct_missing_prefix() { - let builder = ProviderBuilder::new("localhost:8545"); - assert!(builder.url.is_ok()); - - let url = builder.url.unwrap(); - assert_eq!(url, Url::parse("http://localhost:8545").unwrap()); - } -} diff --git a/crates/common/src/provider/ethers.rs b/crates/common/src/provider/ethers.rs deleted file mode 100644 index 0d4acd2ce..000000000 --- a/crates/common/src/provider/ethers.rs +++ /dev/null @@ -1,323 +0,0 @@ -//! Commonly used helpers to construct `Provider`s - -use crate::{ - runtime_client::{RuntimeClient, RuntimeClientBuilder}, - ALCHEMY_FREE_TIER_CUPS, REQUEST_TIMEOUT, -}; -use ethers_core::types::U256; -use ethers_middleware::gas_oracle::{GasCategory, GasOracle, Polygon}; -use ethers_providers::{is_local_endpoint, Middleware, Provider, DEFAULT_LOCAL_POLL_INTERVAL}; -use eyre::{Result, WrapErr}; -use foundry_config::NamedChain; -use reqwest::Url; -use std::{ - path::{Path, PathBuf}, - time::Duration, -}; -use url::ParseError; - -/// Helper type alias for a retry provider -pub type RetryProvider = Provider; - -/// Helper type alias for a rpc url -pub type RpcUrl = String; - -/// Constructs a provider with a 100 millisecond interval poll if it's a localhost URL (most likely -/// an anvil or other dev node) and with the default, or 7 second otherwise. -/// -/// See [`try_get_http_provider`] for more details. -/// -/// # Panics -/// -/// Panics if the URL is invalid. -/// -/// # Examples -/// -/// ``` -/// use foundry_common::provider::ethers::get_http_provider; -/// -/// let retry_provider = get_http_provider("http://localhost:8545"); -/// ``` -#[inline] -#[track_caller] -pub fn get_http_provider(builder: impl AsRef) -> RetryProvider { - try_get_http_provider(builder).unwrap() -} - -/// Constructs a provider with a 100 millisecond interval poll if it's a localhost URL (most likely -/// an anvil or other dev node) and with the default, or 7 second otherwise. -#[inline] -pub fn try_get_http_provider(builder: impl AsRef) -> Result { - ProviderBuilder::new(builder.as_ref()).build() -} - -/// Helper type to construct a `RetryProvider` -#[derive(Debug)] -pub struct ProviderBuilder { - // Note: this is a result, so we can easily chain builder calls - url: Result, - chain: NamedChain, - max_retry: u32, - timeout_retry: u32, - initial_backoff: u64, - timeout: Duration, - /// available CUPS - compute_units_per_second: u64, - /// JWT Secret - jwt: Option, - headers: Vec, -} - -// === impl ProviderBuilder === - -impl ProviderBuilder { - /// Creates a new builder instance - pub fn new(url_str: &str) -> Self { - // a copy is needed for the next lines to work - let mut url_str = url_str; - - // invalid url: non-prefixed URL scheme is not allowed, so we prepend the default http - // prefix - let storage; - if url_str.starts_with("localhost:") || url_str.starts_with("127.0.0.1:") { - storage = format!("http://{url_str}"); - url_str = storage.as_str(); - } - - let url = Url::parse(url_str) - .or_else(|err| match err { - ParseError::RelativeUrlWithoutBase => { - let path = Path::new(url_str); - - if let Ok(path) = resolve_path(path) { - Url::parse(&format!("file://{}", path.display())) - } else { - Err(err) - } - } - _ => Err(err), - }) - .wrap_err_with(|| format!("invalid provider URL: {url_str:?}")); - - Self { - url, - chain: NamedChain::Mainnet, - max_retry: 8, - timeout_retry: 8, - initial_backoff: 800, - timeout: REQUEST_TIMEOUT, - // alchemy max cpus - compute_units_per_second: ALCHEMY_FREE_TIER_CUPS, - jwt: None, - headers: vec![], - } - } - - /// Enables a request timeout. - /// - /// The timeout is applied from when the request starts connecting until the - /// response body has finished. - /// - /// Default is no timeout. - pub fn timeout(mut self, timeout: Duration) -> Self { - self.timeout = timeout; - self - } - - /// Sets the chain of the node the provider will connect to - pub fn chain(mut self, chain: NamedChain) -> Self { - self.chain = chain; - self - } - - /// How often to retry a failed request - pub fn max_retry(mut self, max_retry: u32) -> Self { - self.max_retry = max_retry; - self - } - - /// How often to retry a failed request. If `None`, defaults to the already-set value. - pub fn maybe_max_retry(mut self, max_retry: Option) -> Self { - self.max_retry = max_retry.unwrap_or(self.max_retry); - self - } - - /// The starting backoff delay to use after the first failed request. If `None`, defaults to - /// the already-set value. - pub fn maybe_initial_backoff(mut self, initial_backoff: Option) -> Self { - self.initial_backoff = initial_backoff.unwrap_or(self.initial_backoff); - self - } - - /// How often to retry a failed request due to connection issues - pub fn timeout_retry(mut self, timeout_retry: u32) -> Self { - self.timeout_retry = timeout_retry; - self - } - - /// The starting backoff delay to use after the first failed request - pub fn initial_backoff(mut self, initial_backoff: u64) -> Self { - self.initial_backoff = initial_backoff; - self - } - - /// Sets the number of assumed available compute units per second - /// - /// See also, - pub fn compute_units_per_second(mut self, compute_units_per_second: u64) -> Self { - self.compute_units_per_second = compute_units_per_second; - self - } - - /// Sets the number of assumed available compute units per second - /// - /// See also, - pub fn compute_units_per_second_opt(mut self, compute_units_per_second: Option) -> Self { - if let Some(cups) = compute_units_per_second { - self.compute_units_per_second = cups; - } - self - } - - /// Sets aggressive `max_retry` and `initial_backoff` values - /// - /// This is only recommend for local dev nodes - pub fn aggressive(self) -> Self { - self.max_retry(100).initial_backoff(100) - } - - /// Sets the JWT secret - pub fn jwt(mut self, jwt: impl Into) -> Self { - self.jwt = Some(jwt.into()); - self - } - - /// Sets http headers - pub fn headers(mut self, headers: Vec) -> Self { - self.headers = headers; - - self - } - - /// Same as [`Self:build()`] but also retrieves the `chainId` in order to derive an appropriate - /// interval. - pub async fn connect(self) -> Result { - let mut provider = self.build()?; - if let Some(blocktime) = provider.get_chainid().await.ok().and_then(|id| { - NamedChain::try_from(id.as_u64()).ok().and_then(|chain| chain.average_blocktime_hint()) - }) { - provider = provider.interval(blocktime / 2); - } - Ok(provider) - } - - /// Constructs the `RetryProvider` taking all configs into account. - pub fn build(self) -> Result { - let ProviderBuilder { - url, - chain, - max_retry, - timeout_retry, - initial_backoff, - timeout, - compute_units_per_second, - jwt, - headers, - } = self; - let url = url?; - - let client_builder = RuntimeClientBuilder::new( - url.clone(), - max_retry, - timeout_retry, - initial_backoff, - timeout, - compute_units_per_second, - ) - .with_headers(headers) - .with_jwt(jwt); - - let mut provider = Provider::new(client_builder.build()); - - let is_local = is_local_endpoint(url.as_str()); - - if is_local { - provider = provider.interval(DEFAULT_LOCAL_POLL_INTERVAL); - } else if let Some(blocktime) = chain.average_blocktime_hint() { - provider = provider.interval(blocktime / 2); - } - - Ok(provider) - } -} - -/// Estimates EIP1559 fees depending on the chain -/// -/// Uses custom gas oracles for -/// - polygon -/// -/// Fallback is the default [`Provider::estimate_eip1559_fees`] implementation -pub async fn estimate_eip1559_fees( - provider: &M, - chain: Option, -) -> Result<(U256, U256)> -where - M::Error: 'static, -{ - let chain = if let Some(chain) = chain { - chain - } else { - provider.get_chainid().await.wrap_err("Failed to get chain id")?.as_u64() - }; - - if let Ok(chain) = NamedChain::try_from(chain) { - // handle chains that deviate from `eth_feeHistory` and have their own oracle - match chain { - NamedChain::Polygon | NamedChain::PolygonMumbai => { - // TODO: phase this out somehow - let chain = match chain { - NamedChain::Polygon => ethers_core::types::Chain::Polygon, - NamedChain::PolygonMumbai => ethers_core::types::Chain::PolygonMumbai, - _ => unreachable!(), - }; - let estimator = Polygon::new(chain)?.category(GasCategory::Standard); - return Ok(estimator.estimate_eip1559_fees().await?); - } - _ => {} - } - } - provider.estimate_eip1559_fees(None).await.wrap_err("Failed fetch EIP1559 fees") -} - -#[cfg(not(windows))] -fn resolve_path(path: &Path) -> Result { - if path.is_absolute() { - Ok(path.to_path_buf()) - } else { - std::env::current_dir().map(|d| d.join(path)).map_err(drop) - } -} - -#[cfg(windows)] -fn resolve_path(path: &Path) -> Result { - if let Some(s) = path.to_str() { - if s.starts_with(r"\\.\pipe\") { - return Ok(path.to_path_buf()); - } - } - Err(()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn can_auto_correct_missing_prefix() { - let builder = ProviderBuilder::new("localhost:8545"); - assert!(builder.url.is_ok()); - - let url = builder.url.unwrap(); - assert_eq!(url, Url::parse("http://localhost:8545").unwrap()); - } -} diff --git a/crates/common/src/provider/mod.rs b/crates/common/src/provider/mod.rs index cbd9ecbd0..ef7b62055 100644 --- a/crates/common/src/provider/mod.rs +++ b/crates/common/src/provider/mod.rs @@ -1,7 +1,342 @@ //! Provider-related instantiation and usage utilities. -pub mod alloy; -pub mod ethers; pub mod retry; pub mod runtime_transport; pub mod tower; + +use crate::{ + provider::runtime_transport::RuntimeTransportBuilder, ALCHEMY_FREE_TIER_CUPS, REQUEST_TIMEOUT, +}; +use alloy_provider::{ + fillers::{ChainIdFiller, FillProvider, GasFiller, JoinFill, NonceFiller, WalletFiller}, + network::{AnyNetwork, EthereumWallet}, + Identity, ProviderBuilder as AlloyProviderBuilder, RootProvider, +}; +use alloy_rpc_client::ClientBuilder; +use alloy_transport::utils::guess_local_url; +use eyre::{Result, WrapErr}; +use foundry_config::NamedChain; +use reqwest::Url; +use runtime_transport::RuntimeTransport; +use std::{ + net::SocketAddr, + path::{Path, PathBuf}, + str::FromStr, + time::Duration, +}; +use tower::{RetryBackoffLayer, RetryBackoffService}; +use url::ParseError; + +/// Helper type alias for a retry provider +pub type RetryProvider = RootProvider, N>; + +/// Helper type alias for a retry provider with a signer +pub type RetryProviderWithSigner = FillProvider< + JoinFill< + JoinFill, NonceFiller>, ChainIdFiller>, + WalletFiller, + >, + RootProvider, N>, + RetryBackoffService, + N, +>; + +/// Constructs a provider with a 100 millisecond interval poll if it's a localhost URL (most likely +/// an anvil or other dev node) and with the default, or 7 second otherwise. +/// +/// See [`try_get_http_provider`] for more details. +/// +/// # Panics +/// +/// Panics if the URL is invalid. +/// +/// # Examples +/// +/// ``` +/// use foundry_common::provider::get_http_provider; +/// +/// let retry_provider = get_http_provider("http://localhost:8545"); +/// ``` +#[inline] +#[track_caller] +pub fn get_http_provider(builder: impl AsRef) -> RetryProvider { + try_get_http_provider(builder).unwrap() +} + +/// Constructs a provider with a 100 millisecond interval poll if it's a localhost URL (most likely +/// an anvil or other dev node) and with the default, or 7 second otherwise. +#[inline] +pub fn try_get_http_provider(builder: impl AsRef) -> Result { + ProviderBuilder::new(builder.as_ref()).build() +} + +/// Helper type to construct a `RetryProvider` +#[derive(Debug)] +pub struct ProviderBuilder { + // Note: this is a result, so we can easily chain builder calls + url: Result, + chain: NamedChain, + max_retry: u32, + timeout_retry: u32, + initial_backoff: u64, + timeout: Duration, + /// available CUPS + compute_units_per_second: u64, + /// JWT Secret + jwt: Option, + headers: Vec, + is_local: bool, +} + +impl ProviderBuilder { + /// Creates a new builder instance + pub fn new(url_str: &str) -> Self { + // a copy is needed for the next lines to work + let mut url_str = url_str; + + // invalid url: non-prefixed URL scheme is not allowed, so we prepend the default http + // prefix + let storage; + if url_str.starts_with("localhost:") { + storage = format!("http://{url_str}"); + url_str = storage.as_str(); + } + + let url = Url::parse(url_str) + .or_else(|err| match err { + ParseError::RelativeUrlWithoutBase => { + if SocketAddr::from_str(url_str).is_ok() { + Url::parse(&format!("http://{url_str}")) + } else { + let path = Path::new(url_str); + + if let Ok(path) = resolve_path(path) { + Url::parse(&format!("file://{}", path.display())) + } else { + Err(err) + } + } + } + _ => Err(err), + }) + .wrap_err_with(|| format!("invalid provider URL: {url_str:?}")); + + // Use the final URL string to guess if it's a local URL. + let is_local = url.as_ref().map_or(false, |url| guess_local_url(url.as_str())); + + Self { + url, + chain: NamedChain::Mainnet, + max_retry: 8, + timeout_retry: 8, + initial_backoff: 800, + timeout: REQUEST_TIMEOUT, + // alchemy max cpus + compute_units_per_second: ALCHEMY_FREE_TIER_CUPS, + jwt: None, + headers: vec![], + is_local, + } + } + + /// Enables a request timeout. + /// + /// The timeout is applied from when the request starts connecting until the + /// response body has finished. + /// + /// Default is no timeout. + pub fn timeout(mut self, timeout: Duration) -> Self { + self.timeout = timeout; + self + } + + /// Sets the chain of the node the provider will connect to + pub fn chain(mut self, chain: NamedChain) -> Self { + self.chain = chain; + self + } + + /// How often to retry a failed request + pub fn max_retry(mut self, max_retry: u32) -> Self { + self.max_retry = max_retry; + self + } + + /// How often to retry a failed request. If `None`, defaults to the already-set value. + pub fn maybe_max_retry(mut self, max_retry: Option) -> Self { + self.max_retry = max_retry.unwrap_or(self.max_retry); + self + } + + /// The starting backoff delay to use after the first failed request. If `None`, defaults to + /// the already-set value. + pub fn maybe_initial_backoff(mut self, initial_backoff: Option) -> Self { + self.initial_backoff = initial_backoff.unwrap_or(self.initial_backoff); + self + } + + /// How often to retry a failed request due to connection issues + pub fn timeout_retry(mut self, timeout_retry: u32) -> Self { + self.timeout_retry = timeout_retry; + self + } + + /// The starting backoff delay to use after the first failed request + pub fn initial_backoff(mut self, initial_backoff: u64) -> Self { + self.initial_backoff = initial_backoff; + self + } + + /// Sets the number of assumed available compute units per second + /// + /// See also, + pub fn compute_units_per_second(mut self, compute_units_per_second: u64) -> Self { + self.compute_units_per_second = compute_units_per_second; + self + } + + /// Sets the number of assumed available compute units per second + /// + /// See also, + pub fn compute_units_per_second_opt(mut self, compute_units_per_second: Option) -> Self { + if let Some(cups) = compute_units_per_second { + self.compute_units_per_second = cups; + } + self + } + + /// Sets the provider to be local. + /// + /// This is useful for local dev nodes. + pub fn local(mut self, is_local: bool) -> Self { + self.is_local = is_local; + self + } + + /// Sets aggressive `max_retry` and `initial_backoff` values + /// + /// This is only recommend for local dev nodes + pub fn aggressive(self) -> Self { + self.max_retry(100).initial_backoff(100).local(true) + } + + /// Sets the JWT secret + pub fn jwt(mut self, jwt: impl Into) -> Self { + self.jwt = Some(jwt.into()); + self + } + + /// Sets http headers + pub fn headers(mut self, headers: Vec) -> Self { + self.headers = headers; + + self + } + + /// Constructs the `RetryProvider` taking all configs into account. + pub fn build(self) -> Result { + let Self { + url, + chain: _, + max_retry, + timeout_retry, + initial_backoff, + timeout, + compute_units_per_second, + jwt, + headers, + is_local, + } = self; + let url = url?; + + let retry_layer = RetryBackoffLayer::new( + max_retry, + timeout_retry, + initial_backoff, + compute_units_per_second, + ); + let transport = RuntimeTransportBuilder::new(url.clone()) + .with_timeout(timeout) + .with_headers(headers) + .with_jwt(jwt) + .build(); + let client = ClientBuilder::default().layer(retry_layer).transport(transport, is_local); + + let provider = AlloyProviderBuilder::<_, _, AnyNetwork>::default() + .on_provider(RootProvider::new(client)); + + Ok(provider) + } + + /// Constructs the `RetryProvider` with a wallet. + pub fn build_with_wallet(self, wallet: EthereumWallet) -> Result { + let Self { + url, + chain: _, + max_retry, + timeout_retry, + initial_backoff, + timeout, + compute_units_per_second, + jwt, + headers, + is_local, + } = self; + let url = url?; + + let retry_layer = RetryBackoffLayer::new( + max_retry, + timeout_retry, + initial_backoff, + compute_units_per_second, + ); + + let transport = RuntimeTransportBuilder::new(url.clone()) + .with_timeout(timeout) + .with_headers(headers) + .with_jwt(jwt) + .build(); + + let client = ClientBuilder::default().layer(retry_layer).transport(transport, is_local); + + let provider = AlloyProviderBuilder::<_, _, AnyNetwork>::default() + .with_recommended_fillers() + .wallet(wallet) + .on_provider(RootProvider::new(client)); + + Ok(provider) + } +} + +#[cfg(not(windows))] +fn resolve_path(path: &Path) -> Result { + if path.is_absolute() { + Ok(path.to_path_buf()) + } else { + std::env::current_dir().map(|d| d.join(path)).map_err(drop) + } +} + +#[cfg(windows)] +fn resolve_path(path: &Path) -> Result { + if let Some(s) = path.to_str() { + if s.starts_with(r"\\.\pipe\") { + return Ok(path.to_path_buf()); + } + } + Err(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn can_auto_correct_missing_prefix() { + let builder = ProviderBuilder::new("localhost:8545"); + assert!(builder.url.is_ok()); + + let url = builder.url.unwrap(); + assert_eq!(url, Url::parse("http://localhost:8545").unwrap()); + } +} diff --git a/crates/common/src/provider/retry.rs b/crates/common/src/provider/retry.rs index 70c5998c7..b7f3079bb 100644 --- a/crates/common/src/provider/retry.rs +++ b/crates/common/src/provider/retry.rs @@ -3,7 +3,7 @@ use alloy_json_rpc::ErrorPayload; use alloy_transport::{TransportError, TransportErrorKind}; use serde::Deserialize; -/// [RetryPolicy] defines logic for which [JsonRpcClient::Error] instances should +/// [RetryPolicy] defines logic for which [TransportError] instances should /// the client retry the request and try to recover from. pub trait RetryPolicy: Send + Sync + std::fmt::Debug { /// Whether to retry the request based on the given `error` @@ -30,23 +30,15 @@ impl RetryPolicy for RateLimitRetryPolicy { // The transport could not serialize the error itself. The request was malformed from // the start. TransportError::SerError(_) => false, - TransportError::DeserError { text, .. } => { - // some providers send invalid JSON RPC in the error case (no `id:u64`), but the - // text should be a `JsonRpcError` - #[derive(Deserialize)] - struct Resp { - error: ErrorPayload, - } - - if let Ok(resp) = serde_json::from_str::(text) { - return should_retry_json_rpc_error(&resp.error) - } - false - } + TransportError::DeserError { text, .. } => should_retry_body(text), TransportError::ErrorResp(err) => should_retry_json_rpc_error(err), + TransportError::NullResp => true, + TransportError::UnsupportedFeature(_) => false, + TransportError::LocalUsageError(_) => false, } } + /// Provides a backoff hint if the error response contains it fn backoff_hint(&self, error: &TransportError) -> Option { if let TransportError::ErrorResp(resp) = error { let data = resp.try_data_as::(); @@ -67,14 +59,48 @@ impl RetryPolicy for RateLimitRetryPolicy { } } +/// Tries to decode the error body as payload and check if it should be retried +fn should_retry_body(body: &str) -> bool { + if let Ok(resp) = serde_json::from_str::(body) { + return should_retry_json_rpc_error(&resp) + } + + // some providers send invalid JSON RPC in the error case (no `id:u64`), but the + // text should be a `JsonRpcError` + #[derive(Deserialize)] + struct Resp { + error: ErrorPayload, + } + + if let Ok(resp) = serde_json::from_str::(body) { + return should_retry_json_rpc_error(&resp.error) + } + + false +} + /// Analyzes the [TransportErrorKind] and decides if the request should be retried based on the /// variant. fn should_retry_transport_level_error(error: &TransportErrorKind) -> bool { match error { // Missing batch response errors can be retried. TransportErrorKind::MissingBatchResponse(_) => true, + TransportErrorKind::Custom(err) => { + // currently http error responses are not standard in alloy + let msg = err.to_string(); + msg.contains("429 Too Many Requests") + } + + TransportErrorKind::HttpError(err) => { + if err.status == 429 { + return true + } + should_retry_body(&err.body) + } // If the backend is gone, or there's a completely custom error, we should assume it's not // retryable. + TransportErrorKind::PubsubUnavailable => false, + TransportErrorKind::BackendGone => false, _ => false, } } diff --git a/crates/common/src/provider/runtime_transport.rs b/crates/common/src/provider/runtime_transport.rs index eab2addb8..72c4dadc9 100644 --- a/crates/common/src/provider/runtime_transport.rs +++ b/crates/common/src/provider/runtime_transport.rs @@ -1,17 +1,18 @@ //! Runtime transport that connects on first request, which can take either of an HTTP, //! WebSocket, or IPC transport and supports retries based on CUPS logic. + use crate::REQUEST_TIMEOUT; use alloy_json_rpc::{RequestPacket, ResponsePacket}; use alloy_pubsub::{PubSubConnect, PubSubFrontend}; +use alloy_rpc_types_engine::{Claims, JwtSecret}; use alloy_transport::{ Authorization, BoxTransport, TransportError, TransportErrorKind, TransportFut, }; use alloy_transport_http::Http; use alloy_transport_ipc::IpcConnect; use alloy_transport_ws::WsConnect; -use ethers_providers::{JwtAuth, JwtKey}; use reqwest::header::{HeaderName, HeaderValue}; -use std::{path::PathBuf, str::FromStr, sync::Arc}; +use std::{fmt, path::PathBuf, str::FromStr, sync::Arc}; use thiserror::Error; use tokio::sync::RwLock; use tower::Service; @@ -25,7 +26,6 @@ pub enum InnerTransport { Http(Http), /// WebSocket transport Ws(PubSubFrontend), - // TODO: IPC /// IPC transport Ipc(PubSubFrontend), } @@ -34,8 +34,8 @@ pub enum InnerTransport { #[derive(Error, Debug)] pub enum RuntimeTransportError { /// Internal transport error - #[error(transparent)] - TransportError(TransportError), + #[error("Internal transport error: {0} with {1}")] + TransportError(TransportError, String), /// Failed to lock the transport #[error("Failed to lock the transport")] @@ -126,8 +126,8 @@ impl RuntimeTransportBuilder { } } -impl ::core::fmt::Display for RuntimeTransport { - fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { +impl fmt::Display for RuntimeTransport { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "RuntimeTransport {}", self.url) } } @@ -145,7 +145,9 @@ impl RuntimeTransport { /// Connects to an HTTP [alloy_transport_http::Http] transport. async fn connect_http(&self) -> Result { - let mut client_builder = reqwest::Client::builder().timeout(self.timeout); + let mut client_builder = reqwest::Client::builder() + .timeout(self.timeout) + .tls_built_in_root_certs(self.url.scheme() == "https"); let mut headers = reqwest::header::HeaderMap::new(); // If there's a JWT, add it to the headers if we can decode it. @@ -177,7 +179,6 @@ impl RuntimeTransport { let client = client_builder.build().map_err(RuntimeTransportError::HttpConstructionError)?; - // todo: retry tower layer Ok(InnerTransport::Http(Http::with_client(client, self.url.clone()))) } @@ -187,7 +188,7 @@ impl RuntimeTransport { let ws = WsConnect { url: self.url.to_string(), auth } .into_service() .await - .map_err(RuntimeTransportError::TransportError)?; + .map_err(|e| RuntimeTransportError::TransportError(e, self.url.to_string()))?; Ok(InnerTransport::Ws(ws)) } @@ -195,9 +196,10 @@ impl RuntimeTransport { async fn connect_ipc(&self) -> Result { let path = url_to_file_path(&self.url) .map_err(|_| RuntimeTransportError::BadPath(self.url.to_string()))?; - let ipc_connector: IpcConnect = path.into(); - let ipc = - ipc_connector.into_service().await.map_err(RuntimeTransportError::TransportError)?; + let ipc_connector = IpcConnect::new(path.clone()); + let ipc = ipc_connector.into_service().await.map_err(|e| { + RuntimeTransportError::TransportError(e, path.clone().display().to_string()) + })?; Ok(InnerTransport::Ipc(ipc)) } @@ -271,7 +273,7 @@ impl tower::Service for RuntimeTransport { } } -impl Service for &RuntimeTransport { +impl tower::Service for &RuntimeTransport { type Response = ResponsePacket; type Error = TransportError; type Future = TransportFut<'static>; @@ -292,12 +294,10 @@ impl Service for &RuntimeTransport { fn build_auth(jwt: String) -> eyre::Result { // Decode jwt from hex, then generate claims (iat with current timestamp) - let jwt = hex::decode(jwt)?; - let secret = JwtKey::from_slice(&jwt).map_err(|err| eyre::eyre!("Invalid JWT: {}", err))?; - let auth = JwtAuth::new(secret, None, None); - let token = auth.generate_token()?; + let secret = JwtSecret::from_hex(jwt)?; + let claims = Claims::default(); + let token = secret.encode(&claims)?; - // Essentially unrolled ethers-rs new_with_auth to accommodate the custom timeout let auth = Authorization::Bearer(token); Ok(auth) diff --git a/crates/common/src/provider/tower.rs b/crates/common/src/provider/tower.rs index 0df22bb01..73088021d 100644 --- a/crates/common/src/provider/tower.rs +++ b/crates/common/src/provider/tower.rs @@ -10,7 +10,6 @@ use std::{ use alloy_json_rpc::{RequestPacket, ResponsePacket}; use alloy_transport::{TransportError, TransportErrorKind, TransportFut}; -use tower::Service; use super::{ retry::{RateLimitRetryPolicy, RetryPolicy}, @@ -32,7 +31,7 @@ pub struct RetryBackoffLayer { } impl RetryBackoffLayer { - /// Creates a new [RetryWithPolicyLayer] with the given parameters + /// Creates a new retry layer with the given parameters. pub fn new( max_rate_limit_retries: u32, max_timeout_retries: u32, @@ -56,7 +55,7 @@ impl tower::layer::Layer for RetryBackoffLayer { inner, policy: RateLimitRetryPolicy, max_rate_limit_retries: self.max_rate_limit_retries, - max_timeout_retries: self.max_timeout_retries, + _max_timeout_retries: self.max_timeout_retries, initial_backoff: self.initial_backoff, compute_units_per_second: self.compute_units_per_second, requests_enqueued: Arc::new(AtomicU32::new(0)), @@ -65,7 +64,7 @@ impl tower::layer::Layer for RetryBackoffLayer { } /// An Alloy Tower Service that is responsible for retrying requests based on the -/// error type. See [TransportError] and [RetryWithPolicyLayer]. +/// error type. See [TransportError] and [RateLimitRetryPolicy]. #[derive(Debug, Clone)] pub struct RetryBackoffService { /// The inner service @@ -75,7 +74,7 @@ pub struct RetryBackoffService { /// The maximum number of retries for rate limit errors max_rate_limit_retries: u32, /// The maximum number of retries for timeout errors - max_timeout_retries: u32, + _max_timeout_retries: u32, /// The initial backoff in milliseconds initial_backoff: u64, /// The number of compute units per second for this service @@ -85,7 +84,7 @@ pub struct RetryBackoffService { } // impl tower service -impl Service for RetryBackoffService { +impl tower::Service for RetryBackoffService { type Response = ResponsePacket; type Error = TransportError; type Future = TransportFut<'static>; @@ -101,7 +100,6 @@ impl Service for RetryBackoffService { Box::pin(async move { let ahead_in_queue = this.requests_enqueued.fetch_add(1, Ordering::SeqCst) as u64; let mut rate_limit_retry_number: u32 = 0; - let mut timeout_retries: u32 = 0; loop { let err; let fut = this.inner.call(request.clone()).await; @@ -158,11 +156,7 @@ impl Service for RetryBackoffService { tokio::time::sleep(total_backoff).await; } else { - if timeout_retries < this.max_timeout_retries { - timeout_retries += 1; - continue; - } - + trace!("encountered non retryable error {err:?}"); this.requests_enqueued.fetch_sub(1, Ordering::SeqCst); return Err(err) } diff --git a/crates/common/src/runtime_client.rs b/crates/common/src/runtime_client.rs deleted file mode 100644 index ea0fe5938..000000000 --- a/crates/common/src/runtime_client.rs +++ /dev/null @@ -1,337 +0,0 @@ -//! Wrap different providers -// todo: remove -use async_trait::async_trait; -use ethers_core::types::U256; -use ethers_providers::{ - Authorization, ConnectionDetails, Http, HttpRateLimitRetryPolicy, Ipc, JsonRpcClient, - JsonRpcError, JwtAuth, JwtKey, ProviderError, PubsubClient, RetryClient, RetryClientBuilder, - RpcError, Ws, -}; -use reqwest::{ - header::{HeaderName, HeaderValue}, - Url, -}; -use serde::{de::DeserializeOwned, Serialize}; -use std::{fmt::Debug, path::PathBuf, str::FromStr, sync::Arc, time::Duration}; -use thiserror::Error; -use tokio::sync::RwLock; - -/// Enum representing a the client types supported by the runtime provider -#[derive(Debug)] -enum InnerClient { - /// HTTP client - Http(RetryClient), - /// WebSocket client - Ws(Ws), - /// IPC client - Ipc(Ipc), -} - -/// Error type for the runtime provider -#[derive(Debug, Error)] -pub enum RuntimeClientError { - /// Internal provider error - #[error(transparent)] - ProviderError(ProviderError), - - /// Failed to lock the client - #[error("Failed to lock the client")] - LockError, - - /// Invalid URL scheme - #[error("URL scheme is not supported: {0}")] - BadScheme(String), - - /// Invalid HTTP header - #[error("Invalid HTTP header: {0}")] - BadHeader(String), - - /// Invalid file path - #[error("Invalid IPC file path: {0}")] - BadPath(String), -} - -impl RpcError for RuntimeClientError { - fn as_error_response(&self) -> Option<&JsonRpcError> { - match self { - RuntimeClientError::ProviderError(err) => err.as_error_response(), - _ => None, - } - } - - fn as_serde_error(&self) -> Option<&serde_json::Error> { - match self { - RuntimeClientError::ProviderError(e) => e.as_serde_error(), - _ => None, - } - } -} - -impl From for ProviderError { - fn from(src: RuntimeClientError) -> Self { - match src { - RuntimeClientError::ProviderError(err) => err, - _ => ProviderError::JsonRpcClientError(Box::new(src)), - } - } -} - -/// A provider that connects on first request allowing handling of different provider types at -/// runtime -#[derive(Clone, Debug, Error)] -pub struct RuntimeClient { - client: Arc>>, - url: Url, - max_retry: u32, - timeout_retry: u32, - initial_backoff: u64, - timeout: Duration, - /// available CUPS - compute_units_per_second: u64, - jwt: Option, - headers: Vec, -} - -/// Builder for RuntimeClient -pub struct RuntimeClientBuilder { - url: Url, - max_retry: u32, - timeout_retry: u32, - initial_backoff: u64, - timeout: Duration, - /// available CUPS - compute_units_per_second: u64, - jwt: Option, - headers: Vec, -} - -impl ::core::fmt::Display for RuntimeClient { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - write!(f, "RuntimeClient") - } -} - -fn build_auth(jwt: String) -> eyre::Result { - // Decode jwt from hex, then generate claims (iat with current timestamp) - let jwt = hex::decode(jwt)?; - let secret = JwtKey::from_slice(&jwt).map_err(|err| eyre::eyre!("Invalid JWT: {}", err))?; - let auth = JwtAuth::new(secret, None, None); - let token = auth.generate_token()?; - - // Essentially unrolled ethers-rs new_with_auth to accommodate the custom timeout - let auth = Authorization::Bearer(token); - - Ok(auth) -} - -impl RuntimeClient { - async fn connect(&self) -> Result { - match self.url.scheme() { - "http" | "https" => { - let mut client_builder = reqwest::Client::builder() - .timeout(self.timeout) - .tls_built_in_root_certs(self.url.scheme() == "https"); - let mut headers = reqwest::header::HeaderMap::new(); - - if let Some(jwt) = self.jwt.as_ref() { - let auth = build_auth(jwt.clone()).map_err(|err| { - RuntimeClientError::ProviderError(ProviderError::CustomError( - err.to_string(), - )) - })?; - - let mut auth_value: HeaderValue = HeaderValue::from_str(&auth.to_string()) - .expect("Header should be valid string"); - auth_value.set_sensitive(true); - - headers.insert(reqwest::header::AUTHORIZATION, auth_value); - }; - - for header in self.headers.iter() { - let make_err = || RuntimeClientError::BadHeader(header.to_string()); - - let (key, val) = header.split_once(':').ok_or_else(make_err)?; - - headers.insert( - HeaderName::from_str(key.trim()).map_err(|_| make_err())?, - HeaderValue::from_str(val.trim()).map_err(|_| make_err())?, - ); - } - - client_builder = client_builder.default_headers(headers); - - let client = client_builder - .build() - .map_err(|e| RuntimeClientError::ProviderError(e.into()))?; - let provider = Http::new_with_client(self.url.clone(), client); - - #[allow(clippy::box_default)] - let provider = RetryClientBuilder::default() - .initial_backoff(Duration::from_millis(self.initial_backoff)) - .rate_limit_retries(self.max_retry) - .timeout_retries(self.timeout_retry) - .compute_units_per_second(self.compute_units_per_second) - .build(provider, Box::new(HttpRateLimitRetryPolicy)); - Ok(InnerClient::Http(provider)) - } - "ws" | "wss" => { - let auth: Option = - self.jwt.as_ref().and_then(|jwt| build_auth(jwt.clone()).ok()); - let connection_details = ConnectionDetails::new(self.url.as_str(), auth); - - let client = - Ws::connect_with_reconnects(connection_details, self.max_retry as usize) - .await - .map_err(|e| RuntimeClientError::ProviderError(e.into()))?; - - Ok(InnerClient::Ws(client)) - } - "file" => { - let path = url_to_file_path(&self.url) - .map_err(|_| RuntimeClientError::BadPath(self.url.to_string()))?; - - let client = Ipc::connect(path) - .await - .map_err(|e| RuntimeClientError::ProviderError(e.into()))?; - - Ok(InnerClient::Ipc(client)) - } - _ => Err(RuntimeClientError::BadScheme(self.url.to_string())), - } - } -} - -impl RuntimeClientBuilder { - /// Create new RuntimeClientBuilder - pub fn new( - url: Url, - max_retry: u32, - timeout_retry: u32, - initial_backoff: u64, - timeout: Duration, - compute_units_per_second: u64, - ) -> Self { - Self { - url, - max_retry, - timeout, - timeout_retry, - initial_backoff, - compute_units_per_second, - jwt: None, - headers: vec![], - } - } - - /// Set jwt to use with RuntimeClient - pub fn with_jwt(mut self, jwt: Option) -> Self { - self.jwt = jwt; - self - } - - /// Set http headers to use with RuntimeClient - /// Only works with http/https schemas - pub fn with_headers(mut self, headers: Vec) -> Self { - self.headers = headers; - self - } - - /// Builds RuntimeClient instance - pub fn build(self) -> RuntimeClient { - RuntimeClient { - client: Arc::new(RwLock::new(None)), - url: self.url, - max_retry: self.max_retry, - timeout_retry: self.timeout_retry, - initial_backoff: self.initial_backoff, - timeout: self.timeout, - compute_units_per_second: self.compute_units_per_second, - jwt: self.jwt, - headers: self.headers, - } - } -} - -#[cfg(windows)] -fn url_to_file_path(url: &Url) -> Result { - const PREFIX: &str = "file:///pipe/"; - - let url_str = url.as_str(); - - if url_str.starts_with(PREFIX) { - let pipe_name = &url_str[PREFIX.len()..]; - let pipe_path = format!(r"\\.\pipe\{}", pipe_name); - return Ok(PathBuf::from(pipe_path)); - } - - url.to_file_path() -} - -#[cfg(not(windows))] -fn url_to_file_path(url: &Url) -> Result { - url.to_file_path() -} - -#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] -#[cfg_attr(not(target_arch = "wasm32"), async_trait)] -impl JsonRpcClient for RuntimeClient { - type Error = RuntimeClientError; - - async fn request(&self, method: &str, params: T) -> Result - where - T: Debug + Serialize + Send + Sync, - R: DeserializeOwned + Send, - { - if self.client.read().await.is_none() { - let mut w = self.client.write().await; - *w = Some( - self.connect().await.map_err(|e| RuntimeClientError::ProviderError(e.into()))?, - ); - } - - let res = match self.client.read().await.as_ref().unwrap() { - InnerClient::Http(http) => RetryClient::request(http, method, params) - .await - .map_err(|e| RuntimeClientError::ProviderError(e.into())), - InnerClient::Ws(ws) => JsonRpcClient::request(ws, method, params) - .await - .map_err(|e| RuntimeClientError::ProviderError(e.into())), - InnerClient::Ipc(ipc) => JsonRpcClient::request(ipc, method, params) - .await - .map_err(|e| RuntimeClientError::ProviderError(e.into())), - }?; - Ok(res) - } -} - -// We can also implement [`PubsubClient`] for our dynamic provider. -impl PubsubClient for RuntimeClient { - // Since both `Ws` and `Ipc`'s `NotificationStream` associated type is the same, - // we can simply return one of them. - type NotificationStream = ::NotificationStream; - - fn subscribe>(&self, id: T) -> Result { - match self.client.try_read().map_err(|_| RuntimeClientError::LockError)?.as_ref().unwrap() { - InnerClient::Http(_) => { - Err(RuntimeClientError::ProviderError(ProviderError::UnsupportedRPC)) - } - InnerClient::Ws(client) => Ok(PubsubClient::subscribe(client, id) - .map_err(|e| RuntimeClientError::ProviderError(e.into()))?), - InnerClient::Ipc(client) => Ok(PubsubClient::subscribe(client, id) - .map_err(|e| RuntimeClientError::ProviderError(e.into()))?), - } - } - - fn unsubscribe>(&self, id: T) -> Result<(), Self::Error> { - match self.client.try_read().map_err(|_| (RuntimeClientError::LockError))?.as_ref().unwrap() - { - InnerClient::Http(_) => { - Err(RuntimeClientError::ProviderError(ProviderError::UnsupportedRPC)) - } - InnerClient::Ws(client) => Ok(PubsubClient::unsubscribe(client, id) - .map_err(|e| RuntimeClientError::ProviderError(e.into()))?), - InnerClient::Ipc(client) => Ok(PubsubClient::unsubscribe(client, id) - .map_err(|e| RuntimeClientError::ProviderError(e.into()))?), - } - } -} diff --git a/crates/common/src/selectors.rs b/crates/common/src/selectors.rs index cda4ccb6c..a051db1f0 100644 --- a/crates/common/src/selectors.rs +++ b/crates/common/src/selectors.rs @@ -19,15 +19,15 @@ use std::{ const SELECTOR_LOOKUP_URL: &str = "https://api.openchain.xyz/signature-database/v1/lookup"; const SELECTOR_IMPORT_URL: &str = "https://api.openchain.xyz/signature-database/v1/import"; -/// The standard request timeout for API requests +/// The standard request timeout for API requests. const REQ_TIMEOUT: Duration = Duration::from_secs(15); -/// How many request can time out before we decide this is a spurious connection +/// How many request can time out before we decide this is a spurious connection. const MAX_TIMEDOUT_REQ: usize = 4usize; -/// A client that can request API data from `https://api.openchain.xyz` +/// A client that can request API data from OpenChain. #[derive(Clone, Debug)] -pub struct SignEthClient { +pub struct OpenChainClient { inner: reqwest::Client, /// Whether the connection is spurious, or API is down spurious_connection: Arc, @@ -37,7 +37,7 @@ pub struct SignEthClient { max_timedout_requests: usize, } -impl SignEthClient { +impl OpenChainClient { /// Creates a new client with default settings pub fn new() -> reqwest::Result { let inner = reqwest::Client::builder() @@ -56,6 +56,7 @@ impl SignEthClient { } async fn get_text(&self, url: &str) -> reqwest::Result { + trace!(%url, "GET"); self.inner .get(url) .send() @@ -73,11 +74,12 @@ impl SignEthClient { } /// Sends a new post request - async fn post_json( + async fn post_json( &self, url: &str, body: &T, ) -> reqwest::Result { + trace!(%url, body=?serde_json::to_string(body), "POST"); self.inner .post(url) .json(body) @@ -111,7 +113,7 @@ impl SignEthClient { } if is_connectivity_err(err) { - warn!("spurious network detected for https://api.openchain.xyz"); + warn!("spurious network detected for OpenChain"); let previous = self.timedout_requests.fetch_add(1, Ordering::SeqCst); if previous >= self.max_timedout_requests { self.set_spurious(); @@ -136,7 +138,7 @@ impl SignEthClient { Ok(()) } - /// Decodes the given function or event selector using https://api.openchain.xyz + /// Decodes the given function or event selector using OpenChain pub async fn decode_selector( &self, selector: &str, @@ -146,10 +148,10 @@ impl SignEthClient { .await? .pop() // Not returning on the previous line ensures a vector with exactly 1 element .unwrap() - .ok_or(eyre::eyre!("No signature found")) + .ok_or_else(|| eyre::eyre!("No signature found")) } - /// Decodes the given function or event selectors using https://api.openchain.xyz + /// Decodes the given function or event selectors using OpenChain pub async fn decode_selectors( &self, selector_type: SelectorType, @@ -166,6 +168,9 @@ impl SignEthClient { return Ok(vec![]); } + debug!(len = selectors.len(), "decoding selectors"); + trace!(?selectors, "decoding selectors"); + // exit early if spurious connection self.ensure_not_spurious()?; @@ -196,8 +201,6 @@ impl SignEthClient { result: ApiResult, } - // using openchain.xyz signature database over 4byte - // see https://github.com/foundry-rs/foundry/issues/1672 let url = format!( "{SELECTOR_LOOKUP_URL}?{ltype}={selectors_str}", ltype = match selector_type { @@ -233,7 +236,7 @@ impl SignEthClient { .collect()) } - /// Fetches a function signature given the selector using https://api.openchain.xyz + /// Fetches a function signature given the selector using OpenChain pub async fn decode_function_selector(&self, selector: &str) -> eyre::Result> { self.decode_selector(selector, SelectorType::Function).await } @@ -258,7 +261,7 @@ impl SignEthClient { .collect::>()) } - /// Fetches an event signature given the 32 byte topic using https://api.openchain.xyz + /// Fetches an event signature given the 32 byte topic using OpenChain pub async fn decode_event_topic(&self, topic: &str) -> eyre::Result> { self.decode_selector(topic, SelectorType::Event).await } @@ -266,10 +269,10 @@ impl SignEthClient { /// Pretty print calldata and if available, fetch possible function signatures /// /// ```no_run - /// use foundry_common::selectors::SignEthClient; + /// use foundry_common::selectors::OpenChainClient; /// /// # async fn foo() -> eyre::Result<()> { - /// let pretty_data = SignEthClient::new()? + /// let pretty_data = OpenChainClient::new()? /// .pretty_calldata( /// "0x70a08231000000000000000000000000d0074f4e6490ae3f888d1d4f7e3e43326bd3f0f5" /// .to_string(), @@ -315,7 +318,7 @@ impl SignEthClient { Ok(possible_info) } - /// uploads selectors to https://api.openchain.xyz using the given data + /// uploads selectors to OpenChain using the given data pub async fn import_selectors( &self, data: SelectorImportData, @@ -364,7 +367,7 @@ pub struct PossibleSigs { impl PossibleSigs { fn new() -> Self { - PossibleSigs { method: SelectorOrSig::Selector("0x00000000".to_string()), data: vec![] } + Self { method: SelectorOrSig::Selector("0x00000000".to_string()), data: vec![] } } } @@ -392,44 +395,47 @@ impl fmt::Display for PossibleSigs { } } +/// The type of selector fetched from OpenChain. #[derive(Clone, Copy)] pub enum SelectorType { + /// A function selector. Function, + /// An event selector. Event, } -/// Decodes the given function or event selector using https://api.openchain.xyz +/// Decodes the given function or event selector using OpenChain. pub async fn decode_selector( selector_type: SelectorType, selector: &str, ) -> eyre::Result> { - SignEthClient::new()?.decode_selector(selector, selector_type).await + OpenChainClient::new()?.decode_selector(selector, selector_type).await } -/// Decodes the given function or event selectors using https://api.openchain.xyz +/// Decodes the given function or event selectors using OpenChain. pub async fn decode_selectors( selector_type: SelectorType, selectors: impl IntoIterator>, ) -> eyre::Result>>> { - SignEthClient::new()?.decode_selectors(selector_type, selectors).await + OpenChainClient::new()?.decode_selectors(selector_type, selectors).await } -/// Fetches a function signature given the selector https://api.openchain.xyz +/// Fetches a function signature given the selector using OpenChain. pub async fn decode_function_selector(selector: &str) -> eyre::Result> { - SignEthClient::new()?.decode_function_selector(selector).await + OpenChainClient::new()?.decode_function_selector(selector).await } -/// Fetches all possible signatures and attempts to abi decode the calldata +/// Fetches all possible signatures and attempts to abi decode the calldata using OpenChain. pub async fn decode_calldata(calldata: &str) -> eyre::Result> { - SignEthClient::new()?.decode_calldata(calldata).await + OpenChainClient::new()?.decode_calldata(calldata).await } -/// Fetches an event signature given the 32 byte topic using https://api.openchain.xyz +/// Fetches an event signature given the 32 byte topic using OpenChain. pub async fn decode_event_topic(topic: &str) -> eyre::Result> { - SignEthClient::new()?.decode_event_topic(topic).await + OpenChainClient::new()?.decode_event_topic(topic).await } -/// Pretty print calldata and if available, fetch possible function signatures +/// Pretty print calldata and if available, fetch possible function signatures. /// /// ```no_run /// use foundry_common::selectors::pretty_calldata; @@ -448,7 +454,7 @@ pub async fn pretty_calldata( calldata: impl AsRef, offline: bool, ) -> eyre::Result { - SignEthClient::new()?.pretty_calldata(calldata, offline).await + OpenChainClient::new()?.pretty_calldata(calldata, offline).await } #[derive(Debug, Default, PartialEq, Eq, Serialize)] @@ -514,13 +520,13 @@ impl SelectorImportResponse { .iter() .for_each(|(k, v)| println!("Duplicated: Event {k}: {v}")); - println!("Selectors successfully uploaded to https://api.openchain.xyz"); + println!("Selectors successfully uploaded to OpenChain"); } } -/// uploads selectors to https://api.openchain.xyz using the given data +/// uploads selectors to OpenChain using the given data pub async fn import_selectors(data: SelectorImportData) -> eyre::Result { - SignEthClient::new()?.import_selectors(data).await + OpenChainClient::new()?.import_selectors(data).await } #[derive(Debug, Default, PartialEq)] @@ -629,7 +635,7 @@ mod tests { let abi: JsonAbi = serde_json::from_str(r#"[{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function", "methodIdentifiers": {"transfer(address,uint256)(uint256)": "0xa9059cbb"}}]"#).unwrap(); let result = import_selectors(SelectorImportData::Abi(vec![abi])).await; - println!("{:?}", result); + println!("{result:?}"); assert_eq!( result.unwrap().result.function.duplicated.get("transfer(address,uint256)").unwrap(), "0xa9059cbb" diff --git a/crates/common/src/serde_helpers.rs b/crates/common/src/serde_helpers.rs index c1b61b6e2..a7cc8bee1 100644 --- a/crates/common/src/serde_helpers.rs +++ b/crates/common/src/serde_helpers.rs @@ -15,10 +15,10 @@ pub enum Numeric { } impl From for U256 { - fn from(n: Numeric) -> U256 { + fn from(n: Numeric) -> Self { match n { Numeric::U256(n) => n, - Numeric::Num(n) => U256::from(n), + Numeric::Num(n) => Self::from(n), } } } @@ -28,7 +28,7 @@ impl FromStr for Numeric { fn from_str(s: &str) -> Result { if let Ok(val) = s.parse::() { - Ok(Numeric::U256(U256::from(val))) + Ok(Self::U256(U256::from(val))) } else if s.starts_with("0x") { U256::from_str_radix(s, 16).map(Numeric::U256).map_err(|err| err.to_string()) } else { @@ -63,10 +63,8 @@ impl NumberOrHexU256 { /// Tries to convert this into a [U256]]. pub fn try_into_u256(self) -> Result { match self { - NumberOrHexU256::Int(num) => { - U256::from_str(num.to_string().as_str()).map_err(E::custom) - } - NumberOrHexU256::Hex(val) => Ok(val), + Self::Int(num) => U256::from_str(num.to_string().as_str()).map_err(E::custom), + Self::Hex(val) => Ok(val), } } } @@ -84,11 +82,11 @@ where #[derive(Deserialize)] #[serde(untagged)] pub enum NumericSeq { - /// Single parameter sequence (e.g [1]) + /// Single parameter sequence (e.g `[1]`). Seq([Numeric; 1]), - /// U256 + /// `U256`. U256(U256), - /// Native u64 + /// Native `u64`. Num(u64), } diff --git a/crates/common/src/shell.rs b/crates/common/src/shell.rs index 9955269ee..898b154fa 100644 --- a/crates/common/src/shell.rs +++ b/crates/common/src/shell.rs @@ -70,8 +70,6 @@ pub struct Shell { verbosity: Verbosity, } -// === impl Shell === - impl Shell { /// Creates a new shell instance pub fn new(output: ShellOut, verbosity: Verbosity) -> Self { @@ -105,14 +103,14 @@ impl Shell { /// Write a fragment to stdout /// - /// Caller is responsible for deciding whether [`Shell::verbosity`] is affects output. + /// Caller is responsible for deciding whether [`Shell`] verbosity affects output. pub fn write_stdout(&self, fragment: impl fmt::Display) -> io::Result<()> { self.output.write_stdout(fragment) } /// Write a fragment to stderr /// - /// Caller is responsible for deciding whether [`Shell::verbosity`] is affects output. + /// Caller is responsible for deciding whether [`Shell`] verbosity affects output. pub fn write_stderr(&self, fragment: impl fmt::Display) -> io::Result<()> { self.output.write_stderr(fragment) } @@ -210,25 +208,23 @@ pub enum ShellOut { Stream, } -// === impl ShellOut === - impl ShellOut { /// Creates a new shell that writes to memory pub fn memory() -> Self { #[allow(clippy::box_default)] #[allow(clippy::arc_with_non_send_sync)] - ShellOut::Write(WriteShellOut(Arc::new(Mutex::new(Box::new(Vec::new()))))) + Self::Write(WriteShellOut(Arc::new(Mutex::new(Box::new(Vec::new()))))) } /// Write a fragment to stdout fn write_stdout(&self, fragment: impl fmt::Display) -> io::Result<()> { match *self { - ShellOut::Stream => { + Self::Stream => { let stdout = io::stdout(); let mut handle = stdout.lock(); writeln!(handle, "{fragment}")?; } - ShellOut::Write(ref w) => { + Self::Write(ref w) => { w.write(fragment)?; } } @@ -238,12 +234,12 @@ impl ShellOut { /// Write output to stderr fn write_stderr(&self, fragment: impl fmt::Display) -> io::Result<()> { match *self { - ShellOut::Stream => { + Self::Stream => { let stderr = io::stderr(); let mut handle = stderr.lock(); writeln!(handle, "{fragment}")?; } - ShellOut::Write(ref w) => { + Self::Write(ref w) => { w.write(fragment)?; } } @@ -256,12 +252,12 @@ impl ShellOut { for<'r> F: FnOnce(&'r mut (dyn Write + 'r)) -> R, { match *self { - ShellOut::Stream => { + Self::Stream => { let stdout = io::stdout(); let mut handler = stdout.lock(); f(&mut handler) } - ShellOut::Write(ref w) => w.with_stdout(f), + Self::Write(ref w) => w.with_stdout(f), } } @@ -272,12 +268,12 @@ impl ShellOut { for<'r> F: FnOnce(&'r mut (dyn Write + 'r)) -> R, { match *self { - ShellOut::Stream => { + Self::Stream => { let stderr = io::stderr(); let mut handler = stderr.lock(); f(&mut handler) } - ShellOut::Write(ref w) => w.with_err(f), + Self::Write(ref w) => w.with_err(f), } } } @@ -294,21 +290,19 @@ pub enum Verbosity { Silent, } -// === impl Verbosity === - impl Verbosity { /// Returns true if json mode pub fn is_json(&self) -> bool { - matches!(self, Verbosity::Json) + matches!(self, Self::Json) } /// Returns true if silent pub fn is_silent(&self) -> bool { - matches!(self, Verbosity::Silent) + matches!(self, Self::Silent) } /// Returns true if normal verbosity pub fn is_normal(&self) -> bool { - matches!(self, Verbosity::Normal) + matches!(self, Self::Normal) } } diff --git a/crates/common/src/term.rs b/crates/common/src/term.rs index bee590cd0..d81ce5548 100644 --- a/crates/common/src/term.rs +++ b/crates/common/src/term.rs @@ -1,8 +1,7 @@ //! terminal utils use foundry_compilers::{ - remappings::Remapping, - report::{self, BasicStdoutReporter, Reporter, SolcCompilerIoReporter}, - CompilerInput, CompilerOutput, Solc, + artifacts::remappings::Remapping, + report::{self, BasicStdoutReporter, Reporter}, }; use once_cell::sync::Lazy; use semver::Version; @@ -35,8 +34,8 @@ pub struct TermSettings { impl TermSettings { /// Returns a new [`TermSettings`], configured from the current environment. - pub fn from_env() -> TermSettings { - TermSettings { indicate_progress: std::io::stdout().is_terminal() } + pub fn from_env() -> Self { + Self { indicate_progress: std::io::stdout().is_terminal() } } } @@ -56,7 +55,7 @@ impl Spinner { } pub fn with_indicator(indicator: &'static [&'static str], msg: impl Into) -> Self { - Spinner { + Self { indicator, no_progress: !TERM_SETTINGS.indicate_progress, message: msg.into(), @@ -69,7 +68,7 @@ impl Spinner { return } - let indicator = Paint::green(self.indicator[self.idx % self.indicator.len()]); + let indicator = self.indicator[self.idx % self.indicator.len()].green(); let indicator = Paint::new(format!("[{indicator}]")).bold(); print!("\r\x33[2K\r{indicator} {}", self.message); io::stdout().flush().unwrap(); @@ -90,9 +89,6 @@ impl Spinner { pub struct SpinnerReporter { /// The sender to the spinner thread. sender: mpsc::Sender, - /// Reporter that logs Solc compiler input and output to separate files if configured via env - /// var. - solc_io_report: SolcCompilerIoReporter, } impl SpinnerReporter { @@ -138,7 +134,7 @@ impl SpinnerReporter { }) .expect("failed to spawn thread"); - SpinnerReporter { sender, solc_io_report: SolcCompilerIoReporter::from_default_env() } + Self { sender } } fn send_msg(&self, msg: impl Into) { @@ -161,49 +157,34 @@ impl Drop for SpinnerReporter { } impl Reporter for SpinnerReporter { - fn on_solc_spawn( - &self, - _solc: &Solc, - version: &Version, - input: &CompilerInput, - dirty_files: &[PathBuf], - ) { + fn on_compiler_spawn(&self, compiler_name: &str, version: &Version, dirty_files: &[PathBuf]) { self.send_msg(format!( - "Compiling {} files with {}.{}.{}", + "Compiling {} files with {} {}.{}.{}", dirty_files.len(), + compiler_name, version.major, version.minor, version.patch )); - self.solc_io_report.log_compiler_input(input, version); } - fn on_solc_success( - &self, - _solc: &Solc, - version: &Version, - output: &CompilerOutput, - duration: &Duration, - ) { - self.solc_io_report.log_compiler_output(output, version); + fn on_compiler_success(&self, compiler_name: &str, version: &Version, duration: &Duration) { self.send_msg(format!( - "Solc {}.{}.{} finished in {duration:.2?}", - version.major, version.minor, version.patch + "{} {}.{}.{} finished in {duration:.2?}", + compiler_name, version.major, version.minor, version.patch )); } - /// Invoked before a new [`Solc`] bin is installed fn on_solc_installation_start(&self, version: &Version) { self.send_msg(format!("Installing Solc version {version}")); } - /// Invoked before a new [`Solc`] bin was successfully installed fn on_solc_installation_success(&self, version: &Version) { self.send_msg(format!("Successfully installed Solc {version}")); } fn on_solc_installation_error(&self, version: &Version, error: &str) { - self.send_msg(Paint::red(format!("Failed to install Solc {version}: {error}")).to_string()); + self.send_msg(format!("Failed to install Solc {version}: {error}").red().to_string()); } fn on_unresolved_imports(&self, imports: &[(&Path, &Path)], remappings: &[Remapping]) { @@ -230,8 +211,8 @@ macro_rules! cli_warn { ($($arg:tt)*) => { eprintln!( "{}{} {}", - yansi::Paint::yellow("warning").bold(), - yansi::Paint::new(":").bold(), + yansi::Painted::new("warning").yellow().bold(), + yansi::Painted::new(":").bold(), format_args!($($arg)*) ) } diff --git a/crates/common/src/traits.rs b/crates/common/src/traits.rs index 4232fb946..64d27563e 100644 --- a/crates/common/src/traits.rs +++ b/crates/common/src/traits.rs @@ -33,6 +33,12 @@ pub trait TestFunctionExt { /// Returns whether this function is a `setUp` function. fn is_setup(&self) -> bool; + + /// Returns whether this function is `afterInvariant` function. + fn is_after_invariant(&self) -> bool; + + /// Returns whether this function is a fixture function. + fn is_fixture(&self) -> bool; } impl TestFunctionExt for Function { @@ -56,6 +62,14 @@ impl TestFunctionExt for Function { fn is_setup(&self) -> bool { self.name.is_setup() } + + fn is_after_invariant(&self) -> bool { + self.name.is_after_invariant() + } + + fn is_fixture(&self) -> bool { + self.name.is_fixture() + } } impl TestFunctionExt for String { @@ -78,6 +92,14 @@ impl TestFunctionExt for String { fn is_setup(&self) -> bool { self.as_str().is_setup() } + + fn is_after_invariant(&self) -> bool { + self.as_str().is_after_invariant() + } + + fn is_fixture(&self) -> bool { + self.as_str().is_fixture() + } } impl TestFunctionExt for str { @@ -100,6 +122,14 @@ impl TestFunctionExt for str { fn is_setup(&self) -> bool { self.eq_ignore_ascii_case("setup") } + + fn is_after_invariant(&self) -> bool { + self.eq_ignore_ascii_case("afterinvariant") + } + + fn is_fixture(&self) -> bool { + self.starts_with("fixture") + } } /// An extension trait for `std::error::Error` for ABI encoding. diff --git a/crates/common/src/transactions.rs b/crates/common/src/transactions.rs index b337147d6..9a6ba190e 100644 --- a/crates/common/src/transactions.rs +++ b/crates/common/src/transactions.rs @@ -1,15 +1,18 @@ -//! wrappers for transactions -use ethers_core::types::{BlockId, TransactionReceipt}; -use ethers_providers::Middleware; +//! Wrappers for transactions. + +use alloy_provider::{network::AnyNetwork, Provider}; +use alloy_rpc_types::{AnyTransactionReceipt, BlockId}; +use alloy_serde::WithOtherFields; +use alloy_transport::Transport; use eyre::Result; use serde::{Deserialize, Serialize}; /// Helper type to carry a transaction along with an optional revert reason -#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TransactionReceiptWithRevertReason { /// The underlying transaction receipt #[serde(flatten)] - pub receipt: TransactionReceipt, + pub receipt: AnyTransactionReceipt, /// The revert reason string if the transaction status is failed #[serde(skip_serializing_if = "Option::is_none", rename = "revertReason")] @@ -18,68 +21,66 @@ pub struct TransactionReceiptWithRevertReason { impl TransactionReceiptWithRevertReason { /// Returns if the status of the transaction is 0 (failure) - pub fn is_failure(&self) -> Option { - self.receipt.status.map(|status| status.as_u64() == 0) + pub fn is_failure(&self) -> bool { + !self.receipt.inner.inner.inner.receipt.status.coerce_status() } /// Updates the revert reason field using `eth_call` and returns an Err variant if the revert /// reason was not successfully updated - pub async fn update_revert_reason(&mut self, provider: &M) -> Result<()> { + pub async fn update_revert_reason>( + &mut self, + provider: &P, + ) -> Result<()> { self.revert_reason = self.fetch_revert_reason(provider).await?; Ok(()) } - async fn fetch_revert_reason(&self, provider: &M) -> Result> { - if let Some(false) | None = self.is_failure() { + async fn fetch_revert_reason>( + &self, + provider: &P, + ) -> Result> { + if !self.is_failure() { return Ok(None) } - if let Some(ref transaction) = provider - .get_transaction(self.receipt.transaction_hash) + let transaction = provider + .get_transaction_by_hash(self.receipt.transaction_hash) .await - .map_err(|_| eyre::eyre!("unable to fetch transaction"))? - { - if let Some(block_hash) = self.receipt.block_hash { - match provider.call(&transaction.into(), Some(BlockId::Hash(block_hash))).await { - Err(e) => return Ok(extract_revert_reason(e.to_string())), - Ok(_) => eyre::bail!("no revert reason as transaction succeeded"), - } + .map_err(|err| eyre::eyre!("unable to fetch transaction: {err}"))? + .ok_or_else(|| eyre::eyre!("transaction not found"))?; + + if let Some(block_hash) = self.receipt.block_hash { + match provider + .call(&WithOtherFields::new(transaction.inner.into())) + .block(BlockId::Hash(block_hash.into())) + .await + { + Err(e) => return Ok(extract_revert_reason(e.to_string())), + Ok(_) => eyre::bail!("no revert reason as transaction succeeded"), } - eyre::bail!("unable to fetch block_hash") } - Err(eyre::eyre!("transaction does not exist")) + eyre::bail!("unable to fetch block_hash") } } -impl From for TransactionReceiptWithRevertReason { - fn from(receipt: TransactionReceipt) -> Self { +impl From for TransactionReceiptWithRevertReason { + fn from(receipt: AnyTransactionReceipt) -> Self { Self { receipt, revert_reason: None } } } -impl From for TransactionReceipt { +impl From for AnyTransactionReceipt { fn from(receipt_with_reason: TransactionReceiptWithRevertReason) -> Self { receipt_with_reason.receipt } } fn extract_revert_reason>(error_string: S) -> Option { - let message_substr = "message: execution reverted: "; - - let mut temp = ""; - + let message_substr = "execution reverted: "; error_string .as_ref() .find(message_substr) - .and_then(|index| { - let (_, rest) = error_string.as_ref().split_at(index + message_substr.len()); - temp = rest; - rest.rfind(", ") - }) - .map(|index| { - let (reason, _) = temp.split_at(index); - reason.to_string() - }) + .map(|index| error_string.as_ref().split_at(index + message_substr.len()).1.to_string()) } #[cfg(test)] @@ -88,16 +89,10 @@ mod tests { #[test] fn test_extract_revert_reason() { - let error_string_1 = "(code: 3, message: execution reverted: Transaction too old, data: Some(String(\"0x08c379a0\")))"; - let error_string_2 = "(code: 3, message: execution reverted: missing data: amountIn, amountOut, data: Some(String(\"0x08c379a0\")))"; - let error_string_3 = - "(code: 4, message: invalid signature, data: Some(String(\"0x08c379a0\")))"; + let error_string_1 = "server returned an error response: error code 3: execution reverted: Transaction too old"; + let error_string_2 = "server returned an error response: error code 3: Invalid signature"; assert_eq!(extract_revert_reason(error_string_1), Some("Transaction too old".to_string())); - assert_eq!( - extract_revert_reason(error_string_2), - Some("missing data: amountIn, amountOut".to_string()) - ); - assert_eq!(extract_revert_reason(error_string_3), None); + assert_eq!(extract_revert_reason(error_string_2), None); } } diff --git a/crates/common/src/types.rs b/crates/common/src/types.rs deleted file mode 100644 index 6f4284b32..000000000 --- a/crates/common/src/types.rs +++ /dev/null @@ -1,262 +0,0 @@ -//! Temporary utility conversion traits between ethers-rs and alloy types. - -use alloy_primitives::{Address, Bloom, Bytes, B256, B64, I256, U128, U256, U64}; -use alloy_rpc_types::{ - other::OtherFields, - request::{TransactionInput, TransactionRequest as CallRequest}, - AccessList, AccessListItem, Signature, Transaction, -}; -use alloy_signer::{LocalWallet, Signer}; -use ethers_core::types::{ - transaction::eip2930::{ - AccessList as EthersAccessList, AccessListItem as EthersAccessListItem, - }, - Bloom as EthersBloom, Bytes as EthersBytes, TransactionRequest, H160, H256, H64, - I256 as EthersI256, U256 as EthersU256, U64 as EthersU64, -}; - -/// Conversion trait to easily convert from Ethers types to Alloy types. -pub trait ToAlloy { - /// The corresponding Alloy type. - type To; - - /// Converts the Ethers type to the corresponding Alloy type. - fn to_alloy(self) -> Self::To; -} - -impl ToAlloy for EthersBytes { - type To = Bytes; - - #[inline(always)] - fn to_alloy(self) -> Self::To { - Bytes(self.0) - } -} - -impl ToAlloy for H64 { - type To = B64; - - #[inline(always)] - fn to_alloy(self) -> Self::To { - B64::new(self.0) - } -} - -impl ToAlloy for H160 { - type To = Address; - - #[inline(always)] - fn to_alloy(self) -> Self::To { - Address::new(self.0) - } -} - -impl ToAlloy for H256 { - type To = B256; - - #[inline(always)] - fn to_alloy(self) -> Self::To { - B256::new(self.0) - } -} - -impl ToAlloy for EthersBloom { - type To = Bloom; - - #[inline(always)] - fn to_alloy(self) -> Self::To { - Bloom::new(self.0) - } -} - -impl ToAlloy for EthersU256 { - type To = U256; - - #[inline(always)] - fn to_alloy(self) -> Self::To { - U256::from_limbs(self.0) - } -} - -impl ToAlloy for EthersI256 { - type To = I256; - - #[inline(always)] - fn to_alloy(self) -> Self::To { - I256::from_raw(self.into_raw().to_alloy()) - } -} - -impl ToAlloy for EthersU64 { - type To = U64; - - #[inline(always)] - fn to_alloy(self) -> Self::To { - U64::from_limbs(self.0) - } -} - -impl ToAlloy for u64 { - type To = U256; - - #[inline(always)] - fn to_alloy(self) -> Self::To { - U256::from(self) - } -} - -impl ToAlloy for ethers_core::types::Transaction { - type To = Transaction; - - fn to_alloy(self) -> Self::To { - Transaction { - hash: self.hash.to_alloy(), - nonce: U64::from(self.nonce.as_u64()), - block_hash: self.block_hash.map(ToAlloy::to_alloy), - block_number: self.block_number.map(|b| U256::from(b.as_u64())), - transaction_index: self.transaction_index.map(|b| U256::from(b.as_u64())), - from: self.from.to_alloy(), - to: self.to.map(ToAlloy::to_alloy), - value: self.value.to_alloy(), - gas_price: self.gas_price.map(|a| U128::from(a.as_u128())), - gas: self.gas.to_alloy(), - max_fee_per_gas: self.max_fee_per_gas.map(|f| U128::from(f.as_u128())), - max_priority_fee_per_gas: self - .max_priority_fee_per_gas - .map(|f| U128::from(f.as_u128())), - max_fee_per_blob_gas: None, - input: self.input.0.into(), - signature: Some(Signature { - r: self.r.to_alloy(), - s: self.s.to_alloy(), - v: U256::from(self.v.as_u64()), - y_parity: None, - }), - chain_id: self.chain_id.map(|c| U64::from(c.as_u64())), - blob_versioned_hashes: Vec::new(), - access_list: self.access_list.map(|a| a.0.into_iter().map(ToAlloy::to_alloy).collect()), - transaction_type: self.transaction_type.map(|t| t.to_alloy()), - other: Default::default(), - } - } -} - -impl ToEthers for alloy_signer::LocalWallet { - type To = ethers_signers::LocalWallet; - - fn to_ethers(self) -> Self::To { - ethers_signers::LocalWallet::new_with_signer( - self.signer().clone(), - self.address().to_ethers(), - self.chain_id().unwrap(), - ) - } -} - -impl ToEthers for Vec { - type To = Vec; - - fn to_ethers(self) -> Self::To { - self.into_iter().map(ToEthers::to_ethers).collect() - } -} - -/// Converts from a [TransactionRequest] to a [CallRequest]. -pub fn to_call_request_from_tx_request(tx: TransactionRequest) -> CallRequest { - CallRequest { - from: tx.from.map(|f| f.to_alloy()), - to: match tx.to { - Some(to) => match to { - ethers_core::types::NameOrAddress::Address(addr) => Some(addr.to_alloy()), - ethers_core::types::NameOrAddress::Name(_) => None, - }, - None => None, - }, - gas_price: tx.gas_price.map(|g| g.to_alloy()), - max_fee_per_gas: None, - max_priority_fee_per_gas: None, - gas: tx.gas.map(|g| g.to_alloy()), - value: tx.value.map(|v| v.to_alloy()), - input: TransactionInput::maybe_input(tx.data.map(|b| b.0.into())), - nonce: tx.nonce.map(|n| U64::from(n.as_u64())), - chain_id: tx.chain_id.map(|c| c.to_alloy()), - access_list: None, - max_fee_per_blob_gas: None, - blob_versioned_hashes: None, - transaction_type: None, - sidecar: None, - other: OtherFields::default(), - } -} - -impl ToAlloy for EthersAccessList { - type To = AccessList; - fn to_alloy(self) -> Self::To { - AccessList(self.0.into_iter().map(ToAlloy::to_alloy).collect()) - } -} - -impl ToAlloy for EthersAccessListItem { - type To = AccessListItem; - - fn to_alloy(self) -> Self::To { - AccessListItem { - address: self.address.to_alloy(), - storage_keys: self.storage_keys.into_iter().map(ToAlloy::to_alloy).collect(), - } - } -} - -/// Conversion trait to easily convert from Alloy types to Ethers types. -pub trait ToEthers { - /// The corresponding Ethers type. - type To; - - /// Converts the Alloy type to the corresponding Ethers type. - fn to_ethers(self) -> Self::To; -} - -impl ToEthers for Address { - type To = H160; - - #[inline(always)] - fn to_ethers(self) -> Self::To { - H160(self.0 .0) - } -} - -impl ToEthers for B256 { - type To = H256; - - #[inline(always)] - fn to_ethers(self) -> Self::To { - H256(self.0) - } -} - -impl ToEthers for U256 { - type To = EthersU256; - - #[inline(always)] - fn to_ethers(self) -> Self::To { - EthersU256(self.into_limbs()) - } -} - -impl ToEthers for U64 { - type To = EthersU64; - - #[inline(always)] - fn to_ethers(self) -> Self::To { - EthersU64(self.into_limbs()) - } -} - -impl ToEthers for Bytes { - type To = EthersBytes; - - #[inline(always)] - fn to_ethers(self) -> Self::To { - EthersBytes(self.0) - } -} diff --git a/crates/common/src/utils.rs b/crates/common/src/utils.rs new file mode 100644 index 000000000..267680747 --- /dev/null +++ b/crates/common/src/utils.rs @@ -0,0 +1,40 @@ +//! Uncategorised utilities. + +use alloy_primitives::{keccak256, B256, U256}; + +/// Block on a future using the current tokio runtime on the current thread. +pub fn block_on(future: F) -> F::Output { + block_on_handle(&tokio::runtime::Handle::current(), future) +} + +/// Block on a future using the current tokio runtime on the current thread with the given handle. +pub fn block_on_handle( + handle: &tokio::runtime::Handle, + future: F, +) -> F::Output { + tokio::task::block_in_place(|| handle.block_on(future)) +} + +/// Computes the storage slot as specified by `ERC-7201`, using the `erc7201` formula ID. +/// +/// This is defined as: +/// +/// ```text +/// erc7201(id: string) = keccak256(keccak256(id) - 1) & ~0xff +/// ``` +/// +/// # Examples +/// +/// ``` +/// use alloy_primitives::b256; +/// use foundry_common::erc7201; +/// +/// assert_eq!( +/// erc7201("example.main"), +/// b256!("183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab500"), +/// ); +/// ``` +pub fn erc7201(id: &str) -> B256 { + let x = U256::from_be_bytes(keccak256(id).0) - U256::from(1); + keccak256(x.to_be_bytes::<32>()) & B256::from(!U256::from(0xff)) +} diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index e3a5a5601..52f27d40d 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -16,32 +16,39 @@ foundry-compilers = { workspace = true, features = ["svm-solc", "async"] } alloy-chains = { workspace = true, features = ["serde"] } alloy-primitives = { workspace = true, features = ["serde"] } -revm-primitives = { workspace = true, default-features = false, features = ["std"] } +revm-primitives.workspace = true + +solang-parser.workspace = true dirs-next = "2" -dunce = "1" +dunce.workspace = true eyre.workspace = true -figment = { version = "0.10", features = ["toml", "env"] } +figment = { workspace = true, features = ["toml", "env"] } globset = "0.4" +glob = "0.3" Inflector = "0.11" number_prefix = "0.4" -once_cell = "1" +once_cell.workspace = true regex = "1" -reqwest = { version = "0.11", default-features = false } -semver = { version = "1", features = ["serde"] } +reqwest.workspace = true +semver = { workspace = true, features = ["serde"] } serde_json.workspace = true serde_regex = "1" serde.workspace = true -thiserror = "1" +thiserror.workspace = true toml = { version = "0.8", features = ["preserve_order"] } -toml_edit = "0.21" +toml_edit = "0.22.4" tracing.workspace = true -walkdir = "2" +walkdir.workspace = true [target.'cfg(target_os = "windows")'.dependencies] path-slash = "0.2.1" [dev-dependencies] -pretty_assertions.workspace = true -figment = { version = "0.10", features = ["test"] } -tempfile = "3" +similar-asserts.workspace = true +figment = { workspace = true, features = ["test"] } +tempfile.workspace = true + +[features] +default = ["rustls"] +rustls = ["reqwest/rustls-tls-native-roots"] diff --git a/crates/config/README.md b/crates/config/README.md index e96040ab4..374bcdd39 100644 --- a/crates/config/README.md +++ b/crates/config/README.md @@ -103,7 +103,7 @@ eth_rpc_url = "https://example.com/" # Setting this option enables decoding of error traces from mainnet deployed / verfied contracts via etherscan etherscan_api_key = "YOURETHERSCANAPIKEY" # ignore solc warnings for missing license and exceeded contract size -# known error codes are: ["unreachable", "unused-return", "unused-param", "unused-var", "code-size", "shadowing", "func-mutability", "license", "pragma-solidity", "virtual-interfaces", "same-varname"] +# known error codes are: ["unreachable", "unused-return", "unused-param", "unused-var", "code-size", "shadowing", "func-mutability", "license", "pragma-solidity", "virtual-interfaces", "same-varname", "too-many-warnings", "constructor-visibility", "init-code-size", "missing-receive-ether", "unnamed-return", "transient-storage"] # additional warnings can be added using their numeric error code: ["license", 1337] ignored_error_codes = ["license", "code-size"] ignored_warnings_from = ["path_to_ignore"] @@ -116,6 +116,7 @@ match_path = "*/Foo*" no_match_path = "*/Bar*" ffi = false always_use_create_2_factory = false +prompt_timeout = 120 # These are the default callers, generated using `address(uint160(uint256(keccak256("foundry default caller"))))` sender = '0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38' tx_origin = '0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38' @@ -189,13 +190,13 @@ no_zksync_reserved_addresses = true [invariant] runs = 256 -depth = 15 +depth = 500 fail_on_revert = false call_override = false dictionary_weight = 80 include_storage = true include_push_bytes = true -shrink_sequence = true +shrink_run_limit = 5000 no_zksync_reserved_addresses = true [fmt] diff --git a/crates/config/src/cache.rs b/crates/config/src/cache.rs index ef0ca40a4..58b3b8cbe 100644 --- a/crates/config/src/cache.rs +++ b/crates/config/src/cache.rs @@ -5,12 +5,12 @@ use number_prefix::NumberPrefix; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::{fmt, fmt::Formatter, str::FromStr}; -/// Settings to configure caching of remote +/// Settings to configure caching of remote. #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct StorageCachingConfig { - /// chains to cache + /// Chains to cache. pub chains: CachedChains, - /// endpoints to cache + /// Endpoints to cache. pub endpoints: CachedEndpoints, } @@ -230,7 +230,7 @@ pub struct ChainCache { #[cfg(test)] mod tests { use super::*; - use pretty_assertions::assert_str_eq; + use similar_asserts::assert_eq; #[test] fn can_parse_storage_config() { @@ -258,7 +258,7 @@ mod tests { Chain::optimism_mainnet(), Chain::from_id(999999) ]), - endpoints: CachedEndpoints::All + endpoints: CachedEndpoints::All, } ) } @@ -307,6 +307,6 @@ mod tests { - Block Explorer (0.0 B)\n\n\t\ - Block 1 (1.0 B)\n\t\ - Block 2 (2.0 B)\n"; - assert_str_eq!(format!("{cache}"), expected); + assert_eq!(format!("{cache}"), expected); } } diff --git a/crates/config/src/endpoints.rs b/crates/config/src/endpoints.rs index 36cc8c7b6..74157b0e9 100644 --- a/crates/config/src/endpoints.rs +++ b/crates/config/src/endpoints.rs @@ -15,8 +15,6 @@ pub struct RpcEndpoints { endpoints: BTreeMap, } -// === impl RpcEndpoints === - impl RpcEndpoints { /// Creates a new list of endpoints pub fn new( @@ -132,8 +130,6 @@ pub enum RpcEndpoint { Env(String), } -// === impl RpcEndpoint === - impl RpcEndpoint { /// Returns the url variant pub fn as_url(&self) -> Option<&str> { @@ -237,7 +233,7 @@ pub struct RpcEndpointConfig { } impl RpcEndpointConfig { - /// Returns the url this type holds, see [RpcEndpoints::resolve()] + /// Returns the url this type holds, see [`RpcEndpoint::resolve`] pub fn resolve(self) -> Result { self.endpoint.resolve() } @@ -333,7 +329,7 @@ impl Default for RpcEndpointConfig { } } -/// Container type for _resolved_ endpoints, see [RpcEndpoints::resolve_all()] +/// Container type for _resolved_ endpoints, see [`RpcEndpoint::resolve`]. #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct ResolvedRpcEndpoints { /// contains all named endpoints and their URL or an error if we failed to resolve the env var @@ -341,8 +337,6 @@ pub struct ResolvedRpcEndpoints { endpoints: BTreeMap>, } -// === impl ResolvedEndpoints === - impl ResolvedRpcEndpoints { /// Returns true if there's an endpoint that couldn't be resolved pub fn has_unresolved(&self) -> bool { diff --git a/crates/config/src/error.rs b/crates/config/src/error.rs index b4f7b85aa..016e32c47 100644 --- a/crates/config/src/error.rs +++ b/crates/config/src/error.rs @@ -110,7 +110,7 @@ pub enum SolidityErrorCode { ContractExceeds24576Bytes, /// Warning after shanghai if init code size exceeds 49152 bytes ContractInitCodeSizeExceeds49152Bytes, - /// Warning that Function state mutability can be restricted to [view,pure] + /// Warning that Function state mutability can be restricted to view/pure. FunctionStateMutabilityCanBeRestricted, /// Warning: Unused local variable UnusedLocalVariable, @@ -134,12 +134,14 @@ pub enum SolidityErrorCode { Unreachable, /// Missing pragma solidity PragmaSolidity, + /// Uses transient opcodes + TransientStorageUsed, + /// There are more than 256 warnings. Ignoring the rest. + TooManyWarnings, /// All other error codes Other(u64), } -// === impl SolidityErrorCode === - impl SolidityErrorCode { /// The textual identifier for this error /// @@ -147,6 +149,7 @@ impl SolidityErrorCode { pub fn as_str(&self) -> Result<&'static str, u64> { let s = match self { SolidityErrorCode::SpdxLicenseNotProvided => "license", + SolidityErrorCode::VisibilityForConstructorIsIgnored => "constructor-visibility", SolidityErrorCode::ContractExceeds24576Bytes => "code-size", SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes => "init-code-size", SolidityErrorCode::FunctionStateMutabilityCanBeRestricted => "func-mutability", @@ -160,8 +163,9 @@ impl SolidityErrorCode { SolidityErrorCode::UnnamedReturnVariable => "unnamed-return", SolidityErrorCode::Unreachable => "unreachable", SolidityErrorCode::PragmaSolidity => "pragma-solidity", + SolidityErrorCode::TransientStorageUsed => "transient-storage", + SolidityErrorCode::TooManyWarnings => "too-many-warnings", SolidityErrorCode::Other(code) => return Err(*code), - SolidityErrorCode::VisibilityForConstructorIsIgnored => "constructor-visibility", }; Ok(s) } @@ -171,7 +175,9 @@ impl From for u64 { fn from(code: SolidityErrorCode) -> u64 { match code { SolidityErrorCode::SpdxLicenseNotProvided => 1878, + SolidityErrorCode::VisibilityForConstructorIsIgnored => 2462, SolidityErrorCode::ContractExceeds24576Bytes => 5574, + SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes => 3860, SolidityErrorCode::FunctionStateMutabilityCanBeRestricted => 2018, SolidityErrorCode::UnusedLocalVariable => 2072, SolidityErrorCode::UnusedFunctionParameter => 5667, @@ -183,8 +189,8 @@ impl From for u64 { SolidityErrorCode::UnnamedReturnVariable => 6321, SolidityErrorCode::Unreachable => 5740, SolidityErrorCode::PragmaSolidity => 3420, - SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes => 3860, - SolidityErrorCode::VisibilityForConstructorIsIgnored => 2462, + SolidityErrorCode::TransientStorageUsed => 2394, + SolidityErrorCode::TooManyWarnings => 4591, SolidityErrorCode::Other(code) => code, } } @@ -204,20 +210,23 @@ impl FromStr for SolidityErrorCode { fn from_str(s: &str) -> Result { let code = match s { - "unreachable" => SolidityErrorCode::Unreachable, - "unused-return" => SolidityErrorCode::UnnamedReturnVariable, - "unused-param" => SolidityErrorCode::UnusedFunctionParameter, - "unused-var" => SolidityErrorCode::UnusedLocalVariable, + "license" => SolidityErrorCode::SpdxLicenseNotProvided, + "constructor-visibility" => SolidityErrorCode::VisibilityForConstructorIsIgnored, "code-size" => SolidityErrorCode::ContractExceeds24576Bytes, "init-code-size" => SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes, - "shadowing" => SolidityErrorCode::ShadowsExistingDeclaration, "func-mutability" => SolidityErrorCode::FunctionStateMutabilityCanBeRestricted, - "license" => SolidityErrorCode::SpdxLicenseNotProvided, - "pragma-solidity" => SolidityErrorCode::PragmaSolidity, + "unused-var" => SolidityErrorCode::UnusedLocalVariable, + "unused-param" => SolidityErrorCode::UnusedFunctionParameter, + "unused-return" => SolidityErrorCode::ReturnValueOfCallsNotUsed, "virtual-interfaces" => SolidityErrorCode::InterfacesExplicitlyVirtual, "missing-receive-ether" => SolidityErrorCode::PayableNoReceiveEther, + "shadowing" => SolidityErrorCode::ShadowsExistingDeclaration, "same-varname" => SolidityErrorCode::DeclarationSameNameAsAnother, - "constructor-visibility" => SolidityErrorCode::VisibilityForConstructorIsIgnored, + "unnamed-return" => SolidityErrorCode::UnnamedReturnVariable, + "unreachable" => SolidityErrorCode::Unreachable, + "pragma-solidity" => SolidityErrorCode::PragmaSolidity, + "transient-storage" => SolidityErrorCode::TransientStorageUsed, + "too-many-warnings" => SolidityErrorCode::TooManyWarnings, _ => return Err(format!("Unknown variant {s}")), }; @@ -229,6 +238,7 @@ impl From for SolidityErrorCode { fn from(code: u64) -> Self { match code { 1878 => SolidityErrorCode::SpdxLicenseNotProvided, + 2462 => SolidityErrorCode::VisibilityForConstructorIsIgnored, 5574 => SolidityErrorCode::ContractExceeds24576Bytes, 3860 => SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes, 2018 => SolidityErrorCode::FunctionStateMutabilityCanBeRestricted, @@ -240,9 +250,9 @@ impl From for SolidityErrorCode { 2519 => SolidityErrorCode::ShadowsExistingDeclaration, 8760 => SolidityErrorCode::DeclarationSameNameAsAnother, 6321 => SolidityErrorCode::UnnamedReturnVariable, - 3420 => SolidityErrorCode::PragmaSolidity, 5740 => SolidityErrorCode::Unreachable, - 2462 => SolidityErrorCode::VisibilityForConstructorIsIgnored, + 3420 => SolidityErrorCode::PragmaSolidity, + 2394 => SolidityErrorCode::TransientStorageUsed, other => SolidityErrorCode::Other(other), } } diff --git a/crates/config/src/etherscan.rs b/crates/config/src/etherscan.rs index 6e2030cb9..c4f3fe700 100644 --- a/crates/config/src/etherscan.rs +++ b/crates/config/src/etherscan.rs @@ -66,8 +66,6 @@ pub struct EtherscanConfigs { configs: BTreeMap, } -// === impl Endpoints === - impl EtherscanConfigs { /// Creates a new list of etherscan configs pub fn new(configs: impl IntoIterator, EtherscanConfig)>) -> Self { @@ -113,7 +111,7 @@ impl DerefMut for EtherscanConfigs { } } -/// Container type for _resolved_ etherscan keys, see [EtherscanConfigs::resolve_all()] +/// Container type for _resolved_ etherscan keys, see [`EtherscanConfigs::resolved`]. #[derive(Clone, Debug, Default, PartialEq, Eq)] pub struct ResolvedEtherscanConfigs { /// contains all named `ResolvedEtherscanConfig` or an error if we failed to resolve the env @@ -121,8 +119,6 @@ pub struct ResolvedEtherscanConfigs { configs: BTreeMap>, } -// === impl ResolvedEtherscanConfigs === - impl ResolvedEtherscanConfigs { /// Creates a new list of resolved etherscan configs pub fn new( @@ -181,8 +177,6 @@ pub struct EtherscanConfig { pub key: EtherscanApiKey, } -// === impl EtherscanConfig === - impl EtherscanConfig { /// Returns the etherscan config required to create a client. /// @@ -262,8 +256,6 @@ pub struct ResolvedEtherscanConfig { pub chain: Option, } -// === impl ResolvedEtherscanConfig === - impl ResolvedEtherscanConfig { /// Creates a new instance using the api key and chain pub fn create(api_key: impl Into, chain: impl Into) -> Option { @@ -306,32 +298,29 @@ impl ResolvedEtherscanConfig { let (mainnet_api, mainnet_url) = NamedChain::Mainnet.etherscan_urls().expect("exist; qed"); let cache = chain - .or_else(|| { - if api_url == mainnet_api { - // try to match against mainnet, which is usually the most common target - Some(NamedChain::Mainnet.into()) - } else { - None - } - }) + // try to match against mainnet, which is usually the most common target + .or_else(|| (api_url == mainnet_api).then(Chain::mainnet)) .and_then(Config::foundry_etherscan_chain_cache_dir); - if let Some(ref cache_path) = cache { + if let Some(cache_path) = &cache { // we also create the `sources` sub dir here if let Err(err) = std::fs::create_dir_all(cache_path.join("sources")) { warn!("could not create etherscan cache dir: {:?}", err); } } + let api_url = into_url(&api_url)?; + let client = reqwest::Client::builder() + .user_agent(ETHERSCAN_USER_AGENT) + .tls_built_in_root_certs(api_url.scheme() == "https") + .build()?; foundry_block_explorers::Client::builder() - .with_client(reqwest::Client::builder().user_agent(ETHERSCAN_USER_AGENT).build()?) + .with_client(client) .with_api_key(api_key) - .with_api_url(api_url.as_str())? - .with_url( - // the browser url is not used/required by the client so we can simply set the - // mainnet browser url here - browser_url.as_deref().unwrap_or(mainnet_url), - )? + .with_api_url(api_url)? + // the browser url is not used/required by the client so we can simply set the + // mainnet browser url here + .with_url(browser_url.as_deref().unwrap_or(mainnet_url))? .with_cache(cache, Duration::from_secs(24 * 60 * 60)) .build() } @@ -353,8 +342,6 @@ pub enum EtherscanApiKey { Env(String), } -// === impl EtherscanApiKey === - impl EtherscanApiKey { /// Returns the key variant pub fn as_key(&self) -> Option<&str> { @@ -419,6 +406,13 @@ impl fmt::Display for EtherscanApiKey { } } +/// This is a hack to work around `IntoUrl`'s sealed private functions, which can't be called +/// normally. +#[inline] +fn into_url(url: impl reqwest::IntoUrl) -> std::result::Result { + url.into_url() +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/config/src/filter.rs b/crates/config/src/filter.rs new file mode 100644 index 000000000..385b44225 --- /dev/null +++ b/crates/config/src/filter.rs @@ -0,0 +1,204 @@ +//! Helpers for constructing and using [FileFilter]s. + +use core::fmt; +use foundry_compilers::FileFilter; +use std::{ + convert::Infallible, + path::{Path, PathBuf}, + str::FromStr, +}; + +/// Expand globs with a root path. +pub fn expand_globs( + root: &Path, + patterns: impl IntoIterator>, +) -> eyre::Result> { + let mut expanded = Vec::new(); + for pattern in patterns { + for paths in glob::glob(&root.join(pattern.as_ref()).display().to_string())? { + expanded.push(paths?); + } + } + Ok(expanded) +} + +/// A `globset::Glob` that creates its `globset::GlobMatcher` when its created, so it doesn't need +/// to be compiled when the filter functions `TestFilter` functions are called. +#[derive(Clone, Debug)] +pub struct GlobMatcher { + /// The compiled glob + pub matcher: globset::GlobMatcher, +} + +impl GlobMatcher { + /// Creates a new `GlobMatcher` from a `globset::Glob`. + pub fn new(glob: globset::Glob) -> Self { + Self { matcher: glob.compile_matcher() } + } + + /// Tests whether the given path matches this pattern or not. + /// + /// The glob `./test/*` won't match absolute paths like `test/Contract.sol`, which is common + /// format here, so we also handle this case here + pub fn is_match(&self, path: &Path) -> bool { + if self.matcher.is_match(path) { + return true; + } + + if let Some(file_name) = path.file_name().and_then(|n| n.to_str()) { + if file_name.contains(self.as_str()) { + return true; + } + } + + if !path.starts_with("./") && self.as_str().starts_with("./") { + return self.matcher.is_match(format!("./{}", path.display())); + } + + false + } + + /// Matches file only if the filter does not apply. + /// + /// This returns the inverse of `self.is_match(file)`. + fn is_match_exclude(&self, path: &Path) -> bool { + !self.is_match(path) + } + + /// Returns the `globset::Glob`. + pub fn glob(&self) -> &globset::Glob { + self.matcher.glob() + } + + /// Returns the `Glob` string used to compile this matcher. + pub fn as_str(&self) -> &str { + self.glob().glob() + } +} + +impl fmt::Display for GlobMatcher { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.glob().fmt(f) + } +} + +impl FromStr for GlobMatcher { + type Err = globset::Error; + + fn from_str(s: &str) -> Result { + s.parse::().map(Self::new) + } +} + +impl From for GlobMatcher { + fn from(glob: globset::Glob) -> Self { + Self::new(glob) + } +} + +/// Bundles multiple `SkipBuildFilter` into a single `FileFilter` +#[derive(Clone, Debug)] +pub struct SkipBuildFilters { + /// All provided filters. + pub matchers: Vec, + /// Root of the project. + pub project_root: PathBuf, +} + +impl FileFilter for SkipBuildFilters { + /// Only returns a match if _no_ exclusion filter matches + fn is_match(&self, file: &Path) -> bool { + self.matchers.iter().all(|matcher| { + if !matcher.is_match_exclude(file) { + false + } else { + file.strip_prefix(&self.project_root) + .map_or(true, |stripped| matcher.is_match_exclude(stripped)) + } + }) + } +} + +impl SkipBuildFilters { + /// Creates a new `SkipBuildFilters` from multiple `SkipBuildFilter`. + pub fn new>( + filters: impl IntoIterator, + project_root: PathBuf, + ) -> Self { + let matchers = filters.into_iter().map(|m| m.into()).collect(); + Self { matchers, project_root } + } +} + +/// A filter that excludes matching contracts from the build +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum SkipBuildFilter { + /// Exclude all `.t.sol` contracts + Tests, + /// Exclude all `.s.sol` contracts + Scripts, + /// Exclude if the file matches + Custom(String), +} + +impl SkipBuildFilter { + fn new(s: &str) -> Self { + match s { + "test" | "tests" => SkipBuildFilter::Tests, + "script" | "scripts" => SkipBuildFilter::Scripts, + s => SkipBuildFilter::Custom(s.to_string()), + } + } + + /// Returns the pattern to match against a file + pub fn file_pattern(&self) -> &str { + match self { + SkipBuildFilter::Tests => ".t.sol", + SkipBuildFilter::Scripts => ".s.sol", + SkipBuildFilter::Custom(s) => s.as_str(), + } + } +} + +impl FromStr for SkipBuildFilter { + type Err = Infallible; + + fn from_str(s: &str) -> Result { + Ok(Self::new(s)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_build_filter() { + let tests = GlobMatcher::from_str(SkipBuildFilter::Tests.file_pattern()).unwrap(); + let scripts = GlobMatcher::from_str(SkipBuildFilter::Scripts.file_pattern()).unwrap(); + let custom = |s| GlobMatcher::from_str(s).unwrap(); + + let file = Path::new("A.t.sol"); + assert!(!tests.is_match_exclude(file)); + assert!(scripts.is_match_exclude(file)); + assert!(!custom("A.t").is_match_exclude(file)); + + let file = Path::new("A.s.sol"); + assert!(tests.is_match_exclude(file)); + assert!(!scripts.is_match_exclude(file)); + assert!(!custom("A.s").is_match_exclude(file)); + + let file = Path::new("/home/test/Foo.sol"); + assert!(!custom("*/test/**").is_match_exclude(file)); + + let file = Path::new("/home/script/Contract.sol"); + assert!(!custom("*/script/**").is_match_exclude(file)); + } + + #[test] + fn can_match_glob_paths() { + let matcher: GlobMatcher = "./test/*".parse().unwrap(); + assert!(matcher.is_match(Path::new("test/Contract.sol"))); + assert!(matcher.is_match(Path::new("./test/Contract.sol"))); + } +} diff --git a/crates/config/src/fix.rs b/crates/config/src/fix.rs index 086fbb7db..73d52d979 100644 --- a/crates/config/src/fix.rs +++ b/crates/config/src/fix.rs @@ -1,4 +1,4 @@ -//! Helpers to automatically fix configuration warnings +//! Helpers to automatically fix configuration warnings. use crate::{Config, Warning}; use figment::providers::Env; @@ -10,32 +10,36 @@ use std::{ /// A convenience wrapper around a TOML document and the path it was read from struct TomlFile { - doc: toml_edit::Document, + doc: toml_edit::DocumentMut, path: PathBuf, } impl TomlFile { - fn open(path: impl AsRef) -> Result> { + fn open(path: impl AsRef) -> eyre::Result { let path = path.as_ref().to_owned(); let doc = fs::read_to_string(&path)?.parse()?; Ok(Self { doc, path }) } - fn doc(&self) -> &toml_edit::Document { + + fn doc(&self) -> &toml_edit::DocumentMut { &self.doc } - fn doc_mut(&mut self) -> &mut toml_edit::Document { + + fn doc_mut(&mut self) -> &mut toml_edit::DocumentMut { &mut self.doc } + fn path(&self) -> &Path { self.path.as_ref() } + fn save(&self) -> io::Result<()> { fs::write(self.path(), self.doc().to_string()) } } impl Deref for TomlFile { - type Target = toml_edit::Document; + type Target = toml_edit::DocumentMut; fn deref(&self) -> &Self::Target { self.doc() } @@ -47,7 +51,7 @@ impl DerefMut for TomlFile { } } -/// The error emitted when failing to insert a profile into [profile] +/// The error emitted when failing to insert into a profile. #[derive(Debug)] struct InsertProfileError { pub message: String, @@ -216,7 +220,7 @@ pub fn fix_tomls() -> Vec { mod tests { use super::*; use figment::Jail; - use pretty_assertions::assert_eq; + use similar_asserts::assert_eq; macro_rules! fix_test { ($(#[$attr:meta])* $name:ident, $fun:expr) => { diff --git a/crates/config/src/fs_permissions.rs b/crates/config/src/fs_permissions.rs index fbbe67af9..5260b7488 100644 --- a/crates/config/src/fs_permissions.rs +++ b/crates/config/src/fs_permissions.rs @@ -17,8 +17,6 @@ pub struct FsPermissions { pub permissions: Vec, } -// === impl FsPermissions === - impl FsPermissions { /// Creates anew instance with the given `permissions` pub fn new(permissions: impl IntoIterator) -> Self { @@ -69,22 +67,20 @@ impl FsPermissions { } /// Updates all `allowed_paths` and joins ([`Path::join`]) the `root` with all entries - pub fn join_all(&mut self, root: impl AsRef) { - let root = root.as_ref(); + pub fn join_all(&mut self, root: &Path) { self.permissions.iter_mut().for_each(|perm| { perm.path = root.join(&perm.path); }) } /// Same as [`Self::join_all`] but consumes the type - pub fn joined(mut self, root: impl AsRef) -> Self { + pub fn joined(mut self, root: &Path) -> Self { self.join_all(root); self } /// Removes all existing permissions for the given path - pub fn remove(&mut self, path: impl AsRef) { - let path = path.as_ref(); + pub fn remove(&mut self, path: &Path) { self.permissions.retain(|permission| permission.path != path) } @@ -108,8 +104,6 @@ pub struct PathPermission { pub path: PathBuf, } -// === impl PathPermission === - impl PathPermission { /// Returns a new permission for the path and the given access pub fn new(path: impl Into, access: FsAccessPermission) -> Self { @@ -174,8 +168,6 @@ pub enum FsAccessPermission { Write, } -// === impl FsAccessPermission === - impl FsAccessPermission { /// Returns true if the access is allowed pub fn is_granted(&self, kind: FsAccessKind) -> bool { diff --git a/crates/config/src/fuzz.rs b/crates/config/src/fuzz.rs index 6b076e54d..601b799b6 100644 --- a/crates/config/src/fuzz.rs +++ b/crates/config/src/fuzz.rs @@ -6,9 +6,10 @@ use crate::inline::{ }; use alloy_primitives::U256; use serde::{Deserialize, Serialize}; +use std::path::PathBuf; /// Contains for fuzz testing -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct FuzzConfig { /// The number of test cases that must execute for each property test pub runs: u32, @@ -23,6 +24,12 @@ pub struct FuzzConfig { /// The fuzz dictionary configuration #[serde(flatten)] pub dictionary: FuzzDictionaryConfig, + /// Number of runs to execute and include in the gas report. + pub gas_report_samples: u32, + /// Path where fuzz failures are recorded and replayed. + pub failure_persist_dir: Option, + /// Name of the file to record fuzz failures, defaults to `failures`. + pub failure_persist_file: Option, /// When enabled, filters all addresses below 2^16, as they are reserved in zkSync. pub no_zksync_reserved_addresses: bool, } @@ -34,6 +41,25 @@ impl Default for FuzzConfig { max_test_rejects: 65536, seed: None, dictionary: FuzzDictionaryConfig::default(), + gas_report_samples: 256, + failure_persist_dir: None, + failure_persist_file: None, + no_zksync_reserved_addresses: false, + } + } +} + +impl FuzzConfig { + /// Creates fuzz configuration to write failures in `{PROJECT_ROOT}/cache/fuzz` dir. + pub fn new(cache_dir: PathBuf) -> Self { + FuzzConfig { + runs: 256, + max_test_rejects: 65536, + seed: None, + dictionary: FuzzDictionaryConfig::default(), + gas_report_samples: 256, + failure_persist_dir: Some(cache_dir), + failure_persist_file: Some("failures".to_string()), no_zksync_reserved_addresses: false, } } @@ -51,8 +77,7 @@ impl InlineConfigParser for FuzzConfig { return Ok(None) } - // self is Copy. We clone it with dereference. - let mut conf_clone = *self; + let mut conf_clone = self.clone(); for pair in overrides { let key = pair.0; @@ -63,6 +88,7 @@ impl InlineConfigParser for FuzzConfig { "dictionary-weight" => { conf_clone.dictionary.dictionary_weight = parse_config_u32(key, value)? } + "failure-persist-file" => conf_clone.failure_persist_file = Some(value), "no-zksync-reserved-addresses" => { conf_clone.no_zksync_reserved_addresses = parse_config_bool(key, value)? } @@ -129,11 +155,13 @@ mod tests { let configs = &[ "forge-config: default.fuzz.runs = 42424242".to_string(), "forge-config: default.fuzz.dictionary-weight = 42".to_string(), + "forge-config: default.fuzz.failure-persist-file = fuzz-failure".to_string(), ]; let base_config = FuzzConfig::default(); let merged: FuzzConfig = base_config.try_merge(configs).expect("No errors").unwrap(); assert_eq!(merged.runs, 42424242); assert_eq!(merged.dictionary.dictionary_weight, 42); + assert_eq!(merged.failure_persist_file, Some("fuzz-failure".to_string())); } #[test] diff --git a/crates/config/src/inline/conf_parser.rs b/crates/config/src/inline/conf_parser.rs index acad057ae..1f6fca6c7 100644 --- a/crates/config/src/inline/conf_parser.rs +++ b/crates/config/src/inline/conf_parser.rs @@ -149,7 +149,7 @@ mod tests { function: Default::default(), line: Default::default(), docs: r" - forge-config: ciii.invariant.depth = 1 + forge-config: ciii.invariant.depth = 1 forge-config: default.invariant.depth = 1 " .into(), @@ -167,7 +167,7 @@ mod tests { function: Default::default(), line: Default::default(), docs: r" - forge-config: ci.invariant.depth = 1 + forge-config: ci.invariant.depth = 1 forge-config: default.invariant.depth = 1 " .into(), diff --git a/crates/config/src/inline/error.rs b/crates/config/src/inline/error.rs index e998a2156..ddcb6a61b 100644 --- a/crates/config/src/inline/error.rs +++ b/crates/config/src/inline/error.rs @@ -1,4 +1,4 @@ -/// Errors returned by the [`InlineConfigParser`] trait. +/// Errors returned by the [`InlineConfigParser`](crate::InlineConfigParser) trait. #[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)] pub enum InlineConfigParserError { /// An invalid configuration property has been provided. @@ -16,8 +16,7 @@ pub enum InlineConfigParserError { ParseBool(String, String), } -/// Wrapper error struct that catches config parsing -/// errors [`InlineConfigParserError`], enriching them with context information +/// Wrapper error struct that catches config parsing errors, enriching them with context information /// reporting the misconfigured line. #[derive(Debug, thiserror::Error)] #[error("Inline config error detected at {line}")] diff --git a/crates/config/src/inline/mod.rs b/crates/config/src/inline/mod.rs index af2cbe7e3..adc67424f 100644 --- a/crates/config/src/inline/mod.rs +++ b/crates/config/src/inline/mod.rs @@ -1,13 +1,15 @@ use crate::Config; -pub use conf_parser::{parse_config_bool, parse_config_u32, validate_profiles, InlineConfigParser}; -pub use error::{InlineConfigError, InlineConfigParserError}; -pub use natspec::NatSpec; use once_cell::sync::Lazy; -use std::{borrow::Cow, collections::HashMap}; +use std::collections::HashMap; mod conf_parser; +pub use conf_parser::*; + mod error; +pub use error::*; + mod natspec; +pub use natspec::*; pub const INLINE_CONFIG_FUZZ_KEY: &str = "fuzz"; pub const INLINE_CONFIG_INVARIANT_KEY: &str = "invariant"; @@ -25,47 +27,29 @@ static INLINE_CONFIG_PREFIX_SELECTED_PROFILE: Lazy = Lazy::new(|| { pub struct InlineConfig { /// Maps a (test-contract, test-function) pair /// to a specific configuration provided by the user. - configs: HashMap, T>, + configs: HashMap<(String, String), T>, } impl InlineConfig { /// Returns an inline configuration, if any, for a test function. /// Configuration is identified by the pair "contract", "function". - pub fn get(&self, contract_id: C, fn_name: F) -> Option<&T> - where - C: Into, - F: Into, - { - // TODO use borrow - let key = InlineConfigKey { - contract: Cow::Owned(contract_id.into()), - function: Cow::Owned(fn_name.into()), - }; + pub fn get(&self, contract_id: &str, fn_name: &str) -> Option<&T> { + let key = (contract_id.to_string(), fn_name.to_string()); self.configs.get(&key) } /// Inserts an inline configuration, for a test function. - /// Configuration is identified by the pair "contract", "function". + /// Configuration is identified by the pair "contract", "function". pub fn insert(&mut self, contract_id: C, fn_name: F, config: T) where C: Into, F: Into, { - let key = InlineConfigKey { - contract: Cow::Owned(contract_id.into()), - function: Cow::Owned(fn_name.into()), - }; + let key = (contract_id.into(), fn_name.into()); self.configs.insert(key, config); } } -/// Represents a (test-contract, test-function) pair -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -struct InlineConfigKey<'a> { - contract: Cow<'a, str>, - function: Cow<'a, str>, -} - pub(crate) fn remove_whitespaces(s: &str) -> String { s.chars().filter(|c| !c.is_whitespace()).collect() } diff --git a/crates/config/src/inline/natspec.rs b/crates/config/src/inline/natspec.rs index 11557fd7a..27742eb56 100644 --- a/crates/config/src/inline/natspec.rs +++ b/crates/config/src/inline/natspec.rs @@ -4,10 +4,11 @@ use foundry_compilers::{ ProjectCompileOutput, }; use serde_json::Value; +use solang_parser::pt; use std::{collections::BTreeMap, path::Path}; /// Convenient struct to hold in-line per-test configurations -#[derive(Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct NatSpec { /// The parent contract of the natspec pub contract: String, @@ -28,14 +29,28 @@ impl NatSpec { pub fn parse(output: &ProjectCompileOutput, root: &Path) -> Vec { let mut natspecs: Vec = vec![]; + let solc = SolcParser::new(); + let solang = SolangParser::new(); for (id, artifact) in output.artifact_ids() { - let Some(ast) = &artifact.ast else { continue }; - let path = id.source.as_path(); - let path = path.strip_prefix(root).unwrap_or(path); - // id.identifier + let abs_path = id.source.as_path(); + let path = abs_path.strip_prefix(root).unwrap_or(abs_path); + let contract_name = id.name.split('.').next().unwrap(); + // `id.identifier` but with the stripped path. let contract = format!("{}:{}", path.display(), id.name); - let Some(node) = contract_root_node(&ast.nodes, &contract) else { continue }; - apply(&mut natspecs, &contract, node) + + let mut used_solc_ast = false; + if let Some(ast) = &artifact.ast { + if let Some(node) = solc.contract_root_node(&ast.nodes, &contract) { + solc.parse(&mut natspecs, &contract, node); + used_solc_ast = true; + } + } + + if !used_solc_ast { + if let Ok(src) = std::fs::read_to_string(abs_path) { + solang.parse(&mut natspecs, &src, &contract, contract_name); + } + } } natspecs @@ -63,89 +78,245 @@ impl NatSpec { /// Returns a list of all the configuration lines available in the natspec pub fn config_lines(&self) -> impl Iterator + '_ { - self.docs.lines().map(remove_whitespaces).filter(|line| line.contains(INLINE_CONFIG_PREFIX)) + self.docs.lines().filter(|line| line.contains(INLINE_CONFIG_PREFIX)).map(remove_whitespaces) } } -/// Given a list of nodes, find a "ContractDefinition" node that matches -/// the provided contract_id. -fn contract_root_node<'a>(nodes: &'a [Node], contract_id: &'a str) -> Option<&'a Node> { - for n in nodes.iter() { - if let NodeType::ContractDefinition = n.node_type { - let contract_data = &n.other; - if let Value::String(contract_name) = contract_data.get("name")? { - if contract_id.ends_with(contract_name) { - return Some(n) +struct SolcParser { + _private: (), +} + +impl SolcParser { + fn new() -> Self { + Self { _private: () } + } + + /// Given a list of nodes, find a "ContractDefinition" node that matches + /// the provided contract_id. + fn contract_root_node<'a>(&self, nodes: &'a [Node], contract_id: &str) -> Option<&'a Node> { + for n in nodes.iter() { + if let NodeType::ContractDefinition = n.node_type { + let contract_data = &n.other; + if let Value::String(contract_name) = contract_data.get("name")? { + if contract_id.ends_with(contract_name) { + return Some(n) + } } } } + None + } + + /// Implements a DFS over a compiler output node and its children. + /// If a natspec is found it is added to `natspecs` + fn parse(&self, natspecs: &mut Vec, contract: &str, node: &Node) { + for n in node.nodes.iter() { + if let Some((function, docs, line)) = self.get_fn_data(n) { + natspecs.push(NatSpec { contract: contract.into(), function, line, docs }) + } + self.parse(natspecs, contract, n); + } } - None -} -/// Implements a DFS over a compiler output node and its children. -/// If a natspec is found it is added to `natspecs` -fn apply(natspecs: &mut Vec, contract: &str, node: &Node) { - for n in node.nodes.iter() { - if let Some((function, docs, line)) = get_fn_data(n) { - natspecs.push(NatSpec { contract: contract.into(), function, line, docs }) + /// Given a compilation output node, if it is a function definition + /// that also contains a natspec then return a tuple of: + /// - Function name + /// - Natspec text + /// - Natspec position with format "row:col:length" + /// + /// Return None otherwise. + fn get_fn_data(&self, node: &Node) -> Option<(String, String, String)> { + if let NodeType::FunctionDefinition = node.node_type { + let fn_data = &node.other; + let fn_name: String = self.get_fn_name(fn_data)?; + let (fn_docs, docs_src_line): (String, String) = self.get_fn_docs(fn_data)?; + return Some((fn_name, fn_docs, docs_src_line)) } - apply(natspecs, contract, n); + + None + } + + /// Given a dictionary of function data returns the name of the function. + fn get_fn_name(&self, fn_data: &BTreeMap) -> Option { + match fn_data.get("name")? { + Value::String(fn_name) => Some(fn_name.into()), + _ => None, + } + } + + /// Inspects Solc compiler output for documentation comments. Returns: + /// - `Some((String, String))` in case the function has natspec comments. First item is a + /// textual natspec representation, the second item is the natspec src line, in the form + /// "raw:col:length". + /// - `None` in case the function has not natspec comments. + fn get_fn_docs(&self, fn_data: &BTreeMap) -> Option<(String, String)> { + if let Value::Object(fn_docs) = fn_data.get("documentation")? { + if let Value::String(comment) = fn_docs.get("text")? { + if comment.contains(INLINE_CONFIG_PREFIX) { + let mut src_line = fn_docs + .get("src") + .map(|src| src.to_string()) + .unwrap_or_else(|| String::from("")); + + src_line.retain(|c| c != '"'); + return Some((comment.into(), src_line)) + } + } + } + None } } -/// Given a compilation output node, if it is a function definition -/// that also contains a natspec then return a tuple of: -/// - Function name -/// - Natspec text -/// - Natspec position with format "row:col:length" -/// -/// Return None otherwise. -fn get_fn_data(node: &Node) -> Option<(String, String, String)> { - if let NodeType::FunctionDefinition = node.node_type { - let fn_data = &node.other; - let fn_name: String = get_fn_name(fn_data)?; - let (fn_docs, docs_src_line): (String, String) = get_fn_docs(fn_data)?; - return Some((fn_name, fn_docs, docs_src_line)) - } - - None +struct SolangParser { + _private: (), } -/// Given a dictionary of function data returns the name of the function. -fn get_fn_name(fn_data: &BTreeMap) -> Option { - match fn_data.get("name")? { - Value::String(fn_name) => Some(fn_name.into()), - _ => None, +impl SolangParser { + fn new() -> Self { + Self { _private: () } } -} -/// Inspects Solc compiler output for documentation comments. Returns: -/// - `Some((String, String))` in case the function has natspec comments. First item is a textual -/// natspec representation, the second item is the natspec src line, in the form "raw:col:length". -/// - `None` in case the function has not natspec comments. -fn get_fn_docs(fn_data: &BTreeMap) -> Option<(String, String)> { - if let Value::Object(fn_docs) = fn_data.get("documentation")? { - if let Value::String(comment) = fn_docs.get("text")? { - if comment.contains(INLINE_CONFIG_PREFIX) { - let mut src_line = fn_docs - .get("src") - .map(|src| src.to_string()) - .unwrap_or_else(|| String::from("")); - - src_line.retain(|c| c != '"'); - return Some((comment.into(), src_line)) + fn parse( + &self, + natspecs: &mut Vec, + src: &str, + contract_id: &str, + contract_name: &str, + ) { + // Fast path to avoid parsing the file. + if !src.contains(INLINE_CONFIG_PREFIX) { + return; + } + + let Ok((pt, comments)) = solang_parser::parse(src, 0) else { return }; + for item in &pt.0 { + let pt::SourceUnitPart::ContractDefinition(c) = item else { continue }; + let Some(id) = c.name.as_ref() else { continue }; + if id.name != contract_name { + continue + }; + let mut prev_end = c.loc.start(); + for part in &c.parts { + let pt::ContractPart::FunctionDefinition(f) = part else { continue }; + let start = f.loc.start(); + // Parse doc comments in between the previous function and the current one. + let docs = solang_parser::doccomment::parse_doccomments(&comments, prev_end, start); + let docs = docs + .into_iter() + .flat_map(|doc| doc.into_comments()) + .filter(|doc| doc.value.contains(INLINE_CONFIG_PREFIX)); + for doc in docs { + natspecs.push(NatSpec { + contract: contract_id.to_string(), + function: f.name.as_ref().map(|id| id.to_string()).unwrap_or_default(), + line: "0:0:0".to_string(), + docs: doc.value, + }); + } + prev_end = f.loc.end(); } } } - None } #[cfg(test)] mod tests { - use crate::{inline::natspec::get_fn_docs, NatSpec}; - use serde_json::{json, Value}; - use std::collections::BTreeMap; + use super::*; + use serde_json::json; + + #[test] + fn parse_solang() { + let src = " +contract C { /// forge-config: default.fuzz.runs = 600 + +\t\t\t\t /// forge-config: default.fuzz.runs = 601 + + function f1() {} + /** forge-config: default.fuzz.runs = 700 */ +function f2() {} /** forge-config: default.fuzz.runs = 800 */ function f3() {} + +/** + * forge-config: default.fuzz.runs = 1024 + * forge-config: default.fuzz.max-test-rejects = 500 + */ + function f4() {} +} +"; + let mut natspecs = vec![]; + let solang = SolangParser::new(); + let id = || "path.sol:C".to_string(); + let default_line = || "0:0:0".to_string(); + solang.parse(&mut natspecs, src, &id(), "C"); + assert_eq!( + natspecs, + [ + // f1 + NatSpec { + contract: id(), + function: "f1".to_string(), + line: default_line(), + docs: "forge-config: default.fuzz.runs = 600\nforge-config: default.fuzz.runs = 601".to_string(), + }, + // f2 + NatSpec { + contract: id(), + function: "f2".to_string(), + line: default_line(), + docs: "forge-config: default.fuzz.runs = 700".to_string(), + }, + // f3 + NatSpec { + contract: id(), + function: "f3".to_string(), + line: default_line(), + docs: "forge-config: default.fuzz.runs = 800".to_string(), + }, + // f4 + NatSpec { + contract: id(), + function: "f4".to_string(), + line: default_line(), + docs: "forge-config: default.fuzz.runs = 1024\nforge-config: default.fuzz.max-test-rejects = 500".to_string(), + }, + ] + ); + } + + #[test] + fn parse_solang_2() { + let src = r#" +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import "ds-test/test.sol"; + +contract FuzzInlineConf is DSTest { + /** + * forge-config: default.fuzz.runs = 1024 + * forge-config: default.fuzz.max-test-rejects = 500 + */ + function testInlineConfFuzz(uint8 x) public { + require(true, "this is not going to revert"); + } +} + "#; + let mut natspecs = vec![]; + let solang = SolangParser::new(); + let id = || "inline/FuzzInlineConf.t.sol:FuzzInlineConf".to_string(); + let default_line = || "0:0:0".to_string(); + solang.parse(&mut natspecs, src, &id(), "FuzzInlineConf"); + assert_eq!( + natspecs, + [ + NatSpec { + contract: id(), + function: "testInlineConfFuzz".to_string(), + line: default_line(), + docs: "forge-config: default.fuzz.runs = 1024\nforge-config: default.fuzz.max-test-rejects = 500".to_string(), + }, + ] + ); + } #[test] fn config_lines() { @@ -195,7 +366,7 @@ mod tests { let mut fn_data: BTreeMap = BTreeMap::new(); let doc_without_src_field = json!({ "text": "forge-config:default.fuzz.runs=600" }); fn_data.insert("documentation".into(), doc_without_src_field); - let (_, src_line) = get_fn_docs(&fn_data).expect("Some docs"); + let (_, src_line) = SolcParser::new().get_fn_docs(&fn_data).expect("Some docs"); assert_eq!(src_line, "".to_string()); } @@ -205,7 +376,7 @@ mod tests { let doc_without_src_field = json!({ "text": "forge-config:default.fuzz.runs=600", "src": "73:21:12" }); fn_data.insert("documentation".into(), doc_without_src_field); - let (_, src_line) = get_fn_docs(&fn_data).expect("Some docs"); + let (_, src_line) = SolcParser::new().get_fn_docs(&fn_data).expect("Some docs"); assert_eq!(src_line, "73:21:12".to_string()); } @@ -229,4 +400,52 @@ mod tests { docs: conf.to_string(), } } + + #[test] + fn parse_solang_multiple_contracts_from_same_file() { + let src = r#" +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity >=0.8.0; + +import "ds-test/test.sol"; + +contract FuzzInlineConf is DSTest { + /// forge-config: default.fuzz.runs = 1 + function testInlineConfFuzz1() {} +} + +contract FuzzInlineConf2 is DSTest { + /// forge-config: default.fuzz.runs = 2 + function testInlineConfFuzz2() {} +} + "#; + let mut natspecs = vec![]; + let solang = SolangParser::new(); + let id = || "inline/FuzzInlineConf.t.sol:FuzzInlineConf".to_string(); + let default_line = || "0:0:0".to_string(); + solang.parse(&mut natspecs, src, &id(), "FuzzInlineConf"); + assert_eq!( + natspecs, + [NatSpec { + contract: id(), + function: "testInlineConfFuzz1".to_string(), + line: default_line(), + docs: "forge-config: default.fuzz.runs = 1".to_string(), + },] + ); + + let mut natspecs = vec![]; + let id = || "inline/FuzzInlineConf2.t.sol:FuzzInlineConf2".to_string(); + solang.parse(&mut natspecs, src, &id(), "FuzzInlineConf2"); + assert_eq!( + natspecs, + [NatSpec { + contract: id(), + function: "testInlineConfFuzz2".to_string(), + line: default_line(), + // should not get config from previous contract + docs: "forge-config: default.fuzz.runs = 2".to_string(), + },] + ); + } } diff --git a/crates/config/src/invariant.rs b/crates/config/src/invariant.rs index e8a7a8322..a8570c4f9 100644 --- a/crates/config/src/invariant.rs +++ b/crates/config/src/invariant.rs @@ -8,9 +8,10 @@ use crate::{ }, }; use serde::{Deserialize, Serialize}; +use std::path::PathBuf; /// Contains for invariant testing -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct InvariantConfig { /// The number of runs that must execute for each invariant test group. pub runs: u32, @@ -24,10 +25,15 @@ pub struct InvariantConfig { /// The fuzz dictionary configuration #[serde(flatten)] pub dictionary: FuzzDictionaryConfig, - /// Attempt to shrink the failure case to its smallest sequence of calls - pub shrink_sequence: bool, /// The maximum number of attempts to shrink the sequence - pub shrink_run_limit: usize, + pub shrink_run_limit: u32, + /// The maximum number of rejects via `vm.assume` which can be encountered during a single + /// invariant run. + pub max_assume_rejects: u32, + /// Number of runs to execute and include in the gas report. + pub gas_report_samples: u32, + /// Path where invariant failures are recorded and replayed. + pub failure_persist_dir: Option, /// When enabled, filters all addresses below 2^16, as they are reserved in zkSync. pub no_zksync_reserved_addresses: bool, } @@ -36,17 +42,45 @@ impl Default for InvariantConfig { fn default() -> Self { InvariantConfig { runs: 256, - depth: 15, + depth: 500, fail_on_revert: false, call_override: false, dictionary: FuzzDictionaryConfig { dictionary_weight: 80, ..Default::default() }, - shrink_sequence: true, - shrink_run_limit: 2usize.pow(18_u32), + shrink_run_limit: 5000, + max_assume_rejects: 65536, + gas_report_samples: 256, + failure_persist_dir: None, no_zksync_reserved_addresses: false, } } } +impl InvariantConfig { + /// Creates invariant configuration to write failures in `{PROJECT_ROOT}/cache/fuzz` dir. + pub fn new(cache_dir: PathBuf) -> Self { + InvariantConfig { + runs: 256, + depth: 500, + fail_on_revert: false, + call_override: false, + dictionary: FuzzDictionaryConfig { dictionary_weight: 80, ..Default::default() }, + shrink_run_limit: 5000, + max_assume_rejects: 65536, + gas_report_samples: 256, + failure_persist_dir: Some(cache_dir), + no_zksync_reserved_addresses: false, + } + } + + /// Returns path to failure dir of given invariant test contract. + pub fn failure_dir(self, contract_name: &str) -> PathBuf { + self.failure_persist_dir + .unwrap() + .join("failures") + .join(contract_name.split(':').last().unwrap()) + } +} + impl InlineConfigParser for InvariantConfig { fn config_key() -> String { INLINE_CONFIG_INVARIANT_KEY.into() @@ -59,8 +93,7 @@ impl InlineConfigParser for InvariantConfig { return Ok(None) } - // self is Copy. We clone it with dereference. - let mut conf_clone = *self; + let mut conf_clone = self.clone(); for pair in overrides { let key = pair.0; @@ -70,7 +103,10 @@ impl InlineConfigParser for InvariantConfig { "depth" => conf_clone.depth = parse_config_u32(key, value)?, "fail-on-revert" => conf_clone.fail_on_revert = parse_config_bool(key, value)?, "call-override" => conf_clone.call_override = parse_config_bool(key, value)?, - "shrink-sequence" => conf_clone.shrink_sequence = parse_config_bool(key, value)?, + "failure-persist-dir" => { + conf_clone.failure_persist_dir = Some(PathBuf::from(value)) + } + "shrink-run-limit" => conf_clone.shrink_run_limit = parse_config_u32(key, value)?, "no-zksync-reserved-addresses" => { conf_clone.no_zksync_reserved_addresses = parse_config_bool(key, value)? } diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index f1c4374bc..c84c45031 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -1,6 +1,9 @@ +//! # foundry-config +//! //! Foundry configuration. -#![warn(missing_docs, unused_crate_dependencies)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #[macro_use] extern crate tracing; @@ -15,19 +18,28 @@ use figment::{ }; use foundry_compilers::{ artifacts::{ - output_selection::ContractOutputSelection, serde_helpers, BytecodeHash, DebuggingSettings, - Libraries, ModelCheckerSettings, ModelCheckerTarget, Optimizer, OptimizerDetails, - RevertStrings, Settings, SettingsMetadata, Severity, + output_selection::{ContractOutputSelection, OutputSelection}, + remappings::{RelativeRemapping, Remapping}, + serde_helpers, BytecodeHash, DebuggingSettings, EvmVersion, Libraries, + ModelCheckerSettings, ModelCheckerTarget, Optimizer, OptimizerDetails, RevertStrings, + Settings, SettingsMetadata, Severity, }, cache::SOLIDITY_FILES_CACHE_FILENAME, + compilers::{ + multi::{MultiCompiler, MultiCompilerSettings}, + solc::{Solc, SolcCompiler}, + vyper::{Vyper, VyperSettings}, + zksolc::ZkSolc, + Compiler, + }, error::SolcError, - remappings::{RelativeRemapping, Remapping}, - zksync::{artifacts::Settings as ZkSolcSettings, compile::ZkSolc, config::ZkSolcConfig}, - ConfigurableArtifacts, EvmVersion, Project, ProjectPathsConfig, Solc, SolcConfig, + zksolc::ZkSolcSettings, + zksync::config::ZkSolcConfig, + ConfigurableArtifacts, Project, ProjectPathsConfig, }; use inflector::Inflector; use regex::Regex; -use revm_primitives::SpecId; +use revm_primitives::{FixedBytes, SpecId}; use semver::Version; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::{ @@ -38,17 +50,19 @@ use std::{ str::FromStr, }; -// Macros useful for creating a figment. mod macros; -// Utilities for making it easier to handle tests. pub mod utils; -pub use crate::utils::*; +pub use utils::*; mod endpoints; pub use endpoints::{ResolvedRpcEndpoints, RpcEndpoint, RpcEndpoints}; mod etherscan; +use etherscan::{ + EtherscanConfigError, EtherscanConfigs, EtherscanEnvProvider, ResolvedEtherscanConfig, +}; + mod resolve; pub use resolve::UnresolvedEnvVarError; @@ -59,45 +73,46 @@ pub mod fmt; pub use fmt::FormatterConfig; pub mod fs_permissions; -pub use crate::fs_permissions::FsPermissions; +pub use fs_permissions::FsPermissions; +use fs_permissions::PathPermission; pub mod error; +use error::ExtractConfigError; pub use error::SolidityErrorCode; pub mod doc; pub use doc::DocConfig; +pub mod filter; +pub use filter::SkipBuildFilters; + mod warning; pub use warning::*; -// helpers for fixing configuration warnings pub mod fix; // reexport so cli types can implement `figment::Provider` to easily merge compiler arguments pub use alloy_chains::{Chain, NamedChain}; pub use figment; -/// config providers pub mod providers; - -use crate::{ - error::ExtractConfigError, - etherscan::{EtherscanConfigError, EtherscanConfigs, ResolvedEtherscanConfig}, -}; -use providers::*; +use providers::{remappings::RemappingsProvider, FallbackProfileProvider, WarningsProvider}; mod fuzz; pub use fuzz::{FuzzConfig, FuzzDictionaryConfig}; mod invariant; -use crate::fs_permissions::PathPermission; pub use invariant::InvariantConfig; -use providers::remappings::RemappingsProvider; mod inline; -use crate::etherscan::EtherscanEnvProvider; pub use inline::{validate_profiles, InlineConfig, InlineConfigError, InlineConfigParser, NatSpec}; +pub mod soldeer; +use soldeer::SoldeerConfig; + +mod vyper; +use vyper::VyperConfig; + mod zksync; pub use zksync::*; @@ -168,6 +183,9 @@ pub struct Config { pub allow_paths: Vec, /// additional solc include paths for `--include-path` pub include_paths: Vec, + /// glob patterns to skip + #[serde(with = "from_vec_glob")] + pub skip: Vec, /// whether to force a `project.clean()` pub force: bool, /// evm version to use @@ -183,7 +201,7 @@ pub struct Config { /// auto-detection. /// /// **Note** for backwards compatibility reasons this also accepts solc_version from the toml - /// file, see [`BackwardsCompatProvider`] + /// file, see `BackwardsCompatTomlProvider`. pub solc: Option, /// whether to autodetect the solc compiler version to use pub auto_detect_solc: bool, @@ -248,6 +266,8 @@ pub struct Config { 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 in seconds for vm.prompt cheatcodes + pub prompt_timeout: u64, /// The address which will be executing all tests pub sender: Address, /// The tx.origin value during EVM execution @@ -355,8 +375,7 @@ pub struct Config { /// Whether to compile in sparse mode /// /// If this option is enabled, only the required contracts/files will be selected to be - /// included in solc's output selection, see also - /// [OutputSelection](foundry_compilers::artifacts::output_selection::OutputSelection) + /// included in solc's output selection, see also [`OutputSelection`]. pub sparse_mode: bool, /// Generates additional build info json files for every new build, containing the /// `CompilerInput` and `CompilerOutput`. @@ -372,27 +391,46 @@ pub struct Config { /// This includes what operations can be executed (read, write) pub fs_permissions: FsPermissions, - /// Temporary config to enable [SpecId::CANCUN] + /// Temporary config to enable [SpecId::PRAGUE] /// - /// - /// Should be removed once EvmVersion Cancun is supported by solc - pub cancun: bool, + /// Should be removed once EvmVersion Prague is supported by solc + pub prague: bool, /// Whether to enable call isolation. /// /// Useful for more correct gas accounting and EVM behavior in general. pub isolate: bool, + /// Whether to disable the block gas limit. + pub disable_block_gas_limit: bool, + /// Address labels pub labels: HashMap, - /// The root path where the config detection started from, `Config::with_root` - #[doc(hidden)] - // We're skipping serialization here, so it won't be included in the [`Config::to_string()`] + /// Whether to enable safety checks for `vm.getCode` and `vm.getDeployedCode` invocations. + /// If disabled, it is possible to access artifacts which were not recompiled or cached. + pub unchecked_cheatcode_artifacts: bool, + + /// CREATE2 salt to use for the library deployment in scripts. + pub create2_library_salt: B256, + + /// Configuration for Vyper compiler + pub vyper: VyperConfig, + + /// Soldeer dependencies + pub dependencies: Option, + + /// The root path where the config detection started from, [`Config::with_root`]. + // We're skipping serialization here, so it won't be included in the [`Config::to_string()`] // representation, but will be deserialized from the `Figment` so that forge commands can // override it. - #[serde(rename = "root", default, skip_serializing)] - pub __root: RootPath, + #[serde(default, skip_serializing)] + pub root: RootPath, + + /// Warnings gathered when loading the Config. See [`WarningsProvider`] for more information + #[serde(rename = "__warnings", default, skip_serializing)] + pub warnings: Vec, + /// PRIVATE: This structure may grow, As such, constructing this structure should /// _always_ be done using a public constructor or update syntax: /// @@ -403,10 +441,7 @@ pub struct Config { /// ``` #[doc(hidden)] #[serde(skip)] - pub __non_exhaustive: (), - /// Warnings gathered when loading the Config. See [`WarningsProvider`] for more information - #[serde(default, skip_serializing)] - pub __warnings: Vec, + pub _non_exhaustive: (), /// @zkSync zkSolc configuration and settings pub zksync: ZkSyncConfig, @@ -431,8 +466,17 @@ impl Config { pub const PROFILE_SECTION: &'static str = "profile"; /// Standalone sections in the config which get integrated into the selected profile - pub const STANDALONE_SECTIONS: &'static [&'static str] = - &["rpc_endpoints", "etherscan", "fmt", "doc", "fuzz", "invariant", "labels"]; + pub const STANDALONE_SECTIONS: &'static [&'static str] = &[ + "rpc_endpoints", + "etherscan", + "fmt", + "doc", + "fuzz", + "invariant", + "labels", + "dependencies", + "vyper", + ]; /// File name of config toml file pub const FILE_NAME: &'static str = "foundry.toml"; @@ -445,6 +489,9 @@ impl Config { /// `0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38` pub const DEFAULT_SENDER: Address = address!("1804c8AB1F12E6bbf3894d4083f33e07309d1f38"); + /// Default salt for create2 library deployments + pub const DEFAULT_CREATE2_LIBRARY_SALT: FixedBytes<32> = FixedBytes::<32>::ZERO; + /// Returns the current `Config` /// /// See `Config::figment` @@ -453,6 +500,14 @@ impl Config { Config::from_provider(Config::figment()) } + /// Returns the current `Config` with the given `providers` preset + /// + /// See `Config::to_figment` + #[track_caller] + pub fn load_with_providers(providers: FigmentProviders) -> Self { + Config::default().to_figment(providers).extract().unwrap() + } + /// Returns the current `Config` /// /// See `Config::figment_with_root` @@ -507,13 +562,93 @@ impl Config { Ok(config) } + /// Returns the populated [Figment] using the requested [FigmentProviders] preset. + /// + /// This will merge various providers, such as env,toml,remappings into the figment. + pub fn to_figment(self, providers: FigmentProviders) -> Figment { + let mut c = self; + let profile = Config::selected_profile(); + let mut figment = Figment::default().merge(DappHardhatDirProvider(&c.root.0)); + + // merge global foundry.toml file + if let Some(global_toml) = Config::foundry_dir_toml().filter(|p| p.exists()) { + figment = Config::merge_toml_provider( + figment, + TomlFileProvider::new(None, global_toml).cached(), + profile.clone(), + ); + } + // merge local foundry.toml file + figment = Config::merge_toml_provider( + figment, + TomlFileProvider::new(Some("FOUNDRY_CONFIG"), c.root.0.join(Config::FILE_NAME)) + .cached(), + profile.clone(), + ); + + // merge environment variables + figment = figment + .merge( + Env::prefixed("DAPP_") + .ignore(&["REMAPPINGS", "LIBRARIES", "FFI", "FS_PERMISSIONS"]) + .global(), + ) + .merge( + Env::prefixed("DAPP_TEST_") + .ignore(&["CACHE", "FUZZ_RUNS", "DEPTH", "FFI", "FS_PERMISSIONS"]) + .global(), + ) + .merge(DappEnvCompatProvider) + .merge(EtherscanEnvProvider::default()) + .merge( + Env::prefixed("FOUNDRY_") + .ignore(&["PROFILE", "REMAPPINGS", "LIBRARIES", "FFI", "FS_PERMISSIONS"]) + .map(|key| { + let key = key.as_str(); + if Config::STANDALONE_SECTIONS.iter().any(|section| { + key.starts_with(&format!("{}_", section.to_ascii_uppercase())) + }) { + key.replacen('_', ".", 1).into() + } else { + key.into() + } + }) + .global(), + ) + .select(profile.clone()); + + // only resolve remappings if all providers are requested + if providers.is_all() { + // we try to merge remappings after we've merged all other providers, this prevents + // redundant fs lookups to determine the default remappings that are eventually updated + // by other providers, like the toml file + let remappings = RemappingsProvider { + auto_detect_remappings: figment + .extract_inner::("auto_detect_remappings") + .unwrap_or(true), + lib_paths: figment + .extract_inner::>("libs") + .map(Cow::Owned) + .unwrap_or_else(|_| Cow::Borrowed(&c.libs)), + root: &c.root.0, + remappings: figment.extract_inner::>("remappings"), + }; + figment = figment.merge(remappings); + } + + // normalize defaults + figment = c.normalize_defaults(figment); + + Figment::from(c).merge(figment).select(profile) + } + /// The config supports relative paths and tracks the root path separately see /// `Config::with_root` /// /// This joins all relative paths with the current root and attempts to make them canonic #[must_use] pub fn canonic(self) -> Self { - let root = self.__root.0.clone(); + let root = self.root.0.clone(); self.canonic_at(root) } @@ -587,12 +722,13 @@ impl Config { self.evm_version = self.get_normalized_evm_version(); } - /// Returns the normalized [EvmVersion] if a [SolcReq] is set to a valid version. + /// Returns the normalized [EvmVersion] if a [SolcReq] is set to a valid version or if the solc + /// path is a valid solc binary. /// /// Otherwise it returns the configured [EvmVersion]. pub fn get_normalized_evm_version(&self) -> EvmVersion { - if let Some(SolcReq::Version(version)) = &self.solc { - if let Some(evm_version) = self.evm_version.normalize_version(version) { + if let Some(version) = self.solc.as_ref().and_then(|solc| solc.try_version().ok()) { + if let Some(evm_version) = self.evm_version.normalize_version_solc(&version) { return evm_version; } } @@ -653,25 +789,22 @@ impl Config { /// let config = Config::load_with_root(".").sanitized(); /// let project = config.project(); /// ``` - pub fn project(&self) -> Result { + pub fn project(&self) -> Result, SolcError> { self.create_project(self.cache, false) } /// Same as [`Self::project()`] but sets configures the project to not emit artifacts and ignore - /// cache, caching causes no output until https://github.com/gakonst/ethers-rs/issues/727 - pub fn ephemeral_no_artifacts_project(&self) -> Result { + /// cache. + pub fn ephemeral_no_artifacts_project(&self) -> Result, SolcError> { self.create_project(false, true) } - fn create_project(&self, cached: bool, no_artifacts: bool) -> Result { - let mut project = Project::builder() + /// Creates a [Project] with the given `cached` and `no_artifacts` flags + pub fn create_project(&self, cached: bool, no_artifacts: bool) -> Result { + let mut builder = Project::builder() .artifacts(self.configured_artifacts_handler()) .paths(self.project_paths()) - .allowed_path(&self.__root.0) - .allowed_paths(&self.libs) - .allowed_paths(&self.allow_paths) - .include_paths(&self.include_paths) - .solc_config(SolcConfig::builder().settings(self.solc_settings()?).build()) + .settings(self.compiler_settings()?) .ignore_error_codes(self.ignored_error_codes.iter().copied().map(Into::into)) .ignore_paths(self.ignored_file_paths.clone()) .set_compiler_severity_filter(if self.deny_warnings { @@ -679,19 +812,20 @@ impl Config { } else { Severity::Error }) - .set_auto_detect(self.is_auto_detect()) .set_offline(self.offline) .set_cached(cached) - .set_build_info(cached && self.build_info) - .set_no_artifacts(no_artifacts) - .build()?; + .set_build_info(!no_artifacts && self.build_info) + .set_no_artifacts(no_artifacts); - if self.force { - project.cleanup()?; + if !self.skip.is_empty() { + let filter = SkipBuildFilters::new(self.skip.clone(), self.root.0.clone()); + builder = builder.sparse_output(filter); } - if let Some(solc) = self.ensure_solc()? { - project.solc = solc; + let mut project = builder.build(self.compiler()?)?; + + if self.force { + self.cleanup(&project)?; } // Set up zksolc project values @@ -699,12 +833,6 @@ impl Config { // when setting up the builder for the sake of consistency (requires dedicated // builder methods) project.zksync_zksolc_config = ZkSolcConfig { settings: self.zksync_zksolc_settings()? }; - project.zksync_avoid_contracts = self.zksync.avoid_contracts.clone().map(|patterns| { - patterns - .into_iter() - .map(|pat| globset::Glob::new(&pat).expect("invalid pattern").compile_matcher()) - .collect::>() - }); if let Some(zksolc) = self.zksync_ensure_zksolc()? { project.zksync_zksolc = zksolc; @@ -727,6 +855,25 @@ impl Config { Ok(project) } + /// Cleans the project. + pub fn cleanup(&self, project: &Project) -> Result<(), SolcError> { + project.cleanup()?; + + // Remove fuzz and invariant cache directories. + let remove_test_dir = |test_dir: &Option| { + if let Some(test_dir) = test_dir { + let path = project.root().join(test_dir); + if path.exists() { + let _ = fs::remove_dir_all(&path); + } + } + }; + remove_test_dir(&self.fuzz.failure_persist_dir); + remove_test_dir(&self.invariant.failure_persist_dir); + + Ok(()) + } + /// Ensures that the configured version is installed if explicitly set /// /// If `solc` is [`SolcReq::Version`] then this will download and install the solc version if @@ -737,18 +884,16 @@ impl Config { if let Some(ref solc) = self.solc { let solc = match solc { SolcReq::Version(version) => { - let v = version.to_string(); - let mut solc = Solc::find_svm_installed_version(&v)?; - if solc.is_none() { + if let Some(solc) = Solc::find_svm_installed_version(version)? { + solc + } else { if self.offline { return Err(SolcError::msg(format!( "can't install missing solc {version} in offline mode" ))) } - Solc::blocking_install(version)?; - solc = Solc::find_svm_installed_version(&v)?; + Solc::blocking_install(version)? } - solc } SolcReq::Local(solc) => { if !solc.is_file() { @@ -757,10 +902,10 @@ impl Config { solc.display() ))) } - Some(Solc::new(solc)) + Solc::new(solc)? } }; - return Ok(solc) + return Ok(Some(solc)) } Ok(None) @@ -807,8 +952,8 @@ impl Config { /// Returns the [SpecId] derived from the configured [EvmVersion] #[inline] pub fn evm_spec_id(&self) -> SpecId { - if self.cancun { - return SpecId::CANCUN + if self.prague { + return SpecId::PRAGUE } evm_spec_id(&self.evm_version) } @@ -831,7 +976,7 @@ impl Config { self.rpc_storage_caching.enable_for_endpoint(endpoint) } - /// Returns the `ProjectPathsConfig` sub set of the config. + /// Returns the `ProjectPathsConfig` sub set of the config. /// /// **NOTE**: this uses the paths as they are and does __not__ modify them, see /// `[Self::sanitized]` @@ -839,11 +984,12 @@ impl Config { /// # Example /// /// ``` + /// use foundry_compilers::solc::Solc; /// use foundry_config::Config; /// let config = Config::load_with_root(".").sanitized(); - /// let paths = config.project_paths(); + /// let paths = config.project_paths::(); /// ``` - pub fn project_paths(&self) -> ProjectPathsConfig { + pub fn project_paths(&self) -> ProjectPathsConfig { let mut builder = ProjectPathsConfig::builder() .cache(self.cache_path.join(SOLIDITY_FILES_CACHE_FILENAME)) .sources(&self.src) @@ -851,16 +997,50 @@ impl Config { .scripts(&self.script) .artifacts(&self.out) .libs(self.libs.iter()) - .remappings(self.get_all_remappings()); + .remappings(self.get_all_remappings()) + .allowed_path(&self.root.0) + .allowed_paths(&self.libs) + .allowed_paths(&self.allow_paths) + .include_paths(&self.include_paths); if let Some(build_info_path) = &self.build_info_path { builder = builder.build_infos(build_info_path); } - builder.build_with_root(&self.__root.0) + builder.build_with_root(&self.root.0) + } + + /// Returns configuration for a compiler to use when setting up a [Project]. + pub fn solc_compiler(&self) -> Result { + if let Some(solc) = self.ensure_solc()? { + Ok(SolcCompiler::Specific(solc)) + } else { + Ok(SolcCompiler::AutoDetect) + } + } + + /// Returns configured [Vyper] compiler. + pub fn vyper_compiler(&self) -> Result, SolcError> { + let vyper = if let Some(path) = &self.vyper.path { + Some(Vyper::new(path)?) + } else { + Vyper::new("vyper").ok() + }; + + Ok(vyper) } - /// Returns all configured [`Remappings`] + /// Returns configuration for a compiler to use when setting up a [Project]. + pub fn compiler(&self) -> Result { + Ok(MultiCompiler { solc: self.solc_compiler()?, vyper: self.vyper_compiler()? }) + } + + /// Returns configured [MultiCompilerSettings]. + pub fn compiler_settings(&self) -> Result { + Ok(MultiCompilerSettings { solc: self.solc_settings()?, vyper: self.vyper_settings()? }) + } + + /// Returns all configured remappings. /// /// **Note:** this will add an additional `/=` remapping here, see /// [Self::get_source_dir_remapping()] @@ -989,6 +1169,8 @@ impl Config { /// Returns /// - the matching `ResolvedEtherscanConfig` of the `etherscan` table if `etherscan_api_key` is /// an alias + /// - the matching `ResolvedEtherscanConfig` of the `etherscan` table if a `chain` is + /// configured. an alias /// - the Mainnet `ResolvedEtherscanConfig` if `etherscan_api_key` is set, `None` otherwise /// /// # Example @@ -1004,18 +1186,7 @@ impl Config { pub fn get_etherscan_config( &self, ) -> Option> { - let maybe_alias = self.etherscan_api_key.as_ref().or(self.eth_rpc_url.as_ref())?; - if self.etherscan.contains_key(maybe_alias) { - // etherscan points to an alias in the `etherscan` table, so we try to resolve that - let mut resolved = self.etherscan.clone().resolved(); - return resolved.remove(maybe_alias) - } - - // we treat the `etherscan_api_key` as actual API key - // if no chain provided, we assume mainnet - let chain = self.chain.unwrap_or(Chain::mainnet()); - let api_key = self.etherscan_api_key.as_ref()?; - ResolvedEtherscanConfig::create(api_key, chain).map(Ok) + self.get_etherscan_config_with_chain(None).transpose() } /// Same as [`Self::get_etherscan_config()`] but optionally updates the config with the given @@ -1035,14 +1206,15 @@ impl Config { } // try to find by comparing chain IDs after resolving - if let Some(res) = - chain.and_then(|chain| self.etherscan.clone().resolved().find_chain(chain)) + if let Some(res) = chain + .or(self.chain) + .and_then(|chain| self.etherscan.clone().resolved().find_chain(chain)) { match (res, self.etherscan_api_key.as_ref()) { (Ok(mut config), Some(key)) => { // we update the key, because if an etherscan_api_key is set, it should take // precedence over the entry, since this is usually set via env var or CLI args. - config.key = key.clone(); + config.key.clone_from(key); return Ok(Some(config)) } (Ok(config), None) => return Ok(Some(config)), @@ -1063,6 +1235,10 @@ impl Config { } /// Helper function to just get the API key + /// + /// Optionally updates the config with the given `chain`. + /// + /// See also [Self::get_etherscan_config_with_chain] pub fn get_etherscan_api_key(&self, chain: Option) -> Option { self.get_etherscan_config_with_chain(chain).ok().flatten().map(|c| c.key) } @@ -1079,7 +1255,7 @@ impl Config { /// Returns the remapping for the project's _test_ directory, but only if it exists pub fn get_test_dir_remapping(&self) -> Option { - if self.__root.0.join(&self.test).exists() { + if self.root.0.join(&self.test).exists() { get_dir_remapping(&self.test) } else { None @@ -1088,7 +1264,7 @@ impl Config { /// Returns the remapping for the project's _script_ directory, but only if it exists pub fn get_script_dir_remapping(&self) -> Option { - if self.__root.0.join(&self.script).exists() { + if self.root.0.join(&self.script).exists() { get_dir_remapping(&self.script) } else { None @@ -1096,11 +1272,18 @@ impl Config { } /// Returns the `Optimizer` based on the configured settings + /// + /// Note: optimizer details can be set independently of `enabled` + /// See also: + /// and pub fn optimizer(&self) -> Optimizer { - // only configure optimizer settings if optimizer is enabled - let details = if self.optimizer { self.optimizer_details.clone() } else { None }; - - Optimizer { enabled: Some(self.optimizer), runs: Some(self.optimizer_runs), details } + Optimizer { + enabled: Some(self.optimizer), + runs: Some(self.optimizer_runs), + // we always set the details because `enabled` is effectively a specific details profile + // that can still be modified + details: self.optimizer_details.clone(), + } } /// returns the [`foundry_compilers::ConfigurableArtifacts`] for this config, that includes the @@ -1128,7 +1311,8 @@ impl Config { /// Returns all libraries with applied remappings. Same as `self.solc_settings()?.libraries`. pub fn libraries_with_remappings(&self) -> Result { - Ok(self.parsed_libraries()?.with_applied_remappings(&self.project_paths())) + let paths: ProjectPathsConfig = self.project_paths(); + Ok(self.parsed_libraries()?.apply(|libs| paths.apply_lib_remappings(libs))) } /// Returns the configured `solc` `Settings` that includes: @@ -1185,13 +1369,30 @@ impl Config { /// - evm version pub fn zksync_zksolc_settings(&self) -> Result { let libraries = match self.parsed_libraries() { - Ok(libs) => libs.with_applied_remappings(&self.project_paths()), + Ok(libs) => self.project_paths::().apply_lib_remappings(libs), Err(e) => return Err(SolcError::msg(format!("Failed to parse libraries: {}", e))), }; Ok(self.zksync.settings(libraries, self.evm_version, self.via_ir)) } + /// Returns the configured [VyperSettings] that includes: + /// - evm version + pub fn vyper_settings(&self) -> Result { + Ok(VyperSettings { + evm_version: Some(self.evm_version), + optimize: self.vyper.optimize, + bytecode_metadata: None, + // TODO: We don't yet have a way to deserialize other outputs correctly, so request only + // those for now. It should be enough to run tests and deploy contracts. + output_selection: OutputSelection::common_output_selection([ + "abi".to_string(), + "evm.bytecode".to_string(), + "evm.deployedBytecode".to_string(), + ]), + }) + } + /// Returns the default figment /// /// The default figment reads from the following sources, in ascending @@ -1242,10 +1443,10 @@ impl Config { pub fn with_root(root: impl Into) -> Self { // autodetect paths let root = root.into(); - let paths = ProjectPathsConfig::builder().build_with_root(&root); + let paths = ProjectPathsConfig::builder().build_with_root::<()>(&root); let artifacts: PathBuf = paths.artifacts.file_name().unwrap().into(); Config { - __root: paths.root.into(), + root: paths.root.into(), src: paths.sources.file_name().unwrap().into(), out: artifacts.clone(), libs: paths.libraries.into_iter().map(|lib| lib.file_name().unwrap().into()).collect(), @@ -1303,7 +1504,7 @@ impl Config { /// [Self::get_config_path()] and if the closure returns `true`. pub fn update_at(root: impl Into, f: F) -> eyre::Result<()> where - F: FnOnce(&Config, &mut toml_edit::Document) -> bool, + F: FnOnce(&Config, &mut toml_edit::DocumentMut) -> bool, { let config = Self::load_with_root(root).sanitized(); config.update(|doc| f(&config, doc)) @@ -1315,14 +1516,14 @@ impl Config { /// [Self::get_config_path()] and if the closure returns `true` pub fn update(&self, f: F) -> eyre::Result<()> where - F: FnOnce(&mut toml_edit::Document) -> bool, + F: FnOnce(&mut toml_edit::DocumentMut) -> bool, { let file_path = self.get_config_path(); if !file_path.exists() { return Ok(()) } let contents = fs::read_to_string(&file_path)?; - let mut doc = contents.parse::()?; + let mut doc = contents.parse::()?; if f(&mut doc) { fs::write(file_path, doc.to_string())?; } @@ -1337,7 +1538,7 @@ impl Config { pub fn update_libs(&self) -> eyre::Result<()> { self.update(|doc| { let profile = self.profile.as_str().as_str(); - let root = &self.__root.0; + let root = &self.root.0; let libs: toml_edit::Value = self .libs .iter() @@ -1392,9 +1593,9 @@ impl Config { toml::to_string_pretty(&toml::Value::Table(wrapping_table)) } - /// Returns the path to the `foundry.toml` of this `Config` + /// Returns the path to the `foundry.toml` of this `Config`. pub fn get_config_path(&self) -> PathBuf { - self.__root.0.join(Config::FILE_NAME) + self.root.0.join(Config::FILE_NAME) } /// Sets the non-inlinable libraries inside a `foundry.toml` file but only if it exists the @@ -1416,70 +1617,70 @@ impl Config { /// Returns the selected profile /// - /// If the `FOUNDRY_PROFILE` env variable is not set, this returns the `DEFAULT_PROFILE` + /// If the `FOUNDRY_PROFILE` env variable is not set, this returns the `DEFAULT_PROFILE`. pub fn selected_profile() -> Profile { Profile::from_env_or("FOUNDRY_PROFILE", Config::DEFAULT_PROFILE) } - /// Returns the path to foundry's global toml file that's stored at `~/.foundry/foundry.toml` + /// Returns the path to foundry's global TOML file: `~/.foundry/foundry.toml`. pub fn foundry_dir_toml() -> Option { Self::foundry_dir().map(|p| p.join(Config::FILE_NAME)) } - /// Returns the path to foundry's config dir `~/.foundry/` + /// Returns the path to foundry's config dir: `~/.foundry/`. pub fn foundry_dir() -> Option { dirs_next::home_dir().map(|p| p.join(Config::FOUNDRY_DIR_NAME)) } - /// Returns the path to foundry's cache dir `~/.foundry/cache` + /// Returns the path to foundry's cache dir: `~/.foundry/cache`. pub fn foundry_cache_dir() -> Option { Self::foundry_dir().map(|p| p.join("cache")) } - /// Returns the path to foundry rpc cache dir `~/.foundry/cache/rpc` + /// Returns the path to foundry rpc cache dir: `~/.foundry/cache/rpc`. pub fn foundry_rpc_cache_dir() -> Option { Some(Self::foundry_cache_dir()?.join("rpc")) } - /// Returns the path to foundry chain's cache dir `~/.foundry/cache/rpc/` + /// Returns the path to foundry chain's cache dir: `~/.foundry/cache/rpc/` pub fn foundry_chain_cache_dir(chain_id: impl Into) -> Option { Some(Self::foundry_rpc_cache_dir()?.join(chain_id.into().to_string())) } - /// Returns the path to foundry's etherscan cache dir `~/.foundry/cache/etherscan` + /// Returns the path to foundry's etherscan cache dir: `~/.foundry/cache/etherscan`. pub fn foundry_etherscan_cache_dir() -> Option { Some(Self::foundry_cache_dir()?.join("etherscan")) } - /// Returns the path to foundry's keystores dir `~/.foundry/keystores` + /// Returns the path to foundry's keystores dir: `~/.foundry/keystores`. pub fn foundry_keystores_dir() -> Option { Some(Self::foundry_dir()?.join("keystores")) } - /// Returns the path to foundry's etherscan cache dir for `chain_id` + /// Returns the path to foundry's etherscan cache dir for `chain_id`: /// `~/.foundry/cache/etherscan/` pub fn foundry_etherscan_chain_cache_dir(chain_id: impl Into) -> Option { Some(Self::foundry_etherscan_cache_dir()?.join(chain_id.into().to_string())) } - /// Returns the path to the cache dir of the `block` on the `chain` - /// `~/.foundry/cache/rpc// + /// Returns the path to the cache dir of the `block` on the `chain`: + /// `~/.foundry/cache/rpc//` pub fn foundry_block_cache_dir(chain_id: impl Into, block: u64) -> Option { Some(Self::foundry_chain_cache_dir(chain_id)?.join(format!("{block}"))) } - /// Returns the path to the cache file of the `block` on the `chain` + /// Returns the path to the cache file of the `block` on the `chain`: /// `~/.foundry/cache/rpc///storage.json` pub fn foundry_block_cache_file(chain_id: impl Into, block: u64) -> Option { Some(Self::foundry_block_cache_dir(chain_id, block)?.join("storage.json")) } - #[doc = r#"Returns the path to `foundry`'s data directory inside the user's data directory - |Platform | Value | Example | - | ------- | ------------------------------------- | -------------------------------- | - | Linux | `$XDG_CONFIG_HOME` or `$HOME`/.config/foundry | /home/alice/.config/foundry| - | macOS | `$HOME`/Library/Application Support/foundry | /Users/Alice/Library/Application Support/foundry | - | Windows | `{FOLDERID_RoamingAppData}/foundry` | C:\Users\Alice\AppData\Roaming/foundry | - "#] + /// Returns the path to `foundry`'s data directory inside the user's data directory. + /// + /// | Platform | Value | Example | + /// | ------- | --------------------------------------------- | ------------------------------------------------ | + /// | Linux | `$XDG_CONFIG_HOME` or `$HOME`/.config/foundry | /home/alice/.config/foundry | + /// | macOS | `$HOME`/Library/Application Support/foundry | /Users/Alice/Library/Application Support/foundry | + /// | Windows | `{FOLDERID_RoamingAppData}/foundry` | C:\Users\Alice\AppData\Roaming/foundry | pub fn data_dir() -> eyre::Result { let path = dirs_next::data_dir().wrap_err("Failed to find data directory")?.join("foundry"); std::fs::create_dir_all(&path).wrap_err("Failed to create module directory")?; @@ -1491,7 +1692,7 @@ impl Config { /// and the first hit is used. /// /// If this search comes up empty, then it checks if a global `foundry.toml` exists at - /// `~/.foundry/foundry.tol`, see [`Self::foundry_dir_toml()`] + /// `~/.foundry/foundry.toml`, see [`Self::foundry_dir_toml`]. pub fn find_config_file() -> Option { fn find(path: &Path) -> Option { if path.is_absolute() { @@ -1514,7 +1715,7 @@ impl Config { .or_else(|| Self::foundry_dir_toml().filter(|p| p.exists())) } - /// Clears the foundry cache + /// Clears the foundry cache. pub fn clean_foundry_cache() -> eyre::Result<()> { if let Some(cache_dir) = Config::foundry_cache_dir() { let path = cache_dir.as_path(); @@ -1526,7 +1727,7 @@ impl Config { Ok(()) } - /// Clears the foundry cache for `chain` + /// Clears the foundry cache for `chain`. pub fn clean_foundry_chain_cache(chain: Chain) -> eyre::Result<()> { if let Some(cache_dir) = Config::foundry_chain_cache_dir(chain) { let path = cache_dir.as_path(); @@ -1538,7 +1739,7 @@ impl Config { Ok(()) } - /// Clears the foundry cache for `chain` and `block` + /// Clears the foundry cache for `chain` and `block`. pub fn clean_foundry_block_cache(chain: Chain, block: u64) -> eyre::Result<()> { if let Some(cache_dir) = Config::foundry_block_cache_dir(chain, block) { let path = cache_dir.as_path(); @@ -1550,7 +1751,7 @@ impl Config { Ok(()) } - /// Clears the foundry etherscan cache + /// Clears the foundry etherscan cache. pub fn clean_foundry_etherscan_cache() -> eyre::Result<()> { if let Some(cache_dir) = Config::foundry_etherscan_cache_dir() { let path = cache_dir.as_path(); @@ -1562,7 +1763,7 @@ impl Config { Ok(()) } - /// Clears the foundry etherscan cache for `chain` + /// Clears the foundry etherscan cache for `chain`. pub fn clean_foundry_etherscan_chain_cache(chain: Chain) -> eyre::Result<()> { if let Some(cache_dir) = Config::foundry_etherscan_chain_cache_dir(chain) { let path = cache_dir.as_path(); @@ -1574,7 +1775,7 @@ impl Config { Ok(()) } - /// List the data in the foundry cache + /// List the data in the foundry cache. pub fn list_foundry_cache() -> eyre::Result { if let Some(cache_dir) = Config::foundry_rpc_cache_dir() { let mut cache = Cache { chains: vec![] }; @@ -1597,7 +1798,7 @@ impl Config { } } - /// List the cached data for `chain` + /// List the cached data for `chain`. pub fn list_foundry_chain_cache(chain: Chain) -> eyre::Result { let block_explorer_data_size = match Config::foundry_etherscan_chain_cache_dir(chain) { Some(cache_dir) => Self::get_cached_block_explorer_data(&cache_dir)?, @@ -1619,23 +1820,30 @@ impl Config { } } - //The path provided to this function should point to a cached chain folder + /// The path provided to this function should point to a cached chain folder. fn get_cached_blocks(chain_path: &Path) -> eyre::Result> { let mut blocks = vec![]; if !chain_path.exists() { return Ok(blocks) } - for block in chain_path.read_dir()?.flatten().filter(|x| x.file_type().unwrap().is_dir()) { - let filepath = block.path().join("storage.json"); - blocks.push(( - block.file_name().to_string_lossy().into_owned(), - fs::metadata(filepath)?.len(), - )); + for block in chain_path.read_dir()?.flatten() { + let file_type = block.file_type()?; + let file_name = block.file_name(); + let filepath = if file_type.is_dir() { + block.path().join("storage.json") + } else if file_type.is_file() && + file_name.to_string_lossy().chars().all(char::is_numeric) + { + block.path() + } else { + continue + }; + blocks.push((file_name.to_string_lossy().into_owned(), fs::metadata(filepath)?.len())); } Ok(blocks) } - //The path provided to this function should point to the etherscan cache for a chain + /// The path provided to this function should point to the etherscan cache for a chain. fn get_cached_block_explorer_data(chain_path: &Path) -> eyre::Result { if !chain_path.exists() { return Ok(0) @@ -1707,11 +1915,15 @@ impl Config { /// /// See also fn normalize_defaults(&mut self, figment: Figment) -> Figment { - if let Ok(version) = figment.extract_inner::("solc") { + if let Ok(solc) = figment.extract_inner::("solc") { // check if evm_version is set // TODO: add a warning if evm_version is provided but incompatible if figment.find_value("evm_version").is_err() { - if let Some(version) = self.evm_version.normalize_version(&version) { + if let Some(version) = solc + .try_version() + .ok() + .and_then(|version| self.evm_version.normalize_version_solc(&version)) + { // normalize evm_version based on the provided solc version self.evm_version = version; } @@ -1723,77 +1935,36 @@ impl Config { } impl From for Figment { - fn from(mut c: Config) -> Figment { - let profile = Config::selected_profile(); - let mut figment = Figment::default().merge(DappHardhatDirProvider(&c.__root.0)); - - // merge global foundry.toml file - if let Some(global_toml) = Config::foundry_dir_toml().filter(|p| p.exists()) { - figment = Config::merge_toml_provider( - figment, - TomlFileProvider::new(None, global_toml).cached(), - profile.clone(), - ); - } - // merge local foundry.toml file - figment = Config::merge_toml_provider( - figment, - TomlFileProvider::new(Some("FOUNDRY_CONFIG"), c.__root.0.join(Config::FILE_NAME)) - .cached(), - profile.clone(), - ); - - // merge environment variables - figment = figment - .merge( - Env::prefixed("DAPP_") - .ignore(&["REMAPPINGS", "LIBRARIES", "FFI", "FS_PERMISSIONS"]) - .global(), - ) - .merge( - Env::prefixed("DAPP_TEST_") - .ignore(&["CACHE", "FUZZ_RUNS", "DEPTH", "FFI", "FS_PERMISSIONS"]) - .global(), - ) - .merge(DappEnvCompatProvider) - .merge(EtherscanEnvProvider::default()) - .merge( - Env::prefixed("FOUNDRY_") - .ignore(&["PROFILE", "REMAPPINGS", "LIBRARIES", "FFI", "FS_PERMISSIONS"]) - .map(|key| { - let key = key.as_str(); - if Config::STANDALONE_SECTIONS.iter().any(|section| { - key.starts_with(&format!("{}_", section.to_ascii_uppercase())) - }) { - key.replacen('_', ".", 1).into() - } else { - key.into() - } - }) - .global(), - ) - .select(profile.clone()); + fn from(c: Config) -> Figment { + c.to_figment(FigmentProviders::All) + } +} - // we try to merge remappings after we've merged all other providers, this prevents - // redundant fs lookups to determine the default remappings that are eventually updated by - // other providers, like the toml file - let remappings = RemappingsProvider { - auto_detect_remappings: figment - .extract_inner::("auto_detect_remappings") - .unwrap_or(true), - lib_paths: figment - .extract_inner::>("libs") - .map(Cow::Owned) - .unwrap_or_else(|_| Cow::Borrowed(&c.libs)), - root: &c.__root.0, - remappings: figment.extract_inner::>("remappings"), - }; - let merge = figment.merge(remappings); +/// Determines what providers should be used when loading the [Figment] for a [Config] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum FigmentProviders { + /// Include all providers + #[default] + All, + /// Only include necessary providers that are useful for cast commands + /// + /// This will exclude more expensive providers such as remappings + Cast, + /// Only include necessary providers that are useful for anvil + /// + /// This will exclude more expensive providers such as remappings + Anvil, +} - // normalize defaults - let merge = c.normalize_defaults(merge); +impl FigmentProviders { + /// Returns true if all providers should be included + pub const fn is_all(&self) -> bool { + matches!(self, Self::All) + } - Figment::from(c).merge(merge).select(profile) + /// Returns true if this is the cast preset + pub const fn is_cast(&self) -> bool { + matches!(self, Self::Cast) } } @@ -1857,6 +2028,30 @@ pub(crate) mod from_opt_glob { } } +/// Ser/de `globset::Glob` explicitly to handle `Option` properly +pub(crate) mod from_vec_glob { + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + pub fn serialize(value: &[globset::Glob], serializer: S) -> Result + where + S: Serializer, + { + let value = value.iter().map(|g| g.glob()).collect::>(); + value.serialize(serializer) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let s: Vec = Vec::deserialize(deserializer)?; + s.into_iter() + .map(|s| globset::Glob::new(&s)) + .collect::, _>>() + .map_err(serde::de::Error::custom) + } +} + /// A helper wrapper around the root path used during Config detection #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] #[serde(transparent)] @@ -1914,7 +2109,7 @@ impl Provider for Config { fn data(&self) -> Result, figment::Error> { let mut data = Serialized::defaults(self).data()?; if let Some(entry) = data.get_mut(&self.profile) { - entry.insert("root".to_string(), Value::serialize(self.__root.clone())?); + entry.insert("root".to_string(), Value::serialize(self.root.clone())?); } Ok(data) } @@ -1929,9 +2124,9 @@ impl Default for Config { Self { profile: Self::DEFAULT_PROFILE, fs_permissions: FsPermissions::new([PathPermission::read("out")]), - cancun: false, + prague: false, isolate: false, - __root: Default::default(), + root: Default::default(), src: "src".into(), test: "test".into(), script: "script".into(), @@ -1943,10 +2138,11 @@ impl Default for Config { allow_paths: vec![], include_paths: vec![], force: false, - evm_version: EvmVersion::Paris, + evm_version: EvmVersion::default(), gas_reports: vec!["*".to_string()], gas_reports_ignore: vec![], solc: None, + vyper: Default::default(), auto_detect_solc: true, offline: false, optimizer: true, @@ -1963,10 +2159,11 @@ impl Default for Config { contract_pattern_inverse: None, path_pattern: None, path_pattern_inverse: None, - fuzz: Default::default(), - invariant: Default::default(), + fuzz: FuzzConfig::new("cache/fuzz".into()), + invariant: InvariantConfig::new("cache/invariant".into()), always_use_create_2_factory: false, ffi: false, + prompt_timeout: 120, sender: Config::DEFAULT_SENDER, tx_origin: Config::DEFAULT_SENDER, initial_balance: U256::from(0xffffffffffffffffffffffffu128), @@ -1982,6 +2179,7 @@ impl Default for Config { block_difficulty: 0, block_prevrandao: Default::default(), block_gas_limit: None, + disable_block_gas_limit: false, memory_limit: 1 << 27, // 2**27 = 128MiB = 134_217_728 bytes eth_rpc_url: None, eth_rpc_jwt: None, @@ -1994,6 +2192,7 @@ impl Default for Config { SolidityErrorCode::SpdxLicenseNotProvided, SolidityErrorCode::ContractExceeds24576Bytes, SolidityErrorCode::ContractInitCodeSizeExceeds49152Bytes, + SolidityErrorCode::TransientStorageUsed, ], ignored_file_paths: vec![], deny_warnings: false, @@ -2014,8 +2213,12 @@ impl Default for Config { fmt: Default::default(), doc: Default::default(), labels: Default::default(), - __non_exhaustive: (), - __warnings: vec![], + unchecked_cheatcode_artifacts: false, + create2_library_salt: Config::DEFAULT_CREATE2_LIBRARY_SALT, + skip: vec![], + dependencies: Default::default(), + warnings: vec![], + _non_exhaustive: (), zksync: Default::default(), } } @@ -2105,6 +2308,19 @@ pub enum SolcReq { Local(PathBuf), } +impl SolcReq { + /// Tries to get the solc version from the `SolcReq` + /// + /// If the `SolcReq` is a `Version` it will return the version, if it's a path to a binary it + /// will try to get the version from the binary. + fn try_version(&self) -> Result { + match self { + SolcReq::Version(version) => Ok(version.clone()), + SolcReq::Local(path) => Solc::new(path).map(|solc| solc.version), + } + } +} + impl> From for SolcReq { fn from(s: T) -> Self { let s = s.as_ref(); @@ -2703,8 +2919,10 @@ mod tests { etherscan::ResolvedEtherscanConfigs, }; use figment::error::Kind::InvalidType; - use foundry_compilers::artifacts::{ModelCheckerEngine, YulDetails}; - use pretty_assertions::assert_eq; + use foundry_compilers::artifacts::{ + vyper::VyperOptimizationMode, ModelCheckerEngine, YulDetails, + }; + use similar_asserts::assert_eq; use std::{collections::BTreeMap, fs::File, io::Write}; use tempfile::tempdir; use NamedChain::Moonbeam; @@ -2712,7 +2930,7 @@ mod tests { // Helper function to clear `__warnings` in config, since it will be populated during loading // from file, causing testing problem when comparing to those created from `default()`, etc. fn clear_warning(config: &mut Config) { - config.__warnings = vec![]; + config.warnings = vec![]; } #[test] @@ -2834,7 +3052,7 @@ mod tests { fn test_default_test_path() { figment::Jail::expect_with(|_| { let config = Config::default(); - let paths_config = config.project_paths(); + let paths_config = config.project_paths::(); assert_eq!(paths_config.tests, PathBuf::from(r"test")); Ok(()) }); @@ -2868,7 +3086,7 @@ mod tests { test = "defaulttest" src = "defaultsrc" libs = ['lib', 'node_modules'] - + [profile.custom] src = "customsrc" "#, @@ -2901,7 +3119,7 @@ mod tests { )?; let config = Config::load(); - let paths_config = config.project_paths(); + let paths_config = config.project_paths::(); assert_eq!(paths_config.tests, PathBuf::from(r"mytest")); Ok(()) }); @@ -3193,6 +3411,29 @@ mod tests { }); } + #[test] + fn test_resolve_etherscan_chain_id() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [profile.default] + chain_id = "sepolia" + + [etherscan] + sepolia = { key = "FX42Z3BBJJEWXWGYV2X1CIPRSCN" } + "#, + )?; + + let config = Config::load(); + let etherscan = config.get_etherscan_config().unwrap().unwrap(); + assert_eq!(etherscan.chain, Some(NamedChain::Sepolia.into())); + assert_eq!(etherscan.key, "FX42Z3BBJJEWXWGYV2X1CIPRSCN"); + + Ok(()) + }); + } + #[test] fn test_resolve_rpc_url() { figment::Jail::expect_with(|jail| { @@ -3561,7 +3802,7 @@ mod tests { Chain::optimism_mainnet(), Chain::from_id(999999) ]), - endpoints: CachedEndpoints::All + endpoints: CachedEndpoints::All, }, use_literal_content: false, bytecode_hash: BytecodeHash::Ipfs, @@ -3667,7 +3908,7 @@ mod tests { tx_origin = '0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38' verbosity = 0 via_ir = false - + [profile.default.rpc_storage_caching] chains = 'all' endpoints = 'all' @@ -3684,10 +3925,10 @@ mod tests { [invariant] runs = 256 - depth = 15 + depth = 500 fail_on_revert = false call_override = false - shrink_sequence = true + shrink_run_limit = 5000 "#, )?; @@ -3730,7 +3971,7 @@ mod tests { )?; let config = Config::load(); - assert_eq!(config.solc, Some(SolcReq::Version("0.8.12".parse().unwrap()))); + assert_eq!(config.solc, Some(SolcReq::Version(Version::new(0, 8, 12)))); jail.create_file( "foundry.toml", @@ -3741,7 +3982,7 @@ mod tests { )?; let config = Config::load(); - assert_eq!(config.solc, Some(SolcReq::Version("0.8.12".parse().unwrap()))); + assert_eq!(config.solc, Some(SolcReq::Version(Version::new(0, 8, 12)))); jail.create_file( "foundry.toml", @@ -3756,7 +3997,7 @@ mod tests { jail.set_env("FOUNDRY_SOLC_VERSION", "0.6.6"); let config = Config::load(); - assert_eq!(config.solc, Some(SolcReq::Version("0.6.6".parse().unwrap()))); + assert_eq!(config.solc, Some(SolcReq::Version(Version::new(0, 6, 6)))); Ok(()) }); } @@ -3775,7 +4016,7 @@ mod tests { )?; let config = Config::load(); - assert_eq!(config.solc, Some(SolcReq::Version("0.8.12".parse().unwrap()))); + assert_eq!(config.solc, Some(SolcReq::Version(Version::new(0, 8, 12)))); Ok(()) }); @@ -3790,7 +4031,7 @@ mod tests { )?; let config = Config::load(); - assert_eq!(config.solc, Some(SolcReq::Version("0.8.20".parse().unwrap()))); + assert_eq!(config.solc, Some(SolcReq::Version(Version::new(0, 8, 20)))); Ok(()) }); @@ -4136,14 +4377,14 @@ mod tests { './src/SizeAuction.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c', './src/test/ChainlinkTWAP.t.sol:ChainlinkTWAP:0xffedba5e171c4f15abaaabc86e8bd01f9b54dae5', './src/SizeAuctionDiscount.sol:Math:0x902f6cf364b8d9470d5793a9b2b2e86bddd21e0c', - ] + ] ", )?; let config = Config::load(); let libs = config.parsed_libraries().unwrap().libs; - pretty_assertions::assert_eq!( + similar_asserts::assert_eq!( libs, BTreeMap::from([ ( @@ -4428,7 +4669,12 @@ mod tests { let loaded = Config::load().sanitized(); assert_eq!( loaded.invariant, - InvariantConfig { runs: 512, depth: 10, ..Default::default() } + InvariantConfig { + runs: 512, + depth: 10, + failure_persist_dir: Some(PathBuf::from("cache/invariant")), + ..Default::default() + } ); Ok(()) @@ -4502,7 +4748,7 @@ mod tests { assert_eq!(loaded.src.file_name().unwrap(), "my-src"); assert_eq!(loaded.out.file_name().unwrap(), "my-out"); assert_eq!( - loaded.__warnings, + loaded.warnings, vec![Warning::UnknownSection { unknown_section: Profile::new("default"), source: Some("foundry.toml".into()) @@ -4591,6 +4837,7 @@ mod tests { stack_allocation: None, optimizer_steps: Some("dhfoDgvulfnTUtnIf".to_string()), }), + simple_counter_for_loop_unchecked_increment: None, }), ..Default::default() }; @@ -4598,6 +4845,7 @@ mod tests { } #[test] + #[allow(unknown_lints, non_local_definitions)] fn can_use_impl_figment_macro() { #[derive(Default, Serialize)] struct MyArgs { @@ -4644,23 +4892,38 @@ mod tests { writeln!(file, "{}", vec![' '; size_bytes - 1].iter().collect::()).unwrap(); } + fn fake_block_cache_block_path_as_file( + chain_path: &Path, + block_number: &str, + size_bytes: usize, + ) { + let block_path = chain_path.join(block_number); + let mut file = File::create(block_path).unwrap(); + writeln!(file, "{}", vec![' '; size_bytes - 1].iter().collect::()).unwrap(); + } + let chain_dir = tempdir()?; fake_block_cache(chain_dir.path(), "1", 100); fake_block_cache(chain_dir.path(), "2", 500); + fake_block_cache_block_path_as_file(chain_dir.path(), "3", 900); // Pollution file that should not show up in the cached block let mut pol_file = File::create(chain_dir.path().join("pol.txt")).unwrap(); writeln!(pol_file, "{}", [' '; 10].iter().collect::()).unwrap(); let result = Config::get_cached_blocks(chain_dir.path())?; - assert_eq!(result.len(), 2); + assert_eq!(result.len(), 3); let block1 = &result.iter().find(|x| x.0 == "1").unwrap(); let block2 = &result.iter().find(|x| x.0 == "2").unwrap(); + let block3 = &result.iter().find(|x| x.0 == "3").unwrap(); + assert_eq!(block1.0, "1"); assert_eq!(block1.1, 100); assert_eq!(block2.0, "2"); assert_eq!(block2.1, 500); + assert_eq!(block3.0, "3"); + assert_eq!(block3.1, 900); chain_dir.close()?; Ok(()) @@ -4789,4 +5052,29 @@ mod tests { Ok(()) }); } + + #[test] + fn test_parse_vyper() { + figment::Jail::expect_with(|jail| { + jail.create_file( + "foundry.toml", + r#" + [vyper] + optimize = "codesize" + path = "/path/to/vyper" + "#, + )?; + + let config = Config::load(); + assert_eq!( + config.vyper, + VyperConfig { + optimize: Some(VyperOptimizationMode::Codesize), + path: Some("/path/to/vyper".into()) + } + ); + + Ok(()) + }); + } } diff --git a/crates/config/src/macros.rs b/crates/config/src/macros.rs index 95b111bdb..d4f3a59ba 100644 --- a/crates/config/src/macros.rs +++ b/crates/config/src/macros.rs @@ -1,7 +1,8 @@ -/// A macro to implement converters from a type to [`Config`] and [`figment::Figment`] +/// A macro to implement converters from a type to [`Config`](crate::Config) and +/// [`figment::Figment`]. /// /// This can be used to remove some boilerplate code that's necessary to add additional layer(s) to -/// the [`Config`]'s default `Figment`. +/// the `Config`'s default `Figment`. /// /// `impl_figment` takes the default `Config` and merges additional `Provider`, therefore the /// targeted type, requires an implementation of `figment::Profile`. @@ -9,7 +10,7 @@ /// # Example /// /// Use `impl_figment` on a type with a `root: Option` field, which will be used for -/// [`Config::figment_with_root()`] +/// [`Config::figment_with_root()`](crate::Config::figment_with_root). /// /// ```rust /// use std::path::PathBuf; @@ -59,12 +60,10 @@ macro_rules! impl_figment_convert { ($name:ty) => { impl<'a> From<&'a $name> for $crate::figment::Figment { fn from(args: &'a $name) -> Self { - if let Some(root) = args.root.clone() { - $crate::Config::figment_with_root(root) - } else { - $crate::Config::figment_with_root($crate::find_project_root_path(None).unwrap()) - } - .merge(args) + let root = args.root.clone() + .unwrap_or_else(|| $crate::find_project_root_path(None) + .unwrap_or_else(|e| panic!("could not find project root: {e}"))); + $crate::Config::figment_with_root(root).merge(args) } } @@ -79,8 +78,8 @@ macro_rules! impl_figment_convert { impl<'a> From<&'a $name> for $crate::figment::Figment { fn from(args: &'a $name) -> Self { let mut figment: $crate::figment::Figment = From::from(&args.$start); - $ ( - figment = figment.merge(&args.$more); + $( + figment = figment.merge(&args.$more); )* figment } @@ -97,8 +96,8 @@ macro_rules! impl_figment_convert { impl<'a> From<&'a $name> for $crate::figment::Figment { fn from(args: &'a $name) -> Self { let mut figment: $crate::figment::Figment = From::from(&args.$start); - $ ( - figment = figment.merge(&args.$more); + $( + figment = figment.merge(&args.$more); )* figment = figment.merge(args); figment @@ -185,13 +184,18 @@ macro_rules! merge_impl_figment_convert { }; } -/// A macro to implement converters from a type to [`Config`] and [`figment::Figment`] +/// A macro to implement converters from a type to [`Config`](crate::Config) and +/// [`figment::Figment`]. +/// +/// Via [Config::to_figment](crate::Config::to_figment) and the +/// [Cast](crate::FigmentProviders::Cast) profile. #[macro_export] macro_rules! impl_figment_convert_cast { ($name:ty) => { impl<'a> From<&'a $name> for $crate::figment::Figment { fn from(args: &'a $name) -> Self { - $crate::Config::figment_with_root($crate::find_project_root_path(None).unwrap()) + $crate::Config::with_root($crate::find_project_root_path(None).unwrap()) + .to_figment($crate::FigmentProviders::Cast) .merge(args) } } diff --git a/crates/config/src/providers/mod.rs b/crates/config/src/providers/mod.rs index 239ee0c74..d8fdbf438 100644 --- a/crates/config/src/providers/mod.rs +++ b/crates/config/src/providers/mod.rs @@ -1,8 +1,11 @@ +//! Config providers. + use crate::{Config, Warning, DEPRECATIONS}; use figment::{ value::{Dict, Map, Value}, Error, Figment, Metadata, Profile, Provider, }; +use std::collections::BTreeMap; /// Remappings provider pub mod remappings; @@ -43,15 +46,15 @@ impl

WarningsProvider

{ impl WarningsProvider

{ /// Collects all warnings. pub fn collect_warnings(&self) -> Result, Error> { + let data = self.provider.data().unwrap_or_default(); + let mut out = self.old_warnings.clone()?; - // add warning for unknown sections + + // Add warning for unknown sections. out.extend( - self.provider - .data() - .unwrap_or_default() - .keys() + data.keys() .filter(|k| { - k != &Config::PROFILE_SECTION && + **k != Config::PROFILE_SECTION && !Config::STANDALONE_SECTIONS.iter().any(|s| s == k) }) .map(|unknown_section| { @@ -59,26 +62,33 @@ impl WarningsProvider

{ Warning::UnknownSection { unknown_section: unknown_section.clone(), source } }), ); - // add warning for deprecated keys - out.extend( - self.provider - .data() - .unwrap_or_default() - .iter() - .flat_map(|(profile, dict)| dict.keys().map(move |key| format!("{profile}.{key}"))) - .filter_map(|key| { - DEPRECATIONS.iter().find_map(|(deprecated_key, new_value)| { - if key == *deprecated_key { - Some(Warning::DeprecatedKey { - old: deprecated_key.to_string(), - new: new_value.to_string(), - }) - } else { - None - } + + // Add warning for deprecated keys. + let deprecated_key_warning = |key| { + DEPRECATIONS.iter().find_map(|(deprecated_key, new_value)| { + if key == *deprecated_key { + Some(Warning::DeprecatedKey { + old: deprecated_key.to_string(), + new: new_value.to_string(), }) - }), + } else { + None + } + }) + }; + let profiles = data + .iter() + .filter(|(profile, _)| **profile == Config::PROFILE_SECTION) + .map(|(_, dict)| dict); + out.extend(profiles.clone().flat_map(BTreeMap::keys).filter_map(deprecated_key_warning)); + out.extend( + profiles + .filter_map(|dict| dict.get(self.profile.as_str().as_str())) + .filter_map(Value::as_dict) + .flat_map(BTreeMap::keys) + .filter_map(deprecated_key_warning), ); + Ok(out) } } @@ -91,6 +101,7 @@ impl Provider for WarningsProvider

{ Metadata::named("Warnings") } } + fn data(&self) -> Result, Error> { Ok(Map::from([( self.profile.clone(), @@ -100,6 +111,7 @@ impl Provider for WarningsProvider

{ )]), )])) } + fn profile(&self) -> Option { Some(self.profile.clone()) } diff --git a/crates/config/src/providers/remappings.rs b/crates/config/src/providers/remappings.rs index 67ae449fb..41b3fb80d 100644 --- a/crates/config/src/providers/remappings.rs +++ b/crates/config/src/providers/remappings.rs @@ -3,7 +3,7 @@ use figment::{ value::{Dict, Map}, Error, Metadata, Profile, Provider, }; -use foundry_compilers::remappings::{RelativeRemapping, Remapping}; +use foundry_compilers::artifacts::remappings::{RelativeRemapping, Remapping}; use std::{ borrow::Cow, collections::{btree_map::Entry, BTreeMap, HashSet}, @@ -102,6 +102,7 @@ impl<'a> RemappingsProvider<'a> { trace!("get all remappings from {:?}", self.root); /// prioritizes remappings that are closer: shorter `path` /// - ("a", "1/2") over ("a", "1/2/3") + /// /// grouped by remapping context fn insert_closest( mappings: &mut BTreeMap, BTreeMap>, diff --git a/crates/config/src/resolve.rs b/crates/config/src/resolve.rs index b817f4367..981ddc886 100644 --- a/crates/config/src/resolve.rs +++ b/crates/config/src/resolve.rs @@ -19,8 +19,6 @@ pub struct UnresolvedEnvVarError { pub source: VarError, } -// === impl UnresolvedEnvVarError === - impl UnresolvedEnvVarError { /// Tries to resolve a value pub fn try_resolve(&self) -> Result { diff --git a/crates/config/src/soldeer.rs b/crates/config/src/soldeer.rs new file mode 100644 index 000000000..511559bb9 --- /dev/null +++ b/crates/config/src/soldeer.rs @@ -0,0 +1,25 @@ +//! Configuration specific to the `forge soldeer` command and the `forge_soldeer` package + +use std::collections::BTreeMap; + +use serde::{Deserialize, Serialize}; + +/// Soldeer dependencies config structure +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct SoldeerDependency { + /// The version of the dependency + pub version: String, + + /// The url from where the dependency was retrieved + #[serde(default, skip_serializing_if = "Option::is_none")] + pub url: Option, +} + +/// Type for Soldeer configs, under dependencies tag in the foundry.toml +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct SoldeerConfig(BTreeMap); +impl AsRef for SoldeerConfig { + fn as_ref(&self) -> &SoldeerConfig { + self + } +} diff --git a/crates/config/src/utils.rs b/crates/config/src/utils.rs index d0d6f8e06..17af4789d 100644 --- a/crates/config/src/utils.rs +++ b/crates/config/src/utils.rs @@ -4,7 +4,7 @@ use crate::Config; use alloy_primitives::U256; use eyre::WrapErr; use figment::value::Value; -use foundry_compilers::{ +use foundry_compilers::artifacts::{ remappings::{Remapping, RemappingError}, EvmVersion, }; @@ -14,7 +14,7 @@ use std::{ path::{Path, PathBuf}, str::FromStr, }; -use toml_edit::{Document, Item}; +use toml_edit::{DocumentMut, Item}; /// Loads the config for the current project workspace pub fn load_config() -> Config { @@ -216,9 +216,9 @@ pub fn get_available_profiles(toml_path: impl AsRef) -> eyre::Result) -> eyre::Result { +fn read_toml(path: impl AsRef) -> eyre::Result { let path = path.as_ref().to_owned(); - let doc: Document = std::fs::read_to_string(path)?.parse()?; + let doc: DocumentMut = std::fs::read_to_string(path)?.parse()?; Ok(doc) } diff --git a/crates/config/src/vyper.rs b/crates/config/src/vyper.rs new file mode 100644 index 000000000..2af46b4b6 --- /dev/null +++ b/crates/config/src/vyper.rs @@ -0,0 +1,15 @@ +//! Vyper specific configuration types. + +use foundry_compilers::artifacts::vyper::VyperOptimizationMode; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct VyperConfig { + /// Vyper optimization mode. "gas", "none" or "codesize" + #[serde(default, skip_serializing_if = "Option::is_none")] + pub optimize: Option, + /// The Vyper instance to use if any. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub path: Option, +} diff --git a/crates/config/src/zksync.rs b/crates/config/src/zksync.rs index b8265418e..d1084de10 100644 --- a/crates/config/src/zksync.rs +++ b/crates/config/src/zksync.rs @@ -1,7 +1,8 @@ use foundry_compilers::{ - artifacts::Libraries, - zksync::artifacts::{BytecodeHash, Optimizer, OptimizerDetails, Settings, SettingsMetadata}, - EvmVersion, + artifacts::{EvmVersion, Libraries}, + zksolc::settings::{ + BytecodeHash, Optimizer, OptimizerDetails, SettingsMetadata, ZkSolcSettings, + }, }; use serde::{Deserialize, Serialize}; @@ -91,7 +92,7 @@ impl ZkSyncConfig { libraries: Libraries, evm_version: EvmVersion, via_ir: bool, - ) -> Settings { + ) -> ZkSolcSettings { let optimizer = Optimizer { enabled: Some(self.optimizer), mode: Some(self.optimizer_mode), @@ -101,7 +102,7 @@ impl ZkSyncConfig { jump_table_density_threshold: None, }; - Settings { + ZkSolcSettings { libraries, optimizer, evm_version: Some(evm_version), @@ -117,4 +118,13 @@ impl ZkSyncConfig { solc: self.solc_path.clone(), } } + + pub fn avoid_contracts(&self) -> Option> { + self.avoid_contracts.clone().map(|patterns| { + patterns + .into_iter() + .map(|pat| globset::Glob::new(&pat).expect("invalid pattern").compile_matcher()) + .collect::>() + }) + } } diff --git a/crates/debugger/Cargo.toml b/crates/debugger/Cargo.toml index 3c1fe8480..812c6fdad 100644 --- a/crates/debugger/Cargo.toml +++ b/crates/debugger/Cargo.toml @@ -9,6 +9,9 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [dependencies] foundry-common.workspace = true foundry-compilers.workspace = true @@ -20,6 +23,6 @@ alloy-primitives.workspace = true crossterm = "0.27" eyre.workspace = true -ratatui = { version = "0.24.0", default-features = false, features = ["crossterm"] } +ratatui = { version = "0.26", default-features = false, features = ["crossterm"] } revm.workspace = true tracing.workspace = true diff --git a/crates/debugger/src/lib.rs b/crates/debugger/src/lib.rs index 5683cb8a5..ed5da9342 100644 --- a/crates/debugger/src/lib.rs +++ b/crates/debugger/src/lib.rs @@ -2,7 +2,8 @@ //! //! Interactive Solidity TUI debugger. -#![warn(unused_crate_dependencies, unreachable_pub)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #[macro_use] extern crate tracing; diff --git a/crates/debugger/src/tui/context.rs b/crates/debugger/src/tui/context.rs index 6f8c30939..22ea7d5d3 100644 --- a/crates/debugger/src/tui/context.rs +++ b/crates/debugger/src/tui/context.rs @@ -5,13 +5,11 @@ use alloy_primitives::Address; use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind}; use foundry_evm_core::debug::{DebugNodeFlat, DebugStep}; use revm_inspectors::tracing::types::CallKind; -use std::{cell::RefCell, ops::ControlFlow}; +use std::ops::ControlFlow; /// This is currently used to remember last scroll position so screen doesn't wiggle as much. #[derive(Default)] pub(crate) struct DrawMemory { - // TODO - pub(crate) current_startline: RefCell, pub(crate) inner_call_index: usize, pub(crate) current_buf_startline: usize, pub(crate) current_stack_startline: usize, @@ -29,18 +27,18 @@ impl BufferKind { /// Helper to cycle through the active buffers. pub(crate) fn next(&self) -> Self { match self { - BufferKind::Memory => BufferKind::Calldata, - BufferKind::Calldata => BufferKind::Returndata, - BufferKind::Returndata => BufferKind::Memory, + Self::Memory => Self::Calldata, + Self::Calldata => Self::Returndata, + Self::Returndata => Self::Memory, } } /// Helper to format the title of the active buffer pane pub(crate) fn title(&self, size: usize) -> String { match self { - BufferKind::Memory => format!("Memory (max expansion: {} bytes)", size), - BufferKind::Calldata => format!("Calldata (size: {} bytes)", size), - BufferKind::Returndata => format!("Returndata (size: {} bytes)", size), + Self::Memory => format!("Memory (max expansion: {size} bytes)"), + Self::Calldata => format!("Calldata (size: {size} bytes)"), + Self::Returndata => format!("Returndata (size: {size} bytes)"), } } } @@ -115,11 +113,16 @@ impl<'a> DebuggerContext<'a> { } fn gen_opcode_list(&mut self) { - self.opcode_list = self.opcode_list(); + self.opcode_list.clear(); + let debug_steps = &self.debugger.debug_arena[self.draw_memory.inner_call_index].steps; + self.opcode_list.extend(debug_steps.iter().map(DebugStep::pretty_opcode)); } - fn opcode_list(&self) -> Vec { - self.debug_steps().iter().map(DebugStep::pretty_opcode).collect() + fn gen_opcode_list_if_necessary(&mut self) { + if self.last_index != self.draw_memory.inner_call_index { + self.gen_opcode_list(); + self.last_index = self.draw_memory.inner_call_index; + } } fn active_buffer(&self) -> &[u8] { @@ -133,19 +136,18 @@ impl<'a> DebuggerContext<'a> { impl DebuggerContext<'_> { pub(crate) fn handle_event(&mut self, event: Event) -> ControlFlow { - if self.last_index != self.draw_memory.inner_call_index { - self.gen_opcode_list(); - self.last_index = self.draw_memory.inner_call_index; - } - - match event { + let ret = match event { Event::Key(event) => self.handle_key_event(event), Event::Mouse(event) => self.handle_mouse_event(event), _ => ControlFlow::Continue(()), - } + }; + // Generate the list after the event has been handled. + self.gen_opcode_list_if_necessary(); + ret } fn handle_key_event(&mut self, event: KeyEvent) -> ControlFlow { + // Breakpoints if let KeyCode::Char(c) = event.code { if c.is_alphabetic() && self.key_buffer.starts_with('\'') { self.handle_breakpoint(c); @@ -153,154 +155,130 @@ impl DebuggerContext<'_> { } } + let control = event.modifiers.contains(KeyModifiers::CONTROL); + match event.code { // Exit KeyCode::Char('q') => return ControlFlow::Break(ExitReason::CharExit), - // Move down - KeyCode::Char('j') | KeyCode::Down => { - // Grab number of times to do it - for _ in 0..buffer_as_number(&self.key_buffer, 1) { - if event.modifiers.contains(KeyModifiers::CONTROL) { - let max_buf = (self.active_buffer().len() / 32).saturating_sub(1); - if self.draw_memory.current_buf_startline < max_buf { - self.draw_memory.current_buf_startline += 1; - } - } else if self.current_step < self.opcode_list.len() - 1 { - self.current_step += 1; - } else if self.draw_memory.inner_call_index < self.debug_arena().len() - 1 { - self.draw_memory.inner_call_index += 1; - self.current_step = 0; - } - } - self.key_buffer.clear(); - } - KeyCode::Char('J') => { - for _ in 0..buffer_as_number(&self.key_buffer, 1) { - let max_stack = self.current_step().stack.len().saturating_sub(1); - if self.draw_memory.current_stack_startline < max_stack { - self.draw_memory.current_stack_startline += 1; - } + + // Scroll up the memory buffer + KeyCode::Char('k') | KeyCode::Up if control => self.repeat(|this| { + this.draw_memory.current_buf_startline = + this.draw_memory.current_buf_startline.saturating_sub(1); + }), + // Scroll down the memory buffer + KeyCode::Char('j') | KeyCode::Down if control => self.repeat(|this| { + let max_buf = (this.active_buffer().len() / 32).saturating_sub(1); + if this.draw_memory.current_buf_startline < max_buf { + this.draw_memory.current_buf_startline += 1; } - self.key_buffer.clear(); - } + }), + // Move up - KeyCode::Char('k') | KeyCode::Up => { - for _ in 0..buffer_as_number(&self.key_buffer, 1) { - if event.modifiers.contains(KeyModifiers::CONTROL) { - self.draw_memory.current_buf_startline = - self.draw_memory.current_buf_startline.saturating_sub(1); - } else if self.current_step > 0 { - self.current_step -= 1; - } else if self.draw_memory.inner_call_index > 0 { - self.draw_memory.inner_call_index -= 1; - self.current_step = self.debug_steps().len() - 1; - } - } - self.key_buffer.clear(); - } - KeyCode::Char('K') => { - for _ in 0..buffer_as_number(&self.key_buffer, 1) { - self.draw_memory.current_stack_startline = - self.draw_memory.current_stack_startline.saturating_sub(1); + KeyCode::Char('k') | KeyCode::Up => self.repeat(Self::step_back), + // Move down + KeyCode::Char('j') | KeyCode::Down => self.repeat(Self::step), + + // Scroll up the stack + KeyCode::Char('K') => self.repeat(|this| { + this.draw_memory.current_stack_startline = + this.draw_memory.current_stack_startline.saturating_sub(1); + }), + // Scroll down the stack + KeyCode::Char('J') => self.repeat(|this| { + let max_stack = this.current_step().stack.len().saturating_sub(1); + if this.draw_memory.current_stack_startline < max_stack { + this.draw_memory.current_stack_startline += 1; } - self.key_buffer.clear(); - } + }), + + // Cycle buffers KeyCode::Char('b') => { self.active_buffer = self.active_buffer.next(); self.draw_memory.current_buf_startline = 0; } + // Go to top of file KeyCode::Char('g') => { self.draw_memory.inner_call_index = 0; self.current_step = 0; - self.key_buffer.clear(); } + // Go to bottom of file KeyCode::Char('G') => { self.draw_memory.inner_call_index = self.debug_arena().len() - 1; - self.current_step = self.debug_steps().len() - 1; - self.key_buffer.clear(); + self.current_step = self.n_steps() - 1; } + // Go to previous call KeyCode::Char('c') => { self.draw_memory.inner_call_index = self.draw_memory.inner_call_index.saturating_sub(1); - self.current_step = self.debug_steps().len() - 1; - self.key_buffer.clear(); + self.current_step = self.n_steps() - 1; } + // Go to next call KeyCode::Char('C') => { if self.debug_arena().len() > self.draw_memory.inner_call_index + 1 { self.draw_memory.inner_call_index += 1; self.current_step = 0; } - self.key_buffer.clear(); } + // Step forward - KeyCode::Char('s') => { - for _ in 0..buffer_as_number(&self.key_buffer, 1) { - let remaining_ops = self.opcode_list[self.current_step..].to_vec(); - self.current_step += remaining_ops - .iter() - .enumerate() - .find_map(|(i, op)| { - if i < remaining_ops.len() - 1 { - match ( - op.contains("JUMP") && op != "JUMPDEST", - &*remaining_ops[i + 1], - ) { - (true, "JUMPDEST") => Some(i + 1), - _ => None, - } - } else { - None - } - }) - .unwrap_or(self.opcode_list.len() - 1); - if self.current_step > self.opcode_list.len() { - self.current_step = self.opcode_list.len() - 1 - }; + KeyCode::Char('s') => self.repeat(|this| { + let remaining_ops = &this.opcode_list[this.current_step..]; + if let Some((i, _)) = remaining_ops.iter().enumerate().skip(1).find(|&(i, op)| { + let prev = &remaining_ops[i - 1]; + let prev_is_jump = prev.contains("JUMP") && prev != "JUMPDEST"; + let is_jumpdest = op == "JUMPDEST"; + prev_is_jump && is_jumpdest + }) { + this.current_step += i; } - self.key_buffer.clear(); - } + }), + // Step backwards - KeyCode::Char('a') => { - for _ in 0..buffer_as_number(&self.key_buffer, 1) { - let prev_ops = self.opcode_list[..self.current_step].to_vec(); - self.current_step = prev_ops - .iter() - .enumerate() - .rev() - .find_map(|(i, op)| { - if i > 0 { - match ( - prev_ops[i - 1].contains("JUMP") && - prev_ops[i - 1] != "JUMPDEST", - &**op, - ) { - (true, "JUMPDEST") => Some(i - 1), - _ => None, - } - } else { - None - } - }) - .unwrap_or_default(); - } - self.key_buffer.clear(); - } - // toggle stack labels + KeyCode::Char('a') => self.repeat(|this| { + let ops = &this.opcode_list[..this.current_step]; + this.current_step = ops + .iter() + .enumerate() + .skip(1) + .rev() + .find(|&(i, op)| { + let prev = &ops[i - 1]; + let prev_is_jump = prev.contains("JUMP") && prev != "JUMPDEST"; + let is_jumpdest = op == "JUMPDEST"; + prev_is_jump && is_jumpdest + }) + .map(|(i, _)| i) + .unwrap_or_default(); + }), + + // Toggle stack labels KeyCode::Char('t') => self.stack_labels = !self.stack_labels, - // toggle memory utf8 decoding + + // Toggle memory UTF-8 decoding KeyCode::Char('m') => self.buf_utf = !self.buf_utf, - // toggle help notice + + // Toggle help notice KeyCode::Char('h') => self.show_shortcuts = !self.show_shortcuts, + + // Numbers for repeating commands or breakpoints KeyCode::Char( other @ ('0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '\''), - ) => self.key_buffer.push(other), - _ => self.key_buffer.clear(), + ) => { + // Early return to not clear the buffer. + self.key_buffer.push(other); + return ControlFlow::Continue(()); + } + + // Unknown/unhandled key code + _ => {} }; + self.key_buffer.clear(); ControlFlow::Continue(()) } @@ -323,37 +301,47 @@ impl DebuggerContext<'_> { fn handle_mouse_event(&mut self, event: MouseEvent) -> ControlFlow { match event.kind { - MouseEventKind::ScrollUp => { - if self.current_step > 0 { - self.current_step -= 1; - } else if self.draw_memory.inner_call_index > 0 { - self.draw_memory.inner_call_index -= 1; - self.draw_memory.current_buf_startline = 0; - self.draw_memory.current_stack_startline = 0; - self.current_step = self.debug_steps().len() - 1; - } - } - MouseEventKind::ScrollDown => { - if self.current_step < self.opcode_list.len() - 1 { - self.current_step += 1; - } else if self.draw_memory.inner_call_index < self.debug_arena().len() - 1 { - self.draw_memory.inner_call_index += 1; - self.draw_memory.current_buf_startline = 0; - self.draw_memory.current_stack_startline = 0; - self.current_step = 0; - } - } + MouseEventKind::ScrollUp => self.step_back(), + MouseEventKind::ScrollDown => self.step(), _ => {} } ControlFlow::Continue(()) } + + fn step_back(&mut self) { + if self.current_step > 0 { + self.current_step -= 1; + } else if self.draw_memory.inner_call_index > 0 { + self.draw_memory.inner_call_index -= 1; + self.current_step = self.n_steps() - 1; + } + } + + fn step(&mut self) { + if self.current_step < self.n_steps() - 1 { + self.current_step += 1; + } else if self.draw_memory.inner_call_index < self.debug_arena().len() - 1 { + self.draw_memory.inner_call_index += 1; + self.current_step = 0; + } + } + + /// Calls a closure `f` the number of times specified in the key buffer, and at least once. + fn repeat(&mut self, mut f: impl FnMut(&mut Self)) { + for _ in 0..buffer_as_number(&self.key_buffer) { + f(self); + } + } + + fn n_steps(&self) -> usize { + self.debug_steps().len() + } } /// Grab number from buffer. Used for something like '10k' to move up 10 operations -fn buffer_as_number(s: &str, default_value: usize) -> usize { - match s.parse() { - Ok(num) if num >= 1 => num, - _ => default_value, - } +fn buffer_as_number(s: &str) -> usize { + const MIN: usize = 1; + const MAX: usize = 100_000; + s.parse().unwrap_or(MIN).clamp(MIN, MAX) } diff --git a/crates/debugger/src/tui/draw.rs b/crates/debugger/src/tui/draw.rs index d06e5c231..7ac0c64e5 100644 --- a/crates/debugger/src/tui/draw.rs +++ b/crates/debugger/src/tui/draw.rs @@ -3,18 +3,19 @@ use super::context::{BufferKind, DebuggerContext}; use crate::op::OpcodeParam; use alloy_primitives::U256; -use foundry_compilers::sourcemap::SourceElement; -use foundry_evm_core::debug::Instruction; +use foundry_compilers::{ + artifacts::sourcemap::SourceElement, compilers::multi::MultiCompilerLanguage, +}; use ratatui::{ layout::{Alignment, Constraint, Direction, Layout, Rect}, style::{Color, Modifier, Style}, terminal::Frame, text::{Line, Span, Text}, - widgets::{Block, Borders, Paragraph, Wrap}, + widgets::{Block, Borders, List, ListItem, ListState, Paragraph, Wrap}, }; use revm::interpreter::opcode; use revm_inspectors::tracing::types::CallKind; -use std::{cmp, collections::VecDeque, fmt::Write, io}; +use std::{collections::VecDeque, fmt::Write, io}; impl DebuggerContext<'_> { /// Draws the TUI layout and subcomponents to the given terminal. @@ -92,25 +93,25 @@ impl DebuggerContext<'_> { // constraints, so the `else` branch is unreachable. // Split off footer. - let [app, footer] = Layout::new() - .constraints([Constraint::Ratio(100 - h_height, 100), Constraint::Ratio(h_height, 100)]) - .direction(Direction::Vertical) - .split(area)[..] - else { + let [app, footer] = Layout::new( + Direction::Vertical, + [Constraint::Ratio(100 - h_height, 100), Constraint::Ratio(h_height, 100)], + ) + .split(area)[..] else { unreachable!() }; // Split the app in 4 vertically to construct all the panes. - let [op_pane, stack_pane, memory_pane, src_pane] = Layout::new() - .direction(Direction::Vertical) - .constraints([ + let [op_pane, stack_pane, memory_pane, src_pane] = Layout::new( + Direction::Vertical, + [ Constraint::Ratio(1, 6), Constraint::Ratio(1, 6), Constraint::Ratio(1, 6), Constraint::Ratio(3, 6), - ]) - .split(app)[..] - else { + ], + ) + .split(app)[..] else { unreachable!() }; @@ -139,37 +140,34 @@ impl DebuggerContext<'_> { let h_height = if self.show_shortcuts { 4 } else { 0 }; // Split off footer. - let [app, footer] = Layout::new() - .direction(Direction::Vertical) - .constraints([Constraint::Ratio(100 - h_height, 100), Constraint::Ratio(h_height, 100)]) - .split(area)[..] - else { + let [app, footer] = Layout::new( + Direction::Vertical, + [Constraint::Ratio(100 - h_height, 100), Constraint::Ratio(h_height, 100)], + ) + .split(area)[..] else { unreachable!() }; // Split app in 2 horizontally. - let [app_left, app_right] = Layout::new() - .direction(Direction::Horizontal) - .constraints([Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]) - .split(app)[..] + let [app_left, app_right] = + Layout::new(Direction::Horizontal, [Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)]) + .split(app)[..] else { unreachable!() }; // Split left pane in 2 vertically to opcode list and source. - let [op_pane, src_pane] = Layout::new() - .direction(Direction::Vertical) - .constraints([Constraint::Ratio(1, 4), Constraint::Ratio(3, 4)]) - .split(app_left)[..] + let [op_pane, src_pane] = + Layout::new(Direction::Vertical, [Constraint::Ratio(1, 4), Constraint::Ratio(3, 4)]) + .split(app_left)[..] else { unreachable!() }; // Split right pane horizontally to construct stack and memory. - let [stack_pane, memory_pane] = Layout::new() - .direction(Direction::Vertical) - .constraints([Constraint::Ratio(1, 4), Constraint::Ratio(3, 4)]) - .split(app_right)[..] + let [stack_pane, memory_pane] = + Layout::new(Direction::Vertical, [Constraint::Ratio(1, 4), Constraint::Ratio(3, 4)]) + .split(app_right)[..] else { unreachable!() }; @@ -195,31 +193,37 @@ impl DebuggerContext<'_> { } fn draw_src(&self, f: &mut Frame<'_>, area: Rect) { - let text_output = self.src_text(area); - let title = match self.call_kind() { + let (text_output, source_name) = self.src_text(area); + let call_kind_text = match self.call_kind() { CallKind::Create | CallKind::Create2 => "Contract creation", CallKind::Call => "Contract call", CallKind::StaticCall => "Contract staticcall", CallKind::CallCode => "Contract callcode", CallKind::DelegateCall => "Contract delegatecall", + CallKind::AuthCall => "Contract authcall", }; + let title = format!( + "{} {} ", + call_kind_text, + source_name.map(|s| format!("| {s}")).unwrap_or_default() + ); let block = Block::default().title(title).borders(Borders::ALL); let paragraph = Paragraph::new(text_output).block(block).wrap(Wrap { trim: false }); f.render_widget(paragraph, area); } - fn src_text(&self, area: Rect) -> Text<'_> { - let (source_element, source_code) = match self.src_map() { + fn src_text(&self, area: Rect) -> (Text<'_>, Option<&str>) { + let (source_element, source_code, source_file) = match self.src_map() { Ok(r) => r, - Err(e) => return Text::from(e), + Err(e) => return (Text::from(e), None), }; // We are handed a vector of SourceElements that give us a span of sourcecode that is // currently being executed. This includes an offset and length. // This vector is in instruction pointer order, meaning the location of the instruction // minus `sum(push_bytes[..pc])`. - let offset = source_element.offset; - let len = source_element.length; + let offset = source_element.offset() as usize; + let len = source_element.length() as usize; let max = source_code.len(); // Split source into before, relevant, and after chunks, split by line, for formatting. @@ -325,10 +329,19 @@ impl DebuggerContext<'_> { lines.push(u_num, line, u_text); } - Text::from(lines.lines) + // pad with empty to each line to ensure the previous text is cleared + for line in &mut lines.lines { + // note that the \n is not included in the line length + if area.width as usize > line.width() + 1 { + line.push_span(Span::raw(" ".repeat(area.width as usize - line.width() - 1))); + } + } + + (Text::from(lines.lines), Some(source_file)) } - fn src_map(&self) -> Result<(SourceElement, &str), String> { + /// Returns source map, source code and source name of the current line. + fn src_map(&self) -> Result<(SourceElement, &str, &str), String> { let address = self.address(); let Some(contract_name) = self.debugger.identified_contracts.get(address) else { return Err(format!("Unknown contract at address {address}")); @@ -346,98 +359,71 @@ impl DebuggerContext<'_> { let is_create = matches!(self.call_kind(), CallKind::Create | CallKind::Create2); let pc = self.current_step().pc; - let Some((source_element, source_code)) = - files_source_code.find_map(|(file_id, (source_code, contract_source))| { + let Some((source_element, source_code, source_file)) = + files_source_code.find_map(|(artifact, source)| { let bytecode = if is_create { - &contract_source.bytecode + &artifact.bytecode.bytecode } else { - contract_source.deployed_bytecode.bytecode.as_ref()? + artifact.bytecode.deployed_bytecode.bytecode.as_ref()? }; - let mut source_map = bytecode.source_map()?.ok()?; + let source_map = bytecode.source_map()?.expect("failed to parse"); let pc_ic_map = if is_create { create_map } else { rt_map }; let ic = pc_ic_map.get(pc)?; - let source_element = source_map.swap_remove(ic); + + // Solc indexes source maps by instruction counter, but Vyper indexes by program + // counter. + let source_element = if matches!(source.language, MultiCompilerLanguage::Solc(_)) { + source_map.get(ic)? + } else { + source_map.get(pc)? + }; // if the source element has an index, find the sourcemap for that index - source_element - .index - .and_then(|index| + let res = source_element + .index() // if index matches current file_id, return current source code - (index == file_id).then(|| (source_element.clone(), source_code))) + .and_then(|index| { + (index == artifact.file_id) + .then(|| (source_element.clone(), source.source.as_str(), &source.name)) + }) .or_else(|| { // otherwise find the source code for the element's index self.debugger .contracts_sources .sources_by_id - .get(&(source_element.index?)) - .map(|(source_code, _)| (source_element.clone(), source_code)) - }) + .get(&artifact.build_id)? + .get(&source_element.index()?) + .map(|source| { + (source_element.clone(), source.source.as_str(), &source.name) + }) + }); + + res }) else { return Err(format!("No source map for contract {contract_name}")); }; - Ok((source_element, source_code)) + Ok((source_element, source_code, source_file)) } fn draw_op_list(&self, f: &mut Frame<'_>, area: Rect) { - let height = area.height as i32; - let extra_top_lines = height / 2; - // Absolute minimum start line - let abs_min_start = 0; - // Adjust for weird scrolling for max top line - let abs_max_start = (self.opcode_list.len() as i32 - 1) - (height / 2); - // actual minimum start line - let mut min_start = - cmp::max(self.current_step as i32 - height + extra_top_lines, abs_min_start) as usize; - - // actual max start line - let mut max_start = cmp::max( - cmp::min(self.current_step as i32 - extra_top_lines, abs_max_start), - abs_min_start, - ) as usize; - - // Sometimes, towards end of file, maximum and minim lines have swapped values. Swap if the - // case - if min_start > max_start { - std::mem::swap(&mut min_start, &mut max_start); - } - - let prev_start = *self.draw_memory.current_startline.borrow(); - let display_start = prev_start.clamp(min_start, max_start); - *self.draw_memory.current_startline.borrow_mut() = display_start; - - let max_pc = self.debug_steps().iter().map(|step| step.pc).max().unwrap_or(0); + let debug_steps = self.debug_steps(); + let max_pc = debug_steps.iter().map(|step| step.pc).max().unwrap_or(0); let max_pc_len = hex_digits(max_pc); - let debug_steps = self.debug_steps(); - let mut lines = Vec::new(); - let mut add_new_line = |line_number: usize| { - let mut line = String::with_capacity(64); - - let is_current_step = line_number == self.current_step; - if line_number < self.debug_steps().len() { - let step = &debug_steps[line_number]; - write!(line, "{:0>max_pc_len$x}|", step.pc).unwrap(); - line.push_str(if is_current_step { "▶" } else { " " }); - if let Some(op) = self.opcode_list.get(line_number) { - line.push_str(op); + let items = debug_steps + .iter() + .enumerate() + .map(|(i, step)| { + let mut content = String::with_capacity(64); + write!(content, "{:0>max_pc_len$x}|", step.pc).unwrap(); + if let Some(op) = self.opcode_list.get(i) { + content.push_str(op); } - } else { - line.push_str("END CALL"); - } - - let bg_color = if is_current_step { Color::DarkGray } else { Color::Reset }; - let style = Style::new().fg(Color::White).bg(bg_color); - lines.push(Line::from(Span::styled(line, style))); - }; - - for number in display_start..self.opcode_list.len() { - add_new_line(number); - } - - // Add one more "phantom" line so we see line where current segment execution ends - add_new_line(self.opcode_list.len()); + ListItem::new(Span::styled(content, Style::new().fg(Color::White))) + }) + .collect::>(); let title = format!( "Address: {} | PC: {} | Gas used in call: {}", @@ -446,8 +432,13 @@ impl DebuggerContext<'_> { self.current_step().total_gas_used, ); let block = Block::default().title(title).borders(Borders::ALL); - let paragraph = Paragraph::new(lines).block(block).wrap(Wrap { trim: true }); - f.render_widget(paragraph, area); + let list = List::new(items) + .block(block) + .highlight_symbol("▶") + .highlight_style(Style::new().fg(Color::White).bg(Color::DarkGray)) + .scroll_padding(1); + let mut state = ListState::default().with_selected(Some(self.current_step)); + f.render_stateful_widget(list, area, &mut state); } fn draw_stack(&self, f: &mut Frame<'_>, area: Rect) { @@ -456,10 +447,9 @@ impl DebuggerContext<'_> { let min_len = decimal_digits(stack.len()).max(2); - let params = - if let Instruction::OpCode(op) = step.instruction { OpcodeParam::of(op) } else { &[] }; + let params = OpcodeParam::of(step.instruction); - let text: Vec = stack + let text: Vec> = stack .iter() .rev() .enumerate() @@ -503,9 +493,9 @@ impl DebuggerContext<'_> { fn draw_buffer(&self, f: &mut Frame<'_>, area: Rect) { let step = self.current_step(); let buf = match self.active_buffer { - BufferKind::Memory => &step.memory, - BufferKind::Calldata => &step.calldata, - BufferKind::Returndata => &step.returndata, + BufferKind::Memory => step.memory.as_ref(), + BufferKind::Calldata => step.calldata.as_ref(), + BufferKind::Returndata => step.returndata.as_ref(), }; let min_len = hex_digits(buf.len()); @@ -516,20 +506,18 @@ impl DebuggerContext<'_> { let mut write_offset = None; let mut write_size = None; let mut color = None; - if let Instruction::OpCode(op) = step.instruction { - let stack_len = step.stack.len(); - if stack_len > 0 { - if let Some(accesses) = get_buffer_accesses(op, &step.stack) { - if let Some(read_access) = accesses.read { - offset = Some(read_access.1.offset); - size = Some(read_access.1.size); - color = Some(Color::Cyan); - } - if let Some(write_access) = accesses.write { - if self.active_buffer == BufferKind::Memory { - write_offset = Some(write_access.offset); - write_size = Some(write_access.size); - } + let stack_len = step.stack.len(); + if stack_len > 0 { + if let Some(accesses) = get_buffer_accesses(step.instruction, &step.stack) { + if let Some(read_access) = accesses.read { + offset = Some(read_access.1.offset); + size = Some(read_access.1.size); + color = Some(Color::Cyan); + } + if let Some(write_access) = accesses.write { + if self.active_buffer == BufferKind::Memory { + write_offset = Some(write_access.offset); + write_size = Some(write_access.size); } } } @@ -542,15 +530,13 @@ impl DebuggerContext<'_> { if self.current_step > 0 { let prev_step = self.current_step - 1; let prev_step = &self.debug_steps()[prev_step]; - if let Instruction::OpCode(op) = prev_step.instruction { - if let Some(write_access) = - get_buffer_accesses(op, &prev_step.stack).and_then(|a| a.write) - { - if self.active_buffer == BufferKind::Memory { - offset = Some(write_access.offset); - size = Some(write_access.size); - color = Some(Color::Green); - } + if let Some(write_access) = + get_buffer_accesses(prev_step.instruction, &prev_step.stack).and_then(|a| a.write) + { + if self.active_buffer == BufferKind::Memory { + offset = Some(write_access.offset); + size = Some(write_access.size); + color = Some(Color::Green); } } } @@ -558,7 +544,7 @@ impl DebuggerContext<'_> { let height = area.height as usize; let end_line = self.draw_memory.current_buf_startline + height; - let text: Vec = buf + let text: Vec> = buf .chunks(32) .enumerate() .skip(self.draw_memory.current_buf_startline) diff --git a/crates/debugger/src/tui/mod.rs b/crates/debugger/src/tui/mod.rs index d9543fc58..c810440e5 100644 --- a/crates/debugger/src/tui/mod.rs +++ b/crates/debugger/src/tui/mod.rs @@ -13,7 +13,6 @@ use ratatui::{ backend::{Backend, CrosstermBackend}, Terminal, }; -use revm::primitives::SpecId; use std::{ collections::{BTreeMap, HashMap}, io, @@ -67,12 +66,12 @@ impl Debugger { ) -> Self { let pc_ic_maps = contracts_sources .entries() - .filter_map(|(contract_name, (_, contract))| { + .filter_map(|(name, artifact, _)| { Some(( - contract_name.clone(), + name.to_owned(), ( - PcIcMap::new(SpecId::LATEST, contract.bytecode.bytes()?), - PcIcMap::new(SpecId::LATEST, contract.deployed_bytecode.bytes()?), + PcIcMap::new(artifact.bytecode.bytecode.bytes()?), + PcIcMap::new(artifact.bytecode.deployed_bytecode.bytes()?), ), )) }) @@ -115,16 +114,13 @@ impl Debugger { .spawn(move || Self::event_listener(tx)) .expect("failed to spawn thread"); - // Draw the initial state. - cx.draw(terminal)?; - // Start the event loop. loop { + cx.draw(terminal)?; match cx.handle_event(rx.recv()?) { ControlFlow::Continue(()) => {} ControlFlow::Break(reason) => return Ok(reason), } - cx.draw(terminal)?; } } diff --git a/crates/doc/Cargo.toml b/crates/doc/Cargo.toml index 73368bd9f..ab74ef697 100644 --- a/crates/doc/Cargo.toml +++ b/crates/doc/Cargo.toml @@ -10,25 +10,27 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [dependencies] forge-fmt.workspace = true -foundry-common.workspace = true foundry-compilers.workspace = true foundry-config.workspace = true alloy-primitives.workspace = true -auto_impl = "1" +auto_impl.workspace = true derive_more = "0.99" eyre.workspace = true itertools.workspace = true mdbook = { version = "0.4", default-features = false, features = ["search"] } -once_cell = "1" -rayon = "1" +once_cell.workspace = true +rayon.workspace = true serde_json.workspace = true serde.workspace = true solang-parser.workspace = true -thiserror = "1" +thiserror.workspace = true toml.workspace = true tracing.workspace = true regex = "1.10.2" diff --git a/crates/doc/src/builder.rs b/crates/doc/src/builder.rs index ac54e97e2..e21e80c22 100644 --- a/crates/doc/src/builder.rs +++ b/crates/doc/src/builder.rs @@ -3,9 +3,8 @@ use crate::{ ParseSource, Parser, Preprocessor, }; use forge_fmt::{FormatterConfig, Visitable}; -use foundry_common::glob::expand_globs; -use foundry_compilers::utils::source_files_iter; -use foundry_config::DocConfig; +use foundry_compilers::{compilers::solc::SOLC_EXTENSIONS, utils::source_files_iter}; +use foundry_config::{filter::expand_globs, DocConfig}; use itertools::Itertools; use mdbook::MDBook; use rayon::prelude::*; @@ -101,7 +100,7 @@ impl DocBuilder { let ignored = expand_globs(&self.root, self.config.ignore.iter())?; // Collect and parse source files - let sources = source_files_iter(&self.sources) + let sources = source_files_iter(&self.sources, SOLC_EXTENSIONS) .filter(|file| !ignored.contains(file)) .collect::>(); @@ -110,7 +109,11 @@ impl DocBuilder { return Ok(()) } - let library_sources = self.libraries.iter().flat_map(source_files_iter).collect::>(); + let library_sources = self + .libraries + .iter() + .flat_map(|lib| source_files_iter(lib, SOLC_EXTENSIONS)) + .collect::>(); let combined_sources = sources .iter() diff --git a/crates/doc/src/document.rs b/crates/doc/src/document.rs index 3f1f2935c..be4c1e647 100644 --- a/crates/doc/src/document.rs +++ b/crates/doc/src/document.rs @@ -90,36 +90,34 @@ pub enum DocumentContent { impl DocumentContent { pub(crate) fn len(&self) -> usize { match self { - DocumentContent::Empty => 0, - DocumentContent::Single(_) => 1, - DocumentContent::Constants(items) => items.len(), - DocumentContent::OverloadedFunctions(items) => items.len(), + Self::Empty => 0, + Self::Single(_) => 1, + Self::Constants(items) => items.len(), + Self::OverloadedFunctions(items) => items.len(), } } pub(crate) fn get_mut(&mut self, index: usize) -> Option<&mut ParseItem> { match self { - DocumentContent::Empty => None, - DocumentContent::Single(item) => { + Self::Empty => None, + Self::Single(item) => { if index == 0 { Some(item) } else { None } } - DocumentContent::Constants(items) => items.get_mut(index), - DocumentContent::OverloadedFunctions(items) => items.get_mut(index), + Self::Constants(items) => items.get_mut(index), + Self::OverloadedFunctions(items) => items.get_mut(index), } } pub fn iter_items(&self) -> ParseItemIter<'_> { match self { - DocumentContent::Empty => ParseItemIter { next: None, other: None }, - DocumentContent::Single(item) => ParseItemIter { next: Some(item), other: None }, - DocumentContent::Constants(items) => { - ParseItemIter { next: None, other: Some(items.iter()) } - } - DocumentContent::OverloadedFunctions(items) => { + Self::Empty => ParseItemIter { next: None, other: None }, + Self::Single(item) => ParseItemIter { next: Some(item), other: None }, + Self::Constants(items) => ParseItemIter { next: None, other: Some(items.iter()) }, + Self::OverloadedFunctions(items) => { ParseItemIter { next: None, other: Some(items.iter()) } } } @@ -127,12 +125,12 @@ impl DocumentContent { pub fn iter_items_mut(&mut self) -> ParseItemIterMut<'_> { match self { - DocumentContent::Empty => ParseItemIterMut { next: None, other: None }, - DocumentContent::Single(item) => ParseItemIterMut { next: Some(item), other: None }, - DocumentContent::Constants(items) => { + Self::Empty => ParseItemIterMut { next: None, other: None }, + Self::Single(item) => ParseItemIterMut { next: Some(item), other: None }, + Self::Constants(items) => { ParseItemIterMut { next: None, other: Some(items.iter_mut()) } } - DocumentContent::OverloadedFunctions(items) => { + Self::OverloadedFunctions(items) => { ParseItemIterMut { next: None, other: Some(items.iter_mut()) } } } diff --git a/crates/doc/src/lib.rs b/crates/doc/src/lib.rs index 3f92be47a..fd8e4d3ba 100644 --- a/crates/doc/src/lib.rs +++ b/crates/doc/src/lib.rs @@ -1,13 +1,9 @@ //! The module for generating Solidity documentation. //! -//! See [DocBuilder] - -#![warn(missing_debug_implementations, missing_docs, unreachable_pub, unused_crate_dependencies)] -#![deny(unused_must_use, rust_2018_idioms)] -#![doc(test( - no_crate_inject, - attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables)) -))] +//! See [`DocBuilder`]. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #[macro_use] extern crate tracing; diff --git a/crates/doc/src/parser/comment.rs b/crates/doc/src/parser/comment.rs index b6e6d08af..0f8e91c79 100644 --- a/crates/doc/src/parser/comment.rs +++ b/crates/doc/src/parser/comment.rs @@ -3,7 +3,7 @@ use solang_parser::doccomment::DocCommentTag; use std::collections::HashMap; /// The natspec comment tag explaining the purpose of the comment. -/// See: https://docs.soliditylang.org/en/v0.8.17/natspec-format.html#tags. +/// See: . #[derive(Clone, Debug, PartialEq)] pub enum CommentTag { /// A title that should describe the contract/interface @@ -28,20 +28,20 @@ impl CommentTag { fn from_str(s: &str) -> Option { let trimmed = s.trim(); let tag = match trimmed { - "title" => CommentTag::Title, - "author" => CommentTag::Author, - "notice" => CommentTag::Notice, - "dev" => CommentTag::Dev, - "param" => CommentTag::Param, - "return" => CommentTag::Return, - "inheritdoc" => CommentTag::Inheritdoc, + "title" => Self::Title, + "author" => Self::Author, + "notice" => Self::Notice, + "dev" => Self::Dev, + "param" => Self::Param, + "return" => Self::Return, + "inheritdoc" => Self::Inheritdoc, _ if trimmed.starts_with("custom:") => { // `@custom:param` tag will be parsed as `CommentTag::Param` due to a limitation // on specifying parameter docs for unnamed function arguments. let custom_tag = trimmed.trim_start_matches("custom:").trim(); match custom_tag { - "param" => CommentTag::Param, - _ => CommentTag::Custom(custom_tag.to_owned()), + "param" => Self::Param, + _ => Self::Custom(custom_tag.to_owned()), } } _ => { @@ -54,7 +54,8 @@ impl CommentTag { } /// The natspec documentation comment. -/// https://docs.soliditylang.org/en/v0.8.17/natspec-format.html +/// +/// Ref: #[derive(Clone, Debug, PartialEq)] pub struct Comment { /// The doc comment tag. @@ -126,9 +127,9 @@ impl Comments { pub fn merge_inheritdoc( &self, ident: &str, - inheritdocs: Option>, - ) -> Comments { - let mut result = Comments(Vec::from_iter(self.iter().cloned())); + inheritdocs: Option>, + ) -> Self { + let mut result = Self(Vec::from_iter(self.iter().cloned())); if let (Some(inheritdocs), Some(base)) = (inheritdocs, self.find_inheritdoc_base()) { let key = format!("{base}.{ident}"); @@ -157,18 +158,18 @@ pub struct CommentsRef<'a>(Vec<&'a Comment>); impl<'a> CommentsRef<'a> { /// Filter a collection of comments and return only those that match a provided tag - pub fn include_tag(&self, tag: CommentTag) -> CommentsRef<'a> { + pub fn include_tag(&self, tag: CommentTag) -> Self { self.include_tags(&[tag]) } /// Filter a collection of comments and return only those that match provided tags - pub fn include_tags(&self, tags: &[CommentTag]) -> CommentsRef<'a> { + pub fn include_tags(&self, tags: &[CommentTag]) -> Self { // Cloning only references here CommentsRef(self.iter().cloned().filter(|c| tags.contains(&c.tag)).collect()) } /// Filter a collection of comments and return only those that do not match provided tags - pub fn exclude_tags(&self, tags: &[CommentTag]) -> CommentsRef<'a> { + pub fn exclude_tags(&self, tags: &[CommentTag]) -> Self { // Cloning only references here CommentsRef(self.iter().cloned().filter(|c| !tags.contains(&c.tag)).collect()) } diff --git a/crates/doc/src/parser/item.rs b/crates/doc/src/parser/item.rs index 60faf78b4..b93f0d199 100644 --- a/crates/doc/src/parser/item.rs +++ b/crates/doc/src/parser/item.rs @@ -75,7 +75,7 @@ impl ParseItem { } /// Set children on the [ParseItem]. - pub fn with_children(mut self, children: Vec) -> Self { + pub fn with_children(mut self, children: Vec) -> Self { self.children = children; self } @@ -171,16 +171,16 @@ impl ParseSource { /// Get the identity of the source pub fn ident(&self) -> String { match self { - ParseSource::Contract(contract) => contract.name.safe_unwrap().name.to_owned(), - ParseSource::Variable(var) => var.name.safe_unwrap().name.to_owned(), - ParseSource::Event(event) => event.name.safe_unwrap().name.to_owned(), - ParseSource::Error(error) => error.name.safe_unwrap().name.to_owned(), - ParseSource::Struct(structure) => structure.name.safe_unwrap().name.to_owned(), - ParseSource::Enum(enumerable) => enumerable.name.safe_unwrap().name.to_owned(), - ParseSource::Function(func) => { + Self::Contract(contract) => contract.name.safe_unwrap().name.to_owned(), + Self::Variable(var) => var.name.safe_unwrap().name.to_owned(), + Self::Event(event) => event.name.safe_unwrap().name.to_owned(), + Self::Error(error) => error.name.safe_unwrap().name.to_owned(), + Self::Struct(structure) => structure.name.safe_unwrap().name.to_owned(), + Self::Enum(enumerable) => enumerable.name.safe_unwrap().name.to_owned(), + Self::Function(func) => { func.name.as_ref().map_or(func.ty.to_string(), |n| n.name.to_owned()) } - ParseSource::Type(ty) => ty.name.name.to_owned(), + Self::Type(ty) => ty.name.name.to_owned(), } } } diff --git a/crates/doc/src/parser/mod.rs b/crates/doc/src/parser/mod.rs index 6f03a5fb2..aa493c5c6 100644 --- a/crates/doc/src/parser/mod.rs +++ b/crates/doc/src/parser/mod.rs @@ -52,7 +52,7 @@ struct ParserContext { impl Parser { /// Create a new instance of [Parser]. pub fn new(comments: Vec, source: String) -> Self { - Parser { comments, source, ..Default::default() } + Self { comments, source, ..Default::default() } } /// Set formatter config on the [Parser] diff --git a/crates/doc/src/preprocessor/infer_hyperlinks.rs b/crates/doc/src/preprocessor/infer_hyperlinks.rs index 865c4302f..a7f7c19c4 100644 --- a/crates/doc/src/preprocessor/infer_hyperlinks.rs +++ b/crates/doc/src/preprocessor/infer_hyperlinks.rs @@ -179,7 +179,7 @@ impl InferInlineHyperlinks { }; if let Some(target) = target { let display_value = link.markdown_link_display_value(); - let markdown_link = format!("[{}]({})", display_value, target); + let markdown_link = format!("[{display_value}]({target})"); // replace the link with the markdown link comment.value = comment.value.as_str().replacen(link.as_str(), markdown_link.as_str(), 1); diff --git a/crates/doc/src/preprocessor/inheritdoc.rs b/crates/doc/src/preprocessor/inheritdoc.rs index 583df72ba..8d29a64fc 100644 --- a/crates/doc/src/preprocessor/inheritdoc.rs +++ b/crates/doc/src/preprocessor/inheritdoc.rs @@ -5,7 +5,7 @@ use crate::{ use forge_fmt::solang_ext::SafeUnwrap; use std::collections::HashMap; -/// [ContractInheritance] preprocessor id. +/// [`Inheritdoc`] preprocessor ID. pub const INHERITDOC_ID: PreprocessorId = PreprocessorId("inheritdoc"); /// The inheritdoc preprocessor. diff --git a/crates/doc/src/writer/as_doc.rs b/crates/doc/src/writer/as_doc.rs index 68e4451fa..a21a59c11 100644 --- a/crates/doc/src/writer/as_doc.rs +++ b/crates/doc/src/writer/as_doc.rs @@ -10,7 +10,7 @@ use itertools::Itertools; use solang_parser::pt::{Base, FunctionDefinition}; use std::path::{Path, PathBuf}; -/// The result of [Asdoc::as_doc] method. +/// The result of [`AsDoc::as_doc`]. pub type AsDocResult = Result; /// A trait for formatting a parse unit as documentation. diff --git a/crates/evm/core/Cargo.toml b/crates/evm/core/Cargo.toml index 52a00aaf3..8885ec34e 100644 --- a/crates/evm/core/Cargo.toml +++ b/crates/evm/core/Cargo.toml @@ -10,23 +10,32 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [dependencies] foundry-cheatcodes-spec.workspace = true foundry-common.workspace = true -foundry-compilers.workspace = true foundry-config.workspace = true +foundry-macros.workspace = true foundry-zksync-core.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } -alloy-json-abi.workspace = true -alloy-primitives = { workspace = true, features = ["serde", "getrandom", "arbitrary", "rlp"] } alloy-genesis.workspace = true -alloy-providers.workspace = true +alloy-json-abi.workspace = true +alloy-primitives = { workspace = true, features = [ + "serde", + "getrandom", + "arbitrary", + "rlp", +] } +alloy-provider.workspace = true alloy-rpc-types.workspace = true +alloy-serde.workspace = true alloy-sol-types.workspace = true alloy-transport.workspace = true -revm = { workspace = true, default-features = false, features = [ +revm = { workspace = true, features = [ "std", "serde", "memory_limit", @@ -35,17 +44,26 @@ revm = { workspace = true, default-features = false, features = [ "optional_no_base_fee", "arbitrary", "optimism", + "c-kzg", ] } revm-inspectors.workspace = true -eyre = "0.6" -futures = "0.3" +arrayvec.workspace = true +auto_impl.workspace = true +derive_more.workspace = true +eyre.workspace = true +futures.workspace = true hex.workspace = true itertools.workspace = true -parking_lot = "0.12" -serde = "1" -serde_json = "1" -thiserror = "1" -tokio = { version = "1", features = ["time", "macros"] } -tracing = "0.1" -url = "2" +once_cell.workspace = true +parking_lot.workspace = true +rustc-hash.workspace = true +serde.workspace = true +serde_json.workspace = true +thiserror.workspace = true +tokio = { workspace = true, features = ["time", "macros"] } +tracing.workspace = true +url.workspace = true + +[dev-dependencies] +foundry-test-utils.workspace = true diff --git a/crates/evm/core/src/abi/HardhatConsole.json b/crates/evm/core/src/abi/HardhatConsole.json new file mode 100644 index 000000000..4013d8753 --- /dev/null +++ b/crates/evm/core/src/abi/HardhatConsole.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"bool","name":"p2","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"uint256","name":"p3","type":"uint256"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"bool","name":"p1","type":"bool"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"string","name":"p1","type":"string"},{"internalType":"string","name":"p2","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"bool","name":"p3","type":"bool"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"address","name":"p2","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"},{"internalType":"address","name":"p1","type":"address"},{"internalType":"uint256","name":"p2","type":"uint256"},{"internalType":"string","name":"p3","type":"string"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"},{"internalType":"uint256","name":"p1","type":"uint256"},{"internalType":"string","name":"p2","type":"string"},{"internalType":"address","name":"p3","type":"address"}],"name":"log","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"p0","type":"address"}],"name":"logAddress","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"p0","type":"bool"}],"name":"logBool","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"p0","type":"bytes"}],"name":"logBytes","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes1","name":"p0","type":"bytes1"}],"name":"logBytes1","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes10","name":"p0","type":"bytes10"}],"name":"logBytes10","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes11","name":"p0","type":"bytes11"}],"name":"logBytes11","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes12","name":"p0","type":"bytes12"}],"name":"logBytes12","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes13","name":"p0","type":"bytes13"}],"name":"logBytes13","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes14","name":"p0","type":"bytes14"}],"name":"logBytes14","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes15","name":"p0","type":"bytes15"}],"name":"logBytes15","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes16","name":"p0","type":"bytes16"}],"name":"logBytes16","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes17","name":"p0","type":"bytes17"}],"name":"logBytes17","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes18","name":"p0","type":"bytes18"}],"name":"logBytes18","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes19","name":"p0","type":"bytes19"}],"name":"logBytes19","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes2","name":"p0","type":"bytes2"}],"name":"logBytes2","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes20","name":"p0","type":"bytes20"}],"name":"logBytes20","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes21","name":"p0","type":"bytes21"}],"name":"logBytes21","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes22","name":"p0","type":"bytes22"}],"name":"logBytes22","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes23","name":"p0","type":"bytes23"}],"name":"logBytes23","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes24","name":"p0","type":"bytes24"}],"name":"logBytes24","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes25","name":"p0","type":"bytes25"}],"name":"logBytes25","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes26","name":"p0","type":"bytes26"}],"name":"logBytes26","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes27","name":"p0","type":"bytes27"}],"name":"logBytes27","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes28","name":"p0","type":"bytes28"}],"name":"logBytes28","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes29","name":"p0","type":"bytes29"}],"name":"logBytes29","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes3","name":"p0","type":"bytes3"}],"name":"logBytes3","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes30","name":"p0","type":"bytes30"}],"name":"logBytes30","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes31","name":"p0","type":"bytes31"}],"name":"logBytes31","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"p0","type":"bytes32"}],"name":"logBytes32","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"p0","type":"bytes4"}],"name":"logBytes4","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes5","name":"p0","type":"bytes5"}],"name":"logBytes5","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes6","name":"p0","type":"bytes6"}],"name":"logBytes6","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes7","name":"p0","type":"bytes7"}],"name":"logBytes7","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes8","name":"p0","type":"bytes8"}],"name":"logBytes8","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes9","name":"p0","type":"bytes9"}],"name":"logBytes9","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int256","name":"p0","type":"int256"}],"name":"logInt","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"p0","type":"string"}],"name":"logString","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"p0","type":"uint256"}],"name":"logUint","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int256","name":"p0","type":"int256"}],"outputs":[],"stateMutability":"view","type":"function","name":"log"},{"inputs":[{"internalType":"string","name":"p0","type":"string"},{"internalType":"int256","name":"p1","type":"int256"}],"outputs":[],"stateMutability":"view","type":"function","name":"log"}] \ No newline at end of file diff --git a/crates/evm/core/src/abi/console.rs b/crates/evm/core/src/abi/console.rs new file mode 100644 index 000000000..e9757a476 --- /dev/null +++ b/crates/evm/core/src/abi/console.rs @@ -0,0 +1,90 @@ +use alloy_primitives::{hex, I256, U256}; +use alloy_sol_types::sol; +use derive_more::Display; +use itertools::Itertools; + +// TODO: Use `UiFmt` + +sol! { +#[sol(abi)] +#[derive(Display)] +interface Console { + #[display(fmt = "{val}")] + event log(string val); + + #[display(fmt = "{}", "hex::encode_prefixed(val)")] + event logs(bytes val); + + #[display(fmt = "{val}")] + event log_address(address val); + + #[display(fmt = "{val}")] + event log_bytes32(bytes32 val); + + #[display(fmt = "{val}")] + event log_int(int val); + + #[display(fmt = "{val}")] + event log_uint(uint val); + + #[display(fmt = "{}", "hex::encode_prefixed(val)")] + event log_bytes(bytes val); + + #[display(fmt = "{val}")] + event log_string(string val); + + #[display(fmt = "[{}]", "val.iter().format(\", \")")] + event log_array(uint256[] val); + + #[display(fmt = "[{}]", "val.iter().format(\", \")")] + event log_array(int256[] val); + + #[display(fmt = "[{}]", "val.iter().format(\", \")")] + event log_array(address[] val); + + #[display(fmt = "{key}: {val}")] + event log_named_address(string key, address val); + + #[display(fmt = "{key}: {val}")] + event log_named_bytes32(string key, bytes32 val); + + #[display(fmt = "{key}: {}", "format_units_int(val, decimals)")] + event log_named_decimal_int(string key, int val, uint decimals); + + #[display(fmt = "{key}: {}", "format_units_uint(val, decimals)")] + event log_named_decimal_uint(string key, uint val, uint decimals); + + #[display(fmt = "{key}: {val}")] + event log_named_int(string key, int val); + + #[display(fmt = "{key}: {val}")] + event log_named_uint(string key, uint val); + + #[display(fmt = "{key}: {}", "hex::encode_prefixed(val)")] + event log_named_bytes(string key, bytes val); + + #[display(fmt = "{key}: {val}")] + event log_named_string(string key, string val); + + #[display(fmt = "{key}: [{}]", "val.iter().format(\", \")")] + event log_named_array(string key, uint256[] val); + + #[display(fmt = "{key}: [{}]", "val.iter().format(\", \")")] + event log_named_array(string key, int256[] val); + + #[display(fmt = "{key}: [{}]", "val.iter().format(\", \")")] + event log_named_array(string key, address[] val); +} +} + +pub fn format_units_int(x: &I256, decimals: &U256) -> String { + let (sign, x) = x.into_sign_and_abs(); + format!("{sign}{}", format_units_uint(&x, decimals)) +} + +pub fn format_units_uint(x: &U256, decimals: &U256) -> String { + match alloy_primitives::utils::Unit::new(decimals.saturating_to::()) { + Some(units) => alloy_primitives::utils::ParseUnits::U256(*x).format_units(units), + None => x.to_string(), + } +} diff --git a/crates/evm/core/src/abi/hardhat_console.rs b/crates/evm/core/src/abi/hardhat_console.rs new file mode 100644 index 000000000..4b9aa3ba2 --- /dev/null +++ b/crates/evm/core/src/abi/hardhat_console.rs @@ -0,0 +1,566 @@ +use alloy_primitives::Selector; +use alloy_sol_types::sol; +use foundry_macros::ConsoleFmt; +use once_cell::sync::Lazy; +use revm::primitives::HashMap; + +sol!( + #[sol(abi)] + #[derive(ConsoleFmt)] + HardhatConsole, + "src/abi/HardhatConsole.json" +); + +/// Patches the given Hardhat `console` function selector to its ABI-normalized form. +/// +/// See [`HARDHAT_CONSOLE_SELECTOR_PATCHES`] for more details. +pub fn patch_hh_console_selector(input: &mut [u8]) { + if let Some(selector) = hh_console_selector(input) { + input[..4].copy_from_slice(selector.as_slice()); + } +} + +/// Returns the ABI-normalized selector for the given Hardhat `console` function selector. +/// +/// See [`HARDHAT_CONSOLE_SELECTOR_PATCHES`] for more details. +pub fn hh_console_selector(input: &[u8]) -> Option<&'static Selector> { + if let Some(selector) = input.get(..4) { + let selector: &[u8; 4] = selector.try_into().unwrap(); + HARDHAT_CONSOLE_SELECTOR_PATCHES.get(selector).map(Into::into) + } else { + None + } +} + +/// Maps all the `hardhat/console.log` log selectors that use the legacy ABI (`int`, `uint`) to +/// their normalized counterparts (`int256`, `uint256`). +/// +/// `hardhat/console.log` logs its events manually, and in functions that accept integers they're +/// encoded as `abi.encodeWithSignature("log(int)", p0)`, which is not the canonical ABI encoding +/// for `int` that Solc (and [`sol!`]) uses. +pub static HARDHAT_CONSOLE_SELECTOR_PATCHES: Lazy> = Lazy::new(|| { + HashMap::from([ + // log(bool,uint256,uint256,address) + ([241, 97, 178, 33], [0, 221, 135, 185]), + // log(uint256,address,address,string) + ([121, 67, 220, 102], [3, 28, 111, 115]), + // log(uint256,bool,address,uint256) + ([65, 181, 239, 59], [7, 130, 135, 245]), + // log(bool,address,bool,uint256) + ([76, 182, 15, 209], [7, 131, 21, 2]), + // log(bool,uint256,address) + ([196, 210, 53, 7], [8, 142, 249, 210]), + // log(uint256,address,address,bool) + ([1, 85, 11, 4], [9, 31, 250, 245]), + // log(address,bool,uint256,string) + ([155, 88, 142, 204], [10, 166, 207, 173]), + // log(bool,bool,uint256,uint256) + ([70, 103, 222, 142], [11, 176, 14, 171]), + // log(bool,address,address,uint256) + ([82, 132, 189, 108], [12, 102, 209, 190]), + // log(uint256,address,uint256,uint256) + ([202, 154, 62, 180], [12, 156, 217, 193]), + // log(string,address,uint256) + ([7, 200, 18, 23], [13, 38, 185, 37]), + // log(address,string,uint256,bool) + ([126, 37, 13, 91], [14, 247, 224, 80]), + // log(address,uint256,address,uint256) + ([165, 217, 135, 104], [16, 15, 101, 14]), + // log(string,string,uint256,address) + ([93, 79, 70, 128], [16, 35, 247, 178]), + // log(bool,string,uint256) + ([192, 56, 42, 172], [16, 147, 238, 17]), + // log(bool,bool,uint256) + ([176, 19, 101, 187], [18, 242, 22, 2]), + // log(bool,address,uint256,address) + ([104, 241, 88, 181], [19, 107, 5, 221]), + // log(bool,uint256,address,uint256) + ([202, 165, 35, 106], [21, 55, 220, 135]), + // log(bool,string,uint256,address) + ([91, 34, 185, 56], [21, 150, 161, 206]), + // log(address,string,string,uint256) + ([161, 79, 208, 57], [21, 159, 137, 39]), + // log(uint256,address,uint256,address) + ([253, 178, 236, 212], [21, 193, 39, 181]), + // log(uint256,uint256,address,bool) + ([168, 232, 32, 174], [21, 202, 196, 118]), + // log(bool,string,bool,uint256) + ([141, 111, 156, 165], [22, 6, 163, 147]), + // log(address,address,uint256) + ([108, 54, 109, 114], [23, 254, 97, 133]), + // log(uint256,uint256,uint256,uint256) + ([92, 160, 173, 62], [25, 63, 184, 0]), + // log(bool,string,uint256,string) + ([119, 161, 171, 237], [26, 217, 109, 230]), + // log(bool,uint256,address,string) + ([24, 9, 19, 65], [27, 179, 176, 154]), + // log(string,uint256,address) + ([227, 132, 159, 121], [28, 126, 196, 72]), + // log(uint256,bool) + ([30, 109, 212, 236], [28, 157, 126, 179]), + // log(address,uint256,address,string) + ([93, 113, 243, 158], [29, 169, 134, 234]), + // log(address,string,uint256,uint256) + ([164, 201, 42, 96], [29, 200, 225, 184]), + // log(uint256,bool,uint256) + ([90, 77, 153, 34], [32, 9, 128, 20]), + // log(uint256,bool,bool) + ([213, 206, 172, 224], [32, 113, 134, 80]), + // log(address,uint256,uint256,address) + ([30, 246, 52, 52], [32, 227, 152, 77]), + // log(uint256,string,string,string) + ([87, 221, 10, 17], [33, 173, 6, 131]), + // log(address,uint256,bool,uint256) + ([105, 143, 67, 146], [34, 246, 185, 153]), + // log(uint256,address,address,address) + ([85, 71, 69, 249], [36, 136, 180, 20]), + // log(string,bool,string,uint256) + ([52, 203, 48, 141], [36, 249, 20, 101]), + // log(bool,uint256,address,address) + ([138, 47, 144, 170], [38, 245, 96, 168]), + // log(uint256,uint256,string,string) + ([124, 3, 42, 50], [39, 216, 175, 210]), + // log(bool,string,uint256,uint256) + ([142, 74, 232, 110], [40, 134, 63, 203]), + // log(uint256,bool,string,uint256) + ([145, 95, 219, 40], [44, 29, 7, 70]), + // log(address,uint256,uint256,uint256) + ([61, 14, 157, 228], [52, 240, 230, 54]), + // log(uint256,bool,address) + ([66, 78, 255, 191], [53, 8, 95, 123]), + // log(string,uint256,bool,bool) + ([227, 127, 243, 208], [53, 76, 54, 214]), + // log(bool,uint256,uint256) + ([59, 92, 3, 224], [55, 16, 51, 103]), + // log(bool,uint256,uint256,uint256) + ([50, 223, 165, 36], [55, 75, 180, 178]), + // log(uint256,string,uint256) + ([91, 109, 232, 63], [55, 170, 125, 76]), + // log(address,bool,uint256,uint256) + ([194, 16, 160, 30], [56, 111, 245, 244]), + // log(address,address,bool,uint256) + ([149, 214, 95, 17], [57, 113, 231, 140]), + // log(bool,uint256) + ([54, 75, 106, 146], [57, 145, 116, 211]), + // log(uint256,string,uint256,address) + ([171, 123, 217, 253], [59, 34, 121, 180]), + // log(address,uint256,bool,bool) + ([254, 161, 213, 90], [59, 245, 229, 55]), + // log(uint256,address,string,string) + ([141, 119, 134, 36], [62, 18, 140, 163]), + // log(string,address,bool,uint256) + ([197, 209, 187, 139], [62, 159, 134, 106]), + // log(uint256,uint256,string,address) + ([67, 50, 133, 162], [66, 210, 29, 183]), + // log(address,string,uint256,string) + ([93, 19, 101, 201], [68, 136, 48, 168]), + // log(uint256,bool,address,bool) + ([145, 251, 18, 66], [69, 77, 84, 165]), + // log(address,string,address,uint256) + ([140, 25, 51, 169], [69, 127, 227, 207]), + // log(uint256,address,string,uint256) + ([160, 196, 20, 232], [70, 130, 107, 93]), + // log(uint256,uint256,bool) + ([103, 87, 15, 247], [71, 102, 218, 114]), + // log(address,uint256,address,address) + ([236, 36, 132, 111], [71, 141, 28, 98]), + // log(address,uint256,uint256,string) + ([137, 52, 13, 171], [74, 40, 192, 23]), + // log(bool,bool,address,uint256) + ([96, 147, 134, 231], [76, 18, 61, 87]), + // log(uint256,string,bool) + ([70, 167, 208, 206], [76, 237, 167, 90]), + // log(string,uint256,address,uint256) + ([88, 73, 122, 254], [79, 4, 253, 198]), + // log(address,string,bool,uint256) + ([231, 32, 82, 28], [81, 94, 56, 182]), + // log(bool,address,uint256,string) + ([160, 104, 88, 51], [81, 240, 159, 248]), + // log(bool,bool,uint256,address) + ([11, 255, 149, 13], [84, 167, 169, 160]), + // log(uint256,uint256,address,address) + ([202, 147, 155, 32], [86, 165, 209, 177]), + // log(string,string,uint256) + ([243, 98, 202, 89], [88, 33, 239, 161]), + // log(string,uint256,string) + ([163, 245, 199, 57], [89, 112, 224, 137]), + // log(uint256,uint256,uint256,string) + ([120, 173, 122, 12], [89, 207, 203, 227]), + // log(string,address,uint256,string) + ([76, 85, 242, 52], [90, 71, 118, 50]), + // log(uint256,address,uint256) + ([136, 67, 67, 170], [90, 155, 94, 213]), + // log(string,uint256,string,string) + ([108, 152, 218, 226], [90, 184, 78, 31]), + // log(uint256,address,bool,uint256) + ([123, 8, 232, 235], [90, 189, 153, 42]), + // log(address,uint256,string,address) + ([220, 121, 38, 4], [92, 67, 13, 71]), + // log(uint256,uint256,address) + ([190, 51, 73, 27], [92, 150, 179, 49]), + // log(string,bool,address,uint256) + ([40, 223, 78, 150], [93, 8, 187, 5]), + // log(string,string,uint256,string) + ([141, 20, 44, 221], [93, 26, 151, 26]), + // log(uint256,uint256,string,uint256) + ([56, 148, 22, 61], [93, 162, 151, 235]), + // log(string,uint256,address,address) + ([234, 200, 146, 129], [94, 162, 183, 174]), + // log(uint256,address,uint256,bool) + ([25, 246, 115, 105], [95, 116, 58, 124]), + // log(bool,address,uint256) + ([235, 112, 75, 175], [95, 123, 154, 251]), + // log(uint256,string,address,address) + ([127, 165, 69, 139], [97, 104, 237, 97]), + // log(bool,bool,uint256,bool) + ([171, 92, 193, 196], [97, 158, 77, 14]), + // log(address,string,uint256,address) + ([223, 215, 216, 11], [99, 24, 54, 120]), + // log(uint256,address,string) + ([206, 131, 4, 123], [99, 203, 65, 249]), + // log(string,address,uint256,address) + ([163, 102, 236, 128], [99, 251, 139, 197]), + // log(uint256,string) + ([15, 163, 243, 69], [100, 63, 208, 223]), + // log(string,bool,uint256,uint256) + ([93, 191, 240, 56], [100, 181, 187, 103]), + // log(address,uint256,uint256,bool) + ([236, 75, 168, 162], [102, 241, 188, 103]), + // log(address,uint256,bool) + ([229, 74, 225, 68], [103, 130, 9, 168]), + // log(address,string,uint256) + ([28, 218, 242, 138], [103, 221, 111, 241]), + // log(uint256,bool,string,string) + ([164, 51, 252, 253], [104, 200, 184, 189]), + // log(uint256,string,uint256,bool) + ([135, 90, 110, 46], [105, 26, 143, 116]), + // log(uint256,address) + ([88, 235, 134, 12], [105, 39, 108, 134]), + // log(uint256,bool,bool,address) + ([83, 6, 34, 93], [105, 100, 11, 89]), + // log(bool,uint256,string,uint256) + ([65, 128, 1, 27], [106, 17, 153, 226]), + // log(bool,string,uint256,bool) + ([32, 187, 201, 175], [107, 14, 93, 83]), + // log(uint256,uint256,address,string) + ([214, 162, 209, 222], [108, 222, 64, 184]), + // log(bool,bool,bool,uint256) + ([194, 72, 131, 77], [109, 112, 69, 193]), + // log(uint256,uint256,string) + ([125, 105, 14, 230], [113, 208, 74, 242]), + // log(uint256,address,address,uint256) + ([154, 60, 191, 150], [115, 110, 251, 182]), + // log(string,bool,uint256,string) + ([66, 185, 162, 39], [116, 45, 110, 231]), + // log(uint256,bool,bool,uint256) + ([189, 37, 173, 89], [116, 100, 206, 35]), + // log(string,uint256,uint256,bool) + ([247, 60, 126, 61], [118, 38, 219, 146]), + // log(uint256,uint256,string,bool) + ([178, 46, 175, 6], [122, 246, 171, 37]), + // log(uint256,string,address) + ([31, 144, 242, 74], [122, 250, 201, 89]), + // log(address,uint256,address) + ([151, 236, 163, 148], [123, 192, 216, 72]), + // log(bool,string,string,uint256) + ([93, 219, 37, 146], [123, 224, 195, 235]), + // log(bool,address,uint256,uint256) + ([155, 254, 114, 188], [123, 241, 129, 161]), + // log(string,uint256,string,address) + ([187, 114, 53, 233], [124, 70, 50, 164]), + // log(string,string,address,uint256) + ([74, 129, 165, 106], [124, 195, 198, 7]), + // log(string,uint256,string,bool) + ([233, 159, 130, 207], [125, 36, 73, 29]), + // log(bool,bool,uint256,string) + ([80, 97, 137, 55], [125, 212, 208, 224]), + // log(bool,uint256,bool,uint256) + ([211, 222, 85, 147], [127, 155, 188, 162]), + // log(address,bool,string,uint256) + ([158, 18, 123, 110], [128, 230, 162, 11]), + // log(string,uint256,address,bool) + ([17, 6, 168, 247], [130, 17, 42, 66]), + // log(uint256,string,uint256,uint256) + ([192, 4, 56, 7], [130, 194, 91, 116]), + // log(address,uint256) + ([34, 67, 207, 163], [131, 9, 232, 168]), + // log(string,uint256,uint256,string) + ([165, 78, 212, 189], [133, 75, 52, 150]), + // log(uint256,bool,string) + ([139, 14, 20, 254], [133, 119, 80, 33]), + // log(address,uint256,string,string) + ([126, 86, 198, 147], [136, 168, 196, 6]), + // log(uint256,bool,uint256,address) + ([79, 64, 5, 142], [136, 203, 96, 65]), + // log(uint256,uint256,address,uint256) + ([97, 11, 168, 192], [136, 246, 228, 178]), + // log(string,bool,uint256,bool) + ([60, 197, 181, 211], [138, 247, 207, 138]), + // log(address,bool,bool,uint256) + ([207, 181, 135, 86], [140, 78, 93, 230]), + // log(address,address,uint256,address) + ([214, 198, 82, 118], [141, 166, 222, 245]), + // log(string,bool,bool,uint256) + ([128, 117, 49, 232], [142, 63, 120, 169]), + // log(bool,uint256,uint256,string) + ([218, 6, 102, 200], [142, 105, 251, 93]), + // log(string,string,string,uint256) + ([159, 208, 9, 245], [142, 175, 176, 43]), + // log(string,address,address,uint256) + ([110, 183, 148, 61], [142, 243, 243, 153]), + // log(uint256,string,address,bool) + ([249, 63, 255, 55], [144, 195, 10, 86]), + // log(uint256,address,bool,string) + ([99, 240, 226, 66], [144, 251, 6, 170]), + // log(bool,uint256,bool,string) + ([182, 213, 105, 212], [145, 67, 219, 177]), + // log(uint256,bool,uint256,bool) + ([210, 171, 196, 253], [145, 160, 46, 42]), + // log(string,address,string,uint256) + ([143, 98, 75, 233], [145, 209, 17, 46]), + // log(string,bool,uint256,address) + ([113, 211, 133, 13], [147, 94, 9, 191]), + // log(address,address,address,uint256) + ([237, 94, 172, 135], [148, 37, 13, 119]), + // log(uint256,uint256,bool,address) + ([225, 23, 116, 79], [154, 129, 106, 131]), + // log(bool,uint256,bool,address) + ([66, 103, 199, 248], [154, 205, 54, 22]), + // log(address,address,uint256,bool) + ([194, 246, 136, 236], [155, 66, 84, 226]), + // log(uint256,address,bool) + ([122, 208, 18, 142], [155, 110, 192, 66]), + // log(uint256,string,address,string) + ([248, 152, 87, 127], [156, 58, 223, 161]), + // log(address,bool,uint256) + ([44, 70, 141, 21], [156, 79, 153, 251]), + // log(uint256,address,string,address) + ([203, 229, 142, 253], [156, 186, 143, 255]), + // log(string,uint256,address,string) + ([50, 84, 194, 232], [159, 251, 47, 147]), + // log(address,uint256,address,bool) + ([241, 129, 161, 233], [161, 188, 201, 179]), + // log(uint256,bool,address,address) + ([134, 237, 193, 12], [161, 239, 76, 187]), + // log(address,uint256,string) + ([186, 249, 104, 73], [161, 242, 232, 170]), + // log(address,uint256,bool,address) + ([35, 229, 73, 114], [163, 27, 253, 204]), + // log(uint256,uint256,bool,string) + ([239, 217, 203, 238], [165, 180, 252, 153]), + // log(bool,string,address,uint256) + ([27, 11, 149, 91], [165, 202, 218, 148]), + // log(address,bool,address,uint256) + ([220, 113, 22, 210], [167, 92, 89, 222]), + // log(string,uint256,uint256,uint256) + ([8, 238, 86, 102], [167, 168, 120, 83]), + // log(uint256,uint256,bool,bool) + ([148, 190, 59, 177], [171, 8, 90, 230]), + // log(string,uint256,bool,string) + ([118, 204, 96, 100], [171, 247, 58, 152]), + // log(uint256,bool,address,string) + ([162, 48, 118, 30], [173, 224, 82, 199]), + // log(uint256,string,bool,address) + ([121, 111, 40, 160], [174, 46, 197, 129]), + // log(uint256,string,string,uint256) + ([118, 236, 99, 94], [176, 40, 201, 189]), + // log(uint256,string,string) + ([63, 87, 194, 149], [177, 21, 97, 31]), + // log(uint256,string,string,bool) + ([18, 134, 43, 152], [179, 166, 182, 189]), + // log(bool,uint256,address,bool) + ([101, 173, 244, 8], [180, 195, 20, 255]), + // log(string,uint256) + ([151, 16, 169, 208], [182, 14, 114, 204]), + // log(address,uint256,uint256) + ([135, 134, 19, 94], [182, 155, 202, 246]), + // log(uint256,bool,bool,bool) + ([78, 108, 83, 21], [182, 245, 119, 161]), + // log(uint256,string,uint256,string) + ([162, 188, 12, 153], [183, 185, 20, 202]), + // log(uint256,string,bool,bool) + ([81, 188, 43, 193], [186, 83, 93, 156]), + // log(uint256,address,address) + ([125, 119, 166, 27], [188, 253, 155, 224]), + // log(address,address,uint256,uint256) + ([84, 253, 243, 228], [190, 85, 52, 129]), + // log(bool,uint256,uint256,bool) + ([164, 29, 129, 222], [190, 152, 67, 83]), + // log(address,uint256,string,uint256) + ([245, 18, 207, 155], [191, 1, 248, 145]), + // log(bool,address,string,uint256) + ([11, 153, 252, 34], [194, 31, 100, 199]), + // log(string,string,uint256,bool) + ([230, 86, 88, 202], [195, 168, 166, 84]), + // log(bool,uint256,string) + ([200, 57, 126, 176], [195, 252, 57, 112]), + // log(address,bool,uint256,bool) + ([133, 205, 197, 175], [196, 100, 62, 32]), + // log(uint256,uint256,uint256,bool) + ([100, 82, 185, 203], [197, 152, 209, 133]), + // log(address,uint256,bool,string) + ([142, 142, 78, 117], [197, 173, 133, 249]), + // log(string,uint256,string,uint256) + ([160, 196, 178, 37], [198, 126, 169, 209]), + // log(uint256,bool,uint256,uint256) + ([86, 130, 141, 164], [198, 172, 199, 168]), + // log(string,bool,uint256) + ([41, 27, 185, 208], [201, 89, 88, 214]), + // log(string,uint256,uint256) + ([150, 156, 221, 3], [202, 71, 196, 235]), + // log(string,uint256,bool) + ([241, 2, 238, 5], [202, 119, 51, 177]), + // log(uint256,address,string,bool) + ([34, 164, 121, 166], [204, 50, 171, 7]), + // log(address,bool,uint256,address) + ([13, 140, 230, 30], [204, 247, 144, 161]), + // log(bool,uint256,bool,bool) + ([158, 1, 247, 65], [206, 181, 244, 215]), + // log(uint256,string,bool,uint256) + ([164, 180, 138, 127], [207, 0, 152, 128]), + // log(address,uint256,string,bool) + ([164, 2, 79, 17], [207, 24, 16, 92]), + // log(uint256,uint256,uint256) + ([231, 130, 10, 116], [209, 237, 122, 60]), + // log(uint256,string,bool,string) + ([141, 72, 156, 160], [210, 212, 35, 205]), + // log(uint256,string,string,address) + ([204, 152, 138, 160], [213, 131, 198, 2]), + // log(bool,address,uint256,bool) + ([238, 141, 134, 114], [214, 1, 159, 28]), + // log(string,string,bool,uint256) + ([134, 129, 138, 122], [214, 174, 250, 210]), + // log(uint256,address,uint256,string) + ([62, 211, 189, 40], [221, 176, 101, 33]), + // log(uint256,bool,bool,string) + ([49, 138, 229, 155], [221, 219, 149, 97]), + // log(uint256,bool,uint256,string) + ([232, 221, 188, 86], [222, 3, 231, 116]), + // log(string,uint256,bool,address) + ([229, 84, 157, 145], [224, 233, 91, 152]), + // log(string,uint256,uint256,address) + ([190, 215, 40, 191], [226, 29, 226, 120]), + // log(uint256,address,bool,bool) + ([126, 39, 65, 13], [227, 81, 20, 15]), + // log(bool,bool,string,uint256) + ([23, 139, 70, 133], [227, 169, 202, 47]), + // log(string,uint256,bool,uint256) + ([85, 14, 110, 245], [228, 27, 111, 111]), + // log(bool,uint256,string,bool) + ([145, 210, 248, 19], [229, 231, 11, 43]), + // log(uint256,string,address,uint256) + ([152, 231, 243, 243], [232, 211, 1, 141]), + // log(bool,uint256,bool) + ([27, 173, 201, 235], [232, 222, 251, 169]), + // log(uint256,uint256,bool,uint256) + ([108, 100, 124, 140], [235, 127, 111, 210]), + // log(uint256,bool,string,bool) + ([52, 110, 184, 199], [235, 146, 141, 127]), + // log(address,address,string,uint256) + ([4, 40, 147, 0], [239, 28, 239, 231]), + // log(uint256,bool,string,address) + ([73, 110, 43, 180], [239, 82, 144, 24]), + // log(uint256,address,bool,address) + ([182, 49, 48, 148], [239, 114, 197, 19]), + // log(string,string,uint256,uint256) + ([213, 207, 23, 208], [244, 93, 125, 44]), + // log(bool,uint256,string,string) + ([211, 42, 101, 72], [245, 188, 34, 73]), + // log(uint256,uint256) + ([108, 15, 105, 128], [246, 102, 113, 90]), + // log(uint256) and logUint(uint256) + ([245, 177, 187, 169], [248, 44, 80, 241]), + // log(string,address,uint256,uint256) + ([218, 163, 148, 189], [248, 245, 27, 30]), + // log(uint256,uint256,uint256,address) + ([224, 133, 63, 105], [250, 129, 133, 175]), + // log(string,address,uint256,bool) + ([90, 193, 193, 60], [252, 72, 69, 240]), + // log(address,address,uint256,string) + ([157, 209, 46, 173], [253, 180, 249, 144]), + // log(bool,uint256,string,address) + ([165, 199, 13, 41], [254, 221, 31, 255]), + // logInt(int256) + ([78, 12, 29, 29], [101, 37, 181, 245]), + // logBytes(bytes) + ([11, 231, 127, 86], [225, 123, 249, 86]), + // logBytes1(bytes1) + ([110, 24, 161, 40], [111, 65, 113, 201]), + // logBytes2(bytes2) + ([233, 182, 34, 150], [155, 94, 148, 62]), + // logBytes3(bytes3) + ([45, 131, 73, 38], [119, 130, 250, 45]), + // logBytes4(bytes4) + ([224, 95, 72, 209], [251, 163, 173, 57]), + // logBytes5(bytes5) + ([166, 132, 128, 141], [85, 131, 190, 46]), + // logBytes6(bytes6) + ([174, 132, 165, 145], [73, 66, 173, 198]), + // logBytes7(bytes7) + ([78, 213, 126, 40], [69, 116, 175, 171]), + // logBytes8(bytes8) + ([79, 132, 37, 46], [153, 2, 228, 127]), + // logBytes9(bytes9) + ([144, 189, 140, 208], [80, 161, 56, 223]), + // logBytes10(bytes10) + ([1, 61, 23, 139], [157, 194, 168, 151]), + // logBytes11(bytes11) + ([4, 0, 74, 46], [220, 8, 182, 167]), + // logBytes12(bytes12) + ([134, 160, 106, 189], [118, 86, 214, 199]), + // logBytes13(bytes13) + ([148, 82, 158, 52], [52, 193, 216, 27]), + // logBytes14(bytes14) + ([146, 102, 240, 127], [60, 234, 186, 101]), + // logBytes15(bytes15) + ([218, 149, 116, 224], [89, 26, 61, 162]), + // logBytes16(bytes16) + ([102, 92, 97, 4], [31, 141, 115, 18]), + // logBytes17(bytes17) + ([51, 159, 103, 58], [248, 154, 83, 47]), + // logBytes18(bytes18) + ([196, 210, 61, 154], [216, 101, 38, 66]), + // logBytes19(bytes19) + ([94, 107, 90, 51], [0, 245, 107, 201]), + // logBytes20(bytes20) + ([81, 136, 227, 233], [236, 184, 86, 126]), + // logBytes21(bytes21) + ([233, 218, 53, 96], [48, 82, 192, 143]), + // logBytes22(bytes22) + ([213, 250, 232, 156], [128, 122, 180, 52]), + // logBytes23(bytes23) + ([171, 161, 207, 13], [73, 121, 176, 55]), + // logBytes24(bytes24) + ([241, 179, 91, 52], [9, 119, 174, 252]), + // logBytes25(bytes25) + ([11, 132, 188, 88], [174, 169, 150, 63]), + // logBytes26(bytes26) + ([248, 177, 73, 241], [211, 99, 86, 40]), + // logBytes27(bytes27) + ([58, 55, 87, 221], [252, 55, 47, 159]), + // logBytes28(bytes28) + ([200, 42, 234, 238], [56, 47, 154, 52]), + // logBytes29(bytes29) + ([75, 105, 195, 213], [122, 24, 118, 65]), + // logBytes30(bytes30) + ([238, 18, 196, 237], [196, 52, 14, 246]), + // logBytes31(bytes31) + ([194, 133, 77, 146], [129, 252, 134, 72]), + // logBytes32(bytes32) + ([39, 183, 207, 133], [45, 33, 214, 247]), + ]) +}); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn hardhat_console_patch() { + for (hh, generated) in HARDHAT_CONSOLE_SELECTOR_PATCHES.iter() { + let mut hh = *hh; + patch_hh_console_selector(&mut hh); + assert_eq!(hh, *generated); + } + } +} diff --git a/crates/evm/core/src/abi/mod.rs b/crates/evm/core/src/abi/mod.rs new file mode 100644 index 000000000..54f35c966 --- /dev/null +++ b/crates/evm/core/src/abi/mod.rs @@ -0,0 +1,12 @@ +//! Several ABI-related utilities for executors. + +pub use foundry_cheatcodes_spec::Vm; + +mod console; +pub use console::{format_units_int, format_units_uint, Console}; + +mod hardhat_console; +pub use hardhat_console::{ + hh_console_selector, patch_hh_console_selector, HardhatConsole, + HARDHAT_CONSOLE_SELECTOR_PATCHES, +}; diff --git a/crates/evm/core/src/backend/fuzz.rs b/crates/evm/core/src/backend/cow.rs similarity index 82% rename from crates/evm/core/src/backend/fuzz.rs rename to crates/evm/core/src/backend/cow.rs index 6b4ff2727..fa6604153 100644 --- a/crates/evm/core/src/backend/fuzz.rs +++ b/crates/evm/core/src/backend/cow.rs @@ -6,15 +6,23 @@ use crate::{ RevertSnapshotAction, }, fork::{CreateFork, ForkId}, + InspectorExt, }; use alloy_genesis::GenesisAccount; use alloy_primitives::{Address, B256, U256}; +use eyre::WrapErr; use revm::{ db::DatabaseRef, - primitives::{Account, AccountInfo, Bytecode, Env, HashMap as Map, ResultAndState}, - Database, DatabaseCommit, Inspector, JournaledState, + primitives::{ + Account, AccountInfo, Bytecode, Env, EnvWithHandlerCfg, HashMap as Map, ResultAndState, + SpecId, + }, + Database, DatabaseCommit, JournaledState, +}; +use std::{ + borrow::Cow, + collections::{BTreeMap, HashMap}, }; -use std::{borrow::Cow, collections::HashMap}; use super::ForkInfo; @@ -29,24 +37,27 @@ use super::ForkInfo; /// function via immutable raw (no state changes) calls. /// /// **N.B.**: we're assuming cheatcodes that alter the state (like multi fork swapping) are niche. -/// If they executed during fuzzing, it will require a clone of the initial input database. This way -/// we can support these cheatcodes in fuzzing cheaply without adding overhead for fuzz tests that +/// If they executed, it will require a clone of the initial input database. +/// This way we can support these cheatcodes cheaply without adding overhead for tests that /// don't make use of them. Alternatively each test case would require its own `Backend` clone, /// which would add significant overhead for large fuzz sets even if the Database is not big after /// setup. #[derive(Clone, Debug)] -pub struct FuzzBackendWrapper<'a> { - /// The underlying immutable `Backend` +pub struct CowBackend<'a> { + /// The underlying `Backend`. /// - /// No calls on the `FuzzBackendWrapper` will ever persistently modify the `backend`'s state. + /// No calls on the `CowBackend` will ever persistently modify the `backend`'s state. pub backend: Cow<'a, Backend>, /// Keeps track of whether the backed is already initialized is_initialized: bool, + /// The [SpecId] of the current backend. + spec_id: SpecId, } -impl<'a> FuzzBackendWrapper<'a> { +impl<'a> CowBackend<'a> { + /// Creates a new `CowBackend` with the given `Backend`. pub fn new(backend: &'a Backend) -> Self { - Self { backend: Cow::Borrowed(backend), is_initialized: false } + Self { backend: Cow::Borrowed(backend), is_initialized: false, spec_id: SpecId::LATEST } } /// Executes the configured zk transaction of the `env` without committing state changes @@ -64,24 +75,28 @@ impl<'a> FuzzBackendWrapper<'a> { } /// Executes the configured transaction of the `env` without committing state changes - pub fn inspect_ref( - &mut self, - env: &mut Env, - mut inspector: INSP, - ) -> eyre::Result - where - INSP: Inspector, - { + /// + /// Note: in case there are any cheatcodes executed that modify the environment, this will + /// update the given `env` with the new values. + pub fn inspect<'b, I: InspectorExt<&'b mut Self>>( + &'b mut self, + env: &mut EnvWithHandlerCfg, + inspector: I, + ) -> eyre::Result { // this is a new call to inspect with a new env, so even if we've cloned the backend // already, we reset the initialized state self.is_initialized = false; - match revm::evm_inner::(env, self, Some(&mut inspector)).transact() { - Ok(result) => Ok(result), - Err(e) => eyre::bail!("fuzz: failed to inspect: {e}"), - } + self.spec_id = env.handler_cfg.spec_id; + let mut evm = crate::utils::new_evm_with_inspector(self, env.clone(), inspector); + + let res = evm.transact().wrap_err("backend: failed while inspecting")?; + + env.env = evm.context.evm.inner.env; + + Ok(res) } - /// Returns whether there was a snapshot failure in the fuzz backend. + /// Returns whether there was a snapshot failure in the backend. /// /// This is bubbled up from the underlying Copy-On-Write backend when a revert occurs. pub fn has_snapshot_failure(&self) -> bool { @@ -94,7 +109,8 @@ impl<'a> FuzzBackendWrapper<'a> { fn backend_mut(&mut self, env: &Env) -> &mut Backend { if !self.is_initialized { let backend = self.backend.to_mut(); - backend.initialize(env); + let env = EnvWithHandlerCfg::new_with_spec_id(Box::new(env.clone()), self.spec_id); + backend.initialize(&env); self.is_initialized = true; return backend } @@ -110,13 +126,12 @@ impl<'a> FuzzBackendWrapper<'a> { } } -impl<'a> DatabaseExt for FuzzBackendWrapper<'a> { +impl<'a> DatabaseExt for CowBackend<'a> { fn get_fork_info(&mut self, id: LocalForkId) -> eyre::Result { self.backend.to_mut().get_fork_info(id) } fn snapshot(&mut self, journaled_state: &JournaledState, env: &Env) -> U256 { - trace!("fuzz: create snapshot"); self.backend_mut(env).snapshot(journaled_state, env) } @@ -127,7 +142,6 @@ impl<'a> DatabaseExt for FuzzBackendWrapper<'a> { current: &mut Env, action: RevertSnapshotAction, ) -> Option { - trace!(?id, "fuzz: revert snapshot"); self.backend_mut(current).revert(id, journaled_state, current, action) } @@ -146,7 +160,6 @@ impl<'a> DatabaseExt for FuzzBackendWrapper<'a> { } fn create_fork(&mut self, fork: CreateFork) -> eyre::Result { - trace!("fuzz: create fork"); self.backend.to_mut().create_fork(fork) } @@ -155,7 +168,6 @@ impl<'a> DatabaseExt for FuzzBackendWrapper<'a> { fork: CreateFork, transaction: B256, ) -> eyre::Result { - trace!(?transaction, "fuzz: create fork at"); self.backend.to_mut().create_fork_at_transaction(fork, transaction) } @@ -165,18 +177,16 @@ impl<'a> DatabaseExt for FuzzBackendWrapper<'a> { env: &mut Env, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { - trace!(?id, "fuzz: select fork"); self.backend_mut(env).select_fork(id, env, journaled_state) } fn roll_fork( &mut self, id: Option, - block_number: U256, + block_number: u64, env: &mut Env, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { - trace!(?id, ?block_number, "fuzz: roll fork"); self.backend_mut(env).roll_fork(id, block_number, env, journaled_state) } @@ -187,11 +197,10 @@ impl<'a> DatabaseExt for FuzzBackendWrapper<'a> { env: &mut Env, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { - trace!(?id, ?transaction, "fuzz: roll fork to transaction"); self.backend_mut(env).roll_fork_to_transaction(id, transaction, env, journaled_state) } - fn transact>( + fn transact>( &mut self, id: Option, transaction: B256, @@ -199,7 +208,6 @@ impl<'a> DatabaseExt for FuzzBackendWrapper<'a> { journaled_state: &mut JournaledState, inspector: &mut I, ) -> eyre::Result<()> { - trace!(?id, ?transaction, "fuzz: execute transaction"); self.backend_mut(env).transact(id, transaction, env, journaled_state, inspector) } @@ -229,7 +237,7 @@ impl<'a> DatabaseExt for FuzzBackendWrapper<'a> { fn load_allocs( &mut self, - allocs: &HashMap, + allocs: &BTreeMap, journaled_state: &mut JournaledState, ) -> Result<(), DatabaseError> { self.backend_mut(&Env::default()).load_allocs(allocs, journaled_state) @@ -268,7 +276,7 @@ impl<'a> DatabaseExt for FuzzBackendWrapper<'a> { } } -impl<'a> DatabaseRef for FuzzBackendWrapper<'a> { +impl<'a> DatabaseRef for CowBackend<'a> { type Error = DatabaseError; fn basic_ref(&self, address: Address) -> Result, Self::Error> { @@ -288,7 +296,7 @@ impl<'a> DatabaseRef for FuzzBackendWrapper<'a> { } } -impl<'a> Database for FuzzBackendWrapper<'a> { +impl<'a> Database for CowBackend<'a> { type Error = DatabaseError; fn basic(&mut self, address: Address) -> Result, Self::Error> { @@ -308,7 +316,7 @@ impl<'a> Database for FuzzBackendWrapper<'a> { } } -impl<'a> DatabaseCommit for FuzzBackendWrapper<'a> { +impl<'a> DatabaseCommit for CowBackend<'a> { fn commit(&mut self, changes: Map) { self.backend.to_mut().commit(changes) } diff --git a/crates/evm/core/src/backend/diagnostic.rs b/crates/evm/core/src/backend/diagnostic.rs index f4de9260a..109190a8f 100644 --- a/crates/evm/core/src/backend/diagnostic.rs +++ b/crates/evm/core/src/backend/diagnostic.rs @@ -26,7 +26,7 @@ impl RevertDiagnostic { |addr: &Address| labels.get(addr).cloned().unwrap_or_else(|| addr.to_string()); match self { - RevertDiagnostic::ContractExistsOnOtherForks { contract, active, available_on } => { + Self::ContractExistsOnOtherForks { contract, active, available_on } => { let contract_label = get_label(contract); format!( @@ -37,7 +37,7 @@ impl RevertDiagnostic { available_on.iter().format(", ") ) } - RevertDiagnostic::ContractDoesNotExist { contract, persistent, .. } => { + Self::ContractDoesNotExist { contract, persistent, .. } => { let contract_label = get_label(contract); if *persistent { format!("Contract {contract_label} does not exist") diff --git a/crates/evm/core/src/backend/error.rs b/crates/evm/core/src/backend/error.rs index 570b5a21d..aedabb0bd 100644 --- a/crates/evm/core/src/backend/error.rs +++ b/crates/evm/core/src/backend/error.rs @@ -1,6 +1,7 @@ use alloy_primitives::{Address, B256, U256}; use alloy_rpc_types::BlockId; use futures::channel::mpsc::{SendError, TrySendError}; +use revm::primitives::EVMError; use std::{ convert::Infallible, sync::{mpsc::RecvError, Arc}, @@ -48,17 +49,19 @@ pub enum DatabaseError { MissingCreate2Deployer, #[error("failed to get bytecode for {0:?}: {1}")] GetBytecode(B256, Arc), + #[error("{0}")] + Other(String), } impl DatabaseError { /// Create a new error with a message pub fn msg(msg: impl Into) -> Self { - DatabaseError::Message(msg.into()) + Self::Message(msg.into()) } /// Create a new error with a message pub fn display(msg: impl std::fmt::Display) -> Self { - DatabaseError::Message(msg.to_string()) + Self::Message(msg.to_string()) } fn get_rpc_error(&self) -> Option<&eyre::Error> { @@ -79,6 +82,7 @@ impl DatabaseError { Self::TransactionNotFound(_) | Self::MissingCreate2Deployer => None, Self::GetBytecode(_, err) => Some(err), + Self::Other(_) => None, } } @@ -95,7 +99,7 @@ impl DatabaseError { impl From for DatabaseError { fn from(value: tokio::task::JoinError) -> Self { - DatabaseError::display(value) + Self::display(value) } } @@ -110,3 +114,13 @@ impl From for DatabaseError { match value {} } } + +// Note: this is mostly necessary to use some revm internals that return an [EVMError] +impl From> for DatabaseError { + fn from(err: EVMError) -> Self { + match err { + EVMError::Database(err) => err, + err => Self::Other(err.to_string()), + } + } +} diff --git a/crates/evm/core/src/backend/fork_type.rs b/crates/evm/core/src/backend/fork_type.rs index ce44e4ef2..f559adb9e 100644 --- a/crates/evm/core/src/backend/fork_type.rs +++ b/crates/evm/core/src/backend/fork_type.rs @@ -1,5 +1,7 @@ use std::collections::HashMap; +use alloy_provider::Provider; + /// Defines a fork of the type EVM or ZK. #[derive(Debug, Clone)] pub enum ForkType { @@ -10,12 +12,12 @@ pub enum ForkType { impl ForkType { /// Returns true if type is [ForkType::Zk] pub fn is_zk(&self) -> bool { - matches!(self, ForkType::Zk) + matches!(self, Self::Zk) } /// Returns true if type is [ForkType::Evm] pub fn is_evm(&self) -> bool { - matches!(self, ForkType::Evm) + matches!(self, Self::Evm) } } @@ -33,13 +35,13 @@ impl CachedForkType { return fork_url_type.clone() } - let is_zk_url = foundry_common::provider::ethers::try_get_http_provider(fork_url) + let is_zk_url = foundry_common::provider::try_get_http_provider(fork_url) .map(|provider| { let is_zk_url = tokio::runtime::Builder::new_multi_thread() .enable_all() .build() .unwrap() - .block_on(provider.request("zks_L1ChainId", ())) + .block_on(provider.raw_request("zks_L1ChainId".into(), ())) .map(|_: String| true) .unwrap_or_default(); diff --git a/crates/evm/core/src/backend/in_memory_db.rs b/crates/evm/core/src/backend/in_memory_db.rs index 3b19d0fce..e8f371f61 100644 --- a/crates/evm/core/src/backend/in_memory_db.rs +++ b/crates/evm/core/src/backend/in_memory_db.rs @@ -1,4 +1,5 @@ -//! The in memory DB +//! In-memory database. + use crate::{backend::error::DatabaseError, snapshot::Snapshots}; use alloy_primitives::{Address, B256, U256}; use revm::{ @@ -7,14 +8,14 @@ use revm::{ Database, DatabaseCommit, }; -/// Type alias for an in memory database +/// Type alias for an in-memory database. /// -/// See `EmptyDBWrapper` +/// See [`EmptyDBWrapper`]. pub type FoundryEvmInMemoryDB = CacheDB; -/// In memory Database for anvil +/// In-memory [`Database`] for Anvil. /// -/// This acts like a wrapper type for [InMemoryDB] but is capable of applying snapshots +/// This acts like a wrapper type for [`FoundryEvmInMemoryDB`] but is capable of applying snapshots. #[derive(Debug)] pub struct MemDb { pub inner: FoundryEvmInMemoryDB, @@ -29,6 +30,7 @@ impl Default for MemDb { impl DatabaseRef for MemDb { type Error = DatabaseError; + fn basic_ref(&self, address: Address) -> Result, Self::Error> { DatabaseRef::basic_ref(&self.inner, address) } @@ -80,7 +82,7 @@ impl DatabaseCommit for MemDb { /// /// This will also _always_ return `Some(AccountInfo)`: /// -/// The [`Database`](revm::Database) implementation for `CacheDB` manages an `AccountState` for the +/// The [`Database`] implementation for `CacheDB` manages an `AccountState` for the /// `DbAccount`, this will be set to `AccountState::NotExisting` if the account does not exist yet. /// This is because there's a distinction between "non-existing" and "empty", /// see . diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index d87425f5e..bcc486a81 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -5,25 +5,28 @@ use crate::{ fork::{CreateFork, ForkId, MultiFork, SharedBackend}, snapshot::Snapshots, utils::configure_tx_env, + InspectorExt, }; use alloy_genesis::GenesisAccount; -use alloy_primitives::{b256, keccak256, Address, B256, U256, U64}; +use alloy_primitives::{b256, keccak256, Address, B256, U256}; use alloy_rpc_types::{Block, BlockNumberOrTag, BlockTransactions, Transaction}; +use alloy_serde::WithOtherFields; +use eyre::Context; use foundry_common::{is_known_system_sender, SYSTEM_TRANSACTION_TYPE}; use foundry_zksync_core::{convert::ConvertH160, L2_BASE_TOKEN_ADDRESS}; use itertools::Itertools; use revm::{ db::{CacheDB, DatabaseRef}, inspectors::NoOpInspector, - precompile::{Precompiles, SpecId}, + precompile::{PrecompileSpecId, Precompiles}, primitives::{ - Account, AccountInfo, Bytecode, CreateScheme, Env, HashMap as Map, Log, ResultAndState, - StorageSlot, TransactTo, KECCAK_EMPTY, + Account, AccountInfo, Bytecode, Env, EnvWithHandlerCfg, EvmState, EvmStorageSlot, + HashMap as Map, Log, ResultAndState, SpecId, TransactTo, KECCAK_EMPTY, }, - Database, DatabaseCommit, Inspector, JournaledState, EVM, + Database, DatabaseCommit, JournaledState, }; use std::{ - collections::{HashMap, HashSet}, + collections::{BTreeMap, HashMap, HashSet}, time::Instant, }; @@ -33,8 +36,8 @@ pub use diagnostic::RevertDiagnostic; mod error; pub use error::{DatabaseError, DatabaseResult}; -mod fuzz; -pub use fuzz::FuzzBackendWrapper; +mod cow; +pub use cow::CowBackend; mod in_memory_db; pub use in_memory_db::{EmptyDBWrapper, FoundryEvmInMemoryDB, MemDb}; @@ -57,7 +60,7 @@ pub type LocalForkId = U256; /// This is used for fast lookup type ForkLookupIndex = usize; -/// All accounts that will have persistent storage across fork swaps. See also [`clone_data()`] +/// All accounts that will have persistent storage across fork swaps. const DEFAULT_PERSISTENT_ACCOUNTS: [Address; 3] = [CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, CALLER]; @@ -75,6 +78,7 @@ pub struct ForkInfo { } /// An extension trait that allows us to easily extend the `revm::Inspector` capabilities +#[auto_impl::auto_impl(&mut)] pub trait DatabaseExt: Database { /// Retrieves information about a fork /// @@ -184,7 +188,7 @@ pub trait DatabaseExt: Database { fn roll_fork( &mut self, id: Option, - block_number: U256, + block_number: u64, env: &mut Env, journaled_state: &mut JournaledState, ) -> eyre::Result<()>; @@ -206,14 +210,16 @@ pub trait DatabaseExt: Database { ) -> eyre::Result<()>; /// Fetches the given transaction for the fork and executes it, committing the state in the DB - fn transact>( + fn transact>( &mut self, id: Option, transaction: B256, env: &mut Env, journaled_state: &mut JournaledState, inspector: &mut I, - ) -> eyre::Result<()>; + ) -> eyre::Result<()> + where + Self: Sized; /// Returns the `ForkId` that's currently used in the database, if fork mode is on fn active_fork_id(&self) -> Option; @@ -278,7 +284,7 @@ pub trait DatabaseExt: Database { /// Returns [Ok] if all accounts were successfully inserted into the journal, [Err] otherwise. fn load_allocs( &mut self, - allocs: &HashMap, + allocs: &BTreeMap, journaled_state: &mut JournaledState, ) -> Result<(), DatabaseError>; @@ -295,14 +301,20 @@ pub trait DatabaseExt: Database { fn add_persistent_account(&mut self, account: Address) -> bool; /// Removes persistent status from all given accounts - fn remove_persistent_accounts(&mut self, accounts: impl IntoIterator) { + fn remove_persistent_accounts(&mut self, accounts: impl IntoIterator) + where + Self: Sized, + { for acc in accounts { self.remove_persistent_account(&acc); } } /// Extends the persistent accounts with the accounts the iterator yields. - fn extend_persistent_accounts(&mut self, accounts: impl IntoIterator) { + fn extend_persistent_accounts(&mut self, accounts: impl IntoIterator) + where + Self: Sized, + { for acc in accounts { self.add_persistent_account(acc); } @@ -344,6 +356,8 @@ pub trait DatabaseExt: Database { fn get_test_contract_address(&self) -> Option

; } +struct _ObjectSafe(dyn DatabaseExt); + /// Provides the underlying `revm::Database` implementation. /// /// A `Backend` can be initialised in two forms: @@ -405,19 +419,19 @@ pub struct Backend { /// The journaled_state to use to initialize new forks with /// /// The way [`revm::JournaledState`] works is, that it holds the "hot" accounts loaded from the - /// underlying `Database` that feeds the Account and State data ([`revm::AccountInfo`])to the - /// journaled_state so it can apply changes to the state while the evm executes. + /// underlying `Database` that feeds the Account and State data to the journaled_state so it + /// can apply changes to the state while the EVM executes. /// /// In a way the `JournaledState` is something like a cache that /// 1. check if account is already loaded (hot) /// 2. if not load from the `Database` (this will then retrieve the account via RPC in forking - /// mode) + /// mode) /// /// To properly initialize we store the `JournaledState` before the first fork is selected /// ([`DatabaseExt::select_fork`]). /// /// This will be an empty `JournaledState`, which will be populated with persistent accounts, - /// See [`Self::update_fork_db()`] and [`clone_data()`]. + /// See [`Self::update_fork_db()`]. fork_init_journaled_state: JournaledState, /// The currently active fork database /// @@ -437,12 +451,10 @@ pub struct Backend { pub is_zk: bool, } -// === impl Backend === - impl Backend { /// Creates a new Backend with a spawned multi fork thread. - pub async fn spawn(fork: Option) -> Self { - Self::new(MultiFork::spawn().await, fork) + pub fn spawn(fork: Option) -> Self { + Self::new(MultiFork::spawn(), fork) } /// Creates a new instance of `Backend` @@ -487,16 +499,11 @@ impl Backend { /// Creates a new instance of `Backend` with fork added to the fork database and sets the fork /// as active - pub(crate) async fn new_with_fork( - id: &ForkId, - fork: Fork, - journaled_state: JournaledState, - ) -> Self { - let mut backend = Self::spawn(None).await; + pub(crate) fn new_with_fork(id: &ForkId, fork: Fork, journaled_state: JournaledState) -> Self { + let mut backend = Self::spawn(None); let fork_ids = backend.inner.insert_new_fork(id.clone(), fork.db, journaled_state); backend.inner.launched_with_fork = Some((id.clone(), fork_ids.0, fork_ids.1)); backend.active_fork_ids = Some(fork_ids); - backend } @@ -528,15 +535,11 @@ impl Backend { slot: U256, value: U256, ) -> Result<(), DatabaseError> { - let ret = if let Some(db) = self.active_fork_db_mut() { + if let Some(db) = self.active_fork_db_mut() { db.insert_account_storage(address, slot, value) } else { self.mem_db.insert_account_storage(address, slot, value) - }; - - debug_assert!(self.storage(address, slot).unwrap() == value); - - ret + } } /// Completely replace an account's storage without overriding account info. @@ -568,11 +571,6 @@ impl Backend { /// This will also grant cheatcode access to the test account pub fn set_test_contract(&mut self, acc: Address) -> &mut Self { trace!(?acc, "setting test account"); - // toggle the previous sender - if let Some(current) = self.inner.test_contract_address.take() { - self.remove_persistent_account(¤t); - self.revoke_cheatcode_access(&acc); - } self.add_persistent_account(acc); self.allow_cheatcode_access(acc); @@ -590,8 +588,8 @@ impl Backend { /// Sets the current spec id pub fn set_spec_id(&mut self, spec_id: SpecId) -> &mut Self { - trace!("setting precompile id"); - self.inner.precompile_id = spec_id; + trace!(?spec_id, "setting spec ID"); + self.inner.spec_id = spec_id; self } @@ -635,11 +633,11 @@ impl Backend { /// Instead, it stores whether an `assert` failed in a boolean variable that we can read pub fn is_failed_test_contract(&self, address: Address) -> bool { /* - contract DSTest { + contract DSTest { bool public IS_TEST = true; // slot 0 offset 1 => second byte of slot0 bool private _failed; - } + } */ let value = self.storage_ref(address, U256::ZERO).unwrap_or_default(); value.as_le_bytes()[1] != 0 @@ -710,15 +708,8 @@ impl Backend { target_fork: &mut Fork, merge_zk_db: bool, ) { - if let Some((_, fork_idx)) = self.active_fork_ids.as_ref() { - let active = self.inner.get_fork(*fork_idx); - merge_account_data( - accounts, - &active.db, - active_journaled_state, - target_fork, - merge_zk_db, - ) + if let Some(db) = self.active_fork_db() { + merge_account_data(accounts, db, active_journaled_state, target_fork, merge_zk_db) } else { merge_account_data( accounts, @@ -765,6 +756,24 @@ impl Backend { self.active_fork_mut().map(|f| &mut f.db) } + /// Returns the current database implementation as a `&dyn` value. + #[inline(always)] + pub fn db(&self) -> &dyn Database { + match self.active_fork_db() { + Some(fork_db) => fork_db, + None => &self.mem_db, + } + } + + /// Returns the current database implementation as a `&mut dyn` value. + #[inline(always)] + pub fn db_mut(&mut self) -> &mut dyn Database { + match self.active_fork_ids.map(|(_, idx)| &mut self.inner.get_fork_mut(idx).db) { + Some(fork_db) => fork_db, + None => &mut self.mem_db, + } + } + /// Creates a snapshot of the currently active database pub(crate) fn create_db_snapshot(&self) -> BackendDatabaseSnapshot { if let Some((id, idx)) = self.active_fork_ids { @@ -802,44 +811,51 @@ impl Backend { /// Initializes settings we need to keep track of. /// /// We need to track these mainly to prevent issues when switching between different evms - pub(crate) fn initialize(&mut self, env: &Env) { + pub(crate) fn initialize(&mut self, env: &EnvWithHandlerCfg) { self.set_caller(env.tx.caller); - self.set_spec_id(revm::precompile::SpecId::from_spec_id(env.cfg.spec_id)); + self.set_spec_id(env.handler_cfg.spec_id); let test_contract = match env.tx.transact_to { TransactTo::Call(to) => to, - TransactTo::Create(CreateScheme::Create) => { - env.tx.caller.create(env.tx.nonce.unwrap_or_default()) - } - TransactTo::Create(CreateScheme::Create2 { salt }) => { - let code_hash = B256::from_slice(keccak256(&env.tx.data).as_slice()); - env.tx.caller.create2(B256::from(salt), code_hash) + TransactTo::Create => { + let nonce = self + .basic_ref(env.tx.caller) + .map(|b| b.unwrap_or_default().nonce) + .unwrap_or_default(); + env.tx.caller.create(nonce) } }; self.set_test_contract(test_contract); } - /// Executes the configured test call of the `env` without committing state changes - pub fn inspect_ref( - &mut self, - env: &mut Env, - mut inspector: INSP, - ) -> eyre::Result - where - INSP: Inspector, - { + /// Returns the `EnvWithHandlerCfg` with the current `spec_id` set. + fn env_with_handler_cfg(&self, env: Env) -> EnvWithHandlerCfg { + EnvWithHandlerCfg::new_with_spec_id(Box::new(env), self.inner.spec_id) + } + + /// Executes the configured test call of the `env` without committing state changes. + /// + /// Note: in case there are any cheatcodes executed that modify the environment, this will + /// update the given `env` with the new values. + pub fn inspect<'a, I: InspectorExt<&'a mut Self>>( + &'a mut self, + env: &mut EnvWithHandlerCfg, + inspector: I, + ) -> eyre::Result { self.initialize(env); + let mut evm = crate::utils::new_evm_with_inspector(self, env.clone(), inspector); - match revm::evm_inner::(env, self, Some(&mut inspector)).transact() { - Ok(res) => Ok(res), - Err(e) => eyre::bail!("backend: failed while inspecting: {e}"), - } + let res = evm.transact().wrap_err("backend: failed while inspecting")?; + + env.env = evm.context.evm.inner.env; + + Ok(res) } /// Executes the configured test call of the `env` without committing state changes pub fn inspect_ref_zk( &mut self, - env: &mut Env, + env: &mut EnvWithHandlerCfg, persisted_factory_deps: &mut HashMap>, factory_deps: Option>>, ) -> eyre::Result { @@ -910,18 +926,18 @@ impl Backend { &self, id: LocalForkId, transaction: B256, - ) -> eyre::Result<(U64, Block)> { + ) -> eyre::Result<(u64, Block)> { let fork = self.inner.get_fork_by_id(id)?; let tx = fork.db.db.get_transaction(transaction)?; // get the block number we need to fork if let Some(tx_block) = tx.block_number { - let block = fork.db.db.get_full_block(tx_block.to::())?; + let block = fork.db.db.get_full_block(tx_block)?; // we need to subtract 1 here because we want the state before the transaction // was mined - let fork_block = tx_block.to::() - 1; - Ok((U64::from(fork_block), block)) + let fork_block = tx_block - 1; + Ok((fork_block, block)) } else { let block = fork.db.db.get_full_block(BlockNumberOrTag::Latest)?; @@ -930,7 +946,7 @@ impl Backend { .number .ok_or_else(|| DatabaseError::BlockNotFound(BlockNumberOrTag::Latest.into()))?; - Ok((number.to::(), block)) + Ok((number, block)) } } @@ -946,8 +962,10 @@ impl Backend { ) -> eyre::Result> { trace!(?id, ?tx_hash, "replay until transaction"); + let persistent_accounts = self.inner.persistent_accounts.clone(); let fork_id = self.ensure_fork_id(id)?.clone(); + let env = self.env_with_handler_cfg(env); let fork = self.inner.get_fork_by_id_mut(id)?; let full_block = fork.db.db.get_full_block(env.block.number.to::())?; @@ -956,7 +974,7 @@ impl Backend { // System transactions such as on L2s don't contain any pricing info so we skip them // otherwise this would cause reverts if is_known_system_sender(tx.from) || - tx.transaction_type.map(|ty| ty.to::()) == Some(SYSTEM_TRANSACTION_TYPE) + tx.transaction_type == Some(SYSTEM_TRANSACTION_TYPE) { trace!(tx=?tx.hash, "skipping system transaction"); continue; @@ -969,12 +987,13 @@ impl Backend { trace!(tx=?tx.hash, "committing transaction"); commit_transaction( - tx, + WithOtherFields::new(tx), env.clone(), journaled_state, fork, &fork_id, - NoOpInspector, + &persistent_accounts, + &mut NoOpInspector, )?; } } @@ -983,8 +1002,6 @@ impl Backend { } } -// === impl a bunch of `revm::Database` adjacent implementations === - impl DatabaseExt for Backend { fn get_fork_info(&mut self, id: LocalForkId) -> eyre::Result { let fork_id = self.ensure_fork_id(id).cloned()?; @@ -1043,7 +1060,7 @@ impl DatabaseExt for Backend { // another caller, so we need to ensure the caller account is present in the // journaled state and database let caller = current.tx.caller; - if !journaled_state.state.contains_key(&caller) { + journaled_state.state.entry(caller).or_insert_with(|| { let caller_account = current_state .state .get(&caller) @@ -1054,8 +1071,8 @@ impl DatabaseExt for Backend { // update the caller account which is required by the evm fork.db.insert_account_info(caller, caller_account.clone()); } - journaled_state.state.insert(caller, caller_account.into()); - } + caller_account.into() + }); self.inner.revert_snapshot(id, fork_id, idx, *fork); self.active_fork_ids = Some((id, idx)) } @@ -1198,7 +1215,7 @@ impl DatabaseExt for Backend { // necessarily the same caller as for the test, however we must always // ensure that fork's state contains the current sender let caller = env.tx.caller; - if !fork.journaled_state.state.contains_key(&caller) { + fork.journaled_state.state.entry(caller).or_insert_with(|| { let caller_account = active_journaled_state .state .get(&env.tx.caller) @@ -1209,8 +1226,8 @@ impl DatabaseExt for Backend { // update the caller account which is required by the evm fork.db.insert_account_info(caller, caller_account.clone()); } - fork.journaled_state.state.insert(caller, caller_account.into()); - } + caller_account.into() + }); self.update_fork_db(active_journaled_state, &mut fork, merge_zk_db); @@ -1230,14 +1247,14 @@ impl DatabaseExt for Backend { fn roll_fork( &mut self, id: Option, - block_number: U256, + block_number: u64, env: &mut Env, journaled_state: &mut JournaledState, ) -> eyre::Result<()> { trace!(?id, ?block_number, "roll fork"); let id = self.ensure_fork(id)?; let (fork_id, backend, fork_env) = - self.forks.roll_fork(self.inner.ensure_fork_id(id).cloned()?, block_number.to())?; + self.forks.roll_fork(self.inner.ensure_fork_id(id).cloned()?, block_number)?; // this will update the local mapping self.inner.roll_fork(id, fork_id, backend)?; @@ -1264,16 +1281,22 @@ impl DatabaseExt for Backend { merge_journaled_state_data(addr, journaled_state, &mut active.journaled_state); } - // ensure all previously loaded accounts are present in the journaled state to + // Ensure all previously loaded accounts are present in the journaled state to // prevent issues in the new journalstate, e.g. assumptions that accounts are loaded - // if the account is not touched, we reload it, if it's touched we clone it + // if the account is not touched, we reload it, if it's touched we clone it. + // + // Special case for accounts that are not created: we don't merge their state but + // load it in order to reflect their state at the new block (they should explicitly + // be marked as persistent if it is desired to keep state between fork rolls). for (addr, acc) in journaled_state.state.iter() { - if acc.is_touched() { - merge_journaled_state_data( - *addr, - journaled_state, - &mut active.journaled_state, - ); + if acc.is_created() { + if acc.is_touched() { + merge_journaled_state_data( + *addr, + journaled_state, + &mut active.journaled_state, + ); + } } else { let _ = active.journaled_state.load_account(*addr, &mut active.db); } @@ -1299,7 +1322,7 @@ impl DatabaseExt for Backend { self.get_block_number_and_block_for_transaction(id, transaction)?; // roll the fork to the transaction's block or latest if it's pending - self.roll_fork(Some(id), fork_block.to(), env, journaled_state)?; + self.roll_fork(Some(id), fork_block, env, journaled_state)?; update_env_block(env, fork_block, &block); @@ -1311,7 +1334,7 @@ impl DatabaseExt for Backend { Ok(()) } - fn transact>( + fn transact>( &mut self, maybe_id: Option, transaction: B256, @@ -1320,6 +1343,7 @@ impl DatabaseExt for Backend { inspector: &mut I, ) -> eyre::Result<()> { trace!(?maybe_id, ?transaction, "execute transaction"); + let persistent_accounts = self.inner.persistent_accounts.clone(); let id = self.ensure_fork(maybe_id)?; let fork_id = self.ensure_fork_id(id).cloned()?; @@ -1335,8 +1359,17 @@ impl DatabaseExt for Backend { let mut env = env.clone(); update_env_block(&mut env, fork_block, &block); + let env = self.env_with_handler_cfg(env); let fork = self.inner.get_fork_by_id_mut(id)?; - commit_transaction(tx, env, journaled_state, fork, &fork_id, inspector) + commit_transaction( + tx, + env, + journaled_state, + fork, + &fork_id, + &persistent_accounts, + inspector, + ) } fn active_fork_id(&self) -> Option { @@ -1414,7 +1447,7 @@ impl DatabaseExt for Backend { /// Returns [Ok] if all accounts were successfully inserted into the journal, [Err] otherwise. fn load_allocs( &mut self, - allocs: &HashMap, + allocs: &BTreeMap, journaled_state: &mut JournaledState, ) -> Result<(), DatabaseError> { // Loop through all of the allocs defined in the map and commit them to the journal. @@ -1438,7 +1471,7 @@ impl DatabaseExt for Backend { let slot = U256::from_be_bytes(slot.0); ( slot, - StorageSlot::new_changed( + EvmStorageSlot::new_changed( state_acc .storage .get(&slot) @@ -1595,8 +1628,6 @@ pub struct Fork { journaled_state: JournaledState, } -// === impl Fork === - impl Fork { /// Returns true if the account is a contract pub fn is_contract(&self, acc: Address) -> bool { @@ -1659,17 +1690,13 @@ pub struct BackendInner { /// All accounts that should be kept persistent when switching forks. /// This means all accounts stored here _don't_ use a separate storage section on each fork /// instead the use only one that's persistent across fork swaps. - /// - /// See also [`clone_data()`] pub persistent_accounts: HashSet
, - /// The configured precompile spec id - pub precompile_id: revm::precompile::SpecId, + /// The configured spec id + pub spec_id: SpecId, /// All accounts that are allowed to execute cheatcodes pub cheatcode_access_accounts: HashSet
, } -// === impl BackendInner === - impl BackendInner { pub fn ensure_fork_id(&self, id: LocalForkId) -> eyre::Result<&ForkId> { self.issued_local_fork_ids @@ -1826,29 +1853,12 @@ impl BackendInner { } pub fn precompiles(&self) -> &'static Precompiles { - Precompiles::new(self.precompile_id) + Precompiles::new(PrecompileSpecId::from_spec_id(self.spec_id)) } /// Returns a new, empty, `JournaledState` with set precompiles pub fn new_journaled_state(&self) -> JournaledState { - /// Helper function to convert from a `revm::precompile::SpecId` into a - /// `revm::primitives::SpecId` This only matters if the spec is Cancun or later, or - /// pre-Spurious Dragon. - fn precompiles_spec_id_to_primitives_spec_id(spec: SpecId) -> revm::primitives::SpecId { - match spec { - SpecId::HOMESTEAD => revm::primitives::SpecId::HOMESTEAD, - SpecId::BYZANTIUM => revm::primitives::SpecId::BYZANTIUM, - SpecId::ISTANBUL => revm::primitives::ISTANBUL, - SpecId::BERLIN => revm::primitives::BERLIN, - SpecId::CANCUN => revm::primitives::CANCUN, - // Point latest to berlin for now, as we don't wanna accidentally point to Cancun. - SpecId::LATEST => revm::primitives::BERLIN, - } - } - JournaledState::new( - precompiles_spec_id_to_primitives_spec_id(self.precompile_id), - self.precompiles().addresses().into_iter().copied().collect(), - ) + JournaledState::new(self.spec_id, self.precompiles().addresses().copied().collect()) } } @@ -1865,7 +1875,7 @@ impl Default for BackendInner { caller: None, next_fork_id: Default::default(), persistent_accounts: Default::default(), - precompile_id: revm::precompile::SpecId::LATEST, + spec_id: SpecId::LATEST, // grant the cheatcode,default test and caller address access to execute cheatcodes // itself cheatcode_access_accounts: HashSet::from([ @@ -1881,6 +1891,7 @@ impl Default for BackendInner { pub(crate) fn update_current_env_with_fork_env(current: &mut Env, fork: Env) { current.block = fork.block; current.cfg = fork.cfg; + current.tx.chain_id = fork.tx.chain_id; } /// Clones the data of the given `accounts` from the `active` database into the `fork_db` @@ -2001,47 +2012,60 @@ fn is_contract_in_state(journaled_state: &JournaledState, acc: Address) -> bool } /// Updates the env's block with the block's data -fn update_env_block(env: &mut Env, fork_block: U64, block: &Block) { - env.block.timestamp = block.header.timestamp; +fn update_env_block(env: &mut Env, fork_block: u64, block: &Block) { + env.block.timestamp = U256::from(block.header.timestamp); env.block.coinbase = block.header.miner; env.block.difficulty = block.header.difficulty; env.block.prevrandao = Some(block.header.mix_hash.unwrap_or_default()); - env.block.basefee = block.header.base_fee_per_gas.unwrap_or_default(); - env.block.gas_limit = block.header.gas_limit; - env.block.number = block.header.number.map(|n| n.to()).unwrap_or(fork_block.to()); + env.block.basefee = U256::from(block.header.base_fee_per_gas.unwrap_or_default()); + env.block.gas_limit = U256::from(block.header.gas_limit); + env.block.number = U256::from(block.header.number.unwrap_or(fork_block)); } /// Executes the given transaction and commits state changes to the database _and_ the journaled /// state, with an optional inspector -fn commit_transaction>( - tx: Transaction, - mut env: Env, +fn commit_transaction>( + tx: WithOtherFields, + mut env: EnvWithHandlerCfg, journaled_state: &mut JournaledState, fork: &mut Fork, fork_id: &ForkId, + persistent_accounts: &HashSet
, inspector: I, ) -> eyre::Result<()> { - configure_tx_env(&mut env, &tx); + configure_tx_env(&mut env.env, &tx); let now = Instant::now(); - let state = { - let mut evm = EVM::new(); - evm.env = env; - + let res = { let fork = fork.clone(); let journaled_state = journaled_state.clone(); - let db = crate::utils::RuntimeOrHandle::new() - .block_on(async move { Backend::new_with_fork(fork_id, fork, journaled_state).await }); - evm.database(db); - - match evm.inspect(inspector) { - Ok(res) => res.state, - Err(e) => eyre::bail!("backend: failed committing transaction: {e}"), - } + let db = Backend::new_with_fork(fork_id, fork, journaled_state); + crate::utils::new_evm_with_inspector(db, env, inspector) + .transact() + .wrap_err("backend: failed committing transaction")? }; trace!(elapsed = ?now.elapsed(), "transacted transaction"); - apply_state_changeset(state, journaled_state, fork); + apply_state_changeset(res.state, journaled_state, fork, persistent_accounts)?; + Ok(()) +} + +/// Helper method which updates data in the state with the data from the database. +/// Does not change state for persistent accounts (for roll fork to transaction and transact). +pub fn update_state( + state: &mut EvmState, + db: &mut DB, + persistent_accounts: Option<&HashSet
>, +) -> Result<(), DB::Error> { + for (addr, acc) in state.iter_mut() { + if !persistent_accounts.map_or(false, |accounts| accounts.contains(addr)) { + acc.info = db.basic(*addr)?.unwrap_or_default(); + for (key, val) in acc.storage.iter_mut() { + val.present_value = db.storage(*addr, *key)?; + } + } + } + Ok(()) } @@ -2051,19 +2075,13 @@ fn apply_state_changeset( state: Map, journaled_state: &mut JournaledState, fork: &mut Fork, -) { - let changed_accounts = state.keys().copied().collect::>(); + persistent_accounts: &HashSet
, +) -> Result<(), DatabaseError> { // commit the state and update the loaded accounts fork.db.commit(state); - for addr in changed_accounts { - // reload all changed accounts by removing them from the journaled state and reloading them - // from the now updated database - if journaled_state.state.remove(&addr).is_some() { - let _ = journaled_state.load_account(addr, &mut fork.db); - } - if fork.journaled_state.state.remove(&addr).is_some() { - let _ = fork.journaled_state.load_account(addr, &mut fork.db); - } - } + update_state(&mut journaled_state.state, &mut fork.db, Some(persistent_accounts))?; + update_state(&mut fork.journaled_state.state, &mut fork.db, Some(persistent_accounts))?; + + Ok(()) } diff --git a/crates/evm/core/src/backend/snapshot.rs b/crates/evm/core/src/backend/snapshot.rs index 35ca9222b..f8961c7a0 100644 --- a/crates/evm/core/src/backend/snapshot.rs +++ b/crates/evm/core/src/backend/snapshot.rs @@ -1,6 +1,6 @@ use alloy_primitives::{Address, B256, U256}; use revm::{ - primitives::{AccountInfo, Env, HashMap as Map}, + primitives::{AccountInfo, Env, HashMap}, JournaledState, }; use serde::{Deserialize, Serialize}; @@ -8,9 +8,9 @@ use serde::{Deserialize, Serialize}; /// A minimal abstraction of a state at a certain point in time #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct StateSnapshot { - pub accounts: Map, - pub storage: Map>, - pub block_hashes: Map, + pub accounts: HashMap, + pub storage: HashMap>, + pub block_hashes: HashMap, } /// Represents a snapshot taken during evm execution @@ -23,8 +23,6 @@ pub struct BackendSnapshot { pub env: Env, } -// === impl BackendSnapshot === - impl BackendSnapshot { /// Takes a new snapshot pub fn new(db: T, journaled_state: JournaledState, env: Env) -> Self { @@ -39,7 +37,7 @@ impl BackendSnapshot { /// journaled_state includes the same logs, we can simply replace use that See also /// `DatabaseExt::revert` pub fn merge(&mut self, current: &JournaledState) { - self.journaled_state.logs = current.logs.clone(); + self.journaled_state.logs.clone_from(¤t.logs); } } @@ -58,6 +56,6 @@ pub enum RevertSnapshotAction { impl RevertSnapshotAction { /// Returns `true` if the action is to keep the snapshot pub fn is_keep(&self) -> bool { - matches!(self, RevertSnapshotAction::RevertKeep) + matches!(self, Self::RevertKeep) } } diff --git a/crates/evm/core/src/constants.rs b/crates/evm/core/src/constants.rs index 99b6457ac..0ae1b6475 100644 --- a/crates/evm/core/src/constants.rs +++ b/crates/evm/core/src/constants.rs @@ -1,14 +1,22 @@ -use alloy_primitives::{address, hex, Address}; +use alloy_primitives::{address, b256, hex, Address, B256}; pub use foundry_common::HARDHAT_CONSOLE_ADDRESS; /// The cheatcode handler address. /// /// This is the same address as the one used in DappTools's HEVM. -/// It is calculated as: +/// +/// This is calculated as: /// `address(bytes20(uint160(uint256(keccak256('hevm cheat code')))))` pub const CHEATCODE_ADDRESS: Address = address!("7109709ECfa91a80626fF3989D68f67F5b1DD12D"); +/// The contract hash at [`CHEATCODE_ADDRESS`]. +/// +/// This is calculated as: +/// `keccak256(abi.encodePacked(CHEATCODE_ADDRESS))`. +pub const CHEATCODE_CONTRACT_HASH: B256 = + b256!("b0450508e5a2349057c3b4c9c84524d62be4bb17e565dbe2df34725a26872291"); + /// Stores the caller address to be used as *sender* account for: /// - deploying Test contracts /// - deploying Script contracts diff --git a/crates/evm/core/src/debug.rs b/crates/evm/core/src/debug.rs index 7e6ab96a4..21705f128 100644 --- a/crates/evm/core/src/debug.rs +++ b/crates/evm/core/src/debug.rs @@ -1,17 +1,29 @@ -use alloy_primitives::{Address, U256}; +use crate::opcodes; +use alloy_primitives::{Address, Bytes, U256}; +use arrayvec::ArrayVec; use revm::interpreter::OpCode; use revm_inspectors::tracing::types::CallKind; use serde::{Deserialize, Serialize}; -use std::fmt::Display; /// An arena of [DebugNode]s -#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct DebugArena { /// The arena of nodes pub arena: Vec, } +impl Default for DebugArena { + fn default() -> Self { + Self::new() + } +} + impl DebugArena { + /// Creates a new debug arena. + pub const fn new() -> Self { + Self { arena: Vec::new() } + } + /// Pushes a new debug node into the arena pub fn push_node(&mut self, mut new_node: DebugNode) -> usize { fn recursively_push( @@ -168,15 +180,17 @@ pub struct DebugStep { /// Stack *prior* to running the associated opcode pub stack: Vec, /// Memory *prior* to running the associated opcode - pub memory: Vec, + pub memory: Bytes, /// Calldata *prior* to running the associated opcode - pub calldata: Vec, + pub calldata: Bytes, /// Returndata *prior* to running the associated opcode - pub returndata: Vec, + pub returndata: Bytes, /// Opcode to be executed - pub instruction: Instruction, - /// Optional bytes that are being pushed onto the stack - pub push_bytes: Option>, + pub instruction: u8, + /// Optional bytes that are being pushed onto the stack. + /// Empty if the opcode is not a push or PUSH0. + #[serde(serialize_with = "hex::serialize", deserialize_with = "deserialize_arrayvec_hex")] + pub push_bytes: ArrayVec, /// The program counter at this step. /// /// Note: To map this step onto source code using a source map, you must convert the program @@ -193,8 +207,8 @@ impl Default for DebugStep { memory: Default::default(), calldata: Default::default(), returndata: Default::default(), - instruction: Instruction::OpCode(revm::interpreter::opcode::INVALID), - push_bytes: None, + instruction: revm::interpreter::opcode::INVALID, + push_bytes: Default::default(), pc: 0, total_gas_used: 0, } @@ -204,48 +218,25 @@ impl Default for DebugStep { impl DebugStep { /// Pretty print the step's opcode pub fn pretty_opcode(&self) -> String { - if let Some(push_bytes) = &self.push_bytes { - format!("{}(0x{})", self.instruction, hex::encode(push_bytes)) + let instruction = OpCode::new(self.instruction).map_or("INVALID", |op| op.as_str()); + if !self.push_bytes.is_empty() { + format!("{instruction}(0x{})", hex::encode(&self.push_bytes)) } else { - self.instruction.to_string() + instruction.to_string() } } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub enum Instruction { - OpCode(u8), - Cheatcode([u8; 4]), -} -impl From for Instruction { - fn from(op: u8) -> Instruction { - Instruction::OpCode(op) + /// Returns `true` if the opcode modifies memory. + pub fn opcode_modifies_memory(&self) -> bool { + OpCode::new(self.instruction).map_or(false, opcodes::modifies_memory) } } -impl Display for Instruction { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Instruction::OpCode(op) => write!( - f, - "{}", - OpCode::new(*op).map_or_else( - || format!("UNDEFINED(0x{op:02x})"), - |opcode| opcode.as_str().to_string(), - ) - ), - Instruction::Cheatcode(cheat) => write!( - f, - "VM_{}", - foundry_cheatcodes_spec::Vm::CHEATCODES - .iter() - .map(|c| &c.func) - .find(|c| c.selector_bytes == *cheat) - .expect("unknown cheatcode found in debugger") - .id - .to_uppercase() - ), - } - } +fn deserialize_arrayvec_hex<'de, D: serde::Deserializer<'de>>( + deserializer: D, +) -> Result, D::Error> { + let bytes: Vec = hex::deserialize(deserializer)?; + let mut array = ArrayVec::new(); + array.try_extend_from_slice(&bytes).map_err(serde::de::Error::custom)?; + Ok(array) } diff --git a/crates/evm/core/src/decode.rs b/crates/evm/core/src/decode.rs index 35d5fe72b..ae44791e4 100644 --- a/crates/evm/core/src/decode.rs +++ b/crates/evm/core/src/decode.rs @@ -8,7 +8,8 @@ use foundry_cheatcodes_spec::Vm; use foundry_common::{Console, SELECTOR_LEN}; use itertools::Itertools; use revm::interpreter::InstructionResult; -use std::{collections::HashMap, sync::OnceLock}; +use rustc_hash::FxHashMap; +use std::sync::OnceLock; /// Decode a set of logs, only returning logs from DSTest logging events and Hardhat's `console.log` pub fn decode_console_logs(logs: &[Log]) -> Vec { @@ -27,7 +28,7 @@ pub fn decode_console_log(log: &Log) -> Option { #[derive(Clone, Debug, Default)] pub struct RevertDecoder { /// The custom errors to use for decoding. - pub errors: HashMap>, + pub errors: FxHashMap>, } impl Default for &RevertDecoder { @@ -104,7 +105,7 @@ impl RevertDecoder { /// Tries to decode an error message from the given revert bytes. /// - /// See [`decode_revert`] for more information. + /// See [`decode`](Self::decode) for more information. pub fn maybe_decode(&self, err: &[u8], status: Option) -> Option { if err.len() < SELECTOR_LEN { if let Some(status) = status { diff --git a/crates/evm/core/src/fork/backend.rs b/crates/evm/core/src/fork/backend.rs index 3b2ceb2b8..2ec2b4489 100644 --- a/crates/evm/core/src/fork/backend.rs +++ b/crates/evm/core/src/fork/backend.rs @@ -4,8 +4,10 @@ use crate::{ fork::{cache::FlushJsonBlockCacheDB, BlockchainDb}, }; use alloy_primitives::{keccak256, Address, Bytes, B256, U256}; -use alloy_providers::provider::TempProvider; +use alloy_provider::{network::AnyNetwork, Provider}; use alloy_rpc_types::{Block, BlockId, Transaction}; +use alloy_serde::WithOtherFields; +use alloy_transport::Transport; use eyre::WrapErr; use foundry_common::NON_ARCHIVE_NODE_WARNING; use futures::{ @@ -18,8 +20,11 @@ use revm::{ db::DatabaseRef, primitives::{AccountInfo, Bytecode, KECCAK_EMPTY}, }; +use rustc_hash::FxHashMap; use std::{ collections::{hash_map::Entry, HashMap, VecDeque}, + future::IntoFuture, + marker::PhantomData, pin::Pin, sync::{ mpsc::{channel as oneshot_channel, Sender as OneshotSender}, @@ -30,13 +35,17 @@ use std::{ // Various future/request type aliases type AccountFuture = - Pin, Address)> + Send>>; + Pin, Address)> + Send>>; type StorageFuture = Pin, Address, U256)> + Send>>; type BlockHashFuture = Pin, u64)> + Send>>; type FullBlockFuture = Pin, Err>, BlockId)> + Send>>; -type TransactionFuture = - Pin, B256)> + Send>>; +type TransactionFuture = Pin< + Box< + dyn Future, Err>, B256)> + + Send, + >, +>; type BytecodeHashFuture = Pin, Err>, B256)> + Send>>; @@ -44,7 +53,7 @@ type AccountInfoSender = OneshotSender>; type StorageSender = OneshotSender>; type BlockHashSender = OneshotSender>; type FullBlockSender = OneshotSender>; -type TransactionSender = OneshotSender>; +type TransactionSender = OneshotSender>>; type ByteCodeHashSender = OneshotSender>; /// Request variants that are executed by the provider @@ -80,9 +89,10 @@ enum BackendRequest { /// /// This handler will remain active as long as it is reachable (request channel still open) and /// requests are in progress. -#[must_use = "BackendHandler does nothing unless polled."] -pub struct BackendHandler

{ +#[must_use = "futures do nothing unless polled"] +pub struct BackendHandler { provider: P, + transport: PhantomData, /// Stores all the data. db: BlockchainDb, /// Requests currently in progress @@ -92,7 +102,7 @@ pub struct BackendHandler

{ /// Listeners that wait for a `get_storage_at` response storage_requests: HashMap<(Address, U256), Vec>, /// Listeners that wait for a `get_block` response - block_requests: HashMap>, + block_requests: FxHashMap>, /// Incoming commands. incoming: Receiver, /// unprocessed queued requests @@ -110,9 +120,10 @@ pub trait ZkSyncMiddleware: Send + Sync { + std::marker::Send; } -impl

BackendHandler

+impl BackendHandler where - P: ZkSyncMiddleware + TempProvider + Clone + 'static, + T: Transport + Clone, + P: ZkSyncMiddleware + Provider + Clone + Unpin + 'static, { fn new( provider: P, @@ -130,6 +141,7 @@ where queued_requests: Default::default(), incoming: rx, block_id, + transport: PhantomData, } } @@ -138,7 +150,7 @@ where /// We always check: /// 1. if the requested value is already stored in the cache, then answer the sender /// 2. otherwise, fetch it via the provider but check if a request for that value is already in - /// progress (e.g. another Sender just requested the same account) + /// progress (e.g. another Sender just requested the same account) fn on_request(&mut self, req: BackendRequest) { match req { BackendRequest::Basic(addr, sender) => { @@ -194,10 +206,13 @@ where trace!(target: "backendhandler", %address, %idx, "preparing storage request"); entry.insert(vec![listener]); let provider = self.provider.clone(); - let block_id = self.block_id; + let block_id = self.block_id.unwrap_or_default(); let fut = Box::pin(async move { - let storage = - provider.get_storage_at(address, idx, block_id).await.map_err(Into::into); + let storage = provider + .get_storage_at(address, idx) + .block_id(block_id) + .await + .map_err(Into::into); (storage, address, idx) }); self.pending_requests.push(ProviderRequest::Storage(fut)); @@ -209,11 +224,11 @@ where fn get_account_req(&self, address: Address) -> ProviderRequest { trace!(target: "backendhandler", "preparing account request, address={:?}", address); let provider = self.provider.clone(); - let block_id = self.block_id; + let block_id = self.block_id.unwrap_or_default(); let fut = Box::pin(async move { - let balance = provider.get_balance(address, block_id); - let nonce = provider.get_transaction_count(address, block_id); - let code = provider.get_code_at(address, block_id); + let balance = provider.get_balance(address).block_id(block_id).into_future(); + let nonce = provider.get_transaction_count(address).block_id(block_id).into_future(); + let code = provider.get_code_at(address).block_id(block_id).into_future(); let resp = tokio::try_join!(balance, nonce, code).map_err(Into::into); (resp, address) }); @@ -237,8 +252,10 @@ where fn request_full_block(&mut self, number: BlockId, sender: FullBlockSender) { let provider = self.provider.clone(); let fut = Box::pin(async move { - let block = - provider.get_block(number, true).await.wrap_err("could not fetch block {number:?}"); + let block = provider + .get_block(number, true.into()) + .await + .wrap_err("could not fetch block {number:?}"); (sender, block, number) }); @@ -252,7 +269,10 @@ where let block = provider .get_transaction_by_hash(tx) .await - .wrap_err("could not get transaction {tx}"); + .wrap_err_with(|| format!("could not get transaction {tx}")) + .and_then(|maybe| { + maybe.ok_or_else(|| eyre::eyre!("could not get transaction {tx}")) + }); (sender, block, tx) }); @@ -312,9 +332,10 @@ where } } -impl

Future for BackendHandler

+impl Future for BackendHandler where - P: ZkSyncMiddleware + TempProvider + Clone + Unpin + 'static, + T: Transport + Clone + Unpin, + P: ZkSyncMiddleware + Provider + Clone + Unpin + 'static, { type Output = (); @@ -372,9 +393,9 @@ where // update the cache let acc = AccountInfo { - nonce: nonce.to(), + nonce, balance, - code: Some(Bytecode::new_raw(code).to_checked()), + code: Some(Bytecode::new_raw(code)), code_hash, }; pin.db.accounts().write().insert(addr, acc.clone()); @@ -521,7 +542,7 @@ where /// that is used by the `BackendHandler` to send the result of an executed `BackendRequest` back to /// `SharedBackend`. /// -/// The `BackendHandler` holds an ethers `Provider` to look up missing accounts or storage slots +/// The `BackendHandler` holds a `Provider` to look up missing accounts or storage slots /// from remote (e.g. infura). It detects duplicate requests from multiple `SharedBackend`s and /// bundles them together, so that always only one provider request is executed. For example, there /// are two `SharedBackend`s, `A` and `B`, both request the basic account info of account @@ -555,9 +576,14 @@ impl SharedBackend { /// dropped. /// /// NOTE: this should be called with `Arc` - pub async fn spawn_backend

(provider: P, db: BlockchainDb, pin_block: Option) -> Self + pub async fn spawn_backend( + provider: P, + db: BlockchainDb, + pin_block: Option, + ) -> Self where - P: ZkSyncMiddleware + TempProvider + Unpin + 'static + Clone, + T: Transport + Clone + Unpin, + P: ZkSyncMiddleware + Provider + Unpin + 'static + Clone, { let (shared, handler) = Self::new(provider, db, pin_block); // spawn the provider handler to a task @@ -568,13 +594,14 @@ impl SharedBackend { /// Same as `Self::spawn_backend` but spawns the `BackendHandler` on a separate `std::thread` in /// its own `tokio::Runtime` - pub fn spawn_backend_thread

( + pub fn spawn_backend_thread( provider: P, db: BlockchainDb, pin_block: Option, ) -> Self where - P: ZkSyncMiddleware + TempProvider + Unpin + 'static + Clone, + T: Transport + Clone + Unpin, + P: ZkSyncMiddleware + Provider + Unpin + 'static + Clone, { let (shared, handler) = Self::new(provider, db, pin_block); @@ -597,13 +624,14 @@ impl SharedBackend { } /// Returns a new `SharedBackend` and the `BackendHandler` - pub fn new

( + pub fn new( provider: P, db: BlockchainDb, pin_block: Option, - ) -> (Self, BackendHandler

) + ) -> (Self, BackendHandler) where - P: ZkSyncMiddleware + TempProvider + Clone + 'static, + T: Transport + Clone + Unpin, + P: ZkSyncMiddleware + Provider + Unpin + 'static + Clone, { let (backend, backend_rx) = channel(1); let cache = Arc::new(FlushJsonBlockCacheDB(Arc::clone(db.cache()))); @@ -628,7 +656,7 @@ impl SharedBackend { } /// Returns the transaction for the hash - pub fn get_transaction(&self, tx: B256) -> DatabaseResult { + pub fn get_transaction(&self, tx: B256) -> DatabaseResult> { tokio::task::block_in_place(|| { let (sender, rx) = oneshot_channel(); let req = BackendRequest::Transaction(tx, sender); @@ -740,7 +768,7 @@ mod tests { fork::{BlockchainDbMeta, CreateFork, JsonBlockCacheDB}, opts::EvmOpts, }; - use foundry_common::provider::alloy::get_http_provider; + use foundry_common::provider::get_http_provider; use foundry_config::{Config, NamedChain}; use std::{collections::BTreeSet, path::PathBuf}; @@ -819,7 +847,7 @@ mod tests { evm_opts, }; - let backend = Backend::spawn(Some(fork)).await; + let backend = Backend::spawn(Some(fork)); // some rng contract from etherscan let address: Address = "63091244180ae240c87d1f528f5f269134cb07b3".parse().unwrap(); diff --git a/crates/evm/core/src/fork/cache.rs b/crates/evm/core/src/fork/cache.rs index 607eced68..9aea93585 100644 --- a/crates/evm/core/src/fork/cache.rs +++ b/crates/evm/core/src/fork/cache.rs @@ -30,9 +30,9 @@ pub struct BlockchainDb { } impl BlockchainDb { - /// Creates a new instance of the [BlockchainDb] + /// Creates a new instance of the [BlockchainDb]. /// - /// if a `cache_path` is provided it attempts to load a previously stored [JsonBlockCacheData] + /// If a `cache_path` is provided it attempts to load a previously stored [JsonBlockCacheData] /// and will try to use the cached entries it holds. /// /// This will return a new and empty [MemDb] if @@ -99,7 +99,7 @@ impl BlockchainDb { &self.db.block_hashes } - /// Returns the [revm::Env] related metadata + /// Returns the Env related metadata pub fn meta(&self) -> &Arc> { &self.meta } @@ -132,11 +132,7 @@ impl BlockchainDbMeta { .and_then(|url| url.host().map(|host| host.to_string())) .unwrap_or(url); - BlockchainDbMeta { - cfg_env: env.cfg.clone(), - block_env: env.block, - hosts: BTreeSet::from([host]), - } + Self { cfg_env: env.cfg.clone(), block_env: env.block, hosts: BTreeSet::from([host]) } } } @@ -331,7 +327,7 @@ impl DatabaseCommit for MemDb { } } -/// A [BlockCacheDB] that stores the cached content in a json file +/// A DB that stores the cached content in a json file #[derive(Debug)] pub struct JsonBlockCacheDB { /// Where this cache file is stored. @@ -451,7 +447,7 @@ impl<'de> Deserialize<'de> for JsonBlockCacheData { let Data { meta, data: StateSnapshot { accounts, storage, block_hashes } } = Data::deserialize(deserializer)?; - Ok(JsonBlockCacheData { + Ok(Self { meta: Arc::new(RwLock::new(meta)), data: Arc::new(MemDb { accounts: RwLock::new(accounts), @@ -487,12 +483,12 @@ mod tests { "meta": { "cfg_env": { "chain_id": 1337, - "spec_id": "LATEST", - "perf_all_precompiles_have_balance": false, - "disable_coinbase_tip": false, "perf_analyse_created_bytecodes": "Analyse", "limit_contract_code_size": 18446744073709551615, - "memory_limit": 4294967295 + "memory_limit": 4294967295, + "disable_block_gas_limit": false, + "disable_eip3607": false, + "disable_base_fee": false }, "block_env": { "number": "0xed3ddf", @@ -500,7 +496,8 @@ mod tests { "timestamp": "0x6324bc3f", "difficulty": "0x0", "basefee": "0x2e5fda223", - "gas_limit": "0x1c9c380" + "gas_limit": "0x1c9c380", + "prevrandao": "0x0000000000000000000000000000000000000000000000000000000000000000" }, "hosts": [ "eth-mainnet.alchemyapi.io" @@ -512,11 +509,17 @@ mod tests { "nonce": 10, "code_hash": "0x3ac64c95eedf82e5d821696a12daac0e1b22c8ee18a9fd688b00cfaf14550aad", "code": { - "bytecode": "0x60806040526004361061006c5763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416634555d5c9811461012b5780634558850c1461015257806348a0c8dd146101965780635c60da1b146101bf57806386070cfe146101d4575b6127107f665fd576fbbe6f247aff98f5c94a561e3f71ec2d3c988d56f12d342396c50cea6000825a10156100e15760003411361583541616156100dc576040513381523460208201527f15eeaa57c7bd188c1388020bcadc2c436ec60d647d36ef5b9eb3c742217ddee1604082a1005b600080fd5b6100e96101e9565b9050610126816000368080601f0160208091040260200160405190810160405280939291908181526020018383808284375061026c945050505050565b505050005b34801561013757600080fd5b506101406102ad565b60408051918252519081900360200190f35b34801561015e57600080fd5b5061016d6004356024356102b2565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b3480156101a257600080fd5b506101ab6102e2565b604080519115158252519081900360200190f35b3480156101cb57600080fd5b5061016d6101e9565b3480156101e057600080fd5b50610140610312565b7f3b4bf6bf3ad5000ecf0f989d5befde585c6860fea3e574a4fab4c49d1c177d9c6000527fc67454ed56db7ff90a4bb32fc9a8de1ab3174b221e5fecea22b7503a3111791f6020527f8e2ed18767e9c33b25344c240cdf92034fae56be99e2c07f3d9946d949ffede45473ffffffffffffffffffffffffffffffffffffffff1690565b600061027783610318565b151561028257600080fd5b612710905060008083516020850186855a03f43d604051816000823e8280156102a9578282f35b8282fd5b600290565b600060208181529281526040808220909352908152205473ffffffffffffffffffffffffffffffffffffffff1681565b600061030d7f665fd576fbbe6f247aff98f5c94a561e3f71ec2d3c988d56f12d342396c50cea610352565b905090565b60015481565b60008073ffffffffffffffffffffffffffffffffffffffff83161515610341576000915061034c565b823b90506000811191505b50919050565b54905600a165627a7a72305820968d404e148c1ec7bb58c8df6cbdcaad4978b93a804e00a1f0e97a5e789eacd40029000000000000000000000000000000000000000000000000000000000000000000", - "hash": "0x3ac64c95eedf82e5d821696a12daac0e1b22c8ee18a9fd688b00cfaf14550aad", - "state": { - "Checked": { - "len": 898 + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 1, + "data": [0] } } } @@ -557,7 +560,7 @@ mod tests { "meta": { "cfg_env": { "chain_id": 1, - "spec_id": "LATEST", + "kzg_settings": "Default", "perf_analyse_created_bytecodes": "Analyse", "limit_contract_code_size": 18446744073709551615, "memory_limit": 134217728, @@ -589,10 +592,17 @@ mod tests { "nonce": 128912, "code_hash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", "code": { - "bytecode": "0x000000000000000000000000000000000000000000000000000000000000000000", - "state": { - "Checked": { - "len": 0 + "LegacyAnalyzed": { + "bytecode": "0x00", + "original_len": 0, + "jump_table": { + "order": "bitvec::order::Lsb0", + "head": { + "width": 8, + "index": 0 + }, + "bits": 1, + "data": [0] } } } diff --git a/crates/evm/core/src/fork/database.rs b/crates/evm/core/src/fork/database.rs index fdc34dbaf..2712b5779 100644 --- a/crates/evm/core/src/fork/database.rs +++ b/crates/evm/core/src/fork/database.rs @@ -207,8 +207,6 @@ pub struct ForkDbSnapshot { pub snapshot: StateSnapshot, } -// === impl DbSnapshot === - impl ForkDbSnapshot { fn get_storage(&self, address: Address, index: U256) -> Option { self.local.accounts.get(&address).and_then(|account| account.storage.get(&index)).copied() @@ -267,14 +265,14 @@ impl DatabaseRef for ForkDbSnapshot { mod tests { use super::*; use crate::fork::BlockchainDbMeta; - use foundry_common::provider::alloy::get_http_provider; + use foundry_common::provider::get_http_provider; use std::collections::BTreeSet; /// Demonstrates that `Database::basic` for `ForkedDatabase` will always return the /// `AccountInfo` #[tokio::test(flavor = "multi_thread")] async fn fork_db_insert_basic_default() { - let rpc = foundry_common::rpc::next_http_rpc_endpoint(); + let rpc = foundry_test_utils::rpc::next_http_rpc_endpoint(); let provider = get_http_provider(rpc.clone()); let meta = BlockchainDbMeta { cfg_env: Default::default(), diff --git a/crates/evm/core/src/fork/init.rs b/crates/evm/core/src/fork/init.rs index 11e659164..b69e02aad 100644 --- a/crates/evm/core/src/fork/init.rs +++ b/crates/evm/core/src/fork/init.rs @@ -1,7 +1,8 @@ use crate::utils::apply_chain_and_block_specific_env_changes; use alloy_primitives::{Address, U256}; -use alloy_providers::provider::TempProvider; +use alloy_provider::{Network, Provider}; use alloy_rpc_types::{Block, BlockNumberOrTag}; +use alloy_transport::Transport; use eyre::WrapErr; use foundry_common::NON_ARCHIVE_NODE_WARNING; @@ -10,13 +11,14 @@ use revm::primitives::{BlockEnv, CfgEnv, Env, TxEnv}; /// Initializes a REVM block environment based on a forked /// ethereum provider. // todo(onbjerg): these bounds needed cus of the bounds in `Provider`, can simplify? -pub async fn environment( +pub async fn environment>( provider: &P, memory_limit: u64, - gas_price: Option, + gas_price: Option, override_chain_id: Option, pin_block: Option, origin: Address, + disable_block_gas_limit: bool, ) -> eyre::Result<(Env, Block)> { let block_number = if let Some(pin_block) = pin_block { pin_block @@ -48,31 +50,32 @@ pub async fn environment( }; let mut cfg = CfgEnv::default(); - cfg.chain_id = override_chain_id.unwrap_or(rpc_chain_id.to::()); + cfg.chain_id = override_chain_id.unwrap_or(rpc_chain_id); cfg.memory_limit = memory_limit; cfg.limit_contract_code_size = Some(usize::MAX); // EIP-3607 rejects transactions from senders with deployed code. // If EIP-3607 is enabled it can cause issues during fuzz/invariant tests if the caller // is a contract. So we disable the check by default. cfg.disable_eip3607 = true; + cfg.disable_block_gas_limit = disable_block_gas_limit; let mut env = Env { cfg, block: BlockEnv { - number: block.header.number.expect("block number not found"), - timestamp: block.header.timestamp, + number: U256::from(block.header.number.expect("block number not found")), + timestamp: U256::from(block.header.timestamp), coinbase: block.header.miner, difficulty: block.header.difficulty, prevrandao: Some(block.header.mix_hash.unwrap_or_default()), - basefee: block.header.base_fee_per_gas.unwrap_or_default(), - gas_limit: block.header.gas_limit, + basefee: U256::from(block.header.base_fee_per_gas.unwrap_or_default()), + gas_limit: U256::from(block.header.gas_limit), ..Default::default() }, tx: TxEnv { caller: origin, - gas_price: gas_price.map(U256::from).unwrap_or(fork_gas_price), - chain_id: Some(override_chain_id.unwrap_or(rpc_chain_id.to::())), - gas_limit: block.header.gas_limit.to::(), + gas_price: U256::from(gas_price.unwrap_or(fork_gas_price)), + chain_id: Some(override_chain_id.unwrap_or(rpc_chain_id)), + gas_limit: block.header.gas_limit as u64, ..Default::default() }, }; diff --git a/crates/evm/core/src/fork/mod.rs b/crates/evm/core/src/fork/mod.rs index 61dd7bf47..a6387b0bb 100644 --- a/crates/evm/core/src/fork/mod.rs +++ b/crates/evm/core/src/fork/mod.rs @@ -8,7 +8,10 @@ mod init; pub use init::environment; mod cache; -pub use cache::{BlockchainDb, BlockchainDbMeta, JsonBlockCacheDB, MemDb}; +pub use cache::{ + BlockchainDb, BlockchainDbMeta, FlushJsonBlockCacheDB, JsonBlockCacheDB, JsonBlockCacheData, + MemDb, StorageInfo, +}; pub mod database; diff --git a/crates/evm/core/src/fork/multi.rs b/crates/evm/core/src/fork/multi.rs index b155acbae..2e04b5d90 100644 --- a/crates/evm/core/src/fork/multi.rs +++ b/crates/evm/core/src/fork/multi.rs @@ -4,9 +4,11 @@ //! concurrently active pairs at once. use crate::fork::{BackendHandler, BlockchainDb, BlockchainDbMeta, CreateFork, SharedBackend}; -use alloy_providers::provider::{Provider, TempProvider}; -use alloy_transport::{BoxTransport, TransportResult}; -use foundry_common::provider::alloy::ProviderBuilder; +use alloy_provider::{Provider, RootProvider}; +use alloy_transport::TransportResult; +use foundry_common::provider::{ + runtime_transport::RuntimeTransport, tower::RetryBackoffService, ProviderBuilder, RetryProvider, +}; use foundry_config::Config; use futures::{ channel::mpsc::{channel, Receiver, Sender}, @@ -41,7 +43,7 @@ impl ForkId { Some(n) => write!(id, "{n:#x}").unwrap(), None => id.push_str("latest"), } - ForkId(id) + Self(id) } /// Returns the identifier of the fork. @@ -72,8 +74,6 @@ pub struct MultiFork { _shutdown: Arc, } -// === impl MultiForkBackend === - impl MultiFork { /// Creates a new pair multi fork pair pub fn new() -> (Self, MultiForkHandler) { @@ -83,7 +83,7 @@ impl MultiFork { } /// Creates a new pair and spawns the `MultiForkHandler` on a background thread. - pub async fn spawn() -> Self { + pub fn spawn() -> Self { trace!(target: "fork::multi", "spawning multifork"); let (fork, mut handler) = Self::new(); @@ -169,7 +169,7 @@ impl MultiFork { } } -type Handler = BackendHandler>>; +type Handler = BackendHandler, Arc>; type CreateFuture = Pin> + Send>>; @@ -222,8 +222,6 @@ pub struct MultiForkHandler { flush_cache_interval: Option, } -// === impl MultiForkHandler === - impl MultiForkHandler { fn new(incoming: Receiver) -> Self { Self { @@ -438,8 +436,6 @@ struct CreatedFork { num_senders: Arc, } -// === impl CreatedFork === - impl CreatedFork { pub fn new(opts: CreateFork, backend: SharedBackend) -> Self { Self { opts, backend, num_senders: Arc::new(AtomicUsize::new(1)) } @@ -485,7 +481,9 @@ impl Drop for ShutDownMultiFork { /// /// This will establish a new `Provider` to the endpoint and return the Fork Backend async fn create_fork(mut fork: CreateFork) -> eyre::Result<(ForkId, CreatedFork, Handler)> { - let provider = Arc::new( + let provider: Arc< + RootProvider, alloy_provider::network::AnyNetwork>, + > = Arc::new( ProviderBuilder::new(fork.url.as_str()) .maybe_max_retry(fork.evm_opts.fork_retries) .maybe_initial_backoff(fork.evm_opts.fork_retry_backoff) @@ -500,7 +498,7 @@ async fn create_fork(mut fork: CreateFork) -> eyre::Result<(ForkId, CreatedFork, // we need to use the block number from the block because the env's number can be different on // some L2s (e.g. Arbitrum). - let number = block.header.number.unwrap_or(meta.block_env.number).to::(); + let number = block.header.number.unwrap_or(meta.block_env.number.to()); // determine the cache path if caching is enabled let cache_path = if fork.enable_caching { @@ -517,18 +515,22 @@ async fn create_fork(mut fork: CreateFork) -> eyre::Result<(ForkId, CreatedFork, Ok((fork_id, fork, handler)) } -impl super::backend::ZkSyncMiddleware for Provider { +impl super::backend::ZkSyncMiddleware + for RootProvider +{ async fn get_bytecode_by_hash( &self, hash: alloy_primitives::B256, ) -> TransportResult> { let bytecode: Option = - self.raw_request("zks_getBytecodeByHash", vec![hash]).await?; + self.raw_request("zks_getBytecodeByHash".into(), vec![hash]).await?; Ok(bytecode.map(revm::primitives::Bytecode::new_raw)) } } -impl super::backend::ZkSyncMiddleware for Arc> { +impl super::backend::ZkSyncMiddleware + for Arc> +{ async fn get_bytecode_by_hash( &self, hash: alloy_primitives::B256, diff --git a/crates/evm/core/src/ic.rs b/crates/evm/core/src/ic.rs index 479a50b0a..c2792ab87 100644 --- a/crates/evm/core/src/ic.rs +++ b/crates/evm/core/src/ic.rs @@ -1,37 +1,27 @@ -use revm::{ - interpreter::{opcode, opcode::spec_opcode_gas}, - primitives::{HashMap, SpecId}, -}; +use revm::interpreter::opcode::{PUSH0, PUSH1, PUSH32}; +use rustc_hash::FxHashMap; /// Maps from program counter to instruction counter. /// /// Inverse of [`IcPcMap`]. pub struct PcIcMap { - pub inner: HashMap, + pub inner: FxHashMap, } impl PcIcMap { /// Creates a new `PcIcMap` for the given code. - pub fn new(spec: SpecId, code: &[u8]) -> Self { - let opcode_infos = spec_opcode_gas(spec); - let mut map = HashMap::new(); + pub fn new(code: &[u8]) -> Self { + Self { inner: make_map::(code) } + } - let mut i = 0; - let mut cumulative_push_size = 0; - while i < code.len() { - let op = code[i]; - map.insert(i, i - cumulative_push_size); - if opcode_infos[op as usize].is_push() { - // Skip the push bytes. - // - // For more context on the math, see: https://github.com/bluealloy/revm/blob/007b8807b5ad7705d3cacce4d92b89d880a83301/crates/revm/src/interpreter/contract.rs#L114-L115 - i += (op - opcode::PUSH1 + 1) as usize; - cumulative_push_size += (op - opcode::PUSH1 + 1) as usize; - } - i += 1; - } + /// Returns the length of the map. + pub fn len(&self) -> usize { + self.inner.len() + } - Self { inner: map } + /// Returns `true` if the map is empty. + pub fn is_empty(&self) -> bool { + self.inner.is_empty() } /// Returns the instruction counter for the given program counter. @@ -44,31 +34,23 @@ impl PcIcMap { /// /// Inverse of [`PcIcMap`]. pub struct IcPcMap { - pub inner: HashMap, + pub inner: FxHashMap, } impl IcPcMap { /// Creates a new `IcPcMap` for the given code. - pub fn new(spec: SpecId, code: &[u8]) -> Self { - let opcode_infos = spec_opcode_gas(spec); - let mut map = HashMap::new(); + pub fn new(code: &[u8]) -> Self { + Self { inner: make_map::(code) } + } - let mut i = 0; - let mut cumulative_push_size = 0; - while i < code.len() { - let op = code[i]; - map.insert(i - cumulative_push_size, i); - if opcode_infos[op as usize].is_push() { - // Skip the push bytes. - // - // For more context on the math, see: https://github.com/bluealloy/revm/blob/007b8807b5ad7705d3cacce4d92b89d880a83301/crates/revm/src/interpreter/contract.rs#L114-L115 - i += (op - opcode::PUSH1 + 1) as usize; - cumulative_push_size += (op - opcode::PUSH1 + 1) as usize; - } - i += 1; - } + /// Returns the length of the map. + pub fn len(&self) -> usize { + self.inner.len() + } - Self { inner: map } + /// Returns `true` if the map is empty. + pub fn is_empty(&self) -> bool { + self.inner.is_empty() } /// Returns the program counter for the given instruction counter. @@ -76,3 +58,28 @@ impl IcPcMap { self.inner.get(&ic).copied() } } + +fn make_map(code: &[u8]) -> FxHashMap { + let mut map = FxHashMap::default(); + + let mut pc = 0; + let mut cumulative_push_size = 0; + while pc < code.len() { + let ic = pc - cumulative_push_size; + if PC_FIRST { + map.insert(pc, ic); + } else { + map.insert(ic, pc); + } + + if (PUSH1..=PUSH32).contains(&code[pc]) { + // Skip the push bytes. + let push_size = (code[pc] - PUSH0) as usize; + pc += push_size; + cumulative_push_size += push_size; + } + + pc += 1; + } + map +} diff --git a/crates/evm/core/src/lib.rs b/crates/evm/core/src/lib.rs index f5cc7dc55..c2c2e2006 100644 --- a/crates/evm/core/src/lib.rs +++ b/crates/evm/core/src/lib.rs @@ -2,18 +2,45 @@ //! //! Core EVM abstractions. -#![warn(unused_crate_dependencies)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +use auto_impl::auto_impl; +use revm::{inspectors::NoOpInspector, interpreter::CreateInputs, Database, EvmContext, Inspector}; +use revm_inspectors::access_list::AccessListInspector; #[macro_use] extern crate tracing; mod ic; +pub mod abi; pub mod backend; pub mod constants; pub mod debug; pub mod decode; pub mod fork; +pub mod opcodes; pub mod opts; pub mod snapshot; pub mod utils; + +/// An extension trait that allows us to add additional hooks to Inspector for later use in +/// handlers. +#[auto_impl(&mut, Box)] +pub trait InspectorExt: Inspector { + /// Determines whether the `DEFAULT_CREATE2_DEPLOYER` should be used for a CREATE2 frame. + /// + /// If this function returns true, we'll replace CREATE2 frame with a CALL frame to CREATE2 + /// factory. + fn should_use_create2_factory( + &mut self, + _context: &mut EvmContext, + _inputs: &mut CreateInputs, + ) -> bool { + false + } +} + +impl InspectorExt for NoOpInspector {} +impl InspectorExt for AccessListInspector {} diff --git a/crates/evm/core/src/opcodes.rs b/crates/evm/core/src/opcodes.rs new file mode 100644 index 000000000..3251036c7 --- /dev/null +++ b/crates/evm/core/src/opcodes.rs @@ -0,0 +1,25 @@ +//! Opcode utils + +use revm::interpreter::OpCode; + +/// Returns true if the opcode modifies memory. +/// +/// +#[inline] +pub const fn modifies_memory(opcode: OpCode) -> bool { + matches!( + opcode, + OpCode::EXTCODECOPY | + OpCode::MLOAD | + OpCode::MSTORE | + OpCode::MSTORE8 | + OpCode::MCOPY | + OpCode::CODECOPY | + OpCode::CALLDATACOPY | + OpCode::RETURNDATACOPY | + OpCode::CALL | + OpCode::CALLCODE | + OpCode::DELEGATECALL | + OpCode::STATICCALL + ) +} diff --git a/crates/evm/core/src/opts.rs b/crates/evm/core/src/opts.rs index 510f14254..4ff429902 100644 --- a/crates/evm/core/src/opts.rs +++ b/crates/evm/core/src/opts.rs @@ -1,16 +1,12 @@ use super::fork::environment; use crate::fork::CreateFork; use alloy_primitives::{Address, B256, U256}; -use alloy_providers::provider::TempProvider; +use alloy_provider::Provider; use alloy_rpc_types::Block; use eyre::WrapErr; -use foundry_common::{ - provider::alloy::{ProviderBuilder, RpcUrl}, - ALCHEMY_FREE_TIER_CUPS, -}; -use foundry_compilers::utils::RuntimeOrHandle; +use foundry_common::{provider::ProviderBuilder, ALCHEMY_FREE_TIER_CUPS}; use foundry_config::{Chain, Config}; -use revm::primitives::{BlockEnv, CfgEnv, SpecId, TxEnv}; +use revm::primitives::{BlockEnv, CfgEnv, TxEnv}; use serde::{Deserialize, Deserializer, Serialize}; #[derive(Clone, Debug, Default, Serialize, Deserialize)] @@ -21,7 +17,7 @@ pub struct EvmOpts { /// Fetch state over a remote instead of starting from empty state. #[serde(rename = "eth_rpc_url")] - pub fork_url: Option, + pub fork_url: Option, /// Pins the block number for the state fork. pub fork_block_number: Option, @@ -64,6 +60,9 @@ pub struct EvmOpts { /// Whether to enable isolation of calls. pub isolate: bool, + + /// Whether to disable block gas limit checks. + pub disable_block_gas_limit: bool, } impl EvmOpts { @@ -92,10 +91,11 @@ impl EvmOpts { environment( &provider, self.memory_limit, - self.env.gas_price, + self.env.gas_price.map(|v| v as u128), self.env.chain_id, self.fork_block_number, self.sender, + self.disable_block_gas_limit, ) .await .wrap_err_with(|| { @@ -107,13 +107,13 @@ impl EvmOpts { pub fn local_evm_env(&self) -> revm::primitives::Env { let mut cfg = CfgEnv::default(); cfg.chain_id = self.env.chain_id.unwrap_or(foundry_common::DEV_CHAIN_ID); - cfg.spec_id = SpecId::MERGE; cfg.limit_contract_code_size = self.env.code_size_limit.or(Some(usize::MAX)); cfg.memory_limit = self.memory_limit; // EIP-3607 rejects transactions from senders with deployed code. // If EIP-3607 is enabled it can cause issues during fuzz/invariant tests if the // caller is a contract. So we disable the check by default. cfg.disable_eip3607 = true; + cfg.disable_block_gas_limit = self.disable_block_gas_limit; revm::primitives::Env { block: BlockEnv { @@ -141,14 +141,14 @@ impl EvmOpts { /// storage caching for the [CreateFork] will be enabled if /// - `fork_url` is present /// - `fork_block_number` is present - /// - [StorageCachingConfig] allows the `fork_url` + chain id pair + /// - `StorageCachingConfig` allows the `fork_url` + chain ID pair /// - storage is allowed (`no_storage_caching = false`) /// /// If all these criteria are met, then storage caching is enabled and storage info will be - /// written to [Config::foundry_cache_dir()]///storage.json + /// written to `///storage.json`. /// /// for `mainnet` and `--fork-block-number 14435000` on mac the corresponding storage cache will - /// be at `~/.foundry/cache/mainnet/14435000/storage.json` + /// be at `~/.foundry/cache/mainnet/14435000/storage.json`. pub fn get_fork(&self, config: &Config, env: revm::primitives::Env) -> Option { let url = self.fork_url.clone()?; let enable_caching = config.enable_caching(&url, env.cfg.chain_id); @@ -165,11 +165,11 @@ impl EvmOpts { /// - mainnet if `fork_url` contains "mainnet" /// - the chain if `fork_url` is set and the endpoints returned its chain id successfully /// - mainnet otherwise - pub fn get_chain_id(&self) -> u64 { + pub async fn get_chain_id(&self) -> u64 { if let Some(id) = self.env.chain_id { return id; } - self.get_remote_chain_id().unwrap_or(Chain::mainnet()).id() + self.get_remote_chain_id().await.unwrap_or(Chain::mainnet()).id() } /// Returns the available compute units per second, which will be @@ -187,7 +187,7 @@ impl EvmOpts { } /// Returns the chain ID from the RPC, if any. - pub fn get_remote_chain_id(&self) -> Option { + pub async fn get_remote_chain_id(&self) -> Option { if let Some(ref url) = self.fork_url { if url.contains("mainnet") { trace!(?url, "auto detected mainnet chain"); @@ -200,8 +200,8 @@ impl EvmOpts { .ok() .unwrap_or_else(|| panic!("Failed to establish provider to {url}")); - if let Ok(id) = RuntimeOrHandle::new().block_on(provider.get_chain_id()) { - return Some(Chain::from(id.to::())); + if let Ok(id) = provider.get_chain_id().await { + return Some(Chain::from(id)); } } diff --git a/crates/evm/core/src/utils.rs b/crates/evm/core/src/utils.rs index e0f0f1ee8..d8114f209 100644 --- a/crates/evm/core/src/utils.rs +++ b/crates/evm/core/src/utils.rs @@ -1,59 +1,29 @@ +pub use crate::ic::*; +use crate::{constants::DEFAULT_CREATE2_DEPLOYER, InspectorExt}; use alloy_json_abi::{Function, JsonAbi}; -use alloy_primitives::{FixedBytes, U256}; +use alloy_primitives::{Address, Selector, U256}; use alloy_rpc_types::{Block, Transaction}; -use eyre::ContextCompat; use foundry_config::NamedChain; use revm::{ - interpreter::InstructionResult, - primitives::{Eval, Halt, SpecId, TransactTo}, + db::WrapDatabaseRef, + handler::register::EvmHandler, + interpreter::{ + return_ok, CallInputs, CallOutcome, CallScheme, CallValue, CreateInputs, CreateOutcome, + Gas, InstructionResult, InterpreterResult, + }, + primitives::{CreateScheme, EVMError, SpecId, TransactTo, KECCAK_EMPTY}, + FrameOrResult, FrameResult, }; +use std::{cell::RefCell, rc::Rc, sync::Arc}; -pub use foundry_compilers::utils::RuntimeOrHandle; -pub use revm::primitives::State as StateChangeset; - -pub use crate::ic::*; - -/// Small helper function to convert an Eval into an InstructionResult -#[inline] -pub fn eval_to_instruction_result(eval: Eval) -> InstructionResult { - match eval { - Eval::Return => InstructionResult::Return, - Eval::Stop => InstructionResult::Stop, - Eval::SelfDestruct => InstructionResult::SelfDestruct, - } -} - -/// Small helper function to convert a Halt into an InstructionResult -#[inline] -pub fn halt_to_instruction_result(halt: Halt) -> InstructionResult { - match halt { - Halt::OutOfGas(_) => InstructionResult::OutOfGas, - Halt::OpcodeNotFound => InstructionResult::OpcodeNotFound, - Halt::InvalidFEOpcode => InstructionResult::InvalidFEOpcode, - Halt::InvalidJump => InstructionResult::InvalidJump, - Halt::NotActivated => InstructionResult::NotActivated, - Halt::StackOverflow => InstructionResult::StackOverflow, - Halt::StackUnderflow => InstructionResult::StackUnderflow, - Halt::OutOfOffset => InstructionResult::OutOfOffset, - Halt::CreateCollision => InstructionResult::CreateCollision, - Halt::PrecompileError => InstructionResult::PrecompileError, - Halt::NonceOverflow => InstructionResult::NonceOverflow, - Halt::CreateContractSizeLimit => InstructionResult::CreateContractSizeLimit, - Halt::CreateContractStartingWithEF => InstructionResult::CreateContractStartingWithEF, - Halt::CreateInitcodeSizeLimit => InstructionResult::CreateInitcodeSizeLimit, - Halt::OverflowPayment => InstructionResult::OverflowPayment, - Halt::StateChangeDuringStaticCall => InstructionResult::StateChangeDuringStaticCall, - Halt::CallNotAllowedInsideStatic => InstructionResult::CallNotAllowedInsideStatic, - Halt::OutOfFund => InstructionResult::OutOfFund, - Halt::CallTooDeep => InstructionResult::CallTooDeep, - Halt::FailedDeposit => InstructionResult::Return, - } -} +pub use revm::primitives::EvmState as StateChangeset; /// Depending on the configured chain id and block number this should apply any specific changes /// -/// This checks for: -/// - prevrandao mixhash after merge +/// - checks for prevrandao mixhash after merge +/// - applies chain specifics: on Arbitrum `block.number` is the L1 block +/// +/// Should be called with proper chain id (retrieved from provider if not provided). pub fn apply_chain_and_block_specific_env_changes(env: &mut revm::primitives::Env, block: &Block) { if let Ok(chain) = NamedChain::try_from(env.cfg.chain_id) { let block_number = block.header.number.unwrap_or_default(); @@ -61,7 +31,7 @@ pub fn apply_chain_and_block_specific_env_changes(env: &mut revm::primitives::En match chain { NamedChain::Mainnet => { // after merge difficulty is supplanted with prevrandao EIP-4399 - if block_number.to::() >= 15_537_351u64 { + if block_number >= 15_537_351u64 { env.block.difficulty = env.block.prevrandao.unwrap_or_default().into(); } @@ -90,28 +60,28 @@ pub fn apply_chain_and_block_specific_env_changes(env: &mut revm::primitives::En } /// Given an ABI and selector, it tries to find the respective function. -pub fn get_function( +pub fn get_function<'a>( contract_name: &str, - selector: &FixedBytes<4>, - abi: &JsonAbi, -) -> eyre::Result { + selector: Selector, + abi: &'a JsonAbi, +) -> eyre::Result<&'a Function> { abi.functions() - .find(|func| func.selector().as_slice() == selector.as_slice()) - .cloned() - .wrap_err(format!("{contract_name} does not have the selector {selector:?}")) + .find(|func| func.selector() == selector) + .ok_or_else(|| eyre::eyre!("{contract_name} does not have the selector {selector}")) } /// Configures the env for the transaction pub fn configure_tx_env(env: &mut revm::primitives::Env, tx: &Transaction) { env.tx.caller = tx.from; - env.tx.gas_limit = tx.gas.to(); - env.tx.gas_price = tx.gas_price.unwrap_or_default().to(); - env.tx.gas_priority_fee = tx.max_priority_fee_per_gas.map(|g| g.to()); - env.tx.nonce = Some(tx.nonce.to()); + env.tx.gas_limit = tx.gas as u64; + env.tx.gas_price = U256::from(tx.gas_price.unwrap_or_default()); + env.tx.gas_priority_fee = tx.max_priority_fee_per_gas.map(U256::from); + env.tx.nonce = Some(tx.nonce); env.tx.access_list = tx .access_list .clone() .unwrap_or_default() + .0 .into_iter() .map(|item| { ( @@ -133,3 +103,188 @@ pub fn gas_used(spec: SpecId, spent: u64, refunded: u64) -> u64 { let refund_quotient = if SpecId::enabled(spec, SpecId::LONDON) { 5 } else { 2 }; spent - (refunded).min(spent / refund_quotient) } + +fn get_create2_factory_call_inputs(salt: U256, inputs: CreateInputs) -> CallInputs { + let calldata = [&salt.to_be_bytes::<32>()[..], &inputs.init_code[..]].concat(); + CallInputs { + caller: inputs.caller, + bytecode_address: DEFAULT_CREATE2_DEPLOYER, + target_address: DEFAULT_CREATE2_DEPLOYER, + scheme: CallScheme::Call, + value: CallValue::Transfer(inputs.value), + input: calldata.into(), + gas_limit: inputs.gas_limit, + is_static: false, + return_memory_offset: 0..0, + is_eof: false, + } +} + +/// Used for routing certain CREATE2 invocations through [DEFAULT_CREATE2_DEPLOYER]. +/// +/// Overrides create hook with CALL frame if [InspectorExt::should_use_create2_factory] returns +/// true. Keeps track of overriden frames and handles outcome in the overriden insert_call_outcome +/// hook by inserting decoded address directly into interpreter. +/// +/// Should be installed after [revm::inspector_handle_register] and before any other registers. +pub fn create2_handler_register>( + handler: &mut EvmHandler<'_, I, DB>, +) { + let create2_overrides = Rc::>>::new(RefCell::new(Vec::new())); + + let create2_overrides_inner = create2_overrides.clone(); + let old_handle = handler.execution.create.clone(); + handler.execution.create = + Arc::new(move |ctx, mut inputs| -> Result> { + let CreateScheme::Create2 { salt } = inputs.scheme else { + return old_handle(ctx, inputs); + }; + if !ctx.external.should_use_create2_factory(&mut ctx.evm, &mut inputs) { + return old_handle(ctx, inputs); + } + + let gas_limit = inputs.gas_limit; + + // Generate call inputs for CREATE2 factory. + let mut call_inputs = get_create2_factory_call_inputs(salt, *inputs); + + // Call inspector to change input or return outcome. + let outcome = ctx.external.call(&mut ctx.evm, &mut call_inputs); + + // Push data about current override to the stack. + create2_overrides_inner + .borrow_mut() + .push((ctx.evm.journaled_state.depth(), call_inputs.clone())); + + // Handle potential inspector override. + if let Some(outcome) = outcome { + return Ok(FrameOrResult::Result(FrameResult::Call(outcome))); + } + + // Sanity check that CREATE2 deployer exists. + let code_hash = ctx.evm.load_account(DEFAULT_CREATE2_DEPLOYER)?.0.info.code_hash; + if code_hash == KECCAK_EMPTY { + return Ok(FrameOrResult::Result(FrameResult::Call(CallOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: "missing CREATE2 deployer".into(), + gas: Gas::new(gas_limit), + }, + memory_offset: 0..0, + }))) + } + + // Create CALL frame for CREATE2 factory invocation. + let mut frame_or_result = ctx.evm.make_call_frame(&call_inputs); + + if let Ok(FrameOrResult::Frame(frame)) = &mut frame_or_result { + ctx.external + .initialize_interp(&mut frame.frame_data_mut().interpreter, &mut ctx.evm) + } + frame_or_result + }); + + let create2_overrides_inner = create2_overrides.clone(); + let old_handle = handler.execution.insert_call_outcome.clone(); + + handler.execution.insert_call_outcome = + Arc::new(move |ctx, frame, shared_memory, mut outcome| { + // If we are on the depth of the latest override, handle the outcome. + if create2_overrides_inner + .borrow() + .last() + .map_or(false, |(depth, _)| *depth == ctx.evm.journaled_state.depth()) + { + let (_, call_inputs) = create2_overrides_inner.borrow_mut().pop().unwrap(); + outcome = ctx.external.call_end(&mut ctx.evm, &call_inputs, outcome); + + // Decode address from output. + let address = match outcome.instruction_result() { + return_ok!() => Address::try_from(outcome.output().as_ref()) + .map_err(|_| { + outcome.result = InterpreterResult { + result: InstructionResult::Revert, + output: "invalid CREATE2 factory output".into(), + gas: Gas::new(call_inputs.gas_limit), + }; + }) + .ok(), + _ => None, + }; + frame + .frame_data_mut() + .interpreter + .insert_create_outcome(CreateOutcome { address, result: outcome.result }); + + Ok(()) + } else { + old_handle(ctx, frame, shared_memory, outcome) + } + }); +} + +/// Creates a new EVM with the given inspector. +pub fn new_evm_with_inspector<'a, DB, I>( + db: DB, + env: revm::primitives::EnvWithHandlerCfg, + inspector: I, +) -> revm::Evm<'a, I, DB> +where + DB: revm::Database, + I: InspectorExt, +{ + let revm::primitives::EnvWithHandlerCfg { env, handler_cfg } = env; + + // NOTE: We could use `revm::Evm::builder()` here, but on the current patch it has some + // performance issues. + /* + revm::Evm::builder() + .with_db(db) + .with_env(env) + .with_external_context(inspector) + .with_handler_cfg(handler_cfg) + .append_handler_register(revm::inspector_handle_register) + .append_handler_register(create2_handler_register) + .build() + */ + + let context = revm::Context::new(revm::EvmContext::new_with_env(db, env), inspector); + let mut handler = revm::Handler::new(handler_cfg); + handler.append_handler_register_plain(revm::inspector_handle_register); + handler.append_handler_register_plain(create2_handler_register); + revm::Evm::new(context, handler) +} + +/// Creates a new EVM with the given inspector and wraps the database in a `WrapDatabaseRef`. +pub fn new_evm_with_inspector_ref<'a, DB, I>( + db: DB, + env: revm::primitives::EnvWithHandlerCfg, + inspector: I, +) -> revm::Evm<'a, I, WrapDatabaseRef> +where + DB: revm::DatabaseRef, + I: InspectorExt>, +{ + new_evm_with_inspector(WrapDatabaseRef(db), env, inspector) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn build_evm() { + let mut db = revm::db::EmptyDB::default(); + + let env = Box::::default(); + let spec = SpecId::LATEST; + let handler_cfg = revm::primitives::HandlerCfg::new(spec); + let cfg = revm::primitives::EnvWithHandlerCfg::new(env, handler_cfg); + + let mut inspector = revm::inspectors::NoOpInspector; + + let mut evm = new_evm_with_inspector(&mut db, cfg, &mut inspector); + let result = evm.transact().unwrap(); + assert!(result.result.is_success()); + } +} diff --git a/crates/evm/core/test-data/storage.json b/crates/evm/core/test-data/storage.json index 4f2562591..6a602f7a6 100644 --- a/crates/evm/core/test-data/storage.json +++ b/crates/evm/core/test-data/storage.json @@ -1 +1 @@ -{"meta":{"cfg_env":{"chain_id":1,"spec_id":"LATEST","perf_all_precompiles_have_balance":false,"memory_limit":4294967295, "perf_analyse_created_bytecodes":"Analyse", "limit_contract_code_size": 24576, "disable_coinbase_tip": false},"block_env":{"number":"0xdc42b8","coinbase":"0x0000000000000000000000000000000000000000","timestamp":"0x1","difficulty":"0x0","basefee":"0x0","gas_limit":"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},"hosts":["mainnet.infura.io"]},"accounts":{"0x63091244180ae240c87d1f528f5f269134cb07b3":{"balance":"0x0","code_hash":"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470","code":null,"nonce":0}},"storage":{"0x63091244180ae240c87d1f528f5f269134cb07b3":{"0x0":"0x0","0x1":"0x0","0x2":"0x0","0x3":"0x0","0x4":"0x0","0x5":"0x0","0x6":"0x0","0x7":"0x0","0x8":"0x0","0x9":"0x0"}},"block_hashes":{}} \ No newline at end of file +{"meta":{"cfg_env":{"chain_id":1,"spec_id":"LATEST","perf_all_precompiles_have_balance":false,"memory_limit":4294967295,"perf_analyse_created_bytecodes":"Analyse","limit_contract_code_size":24576,"disable_coinbase_tip":false},"block_env":{"number":"0xdc42b8","coinbase":"0x0000000000000000000000000000000000000000","timestamp":"0x1","difficulty":"0x0","basefee":"0x0","gas_limit":"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"},"hosts":["mainnet.infura.io"]},"accounts":{"0x63091244180ae240c87d1f528f5f269134cb07b3":{"balance":"0x0","code_hash":"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470","code":null,"nonce":0}},"storage":{"0x63091244180ae240c87d1f528f5f269134cb07b3":{"0x0":"0x0","0x1":"0x0","0x2":"0x0","0x3":"0x0","0x4":"0x0","0x5":"0x0","0x6":"0x0","0x7":"0x0","0x8":"0x0","0x9":"0x0"}},"block_hashes":{}} \ No newline at end of file diff --git a/crates/evm/coverage/Cargo.toml b/crates/evm/coverage/Cargo.toml index 4a9a39276..777c37d99 100644 --- a/crates/evm/coverage/Cargo.toml +++ b/crates/evm/coverage/Cargo.toml @@ -10,13 +10,18 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [dependencies] foundry-common.workspace = true foundry-compilers.workspace = true foundry-evm-core.workspace = true alloy-primitives.workspace = true -eyre = "0.6" +eyre.workspace = true revm.workspace = true -semver = "1" -tracing = "0.1" +semver.workspace = true +tracing.workspace = true +rustc-hash.workspace = true +rayon.workspace = true diff --git a/crates/evm/coverage/src/analysis.rs b/crates/evm/coverage/src/analysis.rs index 2ebd1be10..bbfaa1897 100644 --- a/crates/evm/coverage/src/analysis.rs +++ b/crates/evm/coverage/src/analysis.rs @@ -1,8 +1,9 @@ -use super::{ContractId, CoverageItem, CoverageItemKind, SourceLocation}; +use super::{CoverageItem, CoverageItemKind, SourceLocation}; use foundry_common::TestFunctionExt; use foundry_compilers::artifacts::ast::{self, Ast, Node, NodeType}; -use semver::Version; -use std::collections::{HashMap, HashSet}; +use rayon::prelude::*; +use rustc_hash::FxHashMap; +use std::sync::Arc; /// A visitor that walks the AST of a single contract and finds coverage items. #[derive(Clone, Debug)] @@ -13,7 +14,7 @@ pub struct ContractVisitor<'a> { source: &'a str, /// The name of the contract being walked. - contract_name: String, + contract_name: &'a Arc, /// The current branch ID branch_id: usize, @@ -22,83 +23,81 @@ pub struct ContractVisitor<'a> { /// Coverage items pub items: Vec, - - /// Node IDs of this contract's base contracts, as well as IDs for referenced contracts such as - /// libraries - pub base_contract_node_ids: HashSet, } impl<'a> ContractVisitor<'a> { - pub fn new(source_id: usize, source: &'a str, contract_name: String) -> Self { - Self { - source_id, - source, - contract_name, - branch_id: 0, - last_line: 0, - items: Vec::new(), - base_contract_node_ids: HashSet::new(), - } + pub fn new(source_id: usize, source: &'a str, contract_name: &'a Arc) -> Self { + Self { source_id, source, contract_name, branch_id: 0, last_line: 0, items: Vec::new() } } - pub fn visit(mut self, contract_ast: Node) -> eyre::Result { - let linearized_base_contracts: Vec = - contract_ast.attribute("linearizedBaseContracts").ok_or_else(|| { - eyre::eyre!( - "The contract's AST node is missing a list of linearized base contracts" - ) - })?; - - // We skip the first ID because that's the ID of the contract itself - self.base_contract_node_ids.extend(&linearized_base_contracts[1..]); - + pub fn visit_contract(&mut self, node: &Node) -> eyre::Result<()> { // Find all functions and walk their AST - for node in contract_ast.nodes { - if node.node_type == NodeType::FunctionDefinition { - self.visit_function_definition(node.clone())?; + for node in &node.nodes { + match node.node_type { + NodeType::FunctionDefinition => { + self.visit_function_definition(node)?; + } + NodeType::ModifierDefinition => { + self.visit_modifier_definition(node)?; + } + _ => {} } } - - Ok(self) + Ok(()) } - fn visit_function_definition(&mut self, mut node: Node) -> eyre::Result<()> { + fn visit_function_definition(&mut self, node: &Node) -> eyre::Result<()> { let name: String = node.attribute("name").ok_or_else(|| eyre::eyre!("Function has no name"))?; - // TODO(onbjerg): Re-enable constructor parsing when we walk both the deployment and runtime - // sourcemaps. Currently this fails because we are trying to look for anchors in the runtime - // sourcemap. // TODO(onbjerg): Figure out why we cannot find anchors for the receive function let kind: String = node.attribute("kind").ok_or_else(|| eyre::eyre!("Function has no kind"))?; - if kind == "constructor" || kind == "receive" { + if kind == "receive" { return Ok(()) } - match node.body.take() { + match &node.body { + Some(body) => { + self.push_item(CoverageItem { + kind: CoverageItemKind::Function { name }, + loc: self.source_location_for(&node.src), + hits: 0, + }); + self.visit_block(body) + } + _ => Ok(()), + } + } + + fn visit_modifier_definition(&mut self, node: &Node) -> eyre::Result<()> { + let name: String = + node.attribute("name").ok_or_else(|| eyre::eyre!("Modifier has no name"))?; + + match &node.body { Some(body) => { self.push_item(CoverageItem { kind: CoverageItemKind::Function { name }, loc: self.source_location_for(&node.src), hits: 0, }); - self.visit_block(*body) + self.visit_block(body) } _ => Ok(()), } } - fn visit_block(&mut self, node: Node) -> eyre::Result<()> { + fn visit_block(&mut self, node: &Node) -> eyre::Result<()> { let statements: Vec = node.attribute("statements").unwrap_or_default(); - for statement in statements { + for statement in &statements { self.visit_statement(statement)?; } Ok(()) } - fn visit_statement(&mut self, node: Node) -> eyre::Result<()> { + + fn visit_statement(&mut self, node: &Node) -> eyre::Result<()> { // TODO: YulSwitch, YulForLoop, YulFunctionDefinition, YulVariableDeclaration match node.node_type { // Blocks @@ -107,14 +106,14 @@ impl<'a> ContractVisitor<'a> { } // Inline assembly block NodeType::InlineAssembly => self.visit_block( - node.attribute("AST") + &node + .attribute("AST") .ok_or_else(|| eyre::eyre!("inline assembly block with no AST attribute"))?, ), // Simple statements NodeType::Break | NodeType::Continue | NodeType::EmitStatement | - NodeType::PlaceholderStatement | NodeType::RevertStatement | NodeType::YulAssignment | NodeType::YulBreak | @@ -128,6 +127,9 @@ impl<'a> ContractVisitor<'a> { Ok(()) } + // Skip placeholder statements as they are never referenced in source maps. + NodeType::PlaceholderStatement => Ok(()), + // Return with eventual subcall NodeType::Return => { self.push_item(CoverageItem { @@ -136,7 +138,7 @@ impl<'a> ContractVisitor<'a> { hits: 0, }); if let Some(expr) = node.attribute("expression") { - self.visit_expression(expr)?; + self.visit_expression(&expr)?; } Ok(()) } @@ -149,47 +151,54 @@ impl<'a> ContractVisitor<'a> { hits: 0, }); if let Some(expr) = node.attribute("initialValue") { - self.visit_expression(expr)?; + self.visit_expression(&expr)?; } Ok(()) } // While loops NodeType::DoWhileStatement | NodeType::WhileStatement => { self.visit_expression( - node.attribute("condition") + &node + .attribute("condition") .ok_or_else(|| eyre::eyre!("while statement had no condition"))?, )?; - let body = - node.body.ok_or_else(|| eyre::eyre!("while statement had no body node"))?; - self.visit_block_or_statement(*body) + let body = node + .body + .as_deref() + .ok_or_else(|| eyre::eyre!("while statement had no body node"))?; + self.visit_block_or_statement(body) } // For loops NodeType::ForStatement => { if let Some(stmt) = node.attribute("initializationExpression") { - self.visit_statement(stmt)?; + self.visit_statement(&stmt)?; } if let Some(expr) = node.attribute("condition") { - self.visit_expression(expr)?; + self.visit_expression(&expr)?; } if let Some(stmt) = node.attribute("loopExpression") { - self.visit_statement(stmt)?; + self.visit_statement(&stmt)?; } - let body = - node.body.ok_or_else(|| eyre::eyre!("for statement had no body node"))?; - self.visit_block_or_statement(*body) + let body = node + .body + .as_deref() + .ok_or_else(|| eyre::eyre!("for statement had no body node"))?; + self.visit_block_or_statement(body) } // Expression statement NodeType::ExpressionStatement | NodeType::YulExpressionStatement => self .visit_expression( - node.attribute("expression") + &node + .attribute("expression") .ok_or_else(|| eyre::eyre!("expression statement had no expression"))?, ), // If statement NodeType::IfStatement => { self.visit_expression( - node.attribute("condition") + &node + .attribute("condition") .ok_or_else(|| eyre::eyre!("while statement had no condition"))?, )?; @@ -223,22 +232,25 @@ impl<'a> ContractVisitor<'a> { ); // Process the true branch - self.visit_block_or_statement(true_body)?; + self.visit_block_or_statement(&true_body)?; // Process the false branch - let false_body: Option = node.attribute("falseBody"); - if let Some(false_body) = false_body { - self.visit_block_or_statement(false_body)?; + if let Some(false_body) = node.attribute("falseBody") { + self.visit_block_or_statement(&false_body)?; } Ok(()) } NodeType::YulIf => { self.visit_expression( - node.attribute("condition") + &node + .attribute("condition") .ok_or_else(|| eyre::eyre!("yul if statement had no condition"))?, )?; - let body = node.body.ok_or_else(|| eyre::eyre!("yul if statement had no body"))?; + let body = node + .body + .as_deref() + .ok_or_else(|| eyre::eyre!("yul if statement had no body"))?; // We need to store the current branch ID here since visiting the body of either of // the if blocks may increase `self.branch_id` in the case of nested if statements. @@ -253,7 +265,7 @@ impl<'a> ContractVisitor<'a> { loc: self.source_location_for(&node.src), hits: 0, }); - self.visit_block(*body)?; + self.visit_block(body)?; Ok(()) } @@ -262,7 +274,8 @@ impl<'a> ContractVisitor<'a> { // TODO: Clauses // TODO: This is branching, right? self.visit_expression( - node.attribute("externalCall") + &node + .attribute("externalCall") .ok_or_else(|| eyre::eyre!("try statement had no call"))?, ) } @@ -273,7 +286,7 @@ impl<'a> ContractVisitor<'a> { } } - fn visit_expression(&mut self, node: Node) -> eyre::Result<()> { + fn visit_expression(&mut self, node: &Node) -> eyre::Result<()> { // TODO // elementarytypenameexpression // memberaccess @@ -300,11 +313,11 @@ impl<'a> ContractVisitor<'a> { // There could possibly a function call in the left or right expression // e.g: callFunc(a) + callFunc(b) if let Some(expr) = node.attribute("leftExpression") { - self.visit_expression(expr)?; + self.visit_expression(&expr)?; } if let Some(expr) = node.attribute("rightExpression") { - self.visit_expression(expr)?; + self.visit_expression(&expr)?; } Ok(()) @@ -317,25 +330,13 @@ impl<'a> ContractVisitor<'a> { }); let expr: Option = node.attribute("expression"); - match expr.as_ref().map(|expr| &expr.node_type) { + if let Some(NodeType::Identifier) = expr.as_ref().map(|expr| &expr.node_type) { // Might be a require/assert call - Some(NodeType::Identifier) => { - let name: Option = expr.and_then(|expr| expr.attribute("name")); - if let Some("assert" | "require") = name.as_deref() { - self.push_branches(&node.src, self.branch_id); - self.branch_id += 1; - } - } - // Might be a call to a library - Some(NodeType::MemberAccess) => { - let referenced_declaration_id = expr - .and_then(|expr| expr.attribute("expression")) - .and_then(|subexpr: Node| subexpr.attribute("referencedDeclaration")); - if let Some(id) = referenced_declaration_id { - self.base_contract_node_ids.insert(id); - } + let name: Option = expr.and_then(|expr| expr.attribute("name")); + if let Some("assert" | "require") = name.as_deref() { + self.push_branches(&node.src, self.branch_id); + self.branch_id += 1; } - _ => (), } Ok(()) @@ -363,7 +364,7 @@ impl<'a> ContractVisitor<'a> { } } - fn visit_block_or_statement(&mut self, node: Node) -> eyre::Result<()> { + fn visit_block_or_statement(&mut self, node: &Node) -> eyre::Result<()> { match node.node_type { NodeType::Block => self.visit_block(node), NodeType::Break | @@ -374,12 +375,13 @@ impl<'a> ContractVisitor<'a> { NodeType::ForStatement | NodeType::IfStatement | NodeType::InlineAssembly | - NodeType::PlaceholderStatement | NodeType::Return | NodeType::RevertStatement | NodeType::TryStatement | NodeType::VariableDeclarationStatement | NodeType::WhileStatement => self.visit_statement(node), + // Skip placeholder statements as they are never referenced in source maps. + NodeType::PlaceholderStatement => Ok(()), _ => { warn!("unexpected node type, expected block or statement: {:?}", node.node_type); Ok(()) @@ -410,8 +412,8 @@ impl<'a> ContractVisitor<'a> { SourceLocation { source_id: self.source_id, contract_name: self.contract_name.clone(), - start: loc.start, - length: loc.length, + start: loc.start as u32, + length: loc.length.map(|x| x as u32), line: self.source[..loc.start].lines().count(), } } @@ -430,77 +432,30 @@ impl<'a> ContractVisitor<'a> { } } +/// [`SourceAnalyzer`] result type. #[derive(Debug)] pub struct SourceAnalysis { /// A collection of coverage items. pub items: Vec, - /// A mapping of contract IDs to item IDs relevant to the contract (including items in base - /// contracts). - pub contract_items: HashMap>, } /// Analyzes a set of sources to find coverage items. -#[derive(Clone, Debug, Default)] -pub struct SourceAnalyzer { - /// A map of source IDs to their source code - sources: HashMap, - /// A map of AST node IDs of contracts to their contract IDs. - contract_ids: HashMap, - /// A map of contract IDs to their AST nodes. - contracts: HashMap, - /// A collection of coverage items. - items: Vec, - /// A map of contract IDs to item IDs. - contract_items: HashMap>, - /// A map of contracts to their base contracts - contract_bases: HashMap>, +#[derive(Debug)] +pub struct SourceAnalyzer<'a> { + sources: &'a SourceFiles<'a>, } -impl SourceAnalyzer { +impl<'a> SourceAnalyzer<'a> { /// Creates a new source analyzer. - /// - /// The source analyzer expects all given sources to belong to the same compilation job - /// (defined by `version`). - pub fn new( - version: Version, - asts: HashMap, - sources: HashMap, - ) -> eyre::Result { - let mut analyzer = SourceAnalyzer { sources, ..Default::default() }; - - // TODO: Skip interfaces - for (source_id, ast) in asts.into_iter() { - for child in ast.nodes { - if !matches!(child.node_type, NodeType::ContractDefinition) { - continue - } - - let node_id = - child.id.ok_or_else(|| eyre::eyre!("The contract's AST node has no ID"))?; - let contract_id = ContractId { - version: version.clone(), - source_id, - contract_name: child - .attribute("name") - .ok_or_else(|| eyre::eyre!("Contract has no name"))?, - }; - analyzer.contract_ids.insert(node_id, contract_id.clone()); - analyzer.contracts.insert(contract_id, child); - } - } - - Ok(analyzer) + pub fn new(data: &'a SourceFiles<'a>) -> Self { + Self { sources: data } } /// Analyzes contracts in the sources held by the source analyzer. /// /// Coverage items are found by: /// - Walking the AST of each contract (except interfaces) - /// - Recording the items and base contracts of each contract - /// - /// Finally, the item IDs of each contract and its base contracts are flattened, and the return - /// value represents all coverage items in the project, along with a mapping of contract IDs to - /// item IDs. + /// - Recording the items of each contract /// /// Each coverage item contains relevant information to find opcodes corresponding to them: the /// source ID the item is in, the source code range of the item, and the contract name the item @@ -509,128 +464,64 @@ impl SourceAnalyzer { /// Note: Source IDs are only unique per compilation job; that is, a code base compiled with /// two different solc versions will produce overlapping source IDs if the compiler version is /// not taken into account. - pub fn analyze(mut self) -> eyre::Result { - // Analyze the contracts - self.analyze_contracts()?; - - // Flatten the data - let mut flattened: HashMap> = HashMap::new(); - for contract_id in self.contract_items.keys() { - let mut item_ids: Vec = Vec::new(); - - // - // for a specific contract (id == contract_id): - // - // self.contract_bases.get(contract_id) includes the following contracts: - // 1. all the ancestors of this contract (including parent, grandparent, ... - // contracts) - // 2. the libraries **directly** used by this contract - // - // The missing contracts are: - // 1. libraries used in ancestors of this contracts - // 2. libraries used in libraries (i.e libs indirectly used by this contract) - // - // We want to find out all the above contracts and libraries related to this contract. - - for contract_or_lib in { - // A set of contracts and libraries related to this contract (will include "this" - // contract itself) - let mut contracts_libraries: HashSet<&ContractId> = HashSet::new(); - - // we use a stack for depth-first search. - let mut stack: Vec<&ContractId> = Vec::new(); - - // push "this" contract onto the stack - stack.push(contract_id); - - while let Some(contract_or_lib) = stack.pop() { - // whenever a contract_or_lib is removed from the stack, it is added to the set - contracts_libraries.insert(contract_or_lib); - - // push all ancestors of contract_or_lib and libraries used by contract_or_lib - // onto the stack - if let Some(bases) = self.contract_bases.get(contract_or_lib) { - stack.extend( - bases.iter().filter(|base| !contracts_libraries.contains(base)), - ); + pub fn analyze(&self) -> eyre::Result { + let items = self + .sources + .sources + .par_iter() + .flat_map_iter(|(&source_id, SourceFile { source, ast })| { + ast.nodes.iter().map(move |node| { + if !matches!(node.node_type, NodeType::ContractDefinition) { + return Ok(vec![]); } - } - contracts_libraries - } { - // get items of each contract or library - if let Some(items) = self.contract_items.get(contract_or_lib) { - item_ids.extend(items.iter()); - } - } - - // If there are no items for this contract, then it was most likely filtered - if !item_ids.is_empty() { - flattened.insert(contract_id.clone(), item_ids); - } - } + // Skip interfaces which have no function implementations. + let contract_kind: String = node + .attribute("contractKind") + .ok_or_else(|| eyre::eyre!("Contract has no kind"))?; + if contract_kind == "interface" { + return Ok(vec![]); + } - Ok(SourceAnalysis { items: self.items.clone(), contract_items: flattened }) - } + let name = node + .attribute("name") + .ok_or_else(|| eyre::eyre!("Contract has no name"))?; - fn analyze_contracts(&mut self) -> eyre::Result<()> { - for contract_id in self.contracts.keys() { - // Find this contract's coverage items if we haven't already - if self.contract_items.get(contract_id).is_none() { - let ContractVisitor { items, base_contract_node_ids, .. } = ContractVisitor::new( - contract_id.source_id, - self.sources.get(&contract_id.source_id).unwrap_or_else(|| { - panic!( - "We should have the source code for source ID {}", - contract_id.source_id - ) - }), - contract_id.contract_name.clone(), - ) - .visit( - self.contracts - .get(contract_id) - .unwrap_or_else(|| { - panic!("We should have the AST of contract: {contract_id:?}") - }) - .clone(), - )?; + let mut visitor = ContractVisitor::new(source_id, source, &name); + visitor.visit_contract(node)?; + let mut items = visitor.items; - let is_test = items.iter().any(|item| { - if let CoverageItemKind::Function { name } = &item.kind { - name.is_test() - } else { - false + let is_test = items.iter().any(|item| { + if let CoverageItemKind::Function { name } = &item.kind { + name.is_test() + } else { + false + } + }); + if is_test { + items.clear(); } - }); - // Record this contract's base contracts - // We don't do this for test contracts because we don't care about them - if !is_test { - self.contract_bases.insert( - contract_id.clone(), - base_contract_node_ids - .iter() - .filter_map(|base_contract_node_id| { - self.contract_ids.get(base_contract_node_id).cloned() - }) - .collect(), - ); - } + Ok(items) + }) + }) + .collect::>>>()?; + Ok(SourceAnalysis { items: items.concat() }) + } +} - // For tests and contracts with no items we still record an empty Vec so we don't - // end up here again - if items.is_empty() || is_test { - self.contract_items.insert(contract_id.clone(), Vec::new()); - } else { - let item_ids: Vec = - (self.items.len()..self.items.len() + items.len()).collect(); - self.items.extend(items); - self.contract_items.insert(contract_id.clone(), item_ids.clone()); - } - } - } +/// A list of versioned sources and their ASTs. +#[derive(Debug, Default)] +pub struct SourceFiles<'a> { + /// The versioned sources. + pub sources: FxHashMap>, +} - Ok(()) - } +/// The source code and AST of a file. +#[derive(Debug)] +pub struct SourceFile<'a> { + /// The source code. + pub source: String, + /// The AST of the source code. + pub ast: &'a Ast, } diff --git a/crates/evm/coverage/src/anchors.rs b/crates/evm/coverage/src/anchors.rs index 237d941d1..ad4ad744d 100644 --- a/crates/evm/coverage/src/anchors.rs +++ b/crates/evm/coverage/src/anchors.rs @@ -1,43 +1,47 @@ use super::{CoverageItem, CoverageItemKind, ItemAnchor, SourceLocation}; -use alloy_primitives::Bytes; -use foundry_compilers::sourcemap::{SourceElement, SourceMap}; +use eyre::ensure; +use foundry_compilers::artifacts::sourcemap::{SourceElement, SourceMap}; use foundry_evm_core::utils::IcPcMap; -use revm::{ - interpreter::opcode::{self, spec_opcode_gas}, - primitives::SpecId, -}; +use revm::interpreter::opcode; +use rustc_hash::{FxHashMap, FxHashSet}; /// Attempts to find anchors for the given items using the given source map and bytecode. pub fn find_anchors( - bytecode: &Bytes, + bytecode: &[u8], source_map: &SourceMap, ic_pc_map: &IcPcMap, - item_ids: &[usize], items: &[CoverageItem], + items_by_source_id: &FxHashMap>, ) -> Vec { - item_ids + let mut seen = FxHashSet::default(); + source_map .iter() - .filter_map(|item_id| { - let item = items.get(*item_id)?; + .filter_map(|element| items_by_source_id.get(&(element.index()? as usize))) + .flatten() + .filter_map(|&item_id| { + if !seen.insert(item_id) { + return None; + } + let item = &items[item_id]; match item.kind { CoverageItemKind::Branch { path_id, .. } => { - match find_anchor_branch(bytecode, source_map, *item_id, &item.loc) { + match find_anchor_branch(bytecode, source_map, item_id, &item.loc) { Ok(anchors) => match path_id { 0 => Some(anchors.0), 1 => Some(anchors.1), _ => panic!("Too many paths for branch"), }, Err(e) => { - warn!("Could not find anchor for item: {}, error: {e}", item); + warn!("Could not find anchor for item {item}: {e}"); None } } } - _ => match find_anchor_simple(source_map, ic_pc_map, *item_id, &item.loc) { + _ => match find_anchor_simple(source_map, ic_pc_map, item_id, &item.loc) { Ok(anchor) => Some(anchor), Err(e) => { - warn!("Could not find anchor for item: {}, error: {e}", item); + warn!("Could not find anchor for item {item}: {e}"); None } }, @@ -53,13 +57,10 @@ pub fn find_anchor_simple( item_id: usize, loc: &SourceLocation, ) -> eyre::Result { - let instruction = source_map - .iter() - .enumerate() - .find_map(|(ic, element)| is_in_source_range(element, loc).then_some(ic)) - .ok_or_else(|| { - eyre::eyre!("Could not find anchor: No matching instruction in range {}", loc) - })?; + let instruction = + source_map.iter().position(|element| is_in_source_range(element, loc)).ok_or_else( + || eyre::eyre!("Could not find anchor: No matching instruction in range {loc}"), + )?; Ok(ItemAnchor { instruction: ic_pc_map.get(instruction).ok_or_else(|| { @@ -98,24 +99,25 @@ pub fn find_anchor_simple( /// counter of the first branch, and return an item for that program counter, and the /// program counter immediately after the JUMPI instruction. pub fn find_anchor_branch( - bytecode: &Bytes, + bytecode: &[u8], source_map: &SourceMap, item_id: usize, loc: &SourceLocation, ) -> eyre::Result<(ItemAnchor, ItemAnchor)> { // NOTE(onbjerg): We use `SpecId::LATEST` here since it does not matter; the only difference // is the gas cost. - let opcode_infos = spec_opcode_gas(SpecId::LATEST); let mut anchors: Option<(ItemAnchor, ItemAnchor)> = None; let mut pc = 0; let mut cumulative_push_size = 0; - while pc < bytecode.0.len() { - let op = bytecode.0[pc]; + while pc < bytecode.len() { + let op = bytecode[pc]; // We found a push, so we do some PC -> IC translation accounting, but we also check if // this push is coupled with the JUMPI we are interested in. - if opcode_infos[op as usize].is_push() { + + // Check if Opcode is PUSH + if (opcode::PUSH1..=opcode::PUSH32).contains(&op) { let element = if let Some(element) = source_map.get(pc - cumulative_push_size) { element } else { @@ -131,29 +133,25 @@ pub fn find_anchor_branch( // Check if we are in the source range we are interested in, and if the next opcode // is a JUMPI - if is_in_source_range(element, loc) && bytecode.0[pc + 1] == opcode::JUMPI { + if is_in_source_range(element, loc) && bytecode[pc + 1] == opcode::JUMPI { // We do not support program counters bigger than usize. This is also an // assumption in REVM, so this is just a sanity check. - if push_size > 8 { - panic!("We found the anchor for the branch, but it refers to a program counter bigger than 64 bits."); - } + ensure!(push_size <= 8, "jump destination overflow"); // Convert the push bytes for the second branch's PC to a usize let push_bytes_start = pc - push_size + 1; - let mut pc_bytes: [u8; 8] = [0; 8]; - for (i, push_byte) in - bytecode.0[push_bytes_start..push_bytes_start + push_size].iter().enumerate() - { - pc_bytes[8 - push_size + i] = *push_byte; - } - + let push_bytes = &bytecode[push_bytes_start..push_bytes_start + push_size]; + let mut pc_bytes = [0u8; 8]; + pc_bytes[8 - push_size..].copy_from_slice(push_bytes); + let pc_jump = u64::from_be_bytes(pc_bytes); + let pc_jump = usize::try_from(pc_jump).expect("PC is too big"); anchors = Some(( ItemAnchor { item_id, // The first branch is the opcode directly after JUMPI instruction: pc + 2, }, - ItemAnchor { item_id, instruction: usize::from_be_bytes(pc_bytes) }, + ItemAnchor { item_id, instruction: pc_jump }, )); } } @@ -165,14 +163,25 @@ pub fn find_anchor_branch( /// Calculates whether `element` is within the range of the target `location`. fn is_in_source_range(element: &SourceElement, location: &SourceLocation) -> bool { - let source_ids_match = element.index.map_or(false, |a| a as usize == location.source_id); + // Source IDs must match. + let source_ids_match = element.index().map_or(false, |a| a as usize == location.source_id); + if !source_ids_match { + return false; + } // Needed because some source ranges in the source map mark the entire contract... - let is_within_start = element.offset >= location.start; + let is_within_start = element.offset() >= location.start; + if !is_within_start { + return false; + } - let start_of_ranges = location.start.max(element.offset); - let end_of_ranges = - (location.start + location.length.unwrap_or_default()).min(element.offset + element.length); + let start_of_ranges = location.start.max(element.offset()); + let end_of_ranges = (location.start + location.length.unwrap_or_default()) + .min(element.offset() + element.length()); + let within_ranges = start_of_ranges <= end_of_ranges; + if !within_ranges { + return false; + } - source_ids_match && is_within_start && start_of_ranges <= end_of_ranges + true } diff --git a/crates/evm/coverage/src/inspector.rs b/crates/evm/coverage/src/inspector.rs index 8d75e0e94..4e25ddb51 100644 --- a/crates/evm/coverage/src/inspector.rs +++ b/crates/evm/coverage/src/inspector.rs @@ -1,6 +1,5 @@ use crate::{HitMap, HitMaps}; -use alloy_primitives::Bytes; -use revm::{interpreter::Interpreter, Database, EVMData, Inspector}; +use revm::{interpreter::Interpreter, Database, EvmContext, Inspector}; #[derive(Clone, Debug, Default)] pub struct CoverageCollector { @@ -10,18 +9,16 @@ pub struct CoverageCollector { impl Inspector for CoverageCollector { #[inline] - fn initialize_interp(&mut self, interpreter: &mut Interpreter<'_>, _: &mut EVMData<'_, DB>) { - let hash = interpreter.contract.hash; - self.maps.entry(hash).or_insert_with(|| { - HitMap::new(Bytes::copy_from_slice( - interpreter.contract.bytecode.original_bytecode_slice(), - )) - }); + fn initialize_interp(&mut self, interp: &mut Interpreter, _context: &mut EvmContext) { + let hash = interp.contract.hash.expect("Contract hash is None"); + self.maps + .entry(hash) + .or_insert_with(|| HitMap::new(interp.contract.bytecode.original_bytes())); } #[inline] - fn step(&mut self, interpreter: &mut Interpreter<'_>, _: &mut EVMData<'_, DB>) { - let hash = interpreter.contract.hash; - self.maps.entry(hash).and_modify(|map| map.hit(interpreter.program_counter())); + fn step(&mut self, interp: &mut Interpreter, _context: &mut EvmContext) { + let hash = interp.contract.hash.expect("Contract hash is None"); + self.maps.entry(hash).and_modify(|map| map.hit(interp.program_counter())); } } diff --git a/crates/evm/coverage/src/lib.rs b/crates/evm/coverage/src/lib.rs index 04ecaf948..22f4b9808 100644 --- a/crates/evm/coverage/src/lib.rs +++ b/crates/evm/coverage/src/lib.rs @@ -2,18 +2,21 @@ //! //! EVM bytecode coverage analysis. -#![warn(unreachable_pub, unused_crate_dependencies, rust_2018_idioms)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #[macro_use] extern crate tracing; use alloy_primitives::{Bytes, B256}; -use foundry_compilers::sourcemap::SourceElement; +use foundry_compilers::artifacts::sourcemap::SourceMap; use semver::Version; use std::{ collections::{BTreeMap, HashMap}, fmt::Display, ops::{AddAssign, Deref, DerefMut}, + path::PathBuf, + sync::Arc, }; use eyre::{Context, Result}; @@ -30,66 +33,65 @@ pub use inspector::CoverageCollector; /// "anchors"). A single coverage item may be referred to by multiple anchors. #[derive(Clone, Debug, Default)] pub struct CoverageReport { - /// A map of source IDs to the source path - pub source_paths: HashMap<(Version, usize), String>, - /// A map of source paths to source IDs - pub source_paths_to_ids: HashMap<(Version, String), usize>, + /// A map of source IDs to the source path. + pub source_paths: HashMap<(Version, usize), PathBuf>, + /// A map of source paths to source IDs. + pub source_paths_to_ids: HashMap<(Version, PathBuf), usize>, /// All coverage items for the codebase, keyed by the compiler version. pub items: HashMap>, /// All item anchors for the codebase, keyed by their contract ID. - pub anchors: HashMap>, - /// All the bytecode hits for the codebase + pub anchors: HashMap, Vec)>, + /// All the bytecode hits for the codebase. pub bytecode_hits: HashMap, - /// The bytecode -> source mappings - pub source_maps: HashMap, Vec)>, + /// The bytecode -> source mappings. + pub source_maps: HashMap, } impl CoverageReport { /// Add a source file path. - pub fn add_source(&mut self, version: Version, source_id: usize, path: String) { + pub fn add_source(&mut self, version: Version, source_id: usize, path: PathBuf) { self.source_paths.insert((version.clone(), source_id), path.clone()); self.source_paths_to_ids.insert((version, path), source_id); } /// Get the source ID for a specific source file path. - pub fn get_source_id(&self, version: Version, path: String) -> Option<&usize> { - self.source_paths_to_ids.get(&(version, path)) + pub fn get_source_id(&self, version: Version, path: PathBuf) -> Option { + self.source_paths_to_ids.get(&(version, path)).copied() } - /// Add the source maps + /// Add the source maps. pub fn add_source_maps( &mut self, - source_maps: HashMap, Vec)>, + source_maps: impl IntoIterator, ) { self.source_maps.extend(source_maps); } - /// Add coverage items to this report - pub fn add_items(&mut self, version: Version, items: Vec) { + /// Add coverage items to this report. + pub fn add_items(&mut self, version: Version, items: impl IntoIterator) { self.items.entry(version).or_default().extend(items); } - /// Add anchors to this report - pub fn add_anchors(&mut self, anchors: HashMap>) { + /// Add anchors to this report. + pub fn add_anchors( + &mut self, + anchors: impl IntoIterator, Vec))>, + ) { self.anchors.extend(anchors); } - /// Get coverage summaries by source file path - pub fn summary_by_file(&self) -> impl Iterator { - let mut summaries: BTreeMap = BTreeMap::new(); + /// Get coverage summaries by source file path. + pub fn summary_by_file(&self) -> impl Iterator { + let mut summaries = BTreeMap::new(); for (version, items) in self.items.iter() { for item in items { - let mut summary = summaries - .entry( - self.source_paths - .get(&(version.clone(), item.loc.source_id)) - .cloned() - .unwrap_or_else(|| { - format!("Unknown (ID: {}, solc: {version})", item.loc.source_id) - }), - ) - .or_default(); + let Some(path) = + self.source_paths.get(&(version.clone(), item.loc.source_id)).cloned() + else { + continue; + }; + let mut summary = summaries.entry(path).or_default(); summary += item; } } @@ -97,50 +99,47 @@ impl CoverageReport { summaries.into_iter() } - /// Get coverage items by source file path - pub fn items_by_source(&self) -> impl Iterator)> { - let mut items_by_source: BTreeMap> = BTreeMap::new(); + /// Get coverage items by source file path. + pub fn items_by_source(&self) -> impl Iterator)> { + let mut items_by_source: BTreeMap<_, Vec<_>> = BTreeMap::new(); for (version, items) in self.items.iter() { for item in items { - items_by_source - .entry( - self.source_paths - .get(&(version.clone(), item.loc.source_id)) - .cloned() - .unwrap_or_else(|| { - format!("Unknown (ID: {}, solc: {version})", item.loc.source_id) - }), - ) - .or_default() - .push(item.clone()); + let Some(path) = + self.source_paths.get(&(version.clone(), item.loc.source_id)).cloned() + else { + continue; + }; + items_by_source.entry(path).or_default().push(item.clone()); } } items_by_source.into_iter() } - /// Processes data from a [HitMap] and sets hit counts for coverage items in this coverage map. + /// Processes data from a [`HitMap`] and sets hit counts for coverage items in this coverage + /// map. /// /// This function should only be called *after* all the relevant sources have been processed and - /// added to the map (see [add_source]). - pub fn add_hit_map(&mut self, contract_id: &ContractId, hit_map: &HitMap) -> Result<()> { + /// added to the map (see [`add_source`](Self::add_source)). + pub fn add_hit_map( + &mut self, + contract_id: &ContractId, + hit_map: &HitMap, + is_deployed_code: bool, + ) -> Result<()> { // Add bytecode level hits let e = self .bytecode_hits .entry(contract_id.clone()) .or_insert_with(|| HitMap::new(hit_map.bytecode.clone())); - e.merge(hit_map).context(format!( - "contract_id {:?}, hash {}, hash {}", - contract_id, - e.bytecode.clone(), - hit_map.bytecode.clone(), - ))?; + e.merge(hit_map).wrap_err_with(|| format!("{contract_id:?}"))?; // Add source level hits if let Some(anchors) = self.anchors.get(contract_id) { + let anchors = if is_deployed_code { &anchors.1 } else { &anchors.0 }; for anchor in anchors { - if let Some(hits) = hit_map.hits.get(&anchor.instruction) { + if let Some(&hits) = hit_map.hits.get(&anchor.instruction) { self.items .get_mut(&contract_id.version) .and_then(|items| items.get_mut(anchor.item_id)) @@ -153,20 +152,24 @@ impl CoverageReport { } } -/// A collection of [HitMap]s +/// A collection of [`HitMap`]s. #[derive(Clone, Debug, Default)] pub struct HitMaps(pub HashMap); impl HitMaps { - pub fn merge(mut self, other: HitMaps) -> Self { - for (code_hash, hit_map) in other.0.into_iter() { + pub fn merge(&mut self, other: Self) { + for (code_hash, hit_map) in other.0 { if let Some(HitMap { hits: extra_hits, .. }) = self.insert(code_hash, hit_map) { - for (pc, hits) in extra_hits.into_iter() { + for (pc, hits) in extra_hits { self.entry(code_hash) .and_modify(|map| *map.hits.entry(pc).or_default() += hits); } } } + } + + pub fn merged(mut self, other: Self) -> Self { + self.merge(other); self } } @@ -205,14 +208,14 @@ impl HitMap { } /// Merge another hitmap into this, assuming the bytecode is consistent - pub fn merge(&mut self, other: &HitMap) -> Result<(), eyre::Report> { + pub fn merge(&mut self, other: &Self) -> Result<(), eyre::Report> { for (pc, hits) in &other.hits { *self.hits.entry(*pc).or_default() += hits; } Ok(()) } - pub fn consistent_bytecode(&self, hm1: &HitMap, hm2: &HitMap) -> bool { + pub fn consistent_bytecode(&self, hm1: &Self, hm2: &Self) -> bool { // Consider the bytecodes consistent if they are the same out as far as the // recorded hits let len1 = hm1.hits.last_key_value(); @@ -232,7 +235,7 @@ impl HitMap { pub struct ContractId { pub version: Version, pub source_id: usize, - pub contract_name: String, + pub contract_name: Arc, } impl Display for ContractId { @@ -320,11 +323,11 @@ pub struct SourceLocation { /// The source ID. pub source_id: usize, /// The contract this source range is in. - pub contract_name: String, + pub contract_name: Arc, /// Start byte in the source code. - pub start: usize, + pub start: u32, /// Number of bytes in the source code. - pub length: Option, + pub length: Option, /// The line in the source code. pub line: usize, } diff --git a/crates/evm/evm/Cargo.toml b/crates/evm/evm/Cargo.toml index ccc420ce1..ab10c56b5 100644 --- a/crates/evm/evm/Cargo.toml +++ b/crates/evm/evm/Cargo.toml @@ -10,6 +10,9 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [dependencies] foundry-cheatcodes.workspace = true foundry-common.workspace = true @@ -30,8 +33,6 @@ alloy-primitives = { workspace = true, features = [ "rlp", ] } alloy-sol-types.workspace = true -alloy-rpc-types.workspace = true -hashbrown = { version = "0.14", features = [ "serde" ] } revm = { workspace = true, default-features = false, features = [ "std", "serde", @@ -40,16 +41,15 @@ revm = { workspace = true, default-features = false, features = [ "optional_block_gas_limit", "optional_no_base_fee", "arbitrary", + "c-kzg", ] } revm-inspectors.workspace = true -itertools.workspace = true - -eyre = "0.6" -hex.workspace = true -parking_lot = "0.12" +arrayvec.workspace = true +eyre.workspace = true +parking_lot.workspace = true proptest = "1" -thiserror = "1" -tracing = "0.1" -rayon = "1" -rand.workspace = true +thiserror.workspace = true +tracing.workspace = true +itertools.workspace = true +indicatif = "0.17" diff --git a/crates/evm/evm/src/executors/builder.rs b/crates/evm/evm/src/executors/builder.rs index d8d0f1513..356f2332b 100644 --- a/crates/evm/evm/src/executors/builder.rs +++ b/crates/evm/evm/src/executors/builder.rs @@ -1,19 +1,19 @@ use crate::{executors::Executor, inspectors::InspectorStackBuilder}; use alloy_primitives::U256; use foundry_evm_core::backend::Backend; -use revm::primitives::{Env, SpecId}; +use revm::primitives::{Env, EnvWithHandlerCfg, SpecId}; /// The builder that allows to configure an evm [`Executor`] which a stack of optional /// [`revm::Inspector`]s, such as [`Cheatcodes`]. /// /// By default, the [`Executor`] will be configured with an empty [`InspectorStack`]. /// -/// [`Cheatcodes`]: super::inspector::Cheatcodes -/// [`InspectorStack`]: super::inspector::InspectorStack -#[derive(Debug)] +/// [`Cheatcodes`]: super::Cheatcodes +/// [`InspectorStack`]: super::InspectorStack +#[derive(Clone, Debug)] #[must_use = "builders do nothing unless you call `build` on them"] pub struct ExecutorBuilder { - /// The configuration used to build an [InspectorStack]. + /// The configuration used to build an `InspectorStack`. stack: InspectorStackBuilder, /// The gas limit. gas_limit: Option, @@ -52,7 +52,7 @@ impl ExecutorBuilder { self } - /// Sets the EVM spec to use + /// Sets the EVM spec to use. #[inline] pub fn spec(mut self, spec: SpecId) -> Self { self.spec_id = spec; @@ -60,8 +60,6 @@ impl ExecutorBuilder { } /// Sets the executor gas limit. - /// - /// See [Executor::gas_limit] for more info on why you might want to set this. #[inline] pub fn gas_limit(mut self, gas_limit: U256) -> Self { self.gas_limit = Some(gas_limit); @@ -77,13 +75,17 @@ impl ExecutorBuilder { /// Builds the executor as configured. #[inline] - pub fn build(self, mut env: Env, db: Backend) -> Executor { + pub fn build(self, env: Env, db: Backend) -> Executor { let Self { mut stack, gas_limit, spec_id, use_zk } = self; - env.cfg.spec_id = spec_id; stack.block = Some(env.block.clone()); stack.gas_price = Some(env.tx.gas_price); let gas_limit = gas_limit.unwrap_or(env.block.gas_limit); - let mut exec = Executor::new(db, env, stack.build(), gas_limit); + let mut exec = Executor::new( + db, + EnvWithHandlerCfg::new_with_spec_id(Box::new(env), spec_id), + stack.build(), + gas_limit, + ); exec.use_zk = use_zk; exec } diff --git a/crates/evm/evm/src/executors/fuzz/mod.rs b/crates/evm/evm/src/executors/fuzz/mod.rs index 562a8151e..c55ebeb77 100644 --- a/crates/evm/evm/src/executors/fuzz/mod.rs +++ b/crates/evm/evm/src/executors/fuzz/mod.rs @@ -10,13 +10,11 @@ use foundry_evm_core::{ }; use foundry_evm_coverage::HitMaps; use foundry_evm_fuzz::{ - strategies::{ - build_initial_state, collect_state_from_call, fuzz_calldata, fuzz_calldata_from_state, - EvmFuzzState, - }, - BaseCounterExample, CounterExample, FuzzCase, FuzzError, FuzzTestResult, + strategies::{fuzz_calldata, fuzz_calldata_from_state, EvmFuzzState}, + BaseCounterExample, CounterExample, FuzzCase, FuzzError, FuzzFixtures, FuzzTestResult, }; use foundry_evm_traces::CallTraceArena; +use indicatif::ProgressBar; use proptest::test_runner::{TestCaseError, TestError, TestRunner}; use std::cell::RefCell; @@ -58,9 +56,11 @@ impl FuzzedExecutor { pub fn fuzz( &self, func: &Function, + fuzz_fixtures: &FuzzFixtures, address: Address, should_fail: bool, rd: &RevertDecoder, + progress: Option<&ProgressBar>, ) -> FuzzTestResult { // Stores the first Fuzzcase let first_case: RefCell> = RefCell::default(); @@ -71,33 +71,33 @@ impl FuzzedExecutor { // Stores the result and calldata of the last failed call, if any. let counterexample: RefCell<(Bytes, RawCallResult)> = RefCell::default(); - // Stores the last successful call trace - let traces: RefCell> = RefCell::default(); + // We want to collect at least one trace which will be displayed to user. + let max_traces_to_collect = std::cmp::max(1, self.config.gas_report_samples) as usize; + + // Stores up to `max_traces_to_collect` traces. + let traces: RefCell> = RefCell::default(); // Stores coverage information for all fuzz cases let coverage: RefCell> = RefCell::default(); let state = self.build_fuzz_state(); - let mut weights = vec![]; let dictionary_weight = self.config.dictionary.dictionary_weight.min(100); - if self.config.dictionary.dictionary_weight < 100 { - weights.push(( - 100 - dictionary_weight, - fuzz_calldata(func.clone(), state.read().no_zksync_reserved_addresses()), - )); - } - if dictionary_weight > 0 { - weights.push(( - self.config.dictionary.dictionary_weight, - fuzz_calldata_from_state(func.clone(), state.clone()), - )); - } + let no_zksync_reserved_addresses = state.dictionary_read().no_zksync_reserved_addresses(); + + let strat = proptest::prop_oneof![ + 100 - dictionary_weight => fuzz_calldata(func.clone(), fuzz_fixtures, no_zksync_reserved_addresses), + dictionary_weight => fuzz_calldata_from_state(func.clone(), &state), + ]; - let strat = proptest::strategy::Union::new_weighted(weights); debug!(func=?func.name, should_fail, "fuzzing"); let run_result = self.runner.clone().run(&strat, |calldata| { - let fuzz_res = self.single_fuzz(&state, address, should_fail, calldata)?; + let fuzz_res = self.single_fuzz(address, should_fail, calldata)?; + + // If running with progress then increment current run. + if let Some(progress) = progress { + progress.inc(1); + }; match fuzz_res { FuzzOutcome::Case(case) => { @@ -106,40 +106,43 @@ impl FuzzedExecutor { if first_case.is_none() { first_case.replace(case.case); } + if let Some(call_traces) = case.traces { + if traces.borrow().len() == max_traces_to_collect { + traces.borrow_mut().pop(); + } + traces.borrow_mut().push(call_traces); + } - traces.replace(case.traces); - - if let Some(prev) = coverage.take() { - // Safety: If `Option::or` evaluates to `Some`, then `call.coverage` must - // necessarily also be `Some` - coverage.replace(Some(prev.merge(case.coverage.unwrap()))); - } else { - coverage.replace(case.coverage); + match &mut *coverage.borrow_mut() { + Some(prev) => prev.merge(case.coverage.unwrap()), + opt => *opt = case.coverage, } Ok(()) } FuzzOutcome::CounterExample(CounterExampleOutcome { - exit_reason, - counterexample: _counterexample, + exit_reason: status, + counterexample: outcome, .. }) => { - let status = exit_reason; // We cannot use the calldata returned by the test runner in `TestError::Fail`, // since that input represents the last run case, which may not correspond with // our failure - when a fuzz case fails, proptest will try // to run at least one more case to find a minimal failure // case. - let call_res = _counterexample.1.result.clone(); - *counterexample.borrow_mut() = _counterexample; - // HACK: we have to use an empty string here to denote `None` - let reason = rd.maybe_decode(&call_res, Some(status)); + let reason = rd.maybe_decode(&outcome.1.result, Some(status)); + *counterexample.borrow_mut() = outcome; + // HACK: we have to use an empty string here to denote `None`. Err(TestCaseError::fail(reason.unwrap_or_default())) } } }); let (calldata, call) = counterexample.into_inner(); + + let mut traces = traces.into_inner(); + let last_run_traces = if run_result.is_ok() { traces.pop() } else { call.traces.clone() }; + let mut result = FuzzTestResult { first_case: first_case.take().unwrap_or_default(), gas_by_case: gas_by_case.take(), @@ -149,7 +152,8 @@ impl FuzzedExecutor { decoded_logs: decode_console_logs(&call.logs), logs: call.logs, labeled_addresses: call.labels, - traces: if run_result.is_ok() { traces.into_inner() } else { call.traces.clone() }, + traces: last_run_traces, + gas_report_traces: traces, coverage: coverage.into_inner(), }; @@ -174,19 +178,16 @@ impl FuzzedExecutor { } else { vec![] }; - result.counterexample = Some(CounterExample::Single(BaseCounterExample { - sender: None, - addr: None, - signature: None, - contract_name: None, - traces: call.traces, - calldata, - args, - })); + + result.counterexample = Some(CounterExample::Single( + BaseCounterExample::from_fuzz_call(calldata, args, call.traces), + )); } _ => {} } + state.log_stats(); + result } @@ -194,27 +195,14 @@ impl FuzzedExecutor { /// or a `CounterExampleOutcome` pub fn single_fuzz( &self, - state: &EvmFuzzState, address: Address, should_fail: bool, calldata: alloy_primitives::Bytes, ) -> Result { - let call = self + let mut call = self .executor .call_raw(self.sender, address, calldata.clone(), U256::ZERO) .map_err(|_| TestCaseError::fail(FuzzError::FailedContractCall))?; - let state_changeset = call - .state_changeset - .as_ref() - .ok_or_else(|| TestCaseError::fail(FuzzError::EmptyChangeset))?; - - // Build fuzzer state - collect_state_from_call( - &call.logs, - state_changeset, - state.clone(), - &self.config.dictionary, - ); // When the `assume` cheatcode is called it returns a special string if call.result.as_ref() == MAGIC_ASSUME { @@ -226,9 +214,7 @@ impl FuzzedExecutor { .as_ref() .map_or_else(Default::default, |cheats| cheats.breakpoints.clone()); - let success = - self.executor.is_raw_call_success(address, state_changeset.clone(), &call, should_fail); - + let success = self.executor.is_raw_call_mut_success(address, &mut call, should_fail); if success { Ok(FuzzOutcome::Case(CaseOutcome { case: FuzzCase { calldata, gas: call.gas_used, stipend: call.stipend }, @@ -250,15 +236,15 @@ impl FuzzedExecutor { /// Stores fuzz state for use with [fuzz_calldata_from_state] pub fn build_fuzz_state(&self) -> EvmFuzzState { if let Some(fork_db) = self.executor.backend.active_fork_db() { - build_initial_state( + EvmFuzzState::new( fork_db, - &self.config.dictionary, + self.config.dictionary, self.config.no_zksync_reserved_addresses, ) } else { - build_initial_state( + EvmFuzzState::new( self.executor.backend.mem_db(), - &self.config.dictionary, + self.config.dictionary, self.config.no_zksync_reserved_addresses, ) } diff --git a/crates/evm/evm/src/executors/invariant/error.rs b/crates/evm/evm/src/executors/invariant/error.rs index 0d619740e..7c47ac0a4 100644 --- a/crates/evm/evm/src/executors/invariant/error.rs +++ b/crates/evm/evm/src/executors/invariant/error.rs @@ -1,19 +1,10 @@ use super::{BasicTxDetails, InvariantContract}; -use crate::executors::{Executor, RawCallResult}; -use alloy_json_abi::Function; -use alloy_primitives::{Address, Bytes, Log}; -use eyre::Result; -use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact}; -use foundry_evm_core::{constants::CALLER, decode::RevertDecoder}; -use foundry_evm_fuzz::{BaseCounterExample, CounterExample, FuzzedCases, Reason}; -use foundry_evm_traces::{load_contracts, CallTraceArena, TraceKind, Traces}; -use itertools::Itertools; -use parking_lot::RwLock; +use crate::executors::RawCallResult; +use alloy_primitives::{Address, Bytes}; +use foundry_config::InvariantConfig; +use foundry_evm_core::decode::RevertDecoder; +use foundry_evm_fuzz::{invariant::FuzzRunIdentifiedContracts, Reason}; use proptest::test_runner::TestError; -use rand::{seq, thread_rng, Rng}; -use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; -use revm::primitives::U256; -use std::sync::Arc; /// Stores information about failures and reverts of the invariant tests. #[derive(Clone, Default)] @@ -38,24 +29,28 @@ impl InvariantFailures { } } -/// The outcome of an invariant fuzz test -#[derive(Debug)] -pub struct InvariantFuzzTestResult { - pub error: Option, - /// Every successful fuzz test case - pub cases: Vec, - /// Number of reverted fuzz calls - pub reverts: usize, +#[derive(Clone, Debug)] +pub enum InvariantFuzzError { + Revert(FailedInvariantCaseData), + BrokenInvariant(FailedInvariantCaseData), + MaxAssumeRejects(u32), +} - /// The entire inputs of the last run of the invariant campaign, used for - /// replaying the run for collecting traces. - pub last_run_inputs: Vec, +impl InvariantFuzzError { + pub fn revert_reason(&self) -> Option { + match self { + Self::BrokenInvariant(case_data) | Self::Revert(case_data) => { + (!case_data.revert_reason.is_empty()).then(|| case_data.revert_reason.clone()) + } + Self::MaxAssumeRejects(allowed) => Some(format!( + "The `vm.assume` cheatcode rejected too many inputs ({allowed} allowed)" + )), + } + } } #[derive(Clone, Debug)] -pub struct InvariantFuzzError { - pub logs: Vec, - pub traces: Option, +pub struct FailedInvariantCaseData { /// The proptest error occurred as a result of a test case. pub test_error: TestError>, /// The return reason of the offending call. @@ -64,367 +59,46 @@ pub struct InvariantFuzzError { pub revert_reason: String, /// Address of the invariant asserter. pub addr: Address, - /// Function data for invariant check. - pub func: Option, + /// Function calldata for invariant check. + pub calldata: Bytes, /// Inner fuzzing Sequence coming from overriding calls. pub inner_sequence: Vec>, - /// Shrink the failed test case to the smallest sequence. - pub shrink: bool, /// Shrink run limit - pub shrink_run_limit: usize, + pub shrink_run_limit: u32, + /// Fail on revert, used to check sequence when shrinking. + pub fail_on_revert: bool, } -impl InvariantFuzzError { +impl FailedInvariantCaseData { pub fn new( invariant_contract: &InvariantContract<'_>, - error_func: Option<&Function>, + invariant_config: &InvariantConfig, + targeted_contracts: &FuzzRunIdentifiedContracts, calldata: &[BasicTxDetails], call_result: RawCallResult, inner_sequence: &[Option], - shrink: bool, - shrink_run_limit: usize, ) -> Self { - let (func, origin) = if let Some(f) = error_func { - (Some(f.selector().to_vec().into()), f.name.as_str()) - } else { - (None, "Revert") - }; + // Collect abis of fuzzed and invariant contracts to decode custom error. let revert_reason = RevertDecoder::new() + .with_abis(targeted_contracts.targets.lock().iter().map(|(_, c)| &c.abi)) .with_abi(invariant_contract.abi) .decode(call_result.result.as_ref(), Some(call_result.exit_reason)); - InvariantFuzzError { - logs: call_result.logs, - traces: call_result.traces, - test_error: proptest::test_runner::TestError::Fail( + let func = invariant_contract.invariant_function; + debug_assert!(func.inputs.is_empty()); + let origin = func.name.as_str(); + Self { + test_error: TestError::Fail( format!("{origin}, reason: {revert_reason}").into(), calldata.to_vec(), ), return_reason: "".into(), revert_reason, addr: invariant_contract.address, - func, + calldata: func.selector().to_vec().into(), inner_sequence: inner_sequence.to_vec(), - shrink, - shrink_run_limit, - } - } - - /// Replays the error case and collects all necessary traces. - pub fn replay( - &self, - mut executor: Executor, - known_contracts: Option<&ContractsByArtifact>, - mut ided_contracts: ContractsByAddress, - logs: &mut Vec, - traces: &mut Traces, - ) -> Result> { - let mut counterexample_sequence = vec![]; - let mut calls = match self.test_error { - // Don't use at the moment. - TestError::Abort(_) => return Ok(None), - TestError::Fail(_, ref calls) => calls.clone(), - }; - - if self.shrink { - calls = self.try_shrinking(&calls, &executor).into_iter().cloned().collect(); - } else { - trace!(target: "forge::test", "Shrinking disabled."); - } - - // We want traces for a failed case. - executor.set_tracing(true); - - set_up_inner_replay(&mut executor, &self.inner_sequence); - - // Replay each call from the sequence until we break the invariant. - for (sender, (addr, bytes)) in calls.iter() { - let call_result = executor - .call_raw_committing(*sender, *addr, bytes.clone(), U256::ZERO) - .expect("bad call to evm"); - - logs.extend(call_result.logs); - traces.push((TraceKind::Execution, call_result.traces.clone().unwrap())); - - // Identify newly generated contracts, if they exist. - ided_contracts.extend(load_contracts( - vec![(TraceKind::Execution, call_result.traces.clone().unwrap())], - known_contracts, - )); - - counterexample_sequence.push(BaseCounterExample::create( - *sender, - *addr, - bytes, - &ided_contracts, - call_result.traces, - )); - - // Checks the invariant. - if let Some(func) = &self.func { - let error_call_result = executor - .call_raw(CALLER, self.addr, func.clone(), U256::ZERO) - .expect("bad call to evm"); - - traces.push((TraceKind::Execution, error_call_result.traces.clone().unwrap())); - - logs.extend(error_call_result.logs); - if error_call_result.reverted { - break - } - } - } - - Ok((!counterexample_sequence.is_empty()) - .then_some(CounterExample::Sequence(counterexample_sequence))) - } - - /// Checks that a subsequence of the provided calls fails the provided invariant test - /// and updates an Arc Mutex of the indices of the shortest sequence - fn set_fails_successfully( - &self, - mut executor: Executor, - calls: &[BasicTxDetails], - use_calls: &[usize], - curr_seq: Arc>>, - ) { - if curr_seq.read().len() == 1 { - // if current sequence is already the smallest possible, just return - return; - } - - let mut new_sequence = Vec::with_capacity(calls.len()); - for index in 0..calls.len() { - if !use_calls.contains(&index) { - continue - } - - new_sequence.push(index); - - // If the new sequence is already longer than the known best, skip execution - if new_sequence.len() >= curr_seq.read().len() { - return - } - } - - for (seq_idx, call_index) in new_sequence.iter().enumerate() { - let (sender, (addr, bytes)) = &calls[*call_index]; - - executor - .call_raw_committing(*sender, *addr, bytes.clone(), U256::ZERO) - .expect("bad call to evm"); - - // Checks the invariant. If we exit before the last call, all the better. - if let Some(func) = &self.func { - let error_call_result = executor - .call_raw(CALLER, self.addr, func.clone(), U256::ZERO) - .expect("bad call to evm"); - if error_call_result.reverted { - let mut locked = curr_seq.write(); - if new_sequence[..=seq_idx].len() < locked.len() { - // update the curr_sequence if the new sequence is lower than - *locked = new_sequence[..=seq_idx].to_vec(); - } - } - } - } - } - - /// Tries to shrink the failure case to its smallest sequence of calls. - /// - /// If the number of calls is small enough, we can guarantee maximal shrinkage - fn try_shrinking<'a>( - &self, - calls: &'a [BasicTxDetails], - executor: &Executor, - ) -> Vec<&'a BasicTxDetails> { - trace!(target: "forge::test", "Shrinking."); - - // Special case test: the invariant is *unsatisfiable* - it took 0 calls to - // break the invariant -- consider emitting a warning. - if let Some(func) = &self.func { - let error_call_result = executor - .call_raw(CALLER, self.addr, func.clone(), U256::ZERO) - .expect("bad call to evm"); - if error_call_result.reverted { - return vec![]; - } - } - - let shrunk_call_indices = self.try_shrinking_recurse(calls, executor, 0, 0); - - // we recreate the call sequence in the same order as they reproduce the failure - // otherwise we could end up with inverted sequence - // e.g. in a sequence of: - // 1. Alice calls acceptOwnership and reverts - // 2. Bob calls transferOwnership to Alice - // 3. Alice calls acceptOwnership and test fails - // we shrink to indices of [2, 1] and we recreate call sequence in same order - let mut new_calls_sequence = Vec::with_capacity(shrunk_call_indices.len()); - shrunk_call_indices.iter().for_each(|call_index| { - new_calls_sequence.push(calls.get(*call_index).unwrap()); - }); - new_calls_sequence - } - - /// We try to construct a [powerset](https://en.wikipedia.org/wiki/Power_set) of the sequence if - /// the configuration allows for it and the length of the sequence is small enough. If we - /// do construct the powerset, we run all of the sequences in parallel, finding the smallest - /// one. If we have ran the powerset, we are guaranteed to find the smallest sequence for a - /// given set of calls (however, not guaranteed *global* smallest if a random sequence was - /// used at any point during recursion). - /// - /// If we were unable to construct a powerset, we construct a random sample over the sequence - /// and run these sequences in parallel instead. - /// - /// After running either the powerset or the random sequences, we check if we successfully - /// shrunk the call sequence. - fn try_shrinking_recurse( - &self, - calls: &[BasicTxDetails], - executor: &Executor, - runs: usize, - retries: usize, - ) -> Vec { - // Construct a ArcRwLock vector of indices of `calls` - let shrunk_call_indices = Arc::new(RwLock::new((0..calls.len()).collect())); - let shrink_limit = self.shrink_run_limit - runs; - - let upper_bound = calls.len().saturating_sub(1); - // We construct either a full powerset (this guarantees we maximally shrunk for the given - // calls) or a random subset - let (set_of_indices, is_powerset): (Vec<_>, bool) = if calls.len() <= 64 && - 2_usize.pow(calls.len() as u32) <= shrink_limit - { - // We add the last tx always because thats ultimately what broke the invariant - let powerset = (0..upper_bound) - .powerset() - .map(|mut subset| { - subset.push(upper_bound); - subset - }) - .collect(); - (powerset, true) - } else { - // construct a random set of subsequences - let mut rng = thread_rng(); - ( - (0..shrink_limit / 3) - .map(|_| { - // Select between 1 and calls.len() - 2 number of indices - let amt: usize = rng.gen_range(1..upper_bound); - // Construct a random sequence of indices, up to calls.len() - 1 (sample is - // exclusive range and we dont include the last tx - // because its always included), and amt number of indices - let mut seq = seq::index::sample(&mut rng, upper_bound, amt).into_vec(); - // Sort the indices because seq::index::sample is unordered - seq.sort(); - // We add the last tx always because thats what ultimately broke the - // invariant - seq.push(upper_bound); - seq - }) - .collect(), - false, - ) - }; - - let new_runs = set_of_indices.len(); - - // just try all of them in parallel - set_of_indices.par_iter().for_each(|use_calls| { - self.set_fails_successfully( - executor.clone(), - calls, - use_calls, - Arc::clone(&shrunk_call_indices), - ); - }); - - // SAFETY: there are no more live references to shrunk_call_indices as the parallel - // execution is finished, so it is fine to get the inner value via unwrap & - // into_inner - let shrunk_call_indices = - Arc::>>::try_unwrap(shrunk_call_indices).unwrap().into_inner(); - - if is_powerset { - // a powerset is guaranteed to be smallest local subset, so we return early - return shrunk_call_indices - } - - let computation_budget_not_hit = new_runs + runs < self.shrink_run_limit; - // If the new shrunk_call_indices is less than the input calls length, - // we found a subsequence that is shorter. So we can measure if we made progress by - // comparing them - let made_progress = shrunk_call_indices.len() < calls.len(); - // We limit the number of times we can iterate without making progress - let has_remaining_retries = retries <= 3; - - match (computation_budget_not_hit, made_progress) { - (true, false) if has_remaining_retries => { - // we havent hit the computation budget and we have retries remaining - // - // use the same call set but increase retries which should select a different random - // subset we dont need to do the mapping stuff like above because we dont - // take a subset of the input - self.try_shrinking_recurse(calls, executor, runs + new_runs, retries + 1) - } - (true, true) => { - // We construct a *new* subset of calls using the `shrunk_call_indices` of the - // passed in calls i.e. if shrunk_call_indices == [1, 3], and calls - // is: [call0, call1, call2, call3] then new_calls == [call1, call3] - let new_calls: Vec<_> = calls - .iter() - .enumerate() - .filter_map(|(i, call)| { - if shrunk_call_indices.contains(&i) { - Some(call.clone()) - } else { - None - } - }) - .collect(); - - // We rerun this algorithm as if the new smaller subset above were the original - // calls. i.e. if [call0, call1, call2, call3] got reduced to - // [call1, call3] (in the above line) and we still have progress - // to make, we recall this function with [call1, call3]. Then after this call say it - // returns [1]. This means `call3` is all that is required to break - // the invariant. - let new_calls_idxs = - self.try_shrinking_recurse(&new_calls, executor, runs + new_runs, 0); - - // Notably, the indices returned above are relative to `new_calls`, *not* the - // originally passed in `calls`. So we map back by filtering - // `new_calls` by index if the index was returned above, and finding the position - // of the `new_call` in the passed in `call` - new_calls - .iter() - .enumerate() - .filter_map(|(idx, new_call)| { - if !new_calls_idxs.contains(&idx) { - None - } else { - calls.iter().position(|r| r == new_call) - } - }) - .collect() - } - _ => { - // The computation budget has been hit or no retries remaining, stop trying to make - // progress - shrunk_call_indices - } - } - } -} - -/// Sets up the calls generated by the internal fuzzer, if they exist. -fn set_up_inner_replay(executor: &mut Executor, inner_sequence: &[Option]) { - if let Some(fuzzer) = &mut executor.inspector.fuzzer { - if let Some(call_generator) = &mut fuzzer.call_generator { - call_generator.last_sequence = Arc::new(RwLock::new(inner_sequence.to_owned())); - call_generator.set_replay(true); + shrink_run_limit: invariant_config.shrink_run_limit, + fail_on_revert: invariant_config.fail_on_revert, } } } diff --git a/crates/evm/evm/src/executors/invariant/funcs.rs b/crates/evm/evm/src/executors/invariant/funcs.rs deleted file mode 100644 index 810abb259..000000000 --- a/crates/evm/evm/src/executors/invariant/funcs.rs +++ /dev/null @@ -1,130 +0,0 @@ -use super::{InvariantFailures, InvariantFuzzError}; -use crate::executors::{Executor, RawCallResult}; -use alloy_dyn_abi::JsonAbiExt; -use alloy_json_abi::Function; -use alloy_primitives::Log; -use foundry_common::{ContractsByAddress, ContractsByArtifact}; -use foundry_evm_core::constants::CALLER; -use foundry_evm_coverage::HitMaps; -use foundry_evm_fuzz::invariant::{BasicTxDetails, InvariantContract}; -use foundry_evm_traces::{load_contracts, TraceKind, Traces}; -use revm::primitives::U256; - -/// Given the executor state, asserts that no invariant has been broken. Otherwise, it fills the -/// external `invariant_failures.failed_invariant` map and returns a generic error. -/// Either returns the call result if successful, or nothing if there was an error. -pub fn assert_invariants( - invariant_contract: &InvariantContract<'_>, - executor: &Executor, - calldata: &[BasicTxDetails], - invariant_failures: &mut InvariantFailures, - shrink_sequence: bool, - shrink_run_limit: usize, -) -> Option { - let mut inner_sequence = vec![]; - - if let Some(fuzzer) = &executor.inspector.fuzzer { - if let Some(call_generator) = &fuzzer.call_generator { - inner_sequence.extend(call_generator.last_sequence.read().iter().cloned()); - } - } - - let func = invariant_contract.invariant_function; - let mut call_result = executor - .call_raw( - CALLER, - invariant_contract.address, - func.abi_encode_input(&[]).expect("invariant should have no inputs").into(), - U256::ZERO, - ) - .expect("EVM error"); - - // This will panic and get caught by the executor - let is_err = call_result.reverted || - !executor.is_raw_call_success( - invariant_contract.address, - call_result.state_changeset.take().expect("we should have a state changeset"), - &call_result, - false, - ); - if is_err { - // We only care about invariants which we haven't broken yet. - if invariant_failures.error.is_none() { - invariant_failures.error = Some(InvariantFuzzError::new( - invariant_contract, - Some(func), - calldata, - call_result, - &inner_sequence, - shrink_sequence, - shrink_run_limit, - )); - return None - } - } - - Some(call_result) -} - -/// Replays the provided invariant run for collecting the logs and traces from all depths. -#[allow(clippy::too_many_arguments)] -pub fn replay_run( - invariant_contract: &InvariantContract<'_>, - mut executor: Executor, - known_contracts: Option<&ContractsByArtifact>, - mut ided_contracts: ContractsByAddress, - logs: &mut Vec, - traces: &mut Traces, - coverage: &mut Option, - func: Function, - inputs: Vec, -) { - // We want traces for a failed case. - executor.set_tracing(true); - - // set_up_inner_replay(&mut executor, &inputs); - - // Replay each call from the sequence until we break the invariant. - for (sender, (addr, bytes)) in inputs.iter() { - let call_result = executor - .call_raw_committing(*sender, *addr, bytes.clone(), U256::ZERO) - .expect("bad call to evm"); - - logs.extend(call_result.logs); - traces.push((TraceKind::Execution, call_result.traces.clone().unwrap())); - - let old_coverage = std::mem::take(coverage); - match (old_coverage, call_result.coverage) { - (Some(old_coverage), Some(call_coverage)) => { - *coverage = Some(old_coverage.merge(call_coverage)); - } - (None, Some(call_coverage)) => { - *coverage = Some(call_coverage); - } - (Some(old_coverage), None) => { - *coverage = Some(old_coverage); - } - (None, None) => {} - } - - // Identify newly generated contracts, if they exist. - ided_contracts.extend(load_contracts( - vec![(TraceKind::Execution, call_result.traces.clone().unwrap())], - known_contracts, - )); - - // Checks the invariant. - let error_call_result = executor - .call_raw( - CALLER, - invariant_contract.address, - func.abi_encode_input(&[]).expect("invariant should have no inputs").into(), - U256::ZERO, - ) - .expect("bad call to evm"); - - traces.push((TraceKind::Execution, error_call_result.traces.clone().unwrap())); - - logs.extend(error_call_result.logs); - } -} diff --git a/crates/evm/evm/src/executors/invariant/mod.rs b/crates/evm/evm/src/executors/invariant/mod.rs index 0a6d7384b..8811474a2 100644 --- a/crates/evm/evm/src/executors/invariant/mod.rs +++ b/crates/evm/evm/src/executors/invariant/mod.rs @@ -2,56 +2,98 @@ use crate::{ executors::{Executor, RawCallResult}, inspectors::Fuzzer, }; -use alloy_dyn_abi::DynSolValue; -use alloy_json_abi::JsonAbi; -use alloy_primitives::{Address, FixedBytes, U256}; +use alloy_primitives::{Address, Bytes, FixedBytes, Selector, U256}; +use alloy_sol_types::{sol, SolCall}; use eyre::{eyre, ContextCompat, Result}; use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact}; -use foundry_config::{FuzzDictionaryConfig, InvariantConfig}; -use foundry_evm_core::{ - constants::{CALLER, CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS}, - utils::{get_function, StateChangeset}, +use foundry_config::InvariantConfig; +use foundry_evm_core::constants::{ + CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, HARDHAT_CONSOLE_ADDRESS, MAGIC_ASSUME, }; use foundry_evm_fuzz::{ invariant::{ ArtifactFilters, BasicTxDetails, FuzzRunIdentifiedContracts, InvariantContract, - RandomCallGenerator, SenderFilters, TargetedContracts, + RandomCallGenerator, SenderFilters, TargetedContract, TargetedContracts, }, - strategies::{ - build_initial_state, collect_created_contracts, collect_state_from_call, invariant_strat, - override_call_strat, EvmFuzzState, - }, - FuzzCase, FuzzedCases, + strategies::{invariant_strat, override_call_strat, EvmFuzzState}, + FuzzCase, FuzzFixtures, FuzzedCases, }; -use parking_lot::{Mutex, RwLock}; +use foundry_evm_traces::CallTraceArena; +use indicatif::ProgressBar; +use parking_lot::RwLock; use proptest::{ - strategy::{BoxedStrategy, Strategy, ValueTree}, + strategy::{Strategy, ValueTree}, test_runner::{TestCaseError, TestRunner}, }; -use revm::{primitives::HashMap, DatabaseCommit}; -use std::{cell::RefCell, collections::BTreeMap, sync::Arc}; +use result::{assert_after_invariant, assert_invariants, can_continue}; +use revm::primitives::HashMap; +use shrink::shrink_sequence; +use std::{cell::RefCell, collections::btree_map::Entry, sync::Arc}; mod error; -pub use error::{InvariantFailures, InvariantFuzzError, InvariantFuzzTestResult}; +pub use error::{InvariantFailures, InvariantFuzzError}; -mod funcs; -pub use funcs::{assert_invariants, replay_run}; +mod replay; +pub use replay::{replay_error, replay_run}; -/// Alias for (Dictionary for fuzzing, initial contracts to fuzz and an InvariantStrategy). -type InvariantPreparation = - (EvmFuzzState, FuzzRunIdentifiedContracts, BoxedStrategy>); +mod result; +pub use result::InvariantFuzzTestResult; -/// Enriched results of an invariant run check. -/// -/// Contains the success condition and call results of the last run -struct RichInvariantResults { - success: bool, - call_result: Option, -} +mod shrink; +use crate::executors::EvmError; +pub use shrink::check_sequence; + +sol! { + interface IInvariantTest { + #[derive(Default)] + struct FuzzSelector { + address addr; + bytes4[] selectors; + } + + #[derive(Default)] + struct FuzzArtifactSelector { + string artifact; + bytes4[] selectors; + } + + #[derive(Default)] + struct FuzzInterface { + address addr; + string[] artifacts; + } + + function afterInvariant() external; + + #[derive(Default)] + function excludeArtifacts() public view returns (string[] memory excludedArtifacts); + + #[derive(Default)] + function excludeContracts() public view returns (address[] memory excludedContracts); + + #[derive(Default)] + function excludeSelectors() public view returns (FuzzSelector[] memory excludedSelectors); + + #[derive(Default)] + function excludeSenders() public view returns (address[] memory excludedSenders); -impl RichInvariantResults { - fn new(success: bool, call_result: Option) -> Self { - Self { success, call_result } + #[derive(Default)] + function targetArtifacts() public view returns (string[] memory targetedArtifacts); + + #[derive(Default)] + function targetArtifactSelectors() public view returns (FuzzArtifactSelector[] memory targetedArtifactSelectors); + + #[derive(Default)] + function targetContracts() public view returns (address[] memory targetedContracts); + + #[derive(Default)] + function targetSelectors() public view returns (FuzzSelector[] memory targetedSelectors); + + #[derive(Default)] + function targetSenders() public view returns (address[] memory targetedSenders); + + #[derive(Default)] + function targetInterfaces() public view returns (FuzzInterface[] memory targetedInterfaces); } } @@ -98,13 +140,16 @@ impl<'a> InvariantExecutor<'a> { pub fn invariant_fuzz( &mut self, invariant_contract: InvariantContract<'_>, + fuzz_fixtures: &FuzzFixtures, + progress: Option<&ProgressBar>, ) -> Result { // Throw an error to abort test run if the invariant function accepts input params if !invariant_contract.invariant_function.inputs.is_empty() { return Err(eyre!("Invariant test function should have no inputs")) } - let (fuzz_state, targeted_contracts, strat) = self.prepare_fuzzing(&invariant_contract)?; + let (fuzz_state, targeted_contracts, strat) = + self.prepare_fuzzing(&invariant_contract, fuzz_fixtures)?; // Stores the consumed gas and calldata of every successful fuzz call. let fuzz_cases: RefCell> = RefCell::new(Default::default()); @@ -113,7 +158,10 @@ impl<'a> InvariantExecutor<'a> { let failures = RefCell::new(InvariantFailures::new()); // Stores the calldata in the last run. - let last_run_calldata: RefCell> = RefCell::new(vec![]); + let last_run_inputs: RefCell> = RefCell::new(vec![]); + + // Stores additional traces for gas report. + let gas_report_traces: RefCell>> = RefCell::default(); // Let's make sure the invariant is sound before actually starting the run: // We'll assert the invariant in its initial state, and if it fails, we'll @@ -121,12 +169,12 @@ impl<'a> InvariantExecutor<'a> { // This does not count as a fuzz run. It will just register the revert. let last_call_results = RefCell::new(assert_invariants( &invariant_contract, + &self.config, + &targeted_contracts, &self.executor, &[], &mut failures.borrow_mut(), - self.config.shrink_sequence, - self.config.shrink_run_limit, - )); + )?); if last_call_results.borrow().is_none() { fuzz_cases.borrow_mut().push(FuzzedCases::new(vec![])); @@ -137,7 +185,9 @@ impl<'a> InvariantExecutor<'a> { // during the run. We need another proptest runner to query for random // values. let branch_runner = RefCell::new(self.runner.clone()); - let _ = self.runner.run(&strat, |mut inputs| { + let _ = self.runner.run(&strat, |first_input| { + let mut inputs = vec![first_input]; + // We stop the run immediately if we have reverted, and `fail_on_revert` is set. if self.config.fail_on_revert && failures.borrow().reverts > 0 { return Err(TestCaseError::fail("Revert occurred.")) @@ -152,73 +202,99 @@ impl<'a> InvariantExecutor<'a> { // Created contracts during a run. let mut created_contracts = vec![]; - for current_run in 0..self.config.depth { - let (sender, (address, calldata)) = inputs.last().expect("no input generated"); + // Traces of each call of the sequence. + let mut run_traces = Vec::new(); - // Executes the call from the randomly generated sequence. - let call_result = executor - .call_raw(*sender, *address, calldata.clone(), U256::ZERO) - .expect("could not make raw evm call"); - - // Collect data for fuzzing from the state changeset. - let mut state_changeset = - call_result.state_changeset.to_owned().expect("no changesets"); + let mut current_run = 0; + let mut assume_rejects_counter = 0; - collect_data( - &mut state_changeset, - sender, - &call_result, - fuzz_state.clone(), - &self.config.dictionary, - ); + while current_run < self.config.depth { + let tx = inputs.last().ok_or_else(|| { + TestCaseError::fail("No input generated to call fuzzed target.") + })?; - if let Err(error) = collect_created_contracts( - &state_changeset, - self.project_contracts, - self.setup_contracts, - &self.artifact_filters, - targeted_contracts.clone(), - &mut created_contracts, - ) { - warn!(target: "forge::test", "{error}"); - } + // Execute call from the randomly generated sequence and commit state changes. + let call_result = executor + .transact_raw( + tx.sender, + tx.call_details.target, + tx.call_details.calldata.clone(), + U256::ZERO, + ) + .map_err(|e| { + TestCaseError::fail(format!("Could not make raw evm call: {e}")) + })?; + + if call_result.result.as_ref() == MAGIC_ASSUME { + inputs.pop(); + assume_rejects_counter += 1; + if assume_rejects_counter > self.config.max_assume_rejects { + failures.borrow_mut().error = Some(InvariantFuzzError::MaxAssumeRejects( + self.config.max_assume_rejects, + )); + return Err(TestCaseError::fail("Max number of vm.assume rejects reached.")) + } + } else { + // Collect data for fuzzing from the state changeset. + let mut state_changeset = call_result.state_changeset.clone().unwrap(); + + if !call_result.reverted { + collect_data( + &mut state_changeset, + &targeted_contracts, + tx, + &call_result, + &fuzz_state, + self.config.depth, + ); + } - // Commit changes to the database. - executor.backend.commit(state_changeset.clone()); + // Collect created contracts and add to fuzz targets only if targeted contracts + // are updatable. + if let Err(error) = &targeted_contracts.collect_created_contracts( + &state_changeset, + self.project_contracts, + self.setup_contracts, + &self.artifact_filters, + &mut created_contracts, + ) { + warn!(target: "forge::test", "{error}"); + } - fuzz_runs.push(FuzzCase { - calldata: calldata.clone(), - gas: call_result.gas_used, - stipend: call_result.stipend, - }); + fuzz_runs.push(FuzzCase { + calldata: tx.call_details.calldata.clone(), + gas: call_result.gas_used, + stipend: call_result.stipend, + }); - let RichInvariantResults { success: can_continue, call_result: call_results } = - can_continue( + let result = can_continue( &invariant_contract, + &self.config, call_result, &executor, &inputs, &mut failures.borrow_mut(), &targeted_contracts, - state_changeset, - self.config.fail_on_revert, - self.config.shrink_sequence, - self.config.shrink_run_limit, - ); - - if !can_continue || current_run == self.config.depth - 1 { - *last_run_calldata.borrow_mut() = inputs.clone(); - } + &state_changeset, + &mut run_traces, + ) + .map_err(|e| TestCaseError::fail(e.to_string()))?; - if !can_continue { - break - } + if !result.can_continue || current_run == self.config.depth - 1 { + last_run_inputs.borrow_mut().clone_from(&inputs); + } - *last_call_results.borrow_mut() = call_results; + if !result.can_continue { + break + } + + *last_call_results.borrow_mut() = result.call_result; + current_run += 1; + } // Generates the next call from the run using the recently updated // dictionary. - inputs.extend( + inputs.push( strat .new_tree(&mut branch_runner.borrow_mut()) .map_err(|_| TestCaseError::Fail("Could not generate case".into()))? @@ -226,20 +302,40 @@ impl<'a> InvariantExecutor<'a> { ); } - // We clear all the targeted contracts created during this run. - if !created_contracts.is_empty() { - let mut writable_targeted = targeted_contracts.lock(); - for addr in created_contracts.iter() { - writable_targeted.remove(addr); - } + // Call `afterInvariant` only if it is declared and test didn't fail already. + if invariant_contract.call_after_invariant && failures.borrow().error.is_none() { + assert_after_invariant( + &invariant_contract, + &self.config, + &targeted_contracts, + &mut executor, + &mut failures.borrow_mut(), + &inputs, + ) + .map_err(|_| TestCaseError::Fail("Failed to call afterInvariant".into()))?; } + // We clear all the targeted contracts created during this run. + let _ = &targeted_contracts.clear_created_contracts(created_contracts); + + if gas_report_traces.borrow().len() < self.config.gas_report_samples as usize { + gas_report_traces.borrow_mut().push(run_traces); + } fuzz_cases.borrow_mut().push(FuzzedCases::new(fuzz_runs)); + // Revert state to not persist values between runs. + fuzz_state.revert(); + + // If running with progress then increment completed runs. + if let Some(progress) = progress { + progress.inc(1); + } + Ok(()) }); - trace!(target: "forge::test::invariant::dictionary", "{:?}", fuzz_state.read().values().iter().map(hex::encode).collect::>()); + trace!(?fuzz_fixtures); + fuzz_state.log_stats(); let (reverts, error) = failures.into_inner().into_inner(); @@ -247,7 +343,8 @@ impl<'a> InvariantExecutor<'a> { error, cases: fuzz_cases.into_inner(), reverts, - last_run_inputs: last_run_calldata.take(), + last_run_inputs: last_run_inputs.into_inner(), + gas_report_traces: gas_report_traces.into_inner(), }) } @@ -258,37 +355,30 @@ impl<'a> InvariantExecutor<'a> { fn prepare_fuzzing( &mut self, invariant_contract: &InvariantContract<'_>, - ) -> eyre::Result { + fuzz_fixtures: &FuzzFixtures, + ) -> Result<(EvmFuzzState, FuzzRunIdentifiedContracts, impl Strategy)> + { // Finds out the chosen deployed contracts and/or senders. - self.select_contract_artifacts(invariant_contract.address, invariant_contract.abi)?; + self.select_contract_artifacts(invariant_contract.address)?; let (targeted_senders, targeted_contracts) = - self.select_contracts_and_senders(invariant_contract.address, invariant_contract.abi)?; - - if targeted_contracts.is_empty() { - eyre::bail!("No contracts to fuzz."); - } + self.select_contracts_and_senders(invariant_contract.address)?; // Stores fuzz state for use with [fuzz_calldata_from_state]. - let fuzz_state: EvmFuzzState = build_initial_state( + let fuzz_state = EvmFuzzState::new( self.executor.backend.mem_db(), - &self.config.dictionary, + self.config.dictionary, self.config.no_zksync_reserved_addresses, ); - // During execution, any newly created contract is added here and used through the rest of - // the fuzz run. - let targeted_contracts: FuzzRunIdentifiedContracts = - Arc::new(Mutex::new(targeted_contracts)); - // Creates the invariant strategy. let strat = invariant_strat( fuzz_state.clone(), targeted_senders, targeted_contracts.clone(), self.config.dictionary.dictionary_weight, + fuzz_fixtures.clone(), ) - .no_shrink() - .boxed(); + .no_shrink(); // Allows `override_call_strat` to use the address given by the Fuzzer inspector during // EVM execution. @@ -303,6 +393,7 @@ impl<'a> InvariantExecutor<'a> { fuzz_state.clone(), targeted_contracts.clone(), target_contract_ref.clone(), + fuzz_fixtures.clone(), ), target_contract_ref, )); @@ -323,68 +414,25 @@ impl<'a> InvariantExecutor<'a> { /// Priority: /// /// targetArtifactSelectors > excludeArtifacts > targetArtifacts - pub fn select_contract_artifacts( - &mut self, - invariant_address: Address, - abi: &JsonAbi, - ) -> eyre::Result<()> { - // targetArtifactSelectors -> (string, bytes4[])[]. - let targeted_abi = self - .get_list::<(String, Vec>)>( - invariant_address, - abi, - "targetArtifactSelectors", - |v| { - if let Some(list) = v.as_array() { - list.iter().map(|val| { - if let Some((_, _str, elements)) = val.as_custom_struct() { - let name = elements[0].as_str().unwrap().to_string(); - let selectors = elements[1] - .as_array() - .unwrap() - .iter() - .map(|selector| { - FixedBytes::<4>::from_slice(&selector.as_fixed_bytes().unwrap().0[0..4]) - }) - .collect::>(); - (name, selectors) - } else { - panic!("Could not decode inner value of targetArtifactSelectors. This is a bug.") - } - }).collect::>() - } else { - panic!("Could not decode targetArtifactSelectors as array. This is a bug.") - } - }, - ) - .into_iter() - .collect::>(); + pub fn select_contract_artifacts(&mut self, invariant_address: Address) -> Result<()> { + let result = self + .call_sol_default(invariant_address, &IInvariantTest::targetArtifactSelectorsCall {}); // Insert them into the executor `targeted_abi`. - for (contract, selectors) in targeted_abi { - let identifier = self.validate_selected_contract(contract, &selectors.to_vec())?; - - self.artifact_filters - .targeted - .entry(identifier) - .or_default() - .extend(selectors.to_vec()); + for IInvariantTest::FuzzArtifactSelector { artifact, selectors } in + result.targetedArtifactSelectors + { + let identifier = self.validate_selected_contract(artifact, &selectors)?; + self.artifact_filters.targeted.entry(identifier).or_default().extend(selectors); } - // targetArtifacts -> string[] - // excludeArtifacts -> string[]. - let [selected_abi, excluded_abi] = ["targetArtifacts", "excludeArtifacts"].map(|method| { - self.get_list::(invariant_address, abi, method, |v| { - if let Some(list) = v.as_array() { - list.iter().map(|v| v.as_str().unwrap().to_string()).collect::>() - } else { - panic!("targetArtifacts should be an array") - } - }) - }); + let selected = + self.call_sol_default(invariant_address, &IInvariantTest::targetArtifactsCall {}); + let excluded = + self.call_sol_default(invariant_address, &IInvariantTest::excludeArtifactsCall {}); // Insert `excludeArtifacts` into the executor `excluded_abi`. - for contract in excluded_abi { + for contract in excluded.excludedArtifacts { let identifier = self.validate_selected_contract(contract, &[])?; if !self.artifact_filters.excluded.contains(&identifier) { @@ -393,8 +441,9 @@ impl<'a> InvariantExecutor<'a> { } // Exclude any artifact without mutable functions. - for (artifact, (abi, _)) in self.project_contracts.iter() { - if abi + for (artifact, contract) in self.project_contracts.iter() { + if contract + .abi .functions() .filter(|func| { !matches!( @@ -413,7 +462,7 @@ impl<'a> InvariantExecutor<'a> { // Insert `targetArtifacts` into the executor `targeted_abi`, if they have not been seen // before. - for contract in selected_abi { + for contract in selected.targetedArtifacts { let identifier = self.validate_selected_contract(contract, &[])?; if !self.artifact_filters.targeted.contains_key(&identifier) && @@ -431,13 +480,15 @@ impl<'a> InvariantExecutor<'a> { &mut self, contract: String, selectors: &[FixedBytes<4>], - ) -> eyre::Result { - if let Some((artifact, (abi, _))) = + ) -> Result { + if let Some((artifact, contract_data)) = self.project_contracts.find_by_name_or_identifier(&contract)? { // Check that the selectors really exist for this contract. for selector in selectors { - abi.functions() + contract_data + .abi + .functions() .find(|func| func.selector().as_slice() == selector.as_slice()) .wrap_err(format!("{contract} does not have the selector {selector:?}"))?; } @@ -451,45 +502,52 @@ impl<'a> InvariantExecutor<'a> { /// `targetContracts() -> address[]` and `excludeContracts() -> address[]`. pub fn select_contracts_and_senders( &self, - invariant_address: Address, - abi: &JsonAbi, - ) -> eyre::Result<(SenderFilters, TargetedContracts)> { - let [targeted_senders, excluded_senders, selected, excluded] = - ["targetSenders", "excludeSenders", "targetContracts", "excludeContracts"].map( - |method| { - self.get_list::

(invariant_address, abi, method, |v| { - if let Some(list) = v.as_array() { - list.iter().map(|v| v.as_address().unwrap()).collect::>() - } else { - panic!("targetSenders should be an array") - } - }) - }, - ); - - let mut contracts: TargetedContracts = self + to: Address, + ) -> Result<(SenderFilters, FuzzRunIdentifiedContracts)> { + let targeted_senders = + self.call_sol_default(to, &IInvariantTest::targetSendersCall {}).targetedSenders; + let mut excluded_senders = + self.call_sol_default(to, &IInvariantTest::excludeSendersCall {}).excludedSenders; + // Extend with default excluded addresses - https://github.com/foundry-rs/foundry/issues/4163 + excluded_senders.extend([ + CHEATCODE_ADDRESS, + HARDHAT_CONSOLE_ADDRESS, + DEFAULT_CREATE2_DEPLOYER, + ]); + let sender_filters = SenderFilters::new(targeted_senders, excluded_senders); + + let selected = + self.call_sol_default(to, &IInvariantTest::targetContractsCall {}).targetedContracts; + let excluded = + self.call_sol_default(to, &IInvariantTest::excludeContractsCall {}).excludedContracts; + + let contracts = self .setup_contracts - .clone() - .into_iter() - .filter(|(addr, (identifier, _))| { - *addr != invariant_address && + .iter() + .filter(|&(addr, (identifier, _))| { + *addr != to && *addr != CHEATCODE_ADDRESS && *addr != HARDHAT_CONSOLE_ADDRESS && (selected.is_empty() || selected.contains(addr)) && - (self.artifact_filters.targeted.is_empty() || - self.artifact_filters.targeted.contains_key(identifier)) && (excluded.is_empty() || !excluded.contains(addr)) && - (self.artifact_filters.excluded.is_empty() || - !self.artifact_filters.excluded.contains(identifier)) + self.artifact_filters.matches(identifier) + }) + .map(|(addr, (identifier, abi))| { + (*addr, TargetedContract::new(identifier.clone(), abi.clone())) }) - .map(|(addr, (identifier, abi))| (addr, (identifier, abi, vec![]))) .collect(); + let mut contracts = TargetedContracts { inner: contracts }; - self.target_interfaces(invariant_address, abi, &mut contracts)?; + self.target_interfaces(to, &mut contracts)?; - self.select_selectors(invariant_address, abi, &mut contracts)?; + self.select_selectors(to, &mut contracts)?; - Ok((SenderFilters::new(targeted_senders, excluded_senders), contracts)) + // There should be at least one contract identified as target for fuzz runs. + if contracts.is_empty() { + eyre::bail!("No contracts to fuzz."); + } + + Ok((sender_filters, FuzzRunIdentifiedContracts::new(contracts, selected.is_empty()))) } /// Extends the contracts and selectors to fuzz with the addresses and ABIs specified in @@ -499,51 +557,26 @@ impl<'a> InvariantExecutor<'a> { pub fn target_interfaces( &self, invariant_address: Address, - abi: &JsonAbi, targeted_contracts: &mut TargetedContracts, - ) -> eyre::Result<()> { - let interfaces = self.get_list::<(Address, Vec)>( - invariant_address, - abi, - "targetInterfaces", - |v| { - if let Some(l) = v.as_array() { - l.iter() - .map(|v| { - if let Some((_, _names, elements)) = v.as_custom_struct() { - let addr = elements[0].as_address().unwrap(); - let interfaces = elements[1] - .as_array() - .unwrap() - .iter() - .map(|v| v.as_str().unwrap().to_string()) - .collect::>(); - (addr, interfaces) - } else { - panic!("targetInterfaces should be a tuple array") - } - }) - .collect::>() - } else { - panic!("targetInterfaces should be a tuple array") - } - }, - ); + ) -> Result<()> { + let interfaces = self + .call_sol_default(invariant_address, &IInvariantTest::targetInterfacesCall {}) + .targetedInterfaces; // Since `targetInterfaces` returns a tuple array there is no guarantee // that the addresses are unique this map is used to merge functions of // the specified interfaces for the same address. For example: // `[(addr1, ["IERC20", "IOwnable"])]` and `[(addr1, ["IERC20"]), (addr1, ("IOwnable"))]` // should be equivalent. - let mut combined: TargetedContracts = BTreeMap::new(); + let mut combined = TargetedContracts::new(); // Loop through each address and its associated artifact identifiers. // We're borrowing here to avoid taking full ownership. - for (addr, identifiers) in &interfaces { + for IInvariantTest::FuzzInterface { addr, artifacts } in &interfaces { // Identifiers are specified as an array, so we loop through them. - for identifier in identifiers { + for identifier in artifacts { // Try to find the contract by name or identifier in the project's contracts. - if let Some((_, (abi, _))) = + if let Some((_, contract)) = self.project_contracts.find_by_name_or_identifier(identifier)? { combined @@ -551,18 +584,18 @@ impl<'a> InvariantExecutor<'a> { .entry(*addr) // If the entry exists, extends its ABI with the function list. .and_modify(|entry| { - let (_, contract_abi, _) = entry; - // Extend the ABI's function list with the new functions. - contract_abi.functions.extend(abi.functions.clone()); + entry.abi.functions.extend(contract.abi.functions.clone()); }) // Otherwise insert it into the map. - .or_insert_with(|| (identifier.to_string(), abi.clone(), vec![])); + .or_insert_with(|| { + TargetedContract::new(identifier.to_string(), contract.abi.clone()) + }); } } } - targeted_contracts.extend(combined); + targeted_contracts.extend(combined.inner); Ok(()) } @@ -572,115 +605,66 @@ impl<'a> InvariantExecutor<'a> { pub fn select_selectors( &self, address: Address, - abi: &JsonAbi, targeted_contracts: &mut TargetedContracts, - ) -> eyre::Result<()> { - // `targetArtifactSelectors() -> (string, bytes4[])[]`. - let some_abi_selectors = self - .artifact_filters - .targeted - .iter() - .filter(|(_, selectors)| !selectors.is_empty()) - .collect::>(); - + ) -> Result<()> { for (address, (identifier, _)) in self.setup_contracts.iter() { - if let Some(selectors) = some_abi_selectors.get(identifier) { - self.add_address_with_functions( - *address, - (*selectors).clone(), - targeted_contracts, - )?; + if let Some(selectors) = self.artifact_filters.targeted.get(identifier) { + if selectors.is_empty() { + continue; + } + self.add_address_with_functions(*address, selectors, false, targeted_contracts)?; } } - // `targetSelectors() -> (address, bytes4[])[]`. - let selectors = - self.get_list::<(Address, Vec>)>(address, abi, "targetSelectors", |v| { - if let Some(l) = v.as_array() { - l.iter() - .map(|val| { - if let Some((_, _str, elements)) = val.as_custom_struct() { - let name = elements[0].as_address().unwrap(); - let selectors = elements[1] - .as_array() - .unwrap() - .iter() - .map(|selector| { - FixedBytes::<4>::from_slice( - &selector.as_fixed_bytes().unwrap().0[0..4], - ) - }) - .collect::>(); - (name, selectors) - } else { - panic!("targetSelectors should be a tuple array2") - } - }) - .collect::>() - } else { - panic!("targetSelectors should be a tuple array") - } - }); + // Collect contract functions marked as target for fuzzing campaign. + let selectors = self.call_sol_default(address, &IInvariantTest::targetSelectorsCall {}); + for IInvariantTest::FuzzSelector { addr, selectors } in selectors.targetedSelectors { + self.add_address_with_functions(addr, &selectors, false, targeted_contracts)?; + } - for (address, bytes4_array) in selectors.into_iter() { - self.add_address_with_functions(address, bytes4_array, targeted_contracts)?; + // Collect contract functions excluded from fuzzing campaign. + let selectors = self.call_sol_default(address, &IInvariantTest::excludeSelectorsCall {}); + for IInvariantTest::FuzzSelector { addr, selectors } in selectors.excludedSelectors { + self.add_address_with_functions(addr, &selectors, true, targeted_contracts)?; } + Ok(()) } - /// Adds the address and fuzzable functions to `TargetedContracts`. + /// Adds the address and fuzzed or excluded functions to `TargetedContracts`. fn add_address_with_functions( &self, address: Address, - bytes4_array: Vec>, + selectors: &[Selector], + should_exclude: bool, targeted_contracts: &mut TargetedContracts, ) -> eyre::Result<()> { - if let Some((name, abi, address_selectors)) = targeted_contracts.get_mut(&address) { - // The contract is already part of our filter, and all we do is specify that we're - // only looking at specific functions coming from `bytes4_array`. - for selector in bytes4_array { - address_selectors.push(get_function(name, &selector, abi)?); + let contract = match targeted_contracts.entry(address) { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => { + let (identifier, abi) = self.setup_contracts.get(&address).ok_or_else(|| { + eyre::eyre!( + "[{}] address does not have an associated contract: {}", + if should_exclude { "excludeSelectors" } else { "targetSelectors" }, + address + ) + })?; + entry.insert(TargetedContract::new(identifier.clone(), abi.clone())) } - } else { - let (name, abi) = self.setup_contracts.get(&address).ok_or_else(|| { - eyre::eyre!( - "[targetSelectors] address does not have an associated contract: {address}" - ) - })?; - - let functions = bytes4_array - .into_iter() - .map(|selector| get_function(name, &selector, abi)) - .collect::, _>>()?; - - targeted_contracts.insert(address, (name.to_string(), abi.clone(), functions)); - } + }; + contract.add_selectors(selectors.iter().copied(), should_exclude)?; Ok(()) } - /// Get the function output by calling the contract `method_name` function, encoded as a - /// [DynSolValue]. - fn get_list( - &self, - address: Address, - abi: &JsonAbi, - method_name: &str, - f: fn(DynSolValue) -> Vec, - ) -> Vec { - if let Some(func) = abi.functions().find(|func| func.name == method_name) { - if let Ok(call_result) = - self.executor.call::<_, _>(CALLER, address, func.clone(), vec![], U256::ZERO, None) - { - return f(call_result.result) - } else { - warn!( - "The function {} was found but there was an error querying its data.", - method_name - ); - } - }; - - Vec::new() + fn call_sol_default(&self, to: Address, args: &C) -> C::Return + where + C::Return: Default, + { + self.executor + .call_sol(CALLER, to, args, U256::ZERO, None) + .map(|c| c.decoded_result) + .inspect_err(|e| warn!(target: "forge::test", "failed calling {:?}: {e}", C::SIGNATURE)) + .unwrap_or_default() } } @@ -689,14 +673,16 @@ impl<'a> InvariantExecutor<'a> { /// randomly generated addresses. fn collect_data( state_changeset: &mut HashMap, - sender: &Address, + fuzzed_contracts: &FuzzRunIdentifiedContracts, + tx: &BasicTxDetails, call_result: &RawCallResult, - fuzz_state: EvmFuzzState, - config: &FuzzDictionaryConfig, + fuzz_state: &EvmFuzzState, + run_depth: u32, ) { // Verify it has no code. let mut has_code = false; - if let Some(Some(code)) = state_changeset.get(sender).map(|account| account.info.code.as_ref()) + if let Some(Some(code)) = + state_changeset.get(&tx.sender).map(|account| account.info.code.as_ref()) { has_code = !code.is_empty(); } @@ -704,74 +690,45 @@ fn collect_data( // We keep the nonce changes to apply later. let mut sender_changeset = None; if !has_code { - sender_changeset = state_changeset.remove(sender); + sender_changeset = state_changeset.remove(&tx.sender); } - collect_state_from_call(&call_result.logs, &*state_changeset, fuzz_state, config); + // Collect values from fuzzed call result and add them to fuzz dictionary. + fuzz_state.collect_values_from_call( + fuzzed_contracts, + tx, + &call_result.result, + &call_result.logs, + &*state_changeset, + run_depth, + ); // Re-add changes if let Some(changed) = sender_changeset { - state_changeset.insert(*sender, changed); + state_changeset.insert(tx.sender, changed); } } -/// Verifies that the invariant run execution can continue. -/// Returns the mapping of (Invariant Function Name -> Call Result, Logs, Traces) if invariants were -/// asserted. -#[allow(clippy::too_many_arguments)] -fn can_continue( - invariant_contract: &InvariantContract<'_>, - call_result: RawCallResult, +/// Calls the `afterInvariant()` function on a contract. +/// Returns call result and if call succeeded. +/// The state after the call is not persisted. +pub(crate) fn call_after_invariant_function( executor: &Executor, - calldata: &[BasicTxDetails], - failures: &mut InvariantFailures, - targeted_contracts: &FuzzRunIdentifiedContracts, - state_changeset: StateChangeset, - fail_on_revert: bool, - shrink_sequence: bool, - shrink_run_limit: usize, -) -> RichInvariantResults { - let mut call_results = None; - - // Detect handler assertion failures first. - let handlers_failed = targeted_contracts - .lock() - .iter() - .any(|contract| !executor.is_success(*contract.0, false, state_changeset.clone(), false)); - - // Assert invariants IFF the call did not revert and the handlers did not fail. - if !call_result.reverted && !handlers_failed { - call_results = assert_invariants( - invariant_contract, - executor, - calldata, - failures, - shrink_sequence, - shrink_run_limit, - ); - if call_results.is_none() { - return RichInvariantResults::new(false, None) - } - } else { - // Increase the amount of reverts. - failures.reverts += 1; - // If fail on revert is set, we must return immediately. - if fail_on_revert { - let error = InvariantFuzzError::new( - invariant_contract, - None, - calldata, - call_result, - &[], - shrink_sequence, - shrink_run_limit, - ); - - failures.revert_reason = Some(error.revert_reason.clone()); - failures.error = Some(error); - - return RichInvariantResults::new(false, None) - } - } - RichInvariantResults::new(true, call_results) + to: Address, +) -> std::result::Result<(RawCallResult, bool), EvmError> { + let calldata = Bytes::from_static(&IInvariantTest::afterInvariantCall::SELECTOR); + let mut call_result = executor.call_raw(CALLER, to, calldata, U256::ZERO)?; + let success = executor.is_raw_call_mut_success(to, &mut call_result, false); + Ok((call_result, success)) +} + +/// Calls the invariant function and returns call result and if succeeded. +pub(crate) fn call_invariant_function( + executor: &Executor, + address: Address, + calldata: Bytes, +) -> Result<(RawCallResult, bool)> { + let mut call_result = executor.call_raw(CALLER, address, calldata, U256::ZERO)?; + let success = executor.is_raw_call_mut_success(address, &mut call_result, false); + Ok((call_result, success)) } diff --git a/crates/evm/evm/src/executors/invariant/replay.rs b/crates/evm/evm/src/executors/invariant/replay.rs new file mode 100644 index 000000000..ef4194019 --- /dev/null +++ b/crates/evm/evm/src/executors/invariant/replay.rs @@ -0,0 +1,151 @@ +use super::{ + call_after_invariant_function, call_invariant_function, error::FailedInvariantCaseData, + shrink_sequence, +}; +use crate::executors::Executor; +use alloy_dyn_abi::JsonAbiExt; +use alloy_primitives::Log; +use eyre::Result; +use foundry_common::{ContractsByAddress, ContractsByArtifact}; +use foundry_evm_coverage::HitMaps; +use foundry_evm_fuzz::{ + invariant::{BasicTxDetails, InvariantContract}, + BaseCounterExample, +}; +use foundry_evm_traces::{load_contracts, TraceKind, Traces}; +use indicatif::ProgressBar; +use parking_lot::RwLock; +use proptest::test_runner::TestError; +use revm::primitives::U256; +use std::{collections::HashMap, sync::Arc}; + +/// Replays a call sequence for collecting logs and traces. +/// Returns counterexample to be used when the call sequence is a failed scenario. +#[allow(clippy::too_many_arguments)] +pub fn replay_run( + invariant_contract: &InvariantContract<'_>, + mut executor: Executor, + known_contracts: &ContractsByArtifact, + mut ided_contracts: ContractsByAddress, + logs: &mut Vec, + traces: &mut Traces, + coverage: &mut Option, + inputs: &[BasicTxDetails], +) -> Result> { + // We want traces for a failed case. + executor.set_tracing(true); + + let mut counterexample_sequence = vec![]; + + // Replay each call from the sequence, collect logs, traces and coverage. + for tx in inputs { + let call_result = executor.transact_raw( + tx.sender, + tx.call_details.target, + tx.call_details.calldata.clone(), + U256::ZERO, + )?; + logs.extend(call_result.logs); + traces.push((TraceKind::Execution, call_result.traces.clone().unwrap())); + + if let Some(new_coverage) = call_result.coverage { + if let Some(old_coverage) = coverage { + *coverage = Some(std::mem::take(old_coverage).merged(new_coverage)); + } else { + *coverage = Some(new_coverage); + } + } + + // Identify newly generated contracts, if they exist. + ided_contracts.extend(load_contracts( + call_result.traces.as_slice(), + known_contracts, + &HashMap::new(), + )); + + // Create counter example to be used in failed case. + counterexample_sequence.push(BaseCounterExample::from_invariant_call( + tx.sender, + tx.call_details.target, + &tx.call_details.calldata, + &ided_contracts, + call_result.traces, + )); + } + + // Replay invariant to collect logs and traces. + // We do this only once at the end of the replayed sequence. + // Checking after each call doesn't add valuable info for passing scenario + // (invariant call result is always success) nor for failed scenarios + // (invariant call result is always success until the last call that breaks it). + let (invariant_result, invariant_success) = call_invariant_function( + &executor, + invariant_contract.address, + invariant_contract.invariant_function.abi_encode_input(&[])?.into(), + )?; + traces.push((TraceKind::Execution, invariant_result.traces.clone().unwrap())); + logs.extend(invariant_result.logs); + + // Collect after invariant logs and traces. + if invariant_contract.call_after_invariant && invariant_success { + let (after_invariant_result, _) = + call_after_invariant_function(&executor, invariant_contract.address)?; + traces.push((TraceKind::Execution, after_invariant_result.traces.clone().unwrap())); + logs.extend(after_invariant_result.logs); + } + + Ok(counterexample_sequence) +} + +/// Replays the error case, shrinks the failing sequence and collects all necessary traces. +#[allow(clippy::too_many_arguments)] +pub fn replay_error( + failed_case: &FailedInvariantCaseData, + invariant_contract: &InvariantContract<'_>, + mut executor: Executor, + known_contracts: &ContractsByArtifact, + ided_contracts: ContractsByAddress, + logs: &mut Vec, + traces: &mut Traces, + coverage: &mut Option, + progress: Option<&ProgressBar>, +) -> Result> { + match failed_case.test_error { + // Don't use at the moment. + TestError::Abort(_) => Ok(vec![]), + TestError::Fail(_, ref calls) => { + // Shrink sequence of failed calls. + let calls = shrink_sequence( + failed_case, + calls, + &executor, + invariant_contract.call_after_invariant, + progress, + )?; + + set_up_inner_replay(&mut executor, &failed_case.inner_sequence); + + // Replay calls to get the counterexample and to collect logs, traces and coverage. + replay_run( + invariant_contract, + executor, + known_contracts, + ided_contracts, + logs, + traces, + coverage, + &calls, + ) + } + } +} + +/// Sets up the calls generated by the internal fuzzer, if they exist. +fn set_up_inner_replay(executor: &mut Executor, inner_sequence: &[Option]) { + if let Some(fuzzer) = &mut executor.inspector.fuzzer { + if let Some(call_generator) = &mut fuzzer.call_generator { + call_generator.last_sequence = Arc::new(RwLock::new(inner_sequence.to_owned())); + call_generator.set_replay(true); + } + } +} diff --git a/crates/evm/evm/src/executors/invariant/result.rs b/crates/evm/evm/src/executors/invariant/result.rs new file mode 100644 index 000000000..5ddcccaf0 --- /dev/null +++ b/crates/evm/evm/src/executors/invariant/result.rs @@ -0,0 +1,177 @@ +use super::{ + call_after_invariant_function, call_invariant_function, error::FailedInvariantCaseData, + InvariantFailures, InvariantFuzzError, +}; +use crate::executors::{Executor, RawCallResult}; +use alloy_dyn_abi::JsonAbiExt; +use eyre::Result; +use foundry_config::InvariantConfig; +use foundry_evm_core::utils::StateChangeset; +use foundry_evm_fuzz::{ + invariant::{BasicTxDetails, FuzzRunIdentifiedContracts, InvariantContract}, + FuzzedCases, +}; +use revm_inspectors::tracing::CallTraceArena; +use std::borrow::Cow; + +/// The outcome of an invariant fuzz test +#[derive(Debug)] +pub struct InvariantFuzzTestResult { + pub error: Option, + /// Every successful fuzz test case + pub cases: Vec, + /// Number of reverted fuzz calls + pub reverts: usize, + /// The entire inputs of the last run of the invariant campaign, used for + /// replaying the run for collecting traces. + pub last_run_inputs: Vec, + /// Additional traces used for gas report construction. + pub gas_report_traces: Vec>, +} + +/// Enriched results of an invariant run check. +/// +/// Contains the success condition and call results of the last run +pub(crate) struct RichInvariantResults { + pub(crate) can_continue: bool, + pub(crate) call_result: Option, +} + +impl RichInvariantResults { + fn new(can_continue: bool, call_result: Option) -> Self { + Self { can_continue, call_result } + } +} + +/// Given the executor state, asserts that no invariant has been broken. Otherwise, it fills the +/// external `invariant_failures.failed_invariant` map and returns a generic error. +/// Either returns the call result if successful, or nothing if there was an error. +pub(crate) fn assert_invariants( + invariant_contract: &InvariantContract<'_>, + invariant_config: &InvariantConfig, + targeted_contracts: &FuzzRunIdentifiedContracts, + executor: &Executor, + calldata: &[BasicTxDetails], + invariant_failures: &mut InvariantFailures, +) -> Result> { + let mut inner_sequence = vec![]; + + if let Some(fuzzer) = &executor.inspector.fuzzer { + if let Some(call_generator) = &fuzzer.call_generator { + inner_sequence.extend(call_generator.last_sequence.read().iter().cloned()); + } + } + + let (call_result, success) = call_invariant_function( + executor, + invariant_contract.address, + invariant_contract.invariant_function.abi_encode_input(&[])?.into(), + )?; + if !success { + // We only care about invariants which we haven't broken yet. + if invariant_failures.error.is_none() { + let case_data = FailedInvariantCaseData::new( + invariant_contract, + invariant_config, + targeted_contracts, + calldata, + call_result, + &inner_sequence, + ); + invariant_failures.error = Some(InvariantFuzzError::BrokenInvariant(case_data)); + return Ok(None); + } + } + + Ok(Some(call_result)) +} + +/// Verifies that the invariant run execution can continue. +/// Returns the mapping of (Invariant Function Name -> Call Result, Logs, Traces) if invariants were +/// asserted. +#[allow(clippy::too_many_arguments)] +pub(crate) fn can_continue( + invariant_contract: &InvariantContract<'_>, + invariant_config: &InvariantConfig, + call_result: RawCallResult, + executor: &Executor, + calldata: &[BasicTxDetails], + failures: &mut InvariantFailures, + targeted_contracts: &FuzzRunIdentifiedContracts, + state_changeset: &StateChangeset, + run_traces: &mut Vec, +) -> Result { + let mut call_results = None; + + let handlers_succeeded = || { + targeted_contracts.targets.lock().keys().all(|address| { + executor.is_success(*address, false, Cow::Borrowed(state_changeset), false) + }) + }; + + // Assert invariants if the call did not revert and the handlers did not fail. + if !call_result.reverted && handlers_succeeded() { + if let Some(traces) = call_result.traces { + run_traces.push(traces); + } + + call_results = assert_invariants( + invariant_contract, + invariant_config, + targeted_contracts, + executor, + calldata, + failures, + )?; + if call_results.is_none() { + return Ok(RichInvariantResults::new(false, None)); + } + } else { + // Increase the amount of reverts. + failures.reverts += 1; + // If fail on revert is set, we must return immediately. + if invariant_config.fail_on_revert { + let case_data = FailedInvariantCaseData::new( + invariant_contract, + invariant_config, + targeted_contracts, + calldata, + call_result, + &[], + ); + failures.revert_reason = Some(case_data.revert_reason.clone()); + let error = InvariantFuzzError::Revert(case_data); + failures.error = Some(error); + + return Ok(RichInvariantResults::new(false, None)); + } + } + Ok(RichInvariantResults::new(true, call_results)) +} + +/// Given the executor state, asserts conditions within `afterInvariant` function. +/// If call fails then the invariant test is considered failed. +pub(crate) fn assert_after_invariant( + invariant_contract: &InvariantContract<'_>, + invariant_config: &InvariantConfig, + targeted_contracts: &FuzzRunIdentifiedContracts, + executor: &mut Executor, + invariant_failures: &mut InvariantFailures, + inputs: &[BasicTxDetails], +) -> Result { + let (call_result, success) = + call_after_invariant_function(executor, invariant_contract.address)?; + // Fail the test case if `afterInvariant` doesn't succeed. + if !success { + let case_data = FailedInvariantCaseData::new( + invariant_contract, + invariant_config, + targeted_contracts, + inputs, + call_result, + &[], + ); + invariant_failures.error = Some(InvariantFuzzError::BrokenInvariant(case_data)); + } + Ok(success) +} diff --git a/crates/evm/evm/src/executors/invariant/shrink.rs b/crates/evm/evm/src/executors/invariant/shrink.rs new file mode 100644 index 000000000..5559ec821 --- /dev/null +++ b/crates/evm/evm/src/executors/invariant/shrink.rs @@ -0,0 +1,178 @@ +use crate::executors::{ + invariant::{ + call_after_invariant_function, call_invariant_function, error::FailedInvariantCaseData, + }, + Executor, +}; +use alloy_primitives::{Address, Bytes, U256}; +use foundry_evm_fuzz::invariant::BasicTxDetails; +use indicatif::ProgressBar; +use proptest::bits::{BitSetLike, VarBitSet}; +use std::cmp::min; + +#[derive(Clone, Copy, Debug)] +struct Shrink { + call_index: usize, +} + +/// Shrinker for a call sequence failure. +/// Iterates sequence call sequence top down and removes calls one by one. +/// If the failure is still reproducible with removed call then moves to the next one. +/// If the failure is not reproducible then restore removed call and moves to next one. +#[derive(Debug)] +struct CallSequenceShrinker { + /// Length of call sequence to be shrinked. + call_sequence_len: usize, + /// Call ids contained in current shrinked sequence. + included_calls: VarBitSet, + /// Current shrinked call id. + shrink: Shrink, + /// Previous shrinked call id. + prev_shrink: Option, +} + +impl CallSequenceShrinker { + fn new(call_sequence_len: usize) -> Self { + Self { + call_sequence_len, + included_calls: VarBitSet::saturated(call_sequence_len), + shrink: Shrink { call_index: 0 }, + prev_shrink: None, + } + } + + /// Return candidate shrink sequence to be tested, by removing ids from original sequence. + fn current(&self) -> impl Iterator + '_ { + (0..self.call_sequence_len).filter(|&call_id| self.included_calls.test(call_id)) + } + + /// Removes next call from sequence. + fn simplify(&mut self) -> bool { + if self.shrink.call_index >= self.call_sequence_len { + // We reached the end of call sequence, nothing left to simplify. + false + } else { + // Remove current call. + self.included_calls.clear(self.shrink.call_index); + // Record current call as previous call. + self.prev_shrink = Some(self.shrink); + // Remove next call index + self.shrink = Shrink { call_index: self.shrink.call_index + 1 }; + true + } + } + + /// Reverts removed call from sequence and tries to simplify next call. + fn complicate(&mut self) -> bool { + match self.prev_shrink { + Some(shrink) => { + // Undo the last call removed. + self.included_calls.set(shrink.call_index); + self.prev_shrink = None; + // Try to simplify next call. + self.simplify() + } + None => false, + } + } +} + +/// Shrinks the failure case to its smallest sequence of calls. +/// +/// Maximal shrinkage is guaranteed if the shrink_run_limit is not set to a value lower than the +/// length of failed call sequence. +/// +/// The shrinked call sequence always respect the order failure is reproduced as it is tested +/// top-down. +pub(crate) fn shrink_sequence( + failed_case: &FailedInvariantCaseData, + calls: &[BasicTxDetails], + executor: &Executor, + call_after_invariant: bool, + progress: Option<&ProgressBar>, +) -> eyre::Result> { + trace!(target: "forge::test", "Shrinking sequence of {} calls.", calls.len()); + + // Reset run count and display shrinking message. + if let Some(progress) = progress { + progress.set_length(min(calls.len(), failed_case.shrink_run_limit as usize) as u64); + progress.reset(); + progress.set_message(" Shrink"); + } + + // Special case test: the invariant is *unsatisfiable* - it took 0 calls to + // break the invariant -- consider emitting a warning. + let (_, success) = + call_invariant_function(executor, failed_case.addr, failed_case.calldata.clone())?; + if !success { + return Ok(vec![]); + } + + let mut shrinker = CallSequenceShrinker::new(calls.len()); + for _ in 0..failed_case.shrink_run_limit { + // Check candidate sequence result. + match check_sequence( + executor.clone(), + calls, + shrinker.current().collect(), + failed_case.addr, + failed_case.calldata.clone(), + failed_case.fail_on_revert, + call_after_invariant, + ) { + // If candidate sequence still fails then shrink more if possible. + Ok((false, _)) if !shrinker.simplify() => break, + // If candidate sequence pass then restore last removed call and shrink other + // calls if possible. + Ok((true, _)) if !shrinker.complicate() => break, + _ => {} + } + + if let Some(progress) = progress { + progress.inc(1); + } + } + + Ok(shrinker.current().map(|idx| &calls[idx]).cloned().collect()) +} + +/// Checks if the given call sequence breaks the invariant. +/// Used in shrinking phase for checking candidate sequences and in replay failures phase to test +/// persisted failures. +/// Returns the result of invariant check (and afterInvariant call if needed) and if sequence was +/// entirely applied. +pub fn check_sequence( + mut executor: Executor, + calls: &[BasicTxDetails], + sequence: Vec, + test_address: Address, + calldata: Bytes, + fail_on_revert: bool, + call_after_invariant: bool, +) -> eyre::Result<(bool, bool)> { + // Apply the call sequence. + for call_index in sequence { + let tx = &calls[call_index]; + let call_result = executor.transact_raw( + tx.sender, + tx.call_details.target, + tx.call_details.calldata.clone(), + U256::ZERO, + )?; + if call_result.reverted && fail_on_revert { + // Candidate sequence fails test. + // We don't have to apply remaining calls to check sequence. + return Ok((false, false)); + } + } + + // Check the invariant for call sequence. + let (_, mut success) = call_invariant_function(&executor, test_address, calldata)?; + // Check after invariant result if invariant is success and `afterInvariant` function is + // declared. + if success && call_after_invariant { + (_, success) = call_after_invariant_function(&executor, test_address)?; + } + + Ok((success, true)) +} diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index 5dccf7652..249f31a9e 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -12,15 +12,16 @@ use crate::inspectors::{ use alloy_dyn_abi::{DynSolValue, FunctionExt, JsonAbiExt}; use alloy_json_abi::Function; use alloy_primitives::{Address, Bytes, Log, U256}; -use foundry_common::{abi::IntoFunction, evm::Breakpoints}; +use alloy_sol_types::{sol, SolCall}; use foundry_evm_core::{ - backend::{Backend, DatabaseError, DatabaseExt, DatabaseResult, FuzzBackendWrapper}, + backend::{Backend, CowBackend, DatabaseError, DatabaseExt, DatabaseResult}, constants::{ - CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, DEFAULT_CREATE2_DEPLOYER_CODE, + CALLER, CHEATCODE_ADDRESS, CHEATCODE_CONTRACT_HASH, DEFAULT_CREATE2_DEPLOYER, + DEFAULT_CREATE2_DEPLOYER_CODE, }, debug::DebugArena, decode::RevertDecoder, - utils::{eval_to_instruction_result, halt_to_instruction_result, StateChangeset}, + utils::StateChangeset, }; use foundry_evm_coverage::HitMaps; use foundry_evm_traces::CallTraceArena; @@ -28,13 +29,14 @@ use foundry_zksync_core::ZkTransactionMetadata; use itertools::Itertools; use revm::{ db::{DatabaseCommit, DatabaseRef}, - interpreter::{return_ok, CreateScheme, InstructionResult, Stack}, + interpreter::{return_ok, InstructionResult}, primitives::{ - BlockEnv, Bytecode, Env, ExecutionResult, Output, ResultAndState, SpecId, TransactTo, TxEnv, + BlockEnv, Bytecode, Env, EnvWithHandlerCfg, ExecutionResult, Output, ResultAndState, + SpecId, TransactTo, TxEnv, }, Database, }; -use std::collections::HashMap; +use std::{borrow::Cow, collections::HashMap}; mod builder; pub use builder::ExecutorBuilder; @@ -48,14 +50,24 @@ pub use invariant::InvariantExecutor; mod tracing; pub use tracing::TracingExecutor; -/// A type that can execute calls +sol! { + interface ITest { + function setUp() external; + function failed() external view returns (bool failed); + } +} + +/// EVM executor. /// /// The executor can be configured with various `revm::Inspector`s, like `Cheatcodes`. /// -/// There are two ways of executing calls: -/// - `committing`: any state changes made during the call are recorded and are persisting -/// - `raw`: state changes only exist for the duration of the call and are discarded afterwards, in -/// other words: the state of the underlying database remains unchanged. +/// There are multiple ways of interacting the EVM: +/// - `call`: executes a transaction, but does not persist any state changes; similar to `eth_call`, +/// where the EVM state is unchanged after the call. +/// - `transact`: executes a transaction and persists the state changes +/// - `deploy`: a special case of `transact`, specialized for persisting the state of a contract +/// deployment +/// - `setup`: a special case of `transact`, used to set up the environment for a test #[derive(Clone, Debug)] pub struct Executor { /// The underlying `revm::Database` that contains the EVM storage. @@ -65,7 +77,7 @@ pub struct Executor { // so the performance difference should be negligible. pub backend: Backend, /// The EVM environment. - pub env: Env, + pub env: EnvWithHandlerCfg, /// The Revm inspector stack. pub inspector: InspectorStack, /// The gas limit for calls and deployments. This is different from the gas limit imposed by @@ -82,19 +94,28 @@ pub struct Executor { } impl Executor { + /// Creates a new `Executor` with the given arguments. #[inline] - pub fn new(mut backend: Backend, env: Env, inspector: InspectorStack, gas_limit: U256) -> Self { + pub fn new( + mut backend: Backend, + env: EnvWithHandlerCfg, + inspector: InspectorStack, + gas_limit: U256, + ) -> Self { // Need to create a non-empty contract on the cheatcodes address so `extcodesize` checks // does not fail backend.insert_account_info( CHEATCODE_ADDRESS, revm::primitives::AccountInfo { - code: Some(Bytecode::new_raw(Bytes::from_static(&[0])).to_checked()), + code: Some(Bytecode::new_raw(Bytes::from_static(&[0]))), + // Also set the code hash manually so that it's not computed later. + // The code hash value does not matter, as long as it's not zero or `KECCAK_EMPTY`. + code_hash: CHEATCODE_CONTRACT_HASH, ..Default::default() }, ); - Executor { + Self { backend, env, inspector, @@ -105,6 +126,11 @@ impl Executor { } } + /// Returns the spec ID of the executor. + pub fn spec_id(&self) -> SpecId { + self.env.handler_cfg.spec_id + } + /// Creates the default CREATE2 Contract Deployer for local tests and scripts. pub fn deploy_create2_deployer(&mut self) -> eyre::Result<()> { trace!("deploying local create2 deployer"); @@ -153,7 +179,6 @@ impl Executor { pub fn set_nonce(&mut self, address: Address, nonce: u64) -> DatabaseResult<&mut Self> { let mut account = self.backend.basic_ref(address)?.unwrap_or_default(); account.nonce = nonce; - self.backend.insert_account_info(address, account); if self.use_zk { @@ -169,11 +194,16 @@ impl Executor { Ok(self) } - /// Gets the nonce of an account + /// Returns the nonce of an account. pub fn get_nonce(&self, address: Address) -> DatabaseResult { Ok(self.backend.basic_ref(address)?.map(|acc| acc.nonce).unwrap_or_default()) } + /// Returns `true` if the account has no code. + pub fn is_empty_code(&self, address: Address) -> DatabaseResult { + Ok(self.backend.basic_ref(address)?.map(|acc| acc.is_empty_code_hash()).unwrap_or(true)) + } + #[inline] pub fn set_tracing(&mut self, tracing: bool) -> &mut Self { self.inspector.tracing(tracing); @@ -198,212 +228,236 @@ impl Executor { self } + /// Deploys a contract and commits the new state to the underlying database. + /// + /// Executes a CREATE transaction with the contract `code` and persistent database state + /// modifications. + pub fn deploy( + &mut self, + from: Address, + code: Bytes, + value: U256, + rd: Option<&RevertDecoder>, + ) -> Result { + let env = self.build_test_env(from, TransactTo::Create, code, value); + self.deploy_with_env(env, rd) + } + + /// Deploys a contract using the given `env` and commits the new state to the underlying + /// database. + /// + /// # Panics + /// + /// Panics if `env.tx.transact_to` is not `TransactTo::Create(_)`. + pub fn deploy_with_env( + &mut self, + env: EnvWithHandlerCfg, + rd: Option<&RevertDecoder>, + ) -> Result { + assert!( + matches!(env.tx.transact_to, TransactTo::Create), + "Expected create transaction, got {:?}", + env.tx.transact_to + ); + trace!(sender=%env.tx.caller, "deploying contract"); + + let mut result = self.transact_with_env(env)?; + result = result.into_result(rd)?; + let Some(Output::Create(_, Some(address))) = result.out else { + panic!("Deployment succeeded, but no address was returned: {result:#?}"); + }; + + // also mark this library as persistent, this will ensure that the state of the library is + // persistent across fork swaps in forking mode + self.backend.add_persistent_account(address); + + debug!(%address, "deployed contract"); + + Ok(DeployResult { raw: result, address }) + } + /// Calls the `setUp()` function on a contract. /// /// This will commit any state changes to the underlying database. /// /// Ayn changes made during the setup call to env's block environment are persistent, for /// example `vm.chainId()` will change the `block.chainId` for all subsequent test calls. - pub fn setup(&mut self, from: Option
, to: Address) -> Result { + pub fn setup( + &mut self, + from: Option
, + to: Address, + rd: Option<&RevertDecoder>, + ) -> Result { trace!(?from, ?to, "setting up contract"); let from = from.unwrap_or(CALLER); self.backend.set_test_contract(to).set_caller(from); - let res = self.call_committing::<_, _>(from, to, "setUp()", vec![], U256::ZERO, None)?; + let calldata = Bytes::from_static(&ITest::setUpCall::SELECTOR); + let mut res = self.transact_raw(from, to, calldata, U256::ZERO)?; + res = res.into_result(rd)?; // record any changes made to the block's environment during setup self.env.block = res.env.block.clone(); // and also the chainid, which can be set manually self.env.cfg.chain_id = res.env.cfg.chain_id; - match res.state_changeset.as_ref() { - Some(changeset) => { - let success = self - .ensure_success(to, res.reverted, changeset.clone(), false) - .map_err(|err| EvmError::Eyre(eyre::eyre!(err.to_string())))?; - if success { - Ok(res) - } else { - Err(EvmError::Execution(Box::new(ExecutionErr { - reverted: res.reverted, - reason: "execution error".to_owned(), - traces: res.traces, - gas_used: res.gas_used, - gas_refunded: res.gas_refunded, - stipend: res.stipend, - logs: res.logs, - debug: res.debug, - labels: res.labels, - state_changeset: None, - transactions: None, - }))) - } + if let Some(changeset) = &res.state_changeset { + let success = self.is_raw_call_success(to, Cow::Borrowed(changeset), &res, false); + if !success { + return Err(res.into_execution_error("execution error".to_string()).into()); } - None => Ok(res), } + + Ok(res) } /// Performs a call to an account on the current state of the VM. - /// - /// The state after the call is persisted. - pub fn call_committing>, F: IntoFunction>( - &mut self, + pub fn call( + &self, from: Address, to: Address, - func: F, - args: T, + func: &Function, + args: &[DynSolValue], value: U256, rd: Option<&RevertDecoder>, ) -> Result { - let func = func.into(); - let calldata = Bytes::from(func.abi_encode_input(&args.into())?); - let result = self.call_raw_committing(from, to, calldata, value)?; - convert_call_result(rd, &func, result) + let calldata = Bytes::from(func.abi_encode_input(args)?); + let result = self.call_raw(from, to, calldata, value)?; + result.into_decoded_result(func, rd) } - /// Performs a raw call to an account on the current state of the VM. - /// - /// The state after the call is persisted. - pub fn call_raw_committing( - &mut self, + /// Performs a call to an account on the current state of the VM. + pub fn call_sol( + &self, from: Address, to: Address, - calldata: Bytes, + args: &C, value: U256, - ) -> eyre::Result { - let env = self.build_test_env(from, TransactTo::Call(to), calldata, value); - let mut result = self.call_raw_with_env(env)?; - self.commit(&mut result); - Ok(result) + rd: Option<&RevertDecoder>, + ) -> Result, EvmError> { + let calldata = Bytes::from(args.abi_encode()); + let mut raw = self.call_raw(from, to, calldata, value)?; + raw = raw.into_result(rd)?; + Ok(CallResult { decoded_result: C::abi_decode_returns(&raw.result, false)?, raw }) } - /// Executes the test function call - pub fn execute_test>, F: IntoFunction>( + /// Performs a call to an account on the current state of the VM. + pub fn transact( &mut self, from: Address, - test_contract: Address, - func: F, - args: T, + to: Address, + func: &Function, + args: &[DynSolValue], value: U256, rd: Option<&RevertDecoder>, ) -> Result { - let func = func.into(); - let calldata = Bytes::from(func.abi_encode_input(&args.into())?.to_vec()); - - // execute the call - let env = self.build_test_env(from, TransactTo::Call(test_contract), calldata, value); - let call_result = self.call_raw_with_env(env)?; - convert_call_result(rd, &func, call_result) + let calldata = Bytes::from(func.abi_encode_input(args)?); + let result = self.transact_raw(from, to, calldata, value)?; + result.into_decoded_result(func, rd) } - /// Performs a call to an account on the current state of the VM. - /// - /// The state after the call is not persisted. - pub fn call>, F: IntoFunction>( + /// Performs a raw call to an account on the current state of the VM. + pub fn call_raw( &self, from: Address, to: Address, - func: F, - args: T, + calldata: Bytes, value: U256, - rd: Option<&RevertDecoder>, - ) -> Result { - let func = func.into(); - let calldata = Bytes::from(func.abi_encode_input(&args.into())?.to_vec()); - let call_result = self.call_raw(from, to, calldata, value)?; - convert_call_result(rd, &func, call_result) + ) -> eyre::Result { + let env = self.build_test_env(from, TransactTo::Call(to), calldata, value); + self.call_with_env(env) } /// Performs a raw call to an account on the current state of the VM. - /// - /// Any state modifications made by the call are not committed. - /// - /// This intended for fuzz calls, which try to minimize [Backend] clones by using a Cow of the - /// underlying [Backend] so it only gets cloned when cheatcodes that require mutable access are - /// used. - pub fn call_raw( - &self, + pub fn transact_raw( + &mut self, from: Address, to: Address, calldata: Bytes, value: U256, ) -> eyre::Result { - let mut inspector = self.inspector.clone(); - // Build VM - let mut env = self.build_test_env(from, TransactTo::Call(to), calldata, value); - let mut db = FuzzBackendWrapper::new(&self.backend); + let env = self.build_test_env(from, TransactTo::Call(to), calldata, value); + self.transact_with_env(env) + } + /// Execute the transaction configured in `env.tx`. + /// + /// The state after the call is **not** persisted. + pub fn call_with_env(&self, mut env: EnvWithHandlerCfg) -> eyre::Result { + let mut inspector = self.inspector.clone(); + let mut backend = CowBackend::new(&self.backend); let result = match &self.zk_tx { + None => backend.inspect(&mut env, &mut inspector)?, Some(zk_tx) => { // apply fork-related env instead of cheatcode handler // since it won't be run inside zkvm env.block = self.env.block.clone(); env.tx.gas_price = self.env.tx.gas_price; - db.inspect_ref_zk( + backend.inspect_ref_zk( &mut env, &mut self.zk_persisted_factory_deps.clone(), Some(zk_tx.factory_deps.clone()), )? } - None => db.inspect_ref(&mut env, &mut inspector)?, }; - - // Persist the snapshot failure recorded on the fuzz backend wrapper. - let has_snapshot_failure = db.has_snapshot_failure(); - convert_executed_result(env, inspector, result, has_snapshot_failure) - } - - /// Execute the transaction configured in `env.tx` and commit the changes - pub fn commit_tx_with_env(&mut self, env: Env) -> eyre::Result { - let mut result = self.call_raw_with_env(env)?; - self.commit(&mut result); - Ok(result) + convert_executed_result(env, inspector, result, backend.has_snapshot_failure()) } - /// Execute the transaction configured in `env.tx` - pub fn call_raw_with_env(&mut self, mut env: Env) -> eyre::Result { - // execute the call + /// Execute the transaction configured in `env.tx`. + pub fn transact_with_env(&mut self, mut env: EnvWithHandlerCfg) -> eyre::Result { let mut inspector = self.inspector.clone(); - - let result = match &self.zk_tx { + let backend = &mut self.backend; + let result_and_state = match self.zk_tx.take() { + None => backend.inspect(&mut env, &mut inspector)?, Some(zk_tx) => { // apply fork-related env instead of cheatcode handler // since it won't be run inside zkvm env.block = self.env.block.clone(); env.tx.gas_price = self.env.tx.gas_price; - self.backend.inspect_ref_zk( + backend.inspect_ref_zk( &mut env, - &mut self.zk_persisted_factory_deps.clone(), - Some(zk_tx.factory_deps.clone()), + // this will persist the added factory deps, + // no need to commit them later + &mut self.zk_persisted_factory_deps, + Some(zk_tx.factory_deps), )? } - None => self.backend.inspect_ref(&mut env, &mut inspector)?, }; + let mut result = convert_executed_result( + env, + inspector, + result_and_state.clone(), + backend.has_snapshot_failure(), + )?; + let state = result_and_state.state.clone(); + if let Some(traces) = &mut result.traces { + for trace_node in traces.nodes() { + if let Some(account_info) = state.get(&trace_node.trace.address) { + result.deployments.insert( + trace_node.trace.address, + account_info.info.code.clone().unwrap_or_default().bytes(), + ); + } + } + } - convert_executed_result(env, inspector, result, self.backend.has_snapshot_failure()) + self.commit(&mut result); + Ok(result) } - /// Commit the changeset to the database and adjust `self.inspector_config` - /// values according to the executed call result + /// Commit the changeset to the database and adjust `self.inspector_config` values according to + /// the executed call result. + /// + /// This should not be exposed to the user, as it should be called only by `transact*`. fn commit(&mut self, result: &mut RawCallResult) { - // Persist factory deps from just executed tx - if let Some(zk_tx) = self.zk_tx.take() { - self.zk_persisted_factory_deps.extend( - zk_tx - .factory_deps - .into_iter() - .map(|dep| (foundry_zksync_core::hash_bytecode(&dep), dep)), - ); - } - - // Persist changes to db + // Persist changes to db. if let Some(changes) = &result.state_changeset { self.backend.commit(changes.clone()); } - // Persist cheatcode state - let mut cheatcodes = result.cheatcodes.take(); - if let Some(cheats) = cheatcodes.as_mut() { + // Persist cheatcode state. + self.inspector.cheatcodes = result.cheatcodes.take(); + if let Some(cheats) = self.inspector.cheatcodes.as_mut() { // Clear broadcastable transactions cheats.broadcastable_transactions.clear(); debug!(target: "evm::executors", "cleared broadcastable transactions"); @@ -411,107 +465,55 @@ impl Executor { // corrected_nonce value is needed outside of this context (setUp), so we don't // reset it. } - self.inspector.cheatcodes = cheatcodes; - // Persist the changed environment + // Persist the changed environment. self.inspector.set_env(&result.env); } - /// Deploys a contract using the given `env` and commits the new state to the underlying - /// database - pub fn deploy_with_env( - &mut self, - env: Env, - rd: Option<&RevertDecoder>, - ) -> Result { - debug_assert!( - matches!(env.tx.transact_to, TransactTo::Create(_)), - "Expect create transaction" - ); - trace!(sender=?env.tx.caller, "deploying contract"); - - let mut result = self.call_raw_with_env(env)?; - self.commit(&mut result); - - let RawCallResult { - exit_reason, - out, - gas_used, - gas_refunded, - logs, - labels, - traces, - debug, - env, - coverage, - .. - } = result; - - let result = match &out { - Some(Output::Create(data, _)) => data.to_owned(), - _ => Bytes::default(), - }; - - let address = match exit_reason { - return_ok!() => { - if let Some(Output::Create(_, Some(addr))) = out { - addr - } else { - return Err(EvmError::Execution(Box::new(ExecutionErr { - reverted: true, - reason: "Deployment succeeded, but no address was returned. This is a bug, please report it".to_string(), - traces, - gas_used, - gas_refunded: 0, - stipend: 0, - logs, - debug, - labels, - state_changeset: None, - transactions: None, - }))); - } - } - _ => { - let reason = rd.unwrap_or_default().decode(&result, Some(exit_reason)); - return Err(EvmError::Execution(Box::new(ExecutionErr { - reverted: true, - reason, - traces, - gas_used, - gas_refunded, - stipend: 0, - logs, - debug, - labels, - state_changeset: None, - transactions: None, - }))) - } - }; - - // also mark this library as persistent, this will ensure that the state of the library is - // persistent across fork swaps in forking mode - self.backend.add_persistent_account(address); - - trace!(address=?address, "deployed contract"); - - Ok(DeployResult { address, gas_used, gas_refunded, logs, traces, debug, env, coverage }) + /// Checks if a call to a test contract was successful. + /// + /// This is the same as [`Self::is_success`], but will consume the `state_changeset` map to use + /// internally when calling `failed()`. + pub fn is_raw_call_mut_success( + &self, + address: Address, + call_result: &mut RawCallResult, + should_fail: bool, + ) -> bool { + self.is_raw_call_success( + address, + Cow::Owned(call_result.state_changeset.take().unwrap_or_default()), + call_result, + should_fail, + ) } - /// Deploys a contract and commits the new state to the underlying database. + /// Checks if a call to a test contract was successful. /// - /// Executes a CREATE transaction with the contract `code` and persistent database state - /// modifications - pub fn deploy( - &mut self, - from: Address, - code: Bytes, - value: U256, - rd: Option<&RevertDecoder>, - ) -> Result { - let env = self.build_test_env(from, TransactTo::Create(CreateScheme::Create), code, value); - self.deploy_with_env(env, rd) + /// This is the same as [`Self::is_success`] but intended for outcomes of [`Self::call_raw`]. + /// + /// ## Background + /// + /// Executing and failure checking `Executor::is_success` are two steps, for ds-test + /// legacy reasons failures can be stored in a global variables and needs to be called via a + /// solidity call `failed()(bool)`. + /// + /// Snapshots make this task more complicated because now we also need to keep track of that + /// global variable when we revert to a snapshot (because it is stored in state). Now, the + /// problem is that the `CowBackend` is dropped after every call, so we need to keep track + /// of the snapshot failure in the [`RawCallResult`] instead. + pub fn is_raw_call_success( + &self, + address: Address, + state_changeset: Cow<'_, StateChangeset>, + call_result: &RawCallResult, + should_fail: bool, + ) -> bool { + if call_result.has_snapshot_failure { + // a failure occurred in a reverted snapshot, which is considered a failed test + return should_fail; + } + self.is_success(address, call_result.reverted, state_changeset, should_fail) } /// Check if a call to a test contract was successful. @@ -533,86 +535,61 @@ impl Executor { &self, address: Address, reverted: bool, - state_changeset: StateChangeset, + state_changeset: Cow<'_, StateChangeset>, should_fail: bool, ) -> bool { - self.ensure_success(address, reverted, state_changeset, should_fail).unwrap_or_default() - } - - /// This is the same as [Self::is_success] but intended for outcomes of [Self::call_raw] used in - /// fuzzing and invariant testing. - /// - /// ## Background - /// - /// Executing and failure checking [Executor::ensure_success] are two steps, for ds-test - /// legacy reasons failures can be stored in a global variables and needs to be called via a - /// solidity call `failed()(bool)`. For fuzz tests we’re using the - /// `FuzzBackendWrapper` which is a Cow of the executor’s backend which lazily clones the - /// backend when it’s mutated via cheatcodes like `snapshot`. Snapshots make it even - /// more complicated because now we also need to keep track of that global variable when we - /// revert to a snapshot (because it is stored in state). Now, the problem is that - /// the `FuzzBackendWrapper` is dropped after every call, so we need to keep track of the - /// snapshot failure in the [RawCallResult] instead. - pub fn is_raw_call_success( - &self, - address: Address, - state_changeset: StateChangeset, - call_result: &RawCallResult, - should_fail: bool, - ) -> bool { - if call_result.has_snapshot_failure { - // a failure occurred in a reverted snapshot, which is considered a failed test - return should_fail - } - self.is_success(address, call_result.reverted, state_changeset, should_fail) - } - - pub fn setup_zk_tx(&mut self, zk_tx: ZkTransactionMetadata) { - self.zk_tx = Some(zk_tx); + let success = self.is_success_raw(address, reverted, state_changeset); + should_fail ^ success } - fn ensure_success( + fn is_success_raw( &self, address: Address, reverted: bool, - state_changeset: StateChangeset, - should_fail: bool, - ) -> Result { + state_changeset: Cow<'_, StateChangeset>, + ) -> bool { if self.backend.has_snapshot_failure() { // a failure occurred in a reverted snapshot, which is considered a failed test - return Ok(should_fail) - } - - // Construct a new VM with the state changeset - let mut backend = self.backend.clone_empty(); - - // we only clone the test contract and cheatcode accounts, that's all we need to evaluate - // success - for addr in [address, CHEATCODE_ADDRESS] { - let acc = self.backend.basic_ref(addr)?.unwrap_or_default(); - backend.insert_account_info(addr, acc); + return false; } - // If this test failed any asserts, then this changeset will contain changes `false -> true` - // for the contract's `failed` variable and the `globalFailure` flag in the state of the - // cheatcode address which are both read when we call `"failed()(bool)"` in the next step - backend.commit(state_changeset); - let mut success = !reverted; if success { + // Construct a new bare-bones backend to evaluate success. + let mut backend = self.backend.clone_empty(); + + // We only clone the test contract and cheatcode accounts, + // that's all we need to evaluate success. + for address in [address, CHEATCODE_ADDRESS] { + let Ok(acc) = self.backend.basic_ref(address) else { return false }; + backend.insert_account_info(address, acc.unwrap_or_default()); + } + + // If this test failed any asserts, then this changeset will contain changes + // `false -> true` for the contract's `failed` variable and the `globalFailure` flag + // in the state of the cheatcode address, + // which are both read when we call `"failed()(bool)"` in the next step. + backend.commit(state_changeset.into_owned()); + // Check if a DSTest assertion failed let executor = - Executor::new(backend, self.env.clone(), self.inspector.clone(), self.gas_limit); - let call = executor.call(CALLER, address, "failed()(bool)", vec![], U256::ZERO, None); - if let Ok(CallResult { result: failed, .. }) = call { - debug!(?failed, "DSTest"); - success = !failed.as_bool().unwrap(); + Self::new(backend, self.env.clone(), self.inspector.clone(), self.gas_limit); + let call = executor.call_sol(CALLER, address, &ITest::failedCall {}, U256::ZERO, None); + match call { + Ok(CallResult { raw: _, decoded_result: ITest::failedReturn { failed } }) => { + debug!(failed, "DSTest::failed()"); + success = !failed; + } + Err(err) => { + debug!(%err, "failed to call DSTest::failed()"); + } } } + success + } - let result = should_fail ^ success; - debug!(should_fail, success, result); - Ok(result) + pub fn setup_zk_tx(&mut self, zk_tx: ZkTransactionMetadata) { + self.zk_tx = Some(zk_tx); } /// Creates the environment to use when executing a transaction in a test context @@ -625,8 +602,8 @@ impl Executor { transact_to: TransactTo, data: Bytes, value: U256, - ) -> Env { - Env { + ) -> EnvWithHandlerCfg { + let env = Env { cfg: self.env.cfg.clone(), // We always set the gas price to 0 so we can execute the transaction regardless of // network conditions - the actual gas price is kept in `self.block` and is applied by @@ -647,32 +624,43 @@ impl Executor { gas_limit: self.gas_limit.to(), ..self.env.tx.clone() }, - } + }; + + EnvWithHandlerCfg::new_with_spec_id(Box::new(env), self.env.handler_cfg.spec_id) } } /// Represents the context after an execution error occurred. #[derive(Debug, thiserror::Error)] -#[error("Execution reverted: {reason} (gas: {gas_used})")] +#[error("execution reverted: {reason} (gas: {})", raw.gas_used)] pub struct ExecutionErr { - pub reverted: bool, + /// The raw result of the call. + pub raw: RawCallResult, + /// The revert reason. pub reason: String, - pub gas_used: u64, - pub gas_refunded: u64, - pub stipend: u64, - pub logs: Vec, - pub traces: Option, - pub debug: Option, - pub labels: HashMap, - pub transactions: Option, - pub state_changeset: Option, +} + +impl std::ops::Deref for ExecutionErr { + type Target = RawCallResult; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.raw + } +} + +impl std::ops::DerefMut for ExecutionErr { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.raw + } } #[derive(Debug, thiserror::Error)] pub enum EvmError { /// Error which occurred during execution of a transaction #[error(transparent)] - Execution(Box), + Execution(#[from] Box), /// Error which occurred during ABI encoding/decoding #[error(transparent)] AbiError(#[from] alloy_dyn_abi::Error), @@ -684,62 +672,41 @@ pub enum EvmError { Eyre(#[from] eyre::Error), } +impl From for EvmError { + fn from(err: ExecutionErr) -> Self { + Self::Execution(Box::new(err)) + } +} + +impl From for EvmError { + fn from(err: alloy_sol_types::Error) -> Self { + Self::AbiError(err.into()) + } +} + /// The result of a deployment. #[derive(Debug)] pub struct DeployResult { + /// The raw result of the deployment. + pub raw: RawCallResult, /// The address of the deployed contract pub address: Address, - /// The gas cost of the deployment - pub gas_used: u64, - /// The refunded gas - pub gas_refunded: u64, - /// The logs emitted during the deployment - pub logs: Vec, - /// The traces of the deployment - pub traces: Option, - /// The debug nodes of the call - pub debug: Option, - /// The `revm::Env` after deployment - pub env: Env, - /// The coverage info collected during the deployment - pub coverage: Option, } -/// The result of a call. -#[derive(Debug)] -pub struct CallResult { - pub skipped: bool, - /// Whether the call reverted or not - pub reverted: bool, - /// The decoded result of the call - pub result: DynSolValue, - /// The gas used for the call - pub gas_used: u64, - /// The refunded gas for the call - pub gas_refunded: u64, - /// The initial gas stipend for the transaction - pub stipend: u64, - /// The logs emitted during the call - pub logs: Vec, - /// The labels assigned to addresses during the call - pub labels: HashMap, - /// The traces of the call - pub traces: Option, - /// The coverage info collected during the call - pub coverage: Option, - /// The debug nodes of the call - pub debug: Option, - /// Scripted transactions generated from this call - pub transactions: Option, - /// The changeset of the state. - /// - /// This is only present if the changed state was not committed to the database (i.e. if you - /// used `call` and `call_raw` not `call_committing` or `call_raw_committing`). - pub state_changeset: Option, - /// The `revm::Env` after the call - pub env: Env, - /// breakpoints - pub breakpoints: Breakpoints, +impl std::ops::Deref for DeployResult { + type Target = RawCallResult; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.raw + } +} + +impl std::ops::DerefMut for DeployResult { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.raw + } } /// The result of a raw call. @@ -754,7 +721,7 @@ pub struct RawCallResult { /// This is tracked separately from revert because a snapshot failure can occur without a /// revert, since assert failures are stored in a global variable (ds-test legacy) pub has_snapshot_failure: bool, - /// The raw result of the call + /// The raw result of the call. pub result: Bytes, /// The gas used for the call pub gas_used: u64, @@ -775,23 +742,23 @@ pub struct RawCallResult { /// Scripted transactions generated from this call pub transactions: Option, /// The changeset of the state. - /// - /// This is only present if the changed state was not committed to the database (i.e. if you - /// used `call` and `call_raw` not `call_committing` or `call_raw_committing`). pub state_changeset: Option, /// The `revm::Env` after the call - pub env: Env, + pub env: EnvWithHandlerCfg, /// The cheatcode states after execution pub cheatcodes: Option, /// The raw output of the execution pub out: Option, /// The chisel state - pub chisel_state: Option<(Stack, Vec, InstructionResult)>, + pub chisel_state: Option<(Vec, Vec, InstructionResult)>, + /// The deployments generated during the call + pub deployments: HashMap, } impl Default for RawCallResult { fn default() -> Self { Self { + deployments: HashMap::new(), exit_reason: InstructionResult::Continue, reverted: false, has_snapshot_failure: false, @@ -806,7 +773,7 @@ impl Default for RawCallResult { debug: None, transactions: None, state_changeset: None, - env: Default::default(), + env: EnvWithHandlerCfg::new_with_spec_id(Box::default(), SpecId::LATEST), cheatcodes: Default::default(), out: None, chisel_state: None, @@ -814,15 +781,80 @@ impl Default for RawCallResult { } } -/// Calculates the initial gas stipend for a transaction -fn calc_stipend(calldata: &[u8], spec: SpecId) -> u64 { - let non_zero_data_cost = if SpecId::enabled(spec, SpecId::ISTANBUL) { 16 } else { 68 }; - calldata.iter().fold(21000, |sum, byte| sum + if *byte == 0 { 4 } else { non_zero_data_cost }) +impl RawCallResult { + /// Converts the result of the call into an `EvmError`. + pub fn into_evm_error(self, rd: Option<&RevertDecoder>) -> EvmError { + if self.result[..] == crate::constants::MAGIC_SKIP[..] { + return EvmError::SkipError; + } + let reason = rd.unwrap_or_default().decode(&self.result, Some(self.exit_reason)); + EvmError::Execution(Box::new(self.into_execution_error(reason))) + } + + /// Converts the result of the call into an `ExecutionErr`. + pub fn into_execution_error(self, reason: String) -> ExecutionErr { + ExecutionErr { raw: self, reason } + } + + /// Returns an `EvmError` if the call failed, otherwise returns `self`. + pub fn into_result(self, rd: Option<&RevertDecoder>) -> Result { + if self.exit_reason.is_ok() { + Ok(self) + } else { + Err(self.into_evm_error(rd)) + } + } + + /// Decodes the result of the call with the given function. + pub fn into_decoded_result( + mut self, + func: &Function, + rd: Option<&RevertDecoder>, + ) -> Result { + self = self.into_result(rd)?; + let mut result = func.abi_decode_output(&self.result, false)?; + let decoded_result = if result.len() == 1 { + result.pop().unwrap() + } else { + // combine results into a tuple + DynSolValue::Tuple(result) + }; + Ok(CallResult { raw: self, decoded_result }) + } + + /// Returns the transactions generated from this call. + pub fn transactions(&self) -> Option<&BroadcastableTransactions> { + self.cheatcodes.as_ref().map(|c| &c.broadcastable_transactions) + } +} + +/// The result of a call. +pub struct CallResult { + /// The raw result of the call. + pub raw: RawCallResult, + /// The decoded result of the call. + pub decoded_result: T, +} + +impl std::ops::Deref for CallResult { + type Target = RawCallResult; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.raw + } +} + +impl std::ops::DerefMut for CallResult { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.raw + } } /// Converts the data aggregated in the `inspector` and `call` to a `RawCallResult` fn convert_executed_result( - env: Env, + env: EnvWithHandlerCfg, inspector: InspectorStack, result: ResultAndState, has_snapshot_failure: bool, @@ -830,17 +862,20 @@ fn convert_executed_result( let ResultAndState { result: exec_result, state: state_changeset } = result; let (exit_reason, gas_refunded, gas_used, out) = match exec_result { ExecutionResult::Success { reason, gas_used, gas_refunded, output, .. } => { - (eval_to_instruction_result(reason), gas_refunded, gas_used, Some(output)) + (reason.into(), gas_refunded, gas_used, Some(output)) } ExecutionResult::Revert { gas_used, output } => { // Need to fetch the unused gas (InstructionResult::Revert, 0_u64, gas_used, Some(Output::Call(output))) } - ExecutionResult::Halt { reason, gas_used } => { - (halt_to_instruction_result(reason), 0_u64, gas_used, None) - } + ExecutionResult::Halt { reason, gas_used } => (reason.into(), 0_u64, gas_used, None), }; - let stipend = calc_stipend(&env.tx.data, env.cfg.spec_id); + let stipend = revm::interpreter::gas::validate_initial_tx_gas( + env.spec_id(), + &env.tx.data, + env.tx.transact_to.is_create(), + &env.tx.access_list, + ); let result = match &out { Some(Output::Call(data)) => data.clone(), @@ -881,6 +916,7 @@ fn convert_executed_result( }; Ok(RawCallResult { + deployments: HashMap::new(), exit_reason, reverted: !matches!(exit_reason, return_ok!()), has_snapshot_failure, @@ -901,81 +937,3 @@ fn convert_executed_result( chisel_state, }) } - -fn convert_call_result( - rd: Option<&RevertDecoder>, - func: &Function, - call_result: RawCallResult, -) -> Result { - let RawCallResult { - result, - exit_reason: status, - reverted, - gas_used, - gas_refunded, - stipend, - logs, - labels, - traces, - coverage, - debug, - transactions, - state_changeset, - env, - .. - } = call_result; - - let breakpoints = if let Some(c) = call_result.cheatcodes { - c.breakpoints - } else { - std::collections::HashMap::new() - }; - - match status { - return_ok!() => { - let mut result = func.abi_decode_output(&result, false)?; - let res = if result.len() == 1 { - result.pop().unwrap() - } else { - // combine results into a tuple - DynSolValue::Tuple(result) - }; - Ok(CallResult { - reverted, - result: res, - gas_used, - gas_refunded, - stipend, - logs, - labels, - traces, - coverage, - debug, - transactions, - state_changeset, - env, - breakpoints, - skipped: false, - }) - } - _ => { - if &result == crate::constants::MAGIC_SKIP { - return Err(EvmError::SkipError) - } - let reason = rd.unwrap_or_default().decode(&result, Some(status)); - Err(EvmError::Execution(Box::new(ExecutionErr { - reverted, - reason, - gas_used, - gas_refunded, - stipend, - logs, - traces, - debug, - labels, - transactions, - state_changeset, - }))) - } - } -} diff --git a/crates/evm/evm/src/executors/tracing.rs b/crates/evm/evm/src/executors/tracing.rs index 19db5fc57..08c5d92ef 100644 --- a/crates/evm/evm/src/executors/tracing.rs +++ b/crates/evm/evm/src/executors/tracing.rs @@ -1,8 +1,8 @@ use crate::executors::{Executor, ExecutorBuilder}; -use foundry_compilers::EvmVersion; +use foundry_compilers::artifacts::EvmVersion; use foundry_config::{utils::evm_spec_id, Chain, Config}; use foundry_evm_core::{backend::Backend, fork::CreateFork, opts::EvmOpts}; -use revm::primitives::Env; +use revm::primitives::{Env, SpecId}; use std::ops::{Deref, DerefMut}; /// A default executor with tracing enabled @@ -11,13 +11,13 @@ pub struct TracingExecutor { } impl TracingExecutor { - pub async fn new( + pub fn new( env: revm::primitives::Env, fork: Option, version: Option, debug: bool, ) -> Self { - let db = Backend::spawn(fork).await; + let db = Backend::spawn(fork); Self { // configures a bare version of the evm executor: no cheatcode inspector is enabled, // tracing will be enabled only for the targeted transaction @@ -28,6 +28,11 @@ impl TracingExecutor { } } + /// Returns the spec id of the executor + pub fn spec_id(&self) -> SpecId { + self.executor.spec_id() + } + /// uses the fork block number from the config pub async fn get_fork_material( config: &Config, @@ -40,7 +45,7 @@ impl TracingExecutor { let fork = evm_opts.get_fork(config, env.clone()); - Ok((env, fork, evm_opts.get_remote_chain_id())) + Ok((env, fork, evm_opts.get_remote_chain_id().await)) } } diff --git a/crates/evm/evm/src/inspectors/access_list.rs b/crates/evm/evm/src/inspectors/access_list.rs deleted file mode 100644 index ea43336a7..000000000 --- a/crates/evm/evm/src/inspectors/access_list.rs +++ /dev/null @@ -1,79 +0,0 @@ -use alloy_primitives::{Address, B256}; -use alloy_rpc_types::{AccessList, AccessListItem}; -use hashbrown::{HashMap, HashSet}; -use revm::{ - interpreter::{opcode, Interpreter}, - Database, EVMData, Inspector, -}; - -/// An inspector that collects touched accounts and storage slots. -#[derive(Debug, Default)] -pub struct AccessListTracer { - excluded: HashSet
, - access_list: HashMap>, -} - -impl AccessListTracer { - pub fn new( - access_list: AccessList, - from: Address, - to: Address, - precompiles: Vec
, - ) -> Self { - AccessListTracer { - excluded: [from, to].iter().chain(precompiles.iter()).copied().collect(), - access_list: access_list - .0 - .iter() - .map(|v| (v.address, v.storage_keys.iter().copied().collect())) - .collect(), - } - } - - pub fn access_list(&self) -> AccessList { - AccessList( - self.access_list - .iter() - .map(|(address, slots)| AccessListItem { - address: *address, - storage_keys: slots.iter().copied().collect(), - }) - .collect::>(), - ) - } -} - -impl Inspector for AccessListTracer { - #[inline] - fn step(&mut self, interpreter: &mut Interpreter<'_>, _data: &mut EVMData<'_, DB>) { - match interpreter.current_opcode() { - opcode::SLOAD | opcode::SSTORE => { - if let Ok(slot) = interpreter.stack().peek(0) { - let cur_contract = interpreter.contract.address; - self.access_list.entry(cur_contract).or_default().insert(slot.into()); - } - } - opcode::EXTCODECOPY | - opcode::EXTCODEHASH | - opcode::EXTCODESIZE | - opcode::BALANCE | - opcode::SELFDESTRUCT => { - if let Ok(slot) = interpreter.stack().peek(0) { - let addr: Address = Address::from_word(slot.into()); - if !self.excluded.contains(&addr) { - self.access_list.entry(addr).or_default(); - } - } - } - opcode::DELEGATECALL | opcode::CALL | opcode::STATICCALL | opcode::CALLCODE => { - if let Ok(slot) = interpreter.stack().peek(1) { - let addr: Address = Address::from_word(slot.into()); - if !self.excluded.contains(&addr) { - self.access_list.entry(addr).or_default(); - } - } - } - _ => (), - } - } -} diff --git a/crates/evm/evm/src/inspectors/chisel_state.rs b/crates/evm/evm/src/inspectors/chisel_state.rs index a4d3a1895..023389ed4 100644 --- a/crates/evm/evm/src/inspectors/chisel_state.rs +++ b/crates/evm/evm/src/inspectors/chisel_state.rs @@ -1,6 +1,7 @@ +use alloy_primitives::U256; use revm::{ - interpreter::{InstructionResult, Interpreter, Stack}, - Database, Inspector, + interpreter::{InstructionResult, Interpreter}, + Database, EvmContext, Inspector, }; /// An inspector for Chisel @@ -9,7 +10,7 @@ pub struct ChiselState { /// The PC of the final instruction pub final_pc: usize, /// The final state of the REPL contract call - pub state: Option<(Stack, Vec, InstructionResult)>, + pub state: Option<(Vec, Vec, InstructionResult)>, } impl ChiselState { @@ -21,13 +22,13 @@ impl ChiselState { } impl Inspector for ChiselState { - #[inline] - fn step_end(&mut self, interp: &mut Interpreter<'_>, _: &mut revm::EVMData<'_, DB>) { + #[cold] + fn step_end(&mut self, interp: &mut Interpreter, _context: &mut EvmContext) { // If we are at the final pc of the REPL contract execution, set the state. // Subtraction can't overflow because `pc` is always at least 1 in `step_end`. if self.final_pc == interp.program_counter() - 1 { self.state = Some(( - interp.stack().clone(), + interp.stack.data().clone(), interp.shared_memory.context_memory().to_vec(), interp.instruction_result, )) diff --git a/crates/evm/evm/src/inspectors/debugger.rs b/crates/evm/evm/src/inspectors/debugger.rs index bb327b36e..c970cd671 100644 --- a/crates/evm/evm/src/inspectors/debugger.rs +++ b/crates/evm/evm/src/inspectors/debugger.rs @@ -1,17 +1,17 @@ -use alloy_primitives::{Address, Bytes}; -use foundry_common::{ErrorExt, SELECTOR_LEN}; +use alloy_primitives::Address; +use arrayvec::ArrayVec; +use foundry_common::ErrorExt; use foundry_evm_core::{ backend::DatabaseExt, - constants::CHEATCODE_ADDRESS, - debug::{DebugArena, DebugNode, DebugStep, Instruction}, + debug::{DebugArena, DebugNode, DebugStep}, utils::gas_used, }; use revm::{ interpreter::{ - opcode::{self, spec_opcode_gas}, - CallInputs, CreateInputs, Gas, InstructionResult, Interpreter, + opcode, CallInputs, CallOutcome, CreateInputs, CreateOutcome, Gas, InstructionResult, + Interpreter, InterpreterResult, }, - EVMData, Inspector, + EvmContext, Inspector, }; use revm_inspectors::tracing::types::CallKind; @@ -37,124 +37,114 @@ impl Debugger { pub fn exit(&mut self) { if let Some(parent_id) = self.arena.arena[self.head].parent { let DebugNode { depth, address, kind, .. } = self.arena.arena[parent_id]; - self.context = address; - self.head = - self.arena.push_node(DebugNode { depth, address, kind, ..Default::default() }); + self.enter(depth, address, kind); } } } impl Inspector for Debugger { - #[inline] - fn step(&mut self, interpreter: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { - let pc = interpreter.program_counter(); - let op = interpreter.current_opcode(); - - // Get opcode information - let opcode_infos = spec_opcode_gas(data.env.cfg.spec_id); - let opcode_info = &opcode_infos[op as usize]; + fn step(&mut self, interp: &mut Interpreter, ecx: &mut EvmContext) { + let pc = interp.program_counter(); + let op = interp.current_opcode(); // Extract the push bytes - let push_size = if opcode_info.is_push() { (op - opcode::PUSH1 + 1) as usize } else { 0 }; - let push_bytes = match push_size { - 0 => None, - n => { - let start = pc + 1; - let end = start + n; - Some(interpreter.contract.bytecode.bytecode()[start..end].to_vec()) - } + let push_size = if (opcode::PUSH1..=opcode::PUSH32).contains(&op) { + (op - opcode::PUSH0) as usize + } else { + 0 }; + let push_bytes = (push_size > 0).then(|| { + let start = pc + 1; + let end = start + push_size; + let slice = &interp.contract.bytecode.bytecode()[start..end]; + debug_assert!(slice.len() <= 32); + let mut array = ArrayVec::new(); + array.try_extend_from_slice(slice).unwrap(); + array + }); let total_gas_used = gas_used( - data.env.cfg.spec_id, - interpreter.gas.limit().saturating_sub(interpreter.gas.remaining()), - interpreter.gas.refunded() as u64, + ecx.spec_id(), + interp.gas.limit().saturating_sub(interp.gas.remaining()), + interp.gas.refunded() as u64, ); + // Reuse the memory from the previous step if the previous opcode did not modify it. + let memory = self.arena.arena[self.head] + .steps + .last() + .filter(|step| !step.opcode_modifies_memory()) + .map(|step| step.memory.clone()) + .unwrap_or_else(|| interp.shared_memory.context_memory().to_vec().into()); + self.arena.arena[self.head].steps.push(DebugStep { pc, - stack: interpreter.stack().data().clone(), - memory: interpreter.shared_memory.context_memory().to_vec(), - calldata: interpreter.contract().input.to_vec(), - returndata: interpreter.return_data_buffer.to_vec(), - instruction: Instruction::OpCode(op), - push_bytes, + stack: interp.stack().data().clone(), + memory, + calldata: interp.contract().input.clone(), + returndata: interp.return_data_buffer.clone(), + instruction: op, + push_bytes: push_bytes.unwrap_or_default(), total_gas_used, }); } - #[inline] - fn call( - &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CallInputs, - ) -> (InstructionResult, Gas, Bytes) { + fn call(&mut self, ecx: &mut EvmContext, inputs: &mut CallInputs) -> Option { self.enter( - data.journaled_state.depth() as usize, - call.context.code_address, - call.context.scheme.into(), + ecx.journaled_state.depth() as usize, + inputs.bytecode_address, + inputs.scheme.into(), ); - if call.contract == CHEATCODE_ADDRESS { - if let Some(selector) = call.input.get(..SELECTOR_LEN) { - self.arena.arena[self.head].steps.push(DebugStep { - instruction: Instruction::Cheatcode(selector.try_into().unwrap()), - ..Default::default() - }); - } - } - - (InstructionResult::Continue, Gas::new(call.gas_limit), Bytes::new()) + None } - #[inline] fn call_end( &mut self, - _: &mut EVMData<'_, DB>, - _: &CallInputs, - gas: Gas, - status: InstructionResult, - retdata: Bytes, - ) -> (InstructionResult, Gas, Bytes) { + _context: &mut EvmContext, + _inputs: &CallInputs, + outcome: CallOutcome, + ) -> CallOutcome { self.exit(); - (status, gas, retdata) + outcome } - #[inline] fn create( &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CreateInputs, - ) -> (InstructionResult, Option
, Gas, Bytes) { - // TODO: Does this increase gas cost? - if let Err(err) = data.journaled_state.load_account(call.caller, data.db) { - let gas = Gas::new(call.gas_limit); - return (InstructionResult::Revert, None, gas, err.abi_encode_revert()); + ecx: &mut EvmContext, + inputs: &mut CreateInputs, + ) -> Option { + if let Err(err) = ecx.load_account(inputs.caller) { + let gas = Gas::new(inputs.gas_limit); + return Some(CreateOutcome::new( + InterpreterResult { + result: InstructionResult::Revert, + output: err.abi_encode_revert(), + gas, + }, + None, + )); } - let nonce = data.journaled_state.account(call.caller).info.nonce; + let nonce = ecx.journaled_state.account(inputs.caller).info.nonce; self.enter( - data.journaled_state.depth() as usize, - call.created_address(nonce), + ecx.journaled_state.depth() as usize, + inputs.created_address(nonce), CallKind::Create, ); - (InstructionResult::Continue, None, Gas::new(call.gas_limit), Bytes::new()) + None } - #[inline] fn create_end( &mut self, - _: &mut EVMData<'_, DB>, - _: &CreateInputs, - status: InstructionResult, - address: Option
, - gas: Gas, - retdata: Bytes, - ) -> (InstructionResult, Option
, Gas, Bytes) { + _context: &mut EvmContext, + _inputs: &CreateInputs, + outcome: CreateOutcome, + ) -> CreateOutcome { self.exit(); - (status, address, gas, retdata) + outcome } } diff --git a/crates/evm/evm/src/inspectors/logs.rs b/crates/evm/evm/src/inspectors/logs.rs index 23f72547c..d26420187 100644 --- a/crates/evm/evm/src/inspectors/logs.rs +++ b/crates/evm/evm/src/inspectors/logs.rs @@ -1,4 +1,4 @@ -use alloy_primitives::{Address, Bytes, Log, B256}; +use alloy_primitives::{Bytes, Log}; use alloy_sol_types::{SolEvent, SolInterface, SolValue}; use foundry_common::{ console::{patch_hh_console_selector, Console, HardhatConsole, HARDHAT_CONSOLE_ADDRESS}, @@ -6,8 +6,8 @@ use foundry_common::{ ErrorExt, }; use revm::{ - interpreter::{CallInputs, Gas, InstructionResult}, - Database, EVMData, Inspector, + interpreter::{CallInputs, CallOutcome, Gas, InstructionResult, InterpreterResult}, + Database, EvmContext, Inspector, }; /// An inspector that collects logs during execution. @@ -38,23 +38,31 @@ impl LogCollector { } impl Inspector for LogCollector { - fn log(&mut self, _: &mut EVMData<'_, DB>, address: &Address, topics: &[B256], data: &Bytes) { - if let Some(log) = Log::new(*address, topics.to_vec(), data.clone()) { - self.logs.push(log); - } + fn log(&mut self, _context: &mut EvmContext, log: &Log) { + self.logs.push(log.clone()); } + #[inline] fn call( &mut self, - _: &mut EVMData<'_, DB>, - call: &mut CallInputs, - ) -> (InstructionResult, Gas, Bytes) { - let (status, reason) = if call.contract == HARDHAT_CONSOLE_ADDRESS { - self.hardhat_log(call.input.to_vec()) - } else { - (InstructionResult::Continue, Bytes::new()) - }; - (status, Gas::new(call.gas_limit), reason) + _context: &mut EvmContext, + inputs: &mut CallInputs, + ) -> Option { + if inputs.target_address == HARDHAT_CONSOLE_ADDRESS { + let (res, out) = self.hardhat_log(inputs.input.to_vec()); + if res != InstructionResult::Continue { + return Some(CallOutcome { + result: InterpreterResult { + result: res, + output: out, + gas: Gas::new(inputs.gas_limit), + }, + memory_offset: inputs.return_memory_offset.clone(), + }) + } + } + + None } } @@ -62,6 +70,9 @@ impl Inspector for LogCollector { fn convert_hh_log_to_event(call: HardhatConsole::HardhatConsoleCalls) -> Log { // Convert the parameters of the call to their string representation using `ConsoleFmt`. let fmt = call.fmt(Default::default()); - Log::new(Address::default(), vec![Console::log::SIGNATURE_HASH], fmt.abi_encode().into()) - .unwrap_or_else(|| Log { ..Default::default() }) + Log::new_unchecked( + HARDHAT_CONSOLE_ADDRESS, + vec![Console::log::SIGNATURE_HASH], + fmt.abi_encode().into(), + ) } diff --git a/crates/evm/evm/src/inspectors/mod.rs b/crates/evm/evm/src/inspectors/mod.rs index 20dd340d3..786786b28 100644 --- a/crates/evm/evm/src/inspectors/mod.rs +++ b/crates/evm/evm/src/inspectors/mod.rs @@ -5,8 +5,7 @@ pub use foundry_evm_coverage::CoverageCollector; pub use foundry_evm_fuzz::Fuzzer; pub use foundry_evm_traces::{StackSnapshotType, TracingInspector, TracingInspectorConfig}; -mod access_list; -pub use access_list::AccessListTracer; +pub use revm_inspectors::access_list::AccessListInspector; mod chisel_state; pub use chisel_state::ChiselState; @@ -17,8 +16,5 @@ pub use debugger::Debugger; mod logs; pub use logs::LogCollector; -mod printer; -pub use printer::TracePrinter; - mod stack; pub use stack::{InspectorData, InspectorStack, InspectorStackBuilder}; diff --git a/crates/evm/evm/src/inspectors/printer.rs b/crates/evm/evm/src/inspectors/printer.rs deleted file mode 100644 index 02a47fda1..000000000 --- a/crates/evm/evm/src/inspectors/printer.rs +++ /dev/null @@ -1,65 +0,0 @@ -use alloy_primitives::{Address, Bytes}; -use revm::{ - interpreter::{opcode, CallInputs, CreateInputs, Gas, InstructionResult, Interpreter}, - Database, EVMData, Inspector, -}; - -#[derive(Clone, Debug, Default)] -#[non_exhaustive] -pub struct TracePrinter; - -impl Inspector for TracePrinter { - // get opcode by calling `interp.contract.opcode(interp.program_counter())`. - // all other information can be obtained from interp. - fn step(&mut self, interp: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { - let opcode = interp.current_opcode(); - let opcode_str = opcode::OPCODE_JUMPMAP[opcode as usize]; - let gas_remaining = interp.gas.remaining(); - println!( - "depth:{}, PC:{}, gas:{:#x}({}), OPCODE: {:?}({:?}) refund:{:#x}({}) Stack:{:?}, Data size:{}, Data: 0x{}", - data.journaled_state.depth(), - interp.program_counter(), - gas_remaining, - gas_remaining, - opcode_str.unwrap_or(""), - opcode, - interp.gas.refunded(), - interp.gas.refunded(), - interp.stack.data(), - interp.shared_memory.len(), - hex::encode(interp.shared_memory.context_memory()), - ); - } - - fn call( - &mut self, - _data: &mut EVMData<'_, DB>, - inputs: &mut CallInputs, - ) -> (InstructionResult, Gas, Bytes) { - println!( - "SM CALL: {},context:{:?}, is_static:{:?}, transfer:{:?}, input_size:{:?}", - inputs.contract, - inputs.context, - inputs.is_static, - inputs.transfer, - inputs.input.len(), - ); - (InstructionResult::Continue, Gas::new(0), Bytes::new()) - } - - fn create( - &mut self, - _data: &mut EVMData<'_, DB>, - inputs: &mut CreateInputs, - ) -> (InstructionResult, Option
, Gas, Bytes) { - println!( - "CREATE CALL: caller:{}, scheme:{:?}, value:{:?}, init_code:{:?}, gas:{:?}", - inputs.caller, - inputs.scheme, - inputs.value, - hex::encode(&inputs.init_code), - inputs.gas_limit - ); - (InstructionResult::Continue, None, Gas::new(0), Bytes::new()) - } -} diff --git a/crates/evm/evm/src/inspectors/stack.rs b/crates/evm/evm/src/inspectors/stack.rs index 81c76bb04..85e6675a4 100644 --- a/crates/evm/evm/src/inspectors/stack.rs +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -1,23 +1,23 @@ use super::{ Cheatcodes, CheatsConfig, ChiselState, CoverageCollector, Debugger, Fuzzer, LogCollector, - StackSnapshotType, TracePrinter, TracingInspector, TracingInspectorConfig, + StackSnapshotType, TracingInspector, TracingInspectorConfig, }; -use alloy_primitives::{Address, Bytes, Log, B256, U256}; +use alloy_primitives::{Address, Bytes, Log, U256}; use foundry_evm_core::{ - backend::DatabaseExt, + backend::{update_state, DatabaseExt}, debug::DebugArena, - utils::{eval_to_instruction_result, halt_to_instruction_result}, + InspectorExt, }; use foundry_evm_coverage::HitMaps; use foundry_evm_traces::CallTraceArena; use revm::{ - evm_inner, + inspectors::CustomPrintTracer, interpreter::{ - return_revert, CallInputs, CallScheme, CreateInputs, Gas, InstructionResult, Interpreter, - Stack, + CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, Gas, InstructionResult, + Interpreter, InterpreterResult, }, - primitives::{BlockEnv, Env, ExecutionResult, Output, State, TransactTo}, - DatabaseCommit, EVMData, Inspector, + primitives::{BlockEnv, Env, EnvWithHandlerCfg, ExecutionResult, Output, TransactTo}, + DatabaseCommit, EvmContext, Inspector, }; use std::{collections::HashMap, sync::Arc}; @@ -142,8 +142,6 @@ impl InspectorStackBuilder { } /// Builds the stack of inspectors to use when transacting/committing on the EVM. - /// - /// See also [`revm::Evm::inspect_ref`] and [`revm::Evm::commit_ref`]. pub fn build(self) -> InspectorStack { let Self { block, @@ -194,43 +192,48 @@ impl InspectorStackBuilder { /// dispatch. #[macro_export] macro_rules! call_inspectors { - ([$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $call:expr $(,)?) => {{$( - if let Some($id) = $inspector { - $call - } - )+}} + ([$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $call:expr $(,)?) => { + $( + if let Some($id) = $inspector { + $call + } + )+ + } } /// Same as [call_inspectors] macro, but with depth adjustment for isolated execution. macro_rules! call_inspectors_adjust_depth { + (#[no_ret] [$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $call:expr, $self:ident, $data:ident $(,)?) => { + $data.journaled_state.depth += $self.in_inner_context as usize; + $( + if let Some($id) = $inspector { + $call + } + )+ + $data.journaled_state.depth -= $self.in_inner_context as usize; + }; ([$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $call:expr, $self:ident, $data:ident $(,)?) => { if $self.in_inner_context { $data.journaled_state.depth += 1; - } - {$( - if let Some($id) = $inspector { - if let Some(result) = $call { - if $self.in_inner_context { + $( + if let Some($id) = $inspector { + if let Some(result) = $call { $data.journaled_state.depth -= 1; + return result; } - return result; } - } - )+} - if $self.in_inner_context { + )+ $data.journaled_state.depth -= 1; + } else { + $( + if let Some($id) = $inspector { + if let Some(result) = $call { + return result; + } + } + )+ } - } -} - -/// Helper method which updates data in the state with the data from the database. -fn update_state(state: &mut State, db: &mut DB) { - for (addr, acc) in state.iter_mut() { - acc.info = db.basic(*addr).unwrap().unwrap_or_default(); - for (key, val) in acc.storage.iter_mut() { - val.present_value = db.storage(*addr, *key).unwrap(); - } - } + }; } /// The collected results of [`InspectorStack`]. @@ -241,14 +244,14 @@ pub struct InspectorData { pub debug: Option, pub coverage: Option, pub cheatcodes: Option, - pub chisel_state: Option<(Stack, Vec, InstructionResult)>, + pub chisel_state: Option<(Vec, Vec, InstructionResult)>, } /// Contains data about the state of outer/main EVM which created and invoked the inner EVM context. /// Used to adjust EVM state while in inner context. /// /// We need this to avoid breaking changes due to EVM behavior differences in isolated vs -/// non-isolated mode. For descriptions and workarounds for those changes see: https://github.com/foundry-rs/foundry/pull/7186#issuecomment-1959102195 +/// non-isolated mode. For descriptions and workarounds for those changes see: #[derive(Debug, Clone)] pub struct InnerContextData { /// The sender of the inner EVM context. @@ -274,7 +277,7 @@ pub struct InspectorStack { pub debugger: Option, pub fuzzer: Option, pub log_collector: Option, - pub printer: Option, + pub printer: Option, pub tracer: Option, pub enable_isolation: bool, @@ -294,6 +297,27 @@ impl InspectorStack { Self::default() } + /// Logs the status of the inspectors. + pub fn log_status(&self) { + trace!(enabled=%{ + let mut enabled = Vec::with_capacity(16); + macro_rules! push { + ($($id:ident),* $(,)?) => { + $( + if self.$id.is_some() { + enabled.push(stringify!($id)); + } + )* + }; + } + push!(cheatcodes, chisel_state, coverage, debugger, fuzzer, log_collector, printer, tracer); + if self.enable_isolation { + enabled.push("isolation"); + } + format!("[{}]", enabled.join(", ")) + }); + } + /// Set variables from an environment for the relevant inspectors. #[inline] pub fn set_env(&mut self, env: &Env) { @@ -375,7 +399,6 @@ impl InspectorStack { record_stack_snapshots: StackSnapshotType::None, record_state_diff: false, exclude_precompile_calls: false, - record_call_return_data: true, record_logs: true, }) }); @@ -389,9 +412,7 @@ impl InspectorStack { labels: self .cheatcodes .as_ref() - .map(|cheatcodes| { - cheatcodes.labels.clone().into_iter().map(|l| (l.0, l.1)).collect() - }) + .map(|cheatcodes| cheatcodes.labels.clone()) .unwrap_or_default(), traces: self.tracer.map(|tracer| tracer.get_traces().clone()), debug: self.debugger.map(|debugger| debugger.arena), @@ -403,146 +424,174 @@ impl InspectorStack { fn do_call_end( &mut self, - data: &mut EVMData<'_, DB>, - call: &CallInputs, - remaining_gas: Gas, - status: InstructionResult, - retdata: Bytes, - ) -> (InstructionResult, Gas, Bytes) { + ecx: &mut EvmContext<&mut DB>, + inputs: &CallInputs, + outcome: CallOutcome, + ) -> CallOutcome { + let result = outcome.result.result; call_inspectors_adjust_depth!( [ &mut self.fuzzer, &mut self.debugger, &mut self.tracer, - &mut self.coverage, - &mut self.log_collector, &mut self.cheatcodes, - &mut self.printer + &mut self.printer, ], |inspector| { - let (new_status, new_gas, new_retdata) = - inspector.call_end(data, call, remaining_gas, status, retdata.clone()); + let new_outcome = inspector.call_end(ecx, inputs, outcome.clone()); // If the inspector returns a different status or a revert with a non-empty message, // we assume it wants to tell us something - if new_status != status || - (new_status == InstructionResult::Revert && new_retdata != retdata) - { - Some((new_status, new_gas, new_retdata)) - } else { - None - } + let different = new_outcome.result.result != result || + (new_outcome.result.result == InstructionResult::Revert && + new_outcome.output() != outcome.output()); + different.then_some(new_outcome) }, self, - data + ecx ); - (status, remaining_gas, retdata) + + outcome } fn transact_inner( &mut self, - data: &mut EVMData<'_, DB>, + ecx: &mut EvmContext<&mut DB>, transact_to: TransactTo, caller: Address, input: Bytes, gas_limit: u64, value: U256, - ) -> (InstructionResult, Option
, Gas, Bytes) { - data.db.commit(data.journaled_state.state.clone()); + ) -> (InterpreterResult, Option
) { + let ecx = &mut ecx.inner; + + ecx.db.commit(ecx.journaled_state.state.clone()); - let nonce = data + let nonce = ecx .journaled_state - .load_account(caller, data.db) + .load_account(caller, &mut ecx.db) .expect("failed to load caller") .0 .info .nonce; - let cached_env = data.env.clone(); + let cached_env = ecx.env.clone(); - data.env.block.basefee = U256::ZERO; - data.env.tx.caller = caller; - data.env.tx.transact_to = transact_to.clone(); - data.env.tx.data = input; - data.env.tx.value = value; - data.env.tx.nonce = Some(nonce); + ecx.env.block.basefee = U256::ZERO; + ecx.env.tx.caller = caller; + ecx.env.tx.transact_to = transact_to.clone(); + ecx.env.tx.data = input; + ecx.env.tx.value = value; + ecx.env.tx.nonce = Some(nonce); // Add 21000 to the gas limit to account for the base cost of transaction. - // We might have modified block gas limit earlier and revm will reject tx with gas limit > - // block gas limit, so we adjust. - data.env.tx.gas_limit = std::cmp::min(gas_limit + 21000, data.env.block.gas_limit.to()); - data.env.tx.gas_price = U256::ZERO; + ecx.env.tx.gas_limit = gas_limit + 21000; + // If we haven't disabled gas limit checks, ensure that transaction gas limit will not + // exceed block gas limit. + if !ecx.env.cfg.disable_block_gas_limit { + ecx.env.tx.gas_limit = + std::cmp::min(ecx.env.tx.gas_limit, ecx.env.block.gas_limit.to()); + } + ecx.env.tx.gas_price = U256::ZERO; self.inner_context_data = Some(InnerContextData { - sender: data.env.tx.caller, + sender: ecx.env.tx.caller, original_origin: cached_env.tx.caller, original_sender_nonce: nonce, - is_create: matches!(transact_to, TransactTo::Create(_)), + is_create: matches!(transact_to, TransactTo::Create), }); self.in_inner_context = true; - let res = evm_inner(data.env, data.db, Some(self)).transact(); + + let env = EnvWithHandlerCfg::new_with_spec_id(ecx.env.clone(), ecx.spec_id()); + let res = { + let mut evm = crate::utils::new_evm_with_inspector(&mut *ecx.db, env, &mut *self); + let res = evm.transact(); + + // need to reset the env in case it was modified via cheatcodes during execution + ecx.env = evm.context.evm.inner.env; + res + }; + self.in_inner_context = false; self.inner_context_data = None; - data.env.tx = cached_env.tx; - data.env.block.basefee = cached_env.block.basefee; + ecx.env.tx = cached_env.tx; + ecx.env.block.basefee = cached_env.block.basefee; let mut gas = Gas::new(gas_limit); let Ok(mut res) = res else { // Should we match, encode and propagate error as a revert reason? - return (InstructionResult::Revert, None, gas, Bytes::new()); + let result = + InterpreterResult { result: InstructionResult::Revert, output: Bytes::new(), gas }; + return (result, None) }; // Commit changes after transaction - data.db.commit(res.state.clone()); + ecx.db.commit(res.state.clone()); // Update both states with new DB data after commit. - update_state(&mut data.journaled_state.state, data.db); - update_state(&mut res.state, data.db); + if let Err(e) = update_state(&mut ecx.journaled_state.state, &mut ecx.db, None) { + let res = InterpreterResult { + result: InstructionResult::Revert, + output: Bytes::from(e.to_string()), + gas, + }; + return (res, None) + } + if let Err(e) = update_state(&mut res.state, &mut ecx.db, None) { + let res = InterpreterResult { + result: InstructionResult::Revert, + output: Bytes::from(e.to_string()), + gas, + }; + return (res, None) + } // Merge transaction journal into the active journal. for (addr, acc) in res.state { - if let Some(acc_mut) = data.journaled_state.state.get_mut(&addr) { + if let Some(acc_mut) = ecx.journaled_state.state.get_mut(&addr) { acc_mut.status |= acc.status; for (key, val) in acc.storage { - if !acc_mut.storage.contains_key(&key) { - acc_mut.storage.insert(key, val); - } + acc_mut.storage.entry(key).or_insert(val); } } else { - data.journaled_state.state.insert(addr, acc); + ecx.journaled_state.state.insert(addr, acc); } } - match res.result { + let (result, address, output) = match res.result { ExecutionResult::Success { reason, gas_used, gas_refunded, logs: _, output } => { gas.set_refund(gas_refunded as i64); - gas.record_cost(gas_used); + let _ = gas.record_cost(gas_used); let address = match output { Output::Create(_, address) => address, Output::Call(_) => None, }; - (eval_to_instruction_result(reason), address, gas, output.into_data()) + (reason.into(), address, output.into_data()) } ExecutionResult::Halt { reason, gas_used } => { - gas.record_cost(gas_used); - (halt_to_instruction_result(reason), None, gas, Bytes::new()) + let _ = gas.record_cost(gas_used); + (reason.into(), None, Bytes::new()) } ExecutionResult::Revert { gas_used, output } => { - gas.record_cost(gas_used); - (InstructionResult::Revert, None, gas, output) + let _ = gas.record_cost(gas_used); + (InstructionResult::Revert, None, output) } - } + }; + (InterpreterResult { result, output, gas }, address) } /// Adjusts the EVM data for the inner EVM context. /// Should be called on the top-level call of inner context (depth == 0 && /// self.in_inner_context) Decreases sender nonce for CALLs to keep backwards compatibility /// Updates tx.origin to the value before entering inner context - fn adjust_evm_data_for_inner_context(&mut self, data: &mut EVMData<'_, DB>) { + fn adjust_evm_data_for_inner_context( + &mut self, + ecx: &mut EvmContext<&mut DB>, + ) { let inner_context_data = self.inner_context_data.as_ref().expect("should be called in inner context"); - let sender_acc = data + let sender_acc = ecx .journaled_state .state .get_mut(&inner_context_data.sender) @@ -550,116 +599,71 @@ impl InspectorStack { if !inner_context_data.is_create { sender_acc.info.nonce = inner_context_data.original_sender_nonce; } - data.env.tx.caller = inner_context_data.original_origin; + ecx.env.tx.caller = inner_context_data.original_origin; } } -impl Inspector for InspectorStack { - fn initialize_interp(&mut self, interpreter: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { - let res = interpreter.instruction_result; +// NOTE: `&mut DB` is required because we recurse inside of `transact_inner` and we need to use the +// same reference to the DB, otherwise there's infinite recursion and Rust fails to instatiate this +// implementation. This currently works because internally we only use `&mut DB` anyways, but if +// this ever needs to be changed, this can be reverted back to using just `DB`, and instead using +// dynamic dispatch (`&mut dyn ...`) in `transact_inner`. +impl Inspector<&mut DB> for InspectorStack { + fn initialize_interp(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<&mut DB>) { call_inspectors_adjust_depth!( - [ - &mut self.debugger, - &mut self.coverage, - &mut self.tracer, - &mut self.log_collector, - &mut self.cheatcodes, - &mut self.printer - ], - |inspector| { - inspector.initialize_interp(interpreter, data); - - // Allow inspectors to exit early - if interpreter.instruction_result != res { - Some(()) - } else { - None - } - }, + #[no_ret] + [&mut self.coverage, &mut self.tracer, &mut self.cheatcodes, &mut self.printer], + |inspector| inspector.initialize_interp(interpreter, ecx), self, - data + ecx ); } - fn step(&mut self, interpreter: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { - let res = interpreter.instruction_result; + fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<&mut DB>) { call_inspectors_adjust_depth!( + #[no_ret] [ &mut self.fuzzer, &mut self.debugger, &mut self.tracer, &mut self.coverage, - &mut self.log_collector, &mut self.cheatcodes, - &mut self.printer + &mut self.printer, ], - |inspector| { - inspector.step(interpreter, data); - - // Allow inspectors to exit early - if interpreter.instruction_result != res { - Some(()) - } else { - None - } - }, + |inspector| inspector.step(interpreter, ecx), self, - data + ecx ); } - fn log( - &mut self, - evm_data: &mut EVMData<'_, DB>, - address: &Address, - topics: &[B256], - data: &Bytes, - ) { + fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<&mut DB>) { call_inspectors_adjust_depth!( - [&mut self.tracer, &mut self.log_collector, &mut self.cheatcodes, &mut self.printer], - |inspector| { - inspector.log(evm_data, address, topics, data); - None - }, + #[no_ret] + [&mut self.tracer, &mut self.cheatcodes, &mut self.printer], + |inspector| inspector.step_end(interpreter, ecx), self, - evm_data + ecx ); } - fn step_end(&mut self, interpreter: &mut Interpreter<'_>, data: &mut EVMData<'_, DB>) { - let res = interpreter.instruction_result; + fn log(&mut self, ecx: &mut EvmContext<&mut DB>, log: &Log) { call_inspectors_adjust_depth!( - [ - &mut self.debugger, - &mut self.tracer, - &mut self.log_collector, - &mut self.cheatcodes, - &mut self.printer, - &mut self.chisel_state - ], - |inspector| { - inspector.step_end(interpreter, data); - - // Allow inspectors to exit early - if interpreter.instruction_result != res { - Some(()) - } else { - None - } - }, + #[no_ret] + [&mut self.tracer, &mut self.log_collector, &mut self.cheatcodes, &mut self.printer], + |inspector| inspector.log(ecx, log), self, - data + ecx ); } fn call( &mut self, - data: &mut EVMData<'_, DB>, + ecx: &mut EvmContext<&mut DB>, call: &mut CallInputs, - ) -> (InstructionResult, Gas, Bytes) { - if self.in_inner_context && data.journaled_state.depth == 0 { - self.adjust_evm_data_for_inner_context(data); - return (InstructionResult::Continue, Gas::new(call.gas_limit), Bytes::new()); + ) -> Option { + if self.in_inner_context && ecx.journaled_state.depth == 0 { + self.adjust_evm_data_for_inner_context(ecx); + return None; } call_inspectors_adjust_depth!( @@ -667,177 +671,152 @@ impl Inspector for InspectorStack { &mut self.fuzzer, &mut self.debugger, &mut self.tracer, - &mut self.coverage, &mut self.log_collector, &mut self.cheatcodes, - &mut self.printer + &mut self.printer, ], |inspector| { - let (status, gas, retdata) = inspector.call(data, call); - - // Allow inspectors to exit early - if status != InstructionResult::Continue { - Some((status, gas, retdata)) - } else { - None + let mut out = None; + if let Some(output) = inspector.call(ecx, call) { + if output.result.result != InstructionResult::Continue { + out = Some(Some(output)); + } } + out }, self, - data + ecx ); if self.enable_isolation && - call.context.scheme == CallScheme::Call && + call.scheme == CallScheme::Call && !self.in_inner_context && - data.journaled_state.depth == 1 + ecx.journaled_state.depth == 1 { - let (res, _, gas, output) = self.transact_inner( - data, - TransactTo::Call(call.contract), - call.context.caller, + let (result, _) = self.transact_inner( + ecx, + TransactTo::Call(call.target_address), + call.caller, call.input.clone(), call.gas_limit, - call.transfer.value, + call.value.get(), ); - return (res, gas, output); + return Some(CallOutcome { result, memory_offset: call.return_memory_offset.clone() }) } - (InstructionResult::Continue, Gas::new(call.gas_limit), Bytes::new()) + None } fn call_end( &mut self, - data: &mut EVMData<'_, DB>, - call: &CallInputs, - remaining_gas: Gas, - status: InstructionResult, - retdata: Bytes, - ) -> (InstructionResult, Gas, Bytes) { + ecx: &mut EvmContext<&mut DB>, + inputs: &CallInputs, + outcome: CallOutcome, + ) -> CallOutcome { // Inner context calls with depth 0 are being dispatched as top-level calls with depth 1. // Avoid processing twice. - if self.in_inner_context && data.journaled_state.depth == 0 { - return (status, remaining_gas, retdata); + if self.in_inner_context && ecx.journaled_state.depth == 0 { + return outcome } - let res = self.do_call_end(data, call, remaining_gas, status, retdata); - if matches!(res.0, return_revert!()) { + let outcome = self.do_call_end(ecx, inputs, outcome); + if outcome.result.is_revert() { // Encountered a revert, since cheatcodes may have altered the evm state in such a way // that violates some constraints, e.g. `deal`, we need to manually roll back on revert // before revm reverts the state itself if let Some(cheats) = self.cheatcodes.as_mut() { - cheats.on_revert(data); + cheats.on_revert(ecx); } } - res + outcome } fn create( &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CreateInputs, - ) -> (InstructionResult, Option
, Gas, Bytes) { - if self.in_inner_context && data.journaled_state.depth == 0 { - self.adjust_evm_data_for_inner_context(data); - return (InstructionResult::Continue, None, Gas::new(call.gas_limit), Bytes::new()); + ecx: &mut EvmContext<&mut DB>, + create: &mut CreateInputs, + ) -> Option { + if self.in_inner_context && ecx.journaled_state.depth == 0 { + self.adjust_evm_data_for_inner_context(ecx); + return None; } call_inspectors_adjust_depth!( - [ - &mut self.debugger, - &mut self.tracer, - &mut self.coverage, - &mut self.log_collector, - &mut self.cheatcodes, - &mut self.printer - ], - |inspector| { - let (status, addr, gas, retdata) = inspector.create(data, call); - - // Allow inspectors to exit early - if status != InstructionResult::Continue { - Some((status, addr, gas, retdata)) - } else { - None - } - }, + [&mut self.debugger, &mut self.tracer, &mut self.coverage, &mut self.cheatcodes], + |inspector| inspector.create(ecx, create).map(Some), self, - data + ecx ); - if self.enable_isolation && !self.in_inner_context && data.journaled_state.depth == 1 { - return self.transact_inner( - data, - TransactTo::Create(call.scheme), - call.caller, - call.init_code.clone(), - call.gas_limit, - call.value, + if self.enable_isolation && !self.in_inner_context && ecx.journaled_state.depth == 1 { + let (result, address) = self.transact_inner( + ecx, + TransactTo::Create, + create.caller, + create.init_code.clone(), + create.gas_limit, + create.value, ); + return Some(CreateOutcome { result, address }) } - (InstructionResult::Continue, None, Gas::new(call.gas_limit), Bytes::new()) + None } fn create_end( &mut self, - data: &mut EVMData<'_, DB>, + ecx: &mut EvmContext<&mut DB>, call: &CreateInputs, - status: InstructionResult, - address: Option
, - remaining_gas: Gas, - retdata: Bytes, - ) -> (InstructionResult, Option
, Gas, Bytes) { + outcome: CreateOutcome, + ) -> CreateOutcome { // Inner context calls with depth 0 are being dispatched as top-level calls with depth 1. // Avoid processing twice. - if self.in_inner_context && data.journaled_state.depth == 0 { - return (status, address, remaining_gas, retdata); + if self.in_inner_context && ecx.journaled_state.depth == 0 { + return outcome } + + let result = outcome.result.result; + call_inspectors_adjust_depth!( - [ - &mut self.debugger, - &mut self.tracer, - &mut self.coverage, - &mut self.log_collector, - &mut self.cheatcodes, - &mut self.printer - ], + [&mut self.debugger, &mut self.tracer, &mut self.cheatcodes, &mut self.printer], |inspector| { - let (new_status, new_address, new_gas, new_retdata) = inspector.create_end( - data, - call, - status, - address, - remaining_gas, - retdata.clone(), - ); - - if new_status != status { - Some((new_status, new_address, new_gas, new_retdata)) - } else { - None - } + let new_outcome = inspector.create_end(ecx, call, outcome.clone()); + + // If the inspector returns a different status or a revert with a non-empty message, + // we assume it wants to tell us something + let different = new_outcome.result.result != result || + (new_outcome.result.result == InstructionResult::Revert && + new_outcome.output() != outcome.output()); + different.then_some(new_outcome) }, self, - data + ecx ); - (status, address, remaining_gas, retdata) + outcome } fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) { - call_inspectors!( - [ - &mut self.debugger, - &mut self.tracer, - &mut self.log_collector, - &mut self.cheatcodes, - &mut self.printer, - &mut self.chisel_state - ], - |inspector| { - Inspector::::selfdestruct(inspector, contract, target, value); - } + call_inspectors!([&mut self.tracer, &mut self.printer], |inspector| { + Inspector::::selfdestruct(inspector, contract, target, value) + }); + } +} + +impl InspectorExt<&mut DB> for InspectorStack { + fn should_use_create2_factory( + &mut self, + ecx: &mut EvmContext<&mut DB>, + inputs: &mut CreateInputs, + ) -> bool { + call_inspectors_adjust_depth!( + [&mut self.cheatcodes], + |inspector| { inspector.should_use_create2_factory(ecx, inputs).then_some(true) }, + self, + ecx ); + + false } } diff --git a/crates/evm/evm/src/lib.rs b/crates/evm/evm/src/lib.rs index 7c69c1823..598012770 100644 --- a/crates/evm/evm/src/lib.rs +++ b/crates/evm/evm/src/lib.rs @@ -2,7 +2,8 @@ //! //! Main Foundry EVM backend abstractions. -#![warn(unreachable_pub, unused_crate_dependencies, rust_2018_idioms)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #[macro_use] extern crate tracing; @@ -10,11 +11,17 @@ extern crate tracing; pub mod executors; pub mod inspectors; -pub use foundry_evm_core::{backend, constants, debug, decode, fork, opts, utils}; +pub use foundry_evm_core::{backend, constants, debug, decode, fork, opts, utils, InspectorExt}; pub use foundry_evm_coverage as coverage; pub use foundry_evm_fuzz as fuzz; pub use foundry_evm_traces as traces; // TODO: We should probably remove these, but it's a pretty big breaking change. #[doc(hidden)] -pub use {hashbrown, revm}; +pub use revm; + +#[doc(hidden)] +#[deprecated = "use `{hash_map, hash_set, HashMap, HashSet}` in `std::collections` or `revm::primitives` instead"] +pub mod hashbrown { + pub use revm::primitives::{hash_map, hash_set, HashMap, HashSet}; +} diff --git a/crates/evm/fuzz/Cargo.toml b/crates/evm/fuzz/Cargo.toml index 319e0ea8f..c6449d312 100644 --- a/crates/evm/fuzz/Cargo.toml +++ b/crates/evm/fuzz/Cargo.toml @@ -10,6 +10,9 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [dependencies] foundry-common.workspace = true foundry-compilers.workspace = true @@ -21,8 +24,13 @@ foundry-zksync-core.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } alloy-json-abi.workspace = true -alloy-primitives = { workspace = true, features = ["serde", "getrandom", "arbitrary", "rlp"] } -revm = { workspace = true, default-features = false, features = [ +alloy-primitives = { workspace = true, features = [ + "serde", + "getrandom", + "arbitrary", + "rlp", +] } +revm = { workspace = true, features = [ "std", "serde", "memory_limit", @@ -32,13 +40,13 @@ revm = { workspace = true, default-features = false, features = [ "arbitrary", ] } -arbitrary = "1.3.1" -eyre = "0.6" -hashbrown = { version = "0.14", features = ["serde"] } +eyre .workspace = true itertools.workspace = true -parking_lot = "0.12" +parking_lot.workspace = true proptest = "1" rand.workspace = true -serde = "1" -thiserror = "1" -tracing = "0.1" +serde.workspace = true +thiserror.workspace = true +tracing.workspace = true +indexmap.workspace = true +ahash.workspace = true diff --git a/crates/evm/fuzz/src/error.rs b/crates/evm/fuzz/src/error.rs index 145afa5e7..1371f4969 100644 --- a/crates/evm/fuzz/src/error.rs +++ b/crates/evm/fuzz/src/error.rs @@ -8,8 +8,6 @@ pub enum FuzzError { UnknownContract, #[error("Failed contract call")] FailedContractCall, - #[error("Empty state changeset")] - EmptyChangeset, #[error("`vm.assume` reject")] AssumeReject, #[error("The `vm.assume` cheatcode rejected too many inputs ({0} allowed)")] diff --git a/crates/evm/fuzz/src/inspector.rs b/crates/evm/fuzz/src/inspector.rs index 65c6e5ec9..052d87dac 100644 --- a/crates/evm/fuzz/src/inspector.rs +++ b/crates/evm/fuzz/src/inspector.rs @@ -1,8 +1,7 @@ use crate::{invariant::RandomCallGenerator, strategies::EvmFuzzState}; -use alloy_primitives::Bytes; use revm::{ - interpreter::{CallInputs, CallScheme, Gas, InstructionResult, Interpreter}, - Database, EVMData, Inspector, + interpreter::{CallInputs, CallOutcome, CallScheme, Interpreter}, + Database, EvmContext, Inspector, }; /// An inspector that can fuzz and collect data for that effect. @@ -18,41 +17,35 @@ pub struct Fuzzer { impl Inspector for Fuzzer { #[inline] - fn step(&mut self, interpreter: &mut Interpreter<'_>, _: &mut EVMData<'_, DB>) { + fn step(&mut self, interp: &mut Interpreter, _context: &mut EvmContext) { // We only collect `stack` and `memory` data before and after calls. if self.collect { - self.collect_data(interpreter); + self.collect_data(interp); self.collect = false; } } #[inline] - fn call( - &mut self, - data: &mut EVMData<'_, DB>, - call: &mut CallInputs, - ) -> (InstructionResult, Gas, Bytes) { + fn call(&mut self, ecx: &mut EvmContext, inputs: &mut CallInputs) -> Option { // We don't want to override the very first call made to the test contract. - if self.call_generator.is_some() && data.env.tx.caller != call.context.caller { - self.override_call(call); + if self.call_generator.is_some() && ecx.env.tx.caller != inputs.caller { + self.override_call(inputs); } // We only collect `stack` and `memory` data before and after calls. // this will be turned off on the next `step` self.collect = true; - (InstructionResult::Continue, Gas::new(call.gas_limit), Bytes::new()) + None } #[inline] fn call_end( &mut self, - _: &mut EVMData<'_, DB>, - _: &CallInputs, - remaining_gas: Gas, - status: InstructionResult, - retdata: Bytes, - ) -> (InstructionResult, Gas, Bytes) { + _context: &mut EvmContext, + _inputs: &CallInputs, + outcome: CallOutcome, + ) -> CallOutcome { if let Some(ref mut call_generator) = self.call_generator { call_generator.used = false; } @@ -61,18 +54,14 @@ impl Inspector for Fuzzer { // this will be turned off on the next `step` self.collect = true; - (status, remaining_gas, retdata) + outcome } } impl Fuzzer { /// Collects `stack` and `memory` values into the fuzz dictionary. - fn collect_data(&mut self, interpreter: &Interpreter<'_>) { - let mut state = self.fuzz_state.write(); - - for slot in interpreter.stack().data() { - state.values_mut().insert(slot.to_be_bytes()); - } + fn collect_data(&mut self, interpreter: &Interpreter) { + self.fuzz_state.collect_values(interpreter.stack().data().iter().copied().map(Into::into)); // TODO: disabled for now since it's flooding the dictionary // for index in 0..interpreter.shared_memory.len() / 32 { @@ -87,22 +76,18 @@ impl Fuzzer { fn override_call(&mut self, call: &mut CallInputs) { if let Some(ref mut call_generator) = self.call_generator { // We only override external calls which are not coming from the test contract. - if call.context.caller != call_generator.test_address && - call.context.scheme == CallScheme::Call && + if call.caller != call_generator.test_address && + call.scheme == CallScheme::Call && !call_generator.used { // There's only a 30% chance that an override happens. - if let Some((sender, (contract, input))) = - call_generator.next(call.context.caller, call.contract) - { - *call.input = input.0; - call.context.caller = sender; - call.contract = contract; + if let Some(tx) = call_generator.next(call.caller, call.target_address) { + *call.input = tx.call_details.calldata.0; + call.caller = tx.sender; + call.target_address = tx.call_details.target; // TODO: in what scenarios can the following be problematic - call.context.code_address = contract; - call.context.address = contract; - + call.bytecode_address = tx.call_details.target; call_generator.used = true; } } diff --git a/crates/evm/fuzz/src/invariant/call_override.rs b/crates/evm/fuzz/src/invariant/call_override.rs index 1d9edba5c..dddf59152 100644 --- a/crates/evm/fuzz/src/invariant/call_override.rs +++ b/crates/evm/fuzz/src/invariant/call_override.rs @@ -1,5 +1,5 @@ -use super::BasicTxDetails; -use alloy_primitives::{Address, Bytes}; +use super::{BasicTxDetails, CallDetails}; +use alloy_primitives::Address; use parking_lot::{Mutex, RwLock}; use proptest::{ option::weighted, @@ -17,7 +17,7 @@ pub struct RandomCallGenerator { /// Runner that will generate the call from the strategy. pub runner: Arc>, /// Strategy to be used to generate calls from `target_reference`. - pub strategy: SBoxedStrategy>, + pub strategy: SBoxedStrategy>, /// Reference to which contract we want a fuzzed calldata from. pub target_reference: Arc>, /// Flag to know if a call has been overridden. Don't allow nesting for now. @@ -33,17 +33,15 @@ impl RandomCallGenerator { pub fn new( test_address: Address, runner: TestRunner, - strategy: SBoxedStrategy<(Address, Bytes)>, + strategy: impl Strategy + Send + Sync + 'static, target_reference: Arc>, ) -> Self { - let strategy = weighted(0.9, strategy).sboxed(); - - RandomCallGenerator { + Self { test_address, runner: Arc::new(Mutex::new(runner)), - strategy, + strategy: weighted(0.9, strategy).sboxed(), target_reference, - last_sequence: Arc::new(RwLock::new(vec![])), + last_sequence: Arc::default(), replay: false, used: false, } @@ -71,7 +69,7 @@ impl RandomCallGenerator { ) } else { // TODO: Do we want it to be 80% chance only too ? - let new_caller = original_target; + let sender = original_target; // Set which contract we mostly (80% chance) want to generate calldata from. *self.target_reference.write() = original_caller; @@ -82,7 +80,7 @@ impl RandomCallGenerator { .new_tree(&mut self.runner.lock()) .unwrap() .current() - .map(|(new_target, calldata)| (new_caller, (new_target, calldata))); + .map(|call_details| BasicTxDetails { sender, call_details }); self.last_sequence.write().push(choice.clone()); choice diff --git a/crates/evm/fuzz/src/invariant/filters.rs b/crates/evm/fuzz/src/invariant/filters.rs index c56450c27..520e5b5af 100644 --- a/crates/evm/fuzz/src/invariant/filters.rs +++ b/crates/evm/fuzz/src/invariant/filters.rs @@ -14,12 +14,18 @@ pub struct ArtifactFilters { /// List of `contract_path:contract_name` which are to be excluded. pub excluded: Vec, } + impl ArtifactFilters { + /// Returns `true` if the given identifier matches this filter. + pub fn matches(&self, identifier: &str) -> bool { + (self.targeted.is_empty() || self.targeted.contains_key(identifier)) && + (self.excluded.is_empty() || !self.excluded.iter().any(|id| id == identifier)) + } + /// Gets all the targeted functions from `artifact`. Returns error, if selectors do not match /// the `artifact`. /// - /// An empty vector means that it targets any mutable function. See `select_random_function` for - /// more. + /// An empty vector means that it targets any mutable function. pub fn get_targeted_functions( &self, artifact: &ArtifactId, @@ -28,7 +34,7 @@ impl ArtifactFilters { if let Some(selectors) = self.targeted.get(&artifact.identifier()) { let functions = selectors .iter() - .map(|selector| get_function(&artifact.name, selector, abi)) + .map(|selector| get_function(&artifact.name, *selector, abi).cloned()) .collect::>>()?; // targetArtifactSelectors > excludeArtifacts > targetArtifacts if functions.is_empty() && self.excluded.contains(&artifact.identifier()) { @@ -44,6 +50,7 @@ impl ArtifactFilters { Ok(None) } } + /// Filter for acceptable senders to use for invariant testing. Exclusion takes priority if /// clashing. /// @@ -61,6 +68,6 @@ impl SenderFilters { excluded.push(addr_0); } targeted.retain(|addr| !excluded.contains(addr)); - SenderFilters { targeted, excluded } + Self { targeted, excluded } } } diff --git a/crates/evm/fuzz/src/invariant/mod.rs b/crates/evm/fuzz/src/invariant/mod.rs index 6dfcd8248..d6b3e574d 100644 --- a/crates/evm/fuzz/src/invariant/mod.rs +++ b/crates/evm/fuzz/src/invariant/mod.rs @@ -1,5 +1,6 @@ use alloy_json_abi::{Function, JsonAbi}; -use alloy_primitives::{Address, Bytes}; +use alloy_primitives::{Address, Bytes, Selector}; +use itertools::Either; use parking_lot::Mutex; use std::{collections::BTreeMap, sync::Arc}; @@ -8,12 +9,212 @@ pub use call_override::RandomCallGenerator; mod filters; pub use filters::{ArtifactFilters, SenderFilters}; +use foundry_common::{ContractsByAddress, ContractsByArtifact}; +use foundry_evm_core::utils::{get_function, StateChangeset}; -pub type TargetedContracts = BTreeMap)>; -pub type FuzzRunIdentifiedContracts = Arc>; +/// Contracts identified as targets during a fuzz run. +/// +/// During execution, any newly created contract is added as target and used through the rest of +/// the fuzz run if the collection is updatable (no `targetContract` specified in `setUp`). +#[derive(Clone, Debug)] +pub struct FuzzRunIdentifiedContracts { + /// Contracts identified as targets during a fuzz run. + pub targets: Arc>, + /// Whether target contracts are updatable or not. + pub is_updatable: bool, +} + +impl FuzzRunIdentifiedContracts { + /// Creates a new `FuzzRunIdentifiedContracts` instance. + pub fn new(targets: TargetedContracts, is_updatable: bool) -> Self { + Self { targets: Arc::new(Mutex::new(targets)), is_updatable } + } + + /// If targets are updatable, collect all contracts created during an invariant run (which + /// haven't been discovered yet). + pub fn collect_created_contracts( + &self, + state_changeset: &StateChangeset, + project_contracts: &ContractsByArtifact, + setup_contracts: &ContractsByAddress, + artifact_filters: &ArtifactFilters, + created_contracts: &mut Vec
, + ) -> eyre::Result<()> { + if !self.is_updatable { + return Ok(()); + } + + let mut targets = self.targets.lock(); + for (address, account) in state_changeset { + if setup_contracts.contains_key(address) { + continue; + } + if !account.is_touched() { + continue; + } + let Some(code) = &account.info.code else { + continue; + }; + if code.is_empty() { + continue; + } + let Some((artifact, contract)) = + project_contracts.find_by_deployed_code(code.original_byte_slice()) + else { + continue; + }; + let Some(functions) = + artifact_filters.get_targeted_functions(artifact, &contract.abi)? + else { + continue; + }; + created_contracts.push(*address); + let contract = TargetedContract { + identifier: artifact.name.clone(), + abi: contract.abi.clone(), + targeted_functions: functions, + excluded_functions: Vec::new(), + }; + targets.insert(*address, contract); + } + Ok(()) + } + + /// Clears targeted contracts created during an invariant run. + pub fn clear_created_contracts(&self, created_contracts: Vec
) { + if !created_contracts.is_empty() { + let mut targets = self.targets.lock(); + for addr in created_contracts.iter() { + targets.remove(addr); + } + } + } +} + +/// A collection of contracts identified as targets for invariant testing. +#[derive(Clone, Debug, Default)] +pub struct TargetedContracts { + /// The inner map of targeted contracts. + pub inner: BTreeMap, +} + +impl TargetedContracts { + /// Returns a new `TargetedContracts` instance. + pub fn new() -> Self { + Self::default() + } + + /// Returns fuzzed contract abi and fuzzed function from address and provided calldata. + /// + /// Used to decode return values and logs in order to add values into fuzz dictionary. + pub fn fuzzed_artifacts(&self, tx: &BasicTxDetails) -> (Option<&JsonAbi>, Option<&Function>) { + match self.inner.get(&tx.call_details.target) { + Some(c) => ( + Some(&c.abi), + c.abi.functions().find(|f| f.selector() == tx.call_details.calldata[..4]), + ), + None => (None, None), + } + } + + /// Returns flatten target contract address and functions to be fuzzed. + /// Includes contract targeted functions if specified, else all mutable contract functions. + pub fn fuzzed_functions(&self) -> impl Iterator { + self.inner + .iter() + .filter(|(_, c)| !c.abi.functions.is_empty()) + .flat_map(|(contract, c)| c.abi_fuzzed_functions().map(move |f| (contract, f))) + } +} + +impl std::ops::Deref for TargetedContracts { + type Target = BTreeMap; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} -/// (Sender, (TargetContract, Calldata)) -pub type BasicTxDetails = (Address, (Address, Bytes)); +impl std::ops::DerefMut for TargetedContracts { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +/// A contract identified as target for invariant testing. +#[derive(Clone, Debug)] +pub struct TargetedContract { + /// The contract identifier. This is only used in error messages. + pub identifier: String, + /// The contract's ABI. + pub abi: JsonAbi, + /// The targeted functions of the contract. + pub targeted_functions: Vec, + /// The excluded functions of the contract. + pub excluded_functions: Vec, +} + +impl TargetedContract { + /// Returns a new `TargetedContract` instance. + pub fn new(identifier: String, abi: JsonAbi) -> Self { + Self { identifier, abi, targeted_functions: Vec::new(), excluded_functions: Vec::new() } + } + + /// Helper to retrieve functions to fuzz for specified abi. + /// Returns specified targeted functions if any, else mutable abi functions that are not + /// marked as excluded. + pub fn abi_fuzzed_functions(&self) -> impl Iterator { + if !self.targeted_functions.is_empty() { + Either::Left(self.targeted_functions.iter()) + } else { + Either::Right(self.abi.functions().filter(|&func| { + !matches!( + func.state_mutability, + alloy_json_abi::StateMutability::Pure | alloy_json_abi::StateMutability::View + ) && !self.excluded_functions.contains(func) + })) + } + } + + /// Returns the function for the given selector. + pub fn get_function(&self, selector: Selector) -> eyre::Result<&Function> { + get_function(&self.identifier, selector, &self.abi) + } + + /// Adds the specified selectors to the targeted functions. + pub fn add_selectors( + &mut self, + selectors: impl IntoIterator, + should_exclude: bool, + ) -> eyre::Result<()> { + for selector in selectors { + if should_exclude { + self.excluded_functions.push(self.get_function(selector)?.clone()); + } else { + self.targeted_functions.push(self.get_function(selector)?.clone()); + } + } + Ok(()) + } +} + +/// Details of a transaction generated by invariant strategy for fuzzing a target. +#[derive(Clone, Debug)] +pub struct BasicTxDetails { + // Transaction sender address. + pub sender: Address, + // Transaction call details. + pub call_details: CallDetails, +} + +/// Call details of a transaction generated to fuzz invariant target. +#[derive(Clone, Debug)] +pub struct CallDetails { + // Address of target contract. + pub target: Address, + // The data of the transaction. + pub calldata: Bytes, +} /// Test contract which is testing its invariants. #[derive(Clone, Debug)] @@ -22,6 +223,8 @@ pub struct InvariantContract<'a> { pub address: Address, /// Invariant function present in the test contract. pub invariant_function: &'a Function, + /// If true, `afterInvariant` function is called after each invariant run. + pub call_after_invariant: bool, /// ABI of the test contract. pub abi: &'a JsonAbi, } diff --git a/crates/evm/fuzz/src/lib.rs b/crates/evm/fuzz/src/lib.rs index ed3b50178..f675e7755 100644 --- a/crates/evm/fuzz/src/lib.rs +++ b/crates/evm/fuzz/src/lib.rs @@ -2,7 +2,8 @@ //! //! EVM fuzzing implementation using [`proptest`]. -#![warn(unreachable_pub, unused_crate_dependencies, rust_2018_idioms)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #[macro_use] extern crate tracing; @@ -14,7 +15,7 @@ use foundry_evm_coverage::HitMaps; use foundry_evm_traces::CallTraceArena; use itertools::Itertools; use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, fmt}; +use std::{collections::HashMap, fmt, sync::Arc}; pub use proptest::test_runner::{Config as FuzzConfig, Reason}; @@ -43,19 +44,20 @@ pub struct BaseCounterExample { pub addr: Option
, /// The data to provide pub calldata: Bytes, - /// Function signature if it exists - pub signature: Option, /// Contract name if it exists pub contract_name: Option, + /// Function signature if it exists + pub signature: Option, + /// Args used to call the function + pub args: Option, /// Traces #[serde(skip)] pub traces: Option, - #[serde(skip)] - pub args: Vec, } impl BaseCounterExample { - pub fn create( + /// Creates counter example representing a step from invariant call sequence. + pub fn from_invariant_call( sender: Address, addr: Address, bytes: &Bytes, @@ -66,27 +68,46 @@ impl BaseCounterExample { if let Some(func) = abi.functions().find(|f| f.selector() == bytes[..4]) { // skip the function selector when decoding if let Ok(args) = func.abi_decode_input(&bytes[4..], false) { - return BaseCounterExample { + return Self { sender: Some(sender), addr: Some(addr), calldata: bytes.clone(), - signature: Some(func.signature()), contract_name: Some(name.clone()), + signature: Some(func.signature()), + args: Some( + foundry_common::fmt::format_tokens(&args).format(", ").to_string(), + ), traces, - args, }; } } } - BaseCounterExample { + Self { sender: Some(sender), addr: Some(addr), calldata: bytes.clone(), + contract_name: None, signature: None, + args: None, + traces, + } + } + + /// Creates counter example for a fuzz test failure. + pub fn from_fuzz_call( + bytes: Bytes, + args: Vec, + traces: Option, + ) -> Self { + Self { + sender: None, + addr: None, + calldata: bytes, contract_name: None, + signature: None, + args: Some(foundry_common::fmt::format_tokens(&args).format(", ").to_string()), traces, - args: vec![], } } } @@ -108,10 +129,14 @@ impl fmt::Display for BaseCounterExample { if let Some(sig) = &self.signature { write!(f, "calldata={sig}")? } else { - write!(f, "calldata={}", self.calldata)? + write!(f, "calldata={}", &self.calldata)? } - write!(f, " args=[{}]", foundry_common::fmt::format_tokens(&self.args).format(", ")) + if let Some(args) = &self.args { + write!(f, " args=[{args}]") + } else { + write!(f, " args=[]") + } } } @@ -150,6 +175,10 @@ pub struct FuzzTestResult { /// `num(fuzz_cases)` traces, one for each run, which is neither helpful nor performant. pub traces: Option, + /// Additional traces used for gas report construction. + /// Those traces should not be displayed. + pub gas_report_traces: Vec, + /// Raw coverage info pub coverage: Option, } @@ -268,3 +297,39 @@ impl FuzzedCases { self.lowest().map(|c| c.gas).unwrap_or_default() } } + +/// Fixtures to be used for fuzz tests. +/// The key represents name of the fuzzed parameter, value holds possible fuzzed values. +/// For example, for a fixture function declared as +/// `function fixture_sender() external returns (address[] memory senders)` +/// the fuzz fixtures will contain `sender` key with `senders` array as value +#[derive(Clone, Default, Debug)] +pub struct FuzzFixtures { + inner: Arc>, +} + +impl FuzzFixtures { + pub fn new(fixtures: HashMap) -> Self { + Self { inner: Arc::new(fixtures) } + } + + /// Returns configured fixtures for `param_name` fuzzed parameter. + pub fn param_fixtures(&self, param_name: &str) -> Option<&[DynSolValue]> { + if let Some(param_fixtures) = self.inner.get(&normalize_fixture(param_name)) { + param_fixtures.as_fixed_array().or_else(|| param_fixtures.as_array()) + } else { + None + } + } +} + +/// Extracts fixture name from a function name. +/// For example: fixtures defined in `fixture_Owner` function will be applied for `owner` parameter. +pub fn fixture_name(function_name: String) -> String { + normalize_fixture(function_name.strip_prefix("fixture").unwrap()) +} + +/// Normalize fixture parameter name, for example `_Owner` to `owner`. +fn normalize_fixture(param_name: &str) -> String { + param_name.trim_matches('_').to_ascii_lowercase() +} diff --git a/crates/evm/fuzz/src/strategies/calldata.rs b/crates/evm/fuzz/src/strategies/calldata.rs index 98592b131..5697be1f6 100644 --- a/crates/evm/fuzz/src/strategies/calldata.rs +++ b/crates/evm/fuzz/src/strategies/calldata.rs @@ -1,26 +1,94 @@ -use super::fuzz_param; +use crate::{ + strategies::{fuzz_param_from_state, fuzz_param_with_fixtures, EvmFuzzState}, + FuzzFixtures, +}; use alloy_dyn_abi::JsonAbiExt; use alloy_json_abi::Function; use alloy_primitives::Bytes; -use proptest::prelude::{BoxedStrategy, Strategy}; +use proptest::prelude::Strategy; /// Given a function, it returns a strategy which generates valid calldata -/// for that function's input types. -pub fn fuzz_calldata(func: Function, no_zksync_reserved_addresses: bool) -> BoxedStrategy { +/// for that function's input types, following declared test fixtures. +pub fn fuzz_calldata( + func: Function, + fuzz_fixtures: &FuzzFixtures, + no_zksync_reserved_addresses: bool, +) -> impl Strategy { // We need to compose all the strategies generated for each parameter in all - // possible combinations + // possible combinations, accounting any parameter declared fixture let strats = func .inputs .iter() .map(|input| { - fuzz_param(&input.selector_type().parse().unwrap(), no_zksync_reserved_addresses) + fuzz_param_with_fixtures( + &input.selector_type().parse().unwrap(), + fuzz_fixtures.param_fixtures(&input.name), + &input.name, + no_zksync_reserved_addresses, + ) }) .collect::>(); + strats.prop_map(move |values| { + func.abi_encode_input(&values) + .unwrap_or_else(|_| { + panic!( + "Fuzzer generated invalid arguments for function `{}` with inputs {:?}: {:?}", + func.name, func.inputs, values + ) + }) + .into() + }) +} +/// Given a function and some state, it returns a strategy which generated valid calldata for the +/// given function's input types, based on state taken from the EVM. +pub fn fuzz_calldata_from_state( + func: Function, + state: &EvmFuzzState, +) -> impl Strategy { + let strats = func + .inputs + .iter() + .map(|input| fuzz_param_from_state(&input.selector_type().parse().unwrap(), state)) + .collect::>(); strats - .prop_map(move |tokens| { - trace!(input=?tokens); - func.abi_encode_input(&tokens).unwrap().into() + .prop_map(move |values| { + func.abi_encode_input(&values) + .unwrap_or_else(|_| { + panic!( + "Fuzzer generated invalid arguments for function `{}` with inputs {:?}: {:?}", + func.name, func.inputs, values + ) + }) + .into() }) - .boxed() + .no_shrink() +} + +#[cfg(test)] +mod tests { + use crate::{strategies::fuzz_calldata, FuzzFixtures}; + use alloy_dyn_abi::{DynSolValue, JsonAbiExt}; + use alloy_json_abi::Function; + use alloy_primitives::Address; + use proptest::prelude::Strategy; + use std::collections::HashMap; + + #[test] + fn can_fuzz_with_fixtures() { + let function = Function::parse("test_fuzzed_address(address addressFixture)").unwrap(); + + let address_fixture = DynSolValue::Address(Address::random()); + let mut fixtures = HashMap::new(); + fixtures.insert( + "addressFixture".to_string(), + DynSolValue::Array(vec![address_fixture.clone()]), + ); + + let expected = function.abi_encode_input(&[address_fixture]).unwrap(); + let strategy = fuzz_calldata(function, &FuzzFixtures::new(fixtures), false); + let _ = strategy.prop_map(move |fuzzed| { + assert_eq!(expected, fuzzed); + }); + } } diff --git a/crates/evm/fuzz/src/strategies/int.rs b/crates/evm/fuzz/src/strategies/int.rs index e92c2d464..3732de061 100644 --- a/crates/evm/fuzz/src/strategies/int.rs +++ b/crates/evm/fuzz/src/strategies/int.rs @@ -1,3 +1,4 @@ +use alloy_dyn_abi::{DynSolType, DynSolValue}; use alloy_primitives::{Sign, I256, U256}; use proptest::{ strategy::{NewTree, Strategy, ValueTree}, @@ -6,7 +7,6 @@ use proptest::{ use rand::Rng; /// Value tree for signed ints (up to int256). -/// This is very similar to [proptest::BinarySearch] pub struct IntValueTree { /// Lower base (by absolute value) lo: I256, @@ -55,7 +55,7 @@ impl ValueTree for IntValueTree { } fn simplify(&mut self) -> bool { - if self.fixed || !IntValueTree::magnitude_greater(self.hi, self.lo) { + if self.fixed || !Self::magnitude_greater(self.hi, self.lo) { return false } self.hi = self.curr; @@ -63,11 +63,15 @@ impl ValueTree for IntValueTree { } fn complicate(&mut self) -> bool { - if self.fixed || !IntValueTree::magnitude_greater(self.hi, self.lo) { + if self.fixed || !Self::magnitude_greater(self.hi, self.lo) { return false } - self.lo = self.curr + if self.hi.is_negative() { I256::MINUS_ONE } else { I256::ONE }; + self.lo = if self.curr != I256::MIN && self.curr != I256::MAX { + self.curr + if self.hi.is_negative() { I256::MINUS_ONE } else { I256::ONE } + } else { + self.curr + }; self.reposition() } @@ -76,15 +80,23 @@ impl ValueTree for IntValueTree { /// Value tree for signed ints (up to int256). /// The strategy combines 3 different strategies, each assigned a specific weight: /// 1. Generate purely random value in a range. This will first choose bit size uniformly (up `bits` -/// param). Then generate a value for this bit size. +/// param). Then generate a value for this bit size. /// 2. Generate a random value around the edges (+/- 3 around min, 0 and max possible value) /// 3. Generate a value from a predefined fixtures set +/// +/// To define int fixtures: +/// - return an array of possible values for a parameter named `amount` declare a function `function +/// fixture_amount() public returns (int32[] memory)`. +/// - use `amount` named parameter in fuzzed test in order to include fixtures in fuzzed values +/// `function testFuzz_int32(int32 amount)`. +/// +/// If fixture is not a valid int type then error is raised and random value generated. #[derive(Debug)] pub struct IntStrategy { /// Bit size of int (e.g. 256) bits: usize, /// A set of fixtures to be generated - fixtures: Vec, + fixtures: Vec, /// The weight for edge cases (+/- 3 around 0 and max possible value) edge_weight: usize, /// The weight for fixtures @@ -98,10 +110,10 @@ impl IntStrategy { /// #Arguments /// * `bits` - Size of uint in bits /// * `fixtures` - A set of fixed values to be generated (according to fixtures weight) - pub fn new(bits: usize, fixtures: Vec) -> Self { + pub fn new(bits: usize, fixtures: Option<&[DynSolValue]>) -> Self { Self { bits, - fixtures, + fixtures: Vec::from(fixtures.unwrap_or_default()), edge_weight: 10usize, fixtures_weight: 40usize, random_weight: 50usize, @@ -128,12 +140,22 @@ impl IntStrategy { } fn generate_fixtures_tree(&self, runner: &mut TestRunner) -> NewTree { - // generate edge cases if there's no fixtures + // generate random cases if there's no fixtures if self.fixtures.is_empty() { - return self.generate_edge_tree(runner) + return self.generate_random_tree(runner) + } + + // Generate value tree from fixture. + let fixture = &self.fixtures[runner.rng().gen_range(0..self.fixtures.len())]; + if let Some(int_fixture) = fixture.as_int() { + if int_fixture.1 == self.bits { + return Ok(IntValueTree::new(int_fixture.0, false)); + } } - let idx = runner.rng().gen_range(0..self.fixtures.len()); - Ok(IntValueTree::new(self.fixtures[idx], false)) + + // If fixture is not a valid type, raise error and generate random value. + error!("{:?} is not a valid {} fixture", fixture, DynSolType::Int(self.bits)); + self.generate_random_tree(runner) } fn generate_random_tree(&self, runner: &mut TestRunner) -> NewTree { @@ -192,3 +214,25 @@ impl Strategy for IntStrategy { } } } + +#[cfg(test)] +mod tests { + use crate::strategies::int::IntValueTree; + use alloy_primitives::I256; + use proptest::strategy::ValueTree; + + #[test] + fn test_int_tree_complicate_should_not_overflow() { + let mut int_tree = IntValueTree::new(I256::MAX, false); + assert_eq!(int_tree.hi, I256::MAX); + assert_eq!(int_tree.curr, I256::MAX); + int_tree.complicate(); + assert_eq!(int_tree.lo, I256::MAX); + + let mut int_tree = IntValueTree::new(I256::MIN, false); + assert_eq!(int_tree.hi, I256::MIN); + assert_eq!(int_tree.curr, I256::MIN); + int_tree.complicate(); + assert_eq!(int_tree.lo, I256::MIN); + } +} diff --git a/crates/evm/fuzz/src/strategies/invariants.rs b/crates/evm/fuzz/src/strategies/invariants.rs index 02ec3ac3b..8b7c03779 100644 --- a/crates/evm/fuzz/src/strategies/invariants.rs +++ b/crates/evm/fuzz/src/strategies/invariants.rs @@ -1,12 +1,14 @@ -use super::fuzz_param_from_state; +use super::{fuzz_calldata, fuzz_param_from_state}; use crate::{ - invariant::{BasicTxDetails, FuzzRunIdentifiedContracts, SenderFilters}, - strategies::{fuzz_calldata, fuzz_calldata_from_state, fuzz_param, EvmFuzzState}, + invariant::{BasicTxDetails, CallDetails, FuzzRunIdentifiedContracts, SenderFilters}, + strategies::{fuzz_calldata_from_state, fuzz_param, EvmFuzzState}, + FuzzFixtures, }; -use alloy_json_abi::{Function, JsonAbi}; -use alloy_primitives::{Address, Bytes}; +use alloy_json_abi::Function; +use alloy_primitives::Address; use parking_lot::RwLock; use proptest::prelude::*; +use rand::seq::IteratorRandom; use std::{rc::Rc, sync::Arc}; /// Given a target address, we generate random calldata. @@ -14,26 +16,34 @@ pub fn override_call_strat( fuzz_state: EvmFuzzState, contracts: FuzzRunIdentifiedContracts, target: Arc>, -) -> SBoxedStrategy<(Address, Bytes)> { - let contracts_ref = contracts.clone(); - - let random_contract = any::() - .prop_map(move |selector| *selector.select(contracts_ref.lock().keys())); - let target = any::().prop_map(move |_| *target.read()); - - proptest::strategy::Union::new_weighted(vec![ - (80, target.sboxed()), - (20, random_contract.sboxed()), - ]) + fuzz_fixtures: FuzzFixtures, +) -> impl Strategy + Send + Sync + 'static { + let contracts_ref = contracts.targets.clone(); + proptest::prop_oneof![ + 80 => proptest::strategy::LazyJust::new(move || *target.read()), + 20 => any::() + .prop_map(move |selector| *selector.select(contracts_ref.lock().keys())), + ] .prop_flat_map(move |target_address| { let fuzz_state = fuzz_state.clone(); - let (_, abi, functions) = contracts.lock().get(&target_address).unwrap().clone(); - let func = select_random_function(abi, functions); + let fuzz_fixtures = fuzz_fixtures.clone(); + + let func = { + let contracts = contracts.targets.lock(); + let contract = contracts.get(&target_address).unwrap_or_else(|| { + // Choose a random contract if target selected by lazy strategy is not in fuzz run + // identified contracts. This can happen when contract is created in `setUp` call + // but is not included in targetContracts. + contracts.values().choose(&mut rand::thread_rng()).unwrap() + }); + let fuzzed_functions: Vec<_> = contract.abi_fuzzed_functions().cloned().collect(); + any::().prop_map(move |index| index.get(&fuzzed_functions).clone()) + }; + func.prop_flat_map(move |func| { - fuzz_contract_with_calldata(fuzz_state.clone(), target_address, func) + fuzz_contract_with_calldata(&fuzz_state, &fuzz_fixtures, target_address, func) }) }) - .sboxed() } /// Creates the invariant strategy. @@ -51,135 +61,70 @@ pub fn invariant_strat( senders: SenderFilters, contracts: FuzzRunIdentifiedContracts, dictionary_weight: u32, -) -> impl Strategy> { - // We only want to seed the first value, since we want to generate the rest as we mutate the - // state - generate_call(fuzz_state, senders, contracts, dictionary_weight).prop_map(|x| vec![x]) -} - -/// Strategy to generate a transaction where the `sender`, `target` and `calldata` are all generated -/// through specific strategies. -fn generate_call( - fuzz_state: EvmFuzzState, - senders: SenderFilters, - contracts: FuzzRunIdentifiedContracts, - dictionary_weight: u32, -) -> BoxedStrategy { - let random_contract = select_random_contract(contracts); + fuzz_fixtures: FuzzFixtures, +) -> impl Strategy { let senders = Rc::new(senders); - random_contract - .prop_flat_map(move |(contract, abi, functions)| { - let func = select_random_function(abi, functions); - let senders = senders.clone(); - let fuzz_state = fuzz_state.clone(); - func.prop_flat_map(move |func| { - let sender = - select_random_sender(fuzz_state.clone(), senders.clone(), dictionary_weight); - (sender, fuzz_contract_with_calldata(fuzz_state.clone(), contract, func)) - }) + any::() + .prop_flat_map(move |selector| { + let contracts = contracts.targets.lock(); + let functions = contracts.fuzzed_functions(); + let (target_address, target_function) = selector.select(functions); + let sender = select_random_sender(&fuzz_state, senders.clone(), dictionary_weight); + let call_details = fuzz_contract_with_calldata( + &fuzz_state, + &fuzz_fixtures, + *target_address, + target_function.clone(), + ); + (sender, call_details) }) - .boxed() + .prop_map(|(sender, call_details)| BasicTxDetails { sender, call_details }) } /// Strategy to select a sender address: /// * If `senders` is empty, then it's either a random address (10%) or from the dictionary (90%). /// * If `senders` is not empty, a random address is chosen from the list of senders. fn select_random_sender( - fuzz_state: EvmFuzzState, + fuzz_state: &EvmFuzzState, senders: Rc, dictionary_weight: u32, -) -> BoxedStrategy
{ - let senders_ref = senders.clone(); - let no_zksync_reserved_addresses = fuzz_state.read().no_zksync_reserved_addresses(); - let fuzz_strategy = proptest::strategy::Union::new_weighted(vec![ - ( - 100 - dictionary_weight, - fuzz_param(&alloy_dyn_abi::DynSolType::Address, no_zksync_reserved_addresses) - .prop_map(move |addr| addr.as_address().unwrap()) - .boxed(), - ), - ( - dictionary_weight, - fuzz_param_from_state(&alloy_dyn_abi::DynSolType::Address, fuzz_state) - .prop_map(move |addr| addr.as_address().unwrap()) - .boxed(), - ), - ]) - // Too many exclusions can slow down testing. - .prop_filter("senders not allowed", move |addr| !senders_ref.excluded.contains(addr)) - .boxed(); +) -> impl Strategy { + let no_zksync_reserved_addresses = fuzz_state.dictionary_read().no_zksync_reserved_addresses(); if !senders.targeted.is_empty() { - any::() - .prop_map(move |selector| *selector.select(&*senders.targeted)) - .boxed() + any::().prop_map(move |index| *index.get(&senders.targeted)).boxed() } else { - fuzz_strategy - } -} - -/// Strategy to randomly select a contract from the `contracts` list that has at least 1 function -fn select_random_contract( - contracts: FuzzRunIdentifiedContracts, -) -> impl Strategy)> { - let selectors = any::(); - selectors.prop_map(move |selector| { - let contracts = contracts.lock(); - let (addr, (_, abi, functions)) = - selector.select(contracts.iter().filter(|(_, (_, abi, _))| !abi.functions.is_empty())); - (*addr, abi.clone(), functions.clone()) - }) -} - -/// Strategy to select a random mutable function from the abi. -/// -/// If `targeted_functions` is not empty, select one from it. Otherwise, take any -/// of the available abi functions. -fn select_random_function( - abi: JsonAbi, - targeted_functions: Vec, -) -> BoxedStrategy { - let selectors = any::(); - let possible_funcs: Vec = abi - .functions() - .filter(|func| { - !matches!( - func.state_mutability, - alloy_json_abi::StateMutability::Pure | alloy_json_abi::StateMutability::View - ) - }) - .cloned() - .collect(); - let total_random = selectors.prop_map(move |selector| { - let func = selector.select(&possible_funcs); - func.clone() - }); - if !targeted_functions.is_empty() { - let selector = any::() - .prop_map(move |selector| selector.select(targeted_functions.clone())); - selector.boxed() - } else { - total_random.boxed() + assert!(dictionary_weight <= 100, "dictionary_weight must be <= 100"); + proptest::prop_oneof![ + 100 - dictionary_weight => fuzz_param(&alloy_dyn_abi::DynSolType::Address, no_zksync_reserved_addresses), + dictionary_weight => fuzz_param_from_state(&alloy_dyn_abi::DynSolType::Address, fuzz_state), + ] + .prop_map(move |addr| addr.as_address().unwrap()) + // Too many exclusions can slow down testing. + .prop_filter("excluded sender", move |addr| !senders.excluded.contains(addr)) + .boxed() } } /// Given a function, it returns a proptest strategy which generates valid abi-encoded calldata /// for that function's input types. pub fn fuzz_contract_with_calldata( - fuzz_state: EvmFuzzState, - contract: Address, + fuzz_state: &EvmFuzzState, + fuzz_fixtures: &FuzzFixtures, + target: Address, func: Function, -) -> impl Strategy { - let no_zksync_reserved_addresses = fuzz_state.read().no_zksync_reserved_addresses(); - // We need to compose all the strategies generated for each parameter in all - // possible combinations - // `prop_oneof!` / `TupleUnion` `Arc`s for cheap cloning +) -> impl Strategy { + let no_zksync_reserved_addresses = fuzz_state.dictionary_read().no_zksync_reserved_addresses(); + + // We need to compose all the strategies generated for each parameter in all possible + // combinations. + // `prop_oneof!` / `TupleUnion` `Arc`s for cheap cloning. #[allow(clippy::arc_with_non_send_sync)] - let strats = prop_oneof![ - 60 => fuzz_calldata(func.clone(), no_zksync_reserved_addresses), + prop_oneof![ + 60 => fuzz_calldata(func.clone(), fuzz_fixtures, no_zksync_reserved_addresses), 40 => fuzz_calldata_from_state(func, fuzz_state), - ]; - strats.prop_map(move |calldata| { + ] + .prop_map(move |calldata| { trace!(input=?calldata); - (contract, calldata) + CallDetails { target, calldata } }) } diff --git a/crates/evm/fuzz/src/strategies/mod.rs b/crates/evm/fuzz/src/strategies/mod.rs index a795905de..1d8e647a5 100644 --- a/crates/evm/fuzz/src/strategies/mod.rs +++ b/crates/evm/fuzz/src/strategies/mod.rs @@ -5,16 +5,13 @@ mod uint; pub use uint::UintStrategy; mod param; -pub use param::{fuzz_param, fuzz_param_from_state}; +pub use param::{fuzz_param, fuzz_param_from_state, fuzz_param_with_fixtures}; mod calldata; -pub use calldata::fuzz_calldata; +pub use calldata::{fuzz_calldata, fuzz_calldata_from_state}; mod state; -pub use state::{ - build_initial_state, collect_created_contracts, collect_state_from_call, - fuzz_calldata_from_state, EvmFuzzState, -}; +pub use state::EvmFuzzState; mod invariants; pub use invariants::{fuzz_contract_with_calldata, invariant_strat, override_call_strat}; diff --git a/crates/evm/fuzz/src/strategies/param.rs b/crates/evm/fuzz/src/strategies/param.rs index 34b2865ba..ab1fe8c5b 100644 --- a/crates/evm/fuzz/src/strategies/param.rs +++ b/crates/evm/fuzz/src/strategies/param.rs @@ -1,7 +1,6 @@ use super::state::EvmFuzzState; use alloy_dyn_abi::{DynSolType, DynSolValue}; -use alloy_primitives::{Address, FixedBytes, I256, U256}; -use arbitrary::Unstructured; +use alloy_primitives::{Address, B256, I256, U256}; use proptest::prelude::*; /// The max length of arrays we fuzz for is 256. @@ -9,73 +8,115 @@ const MAX_ARRAY_LEN: usize = 256; /// Given a parameter type, returns a strategy for generating values for that type. /// -/// Works with ABI Encoder v2 tuples. +/// See [`fuzz_param_with_fixtures`] for more information. pub fn fuzz_param( param: &DynSolType, no_zksync_reserved_addresses: bool, ) -> BoxedStrategy { - let param = param.to_owned(); - match param { - DynSolType::Address => any::<[u8; 32]>() - .prop_map(move |x| { - let addr = Address::from_word(x.into()); - if no_zksync_reserved_addresses { - DynSolValue::Address(foundry_zksync_core::to_safe_address(addr)) - } else { - DynSolValue::Address(addr) - } - }) - .boxed(), - DynSolType::Int(n) => { - let strat = super::IntStrategy::new(n, vec![]); - let strat = strat.prop_map(move |x| DynSolValue::Int(x, n)); - strat.boxed() - } - DynSolType::Uint(n) => { - let strat = super::UintStrategy::new(n, vec![]); - let strat = strat.prop_map(move |x| DynSolValue::Uint(x, n)); - strat.boxed() + fuzz_param_inner(param, None, no_zksync_reserved_addresses) +} + +/// Given a parameter type and configured fixtures for param name, returns a strategy for generating +/// values for that type. Fixtures can be currently generated for uint, int, address, bytes and +/// string types and are defined for parameter name. +/// +/// For example, fixtures for parameter `owner` of type `address` can be defined in a function with +/// a `function fixture_owner() public returns (address[] memory)` signature. +/// +/// Fixtures are matched on parameter name, hence fixtures defined in +/// `fixture_owner` function can be used in a fuzzed test function with a signature like +/// `function testFuzz_ownerAddress(address owner, uint amount)`. +/// +/// Raises an error if all the fixture types are not of the same type as the input parameter. +/// +/// Works with ABI Encoder v2 tuples. +pub fn fuzz_param_with_fixtures( + param: &DynSolType, + fixtures: Option<&[DynSolValue]>, + name: &str, + no_zksync_reserved_addresses: bool, +) -> BoxedStrategy { + fuzz_param_inner(param, fixtures.map(|f| (f, name)), no_zksync_reserved_addresses) +} + +fn fuzz_param_inner( + param: &DynSolType, + mut fuzz_fixtures: Option<(&[DynSolValue], &str)>, + no_zksync_reserved_addresses: bool, +) -> BoxedStrategy { + if let Some((fixtures, name)) = fuzz_fixtures { + if !fixtures.iter().all(|f| f.matches(param)) { + error!("fixtures for {name:?} do not match type {param}"); + fuzz_fixtures = None; } - DynSolType::Function | DynSolType::Bool | DynSolType::Bytes => { - DynSolValue::type_strategy(¶m).boxed() + } + let fuzz_fixtures = fuzz_fixtures.map(|(f, _)| f); + + let value = || { + let default_strategy = DynSolValue::type_strategy(param); + if let Some(fixtures) = fuzz_fixtures { + proptest::prop_oneof![ + 50 => { + let fixtures = fixtures.to_vec(); + any::() + .prop_map(move |index| index.get(&fixtures).clone()) + }, + 50 => default_strategy, + ] + .boxed() + } else { + default_strategy.boxed() } - DynSolType::FixedBytes(size) => prop::collection::vec(any::(), size) - .prop_map(move |mut v| { - v.reverse(); - while v.len() < 32 { - v.push(0); + }; + + match *param { + DynSolType::Address => value() + .prop_map(move |value| match value.as_address() { + Some(addr) => { + if no_zksync_reserved_addresses { + DynSolValue::Address(foundry_zksync_core::to_safe_address(addr)) + } else { + DynSolValue::Address(addr) + } } - DynSolValue::FixedBytes(FixedBytes::from_slice(&v), size) + None => value, }) .boxed(), - DynSolType::String => DynSolValue::type_strategy(¶m) + DynSolType::Int(n @ 8..=256) => super::IntStrategy::new(n, fuzz_fixtures) + .prop_map(move |x| DynSolValue::Int(x, n)) + .boxed(), + DynSolType::Uint(n @ 8..=256) => super::UintStrategy::new(n, fuzz_fixtures) + .prop_map(move |x| DynSolValue::Uint(x, n)) + .boxed(), + DynSolType::Function | DynSolType::Bool => DynSolValue::type_strategy(param).boxed(), + DynSolType::Bytes => value(), + DynSolType::FixedBytes(_size @ 1..=32) => value(), + DynSolType::String => value() .prop_map(move |value| { DynSolValue::String( - String::from_utf8_lossy(value.as_str().unwrap().as_bytes()) - .trim() - .trim_end_matches('\0') - .to_string(), + value.as_str().unwrap().trim().trim_end_matches('\0').to_string(), ) }) .boxed(), - DynSolType::Tuple(params) => params + DynSolType::Tuple(ref params) => params .iter() - .map(|p| fuzz_param(p, no_zksync_reserved_addresses)) + .map(|param| fuzz_param_inner(param, None, no_zksync_reserved_addresses)) .collect::>() .prop_map(DynSolValue::Tuple) .boxed(), - DynSolType::FixedArray(param, size) => { - proptest::collection::vec(fuzz_param(¶m, no_zksync_reserved_addresses), size) - .prop_map(DynSolValue::FixedArray) - .boxed() - } - DynSolType::Array(param) => proptest::collection::vec( - fuzz_param(¶m, no_zksync_reserved_addresses), + DynSolType::FixedArray(ref param, size) => proptest::collection::vec( + fuzz_param_inner(param, None, no_zksync_reserved_addresses), + size, + ) + .prop_map(DynSolValue::FixedArray) + .boxed(), + DynSolType::Array(ref param) => proptest::collection::vec( + fuzz_param_inner(param, None, no_zksync_reserved_addresses), 0..MAX_ARRAY_LEN, ) .prop_map(DynSolValue::Array) .boxed(), - DynSolType::CustomStruct { .. } => panic!("unsupported type"), + _ => panic!("unsupported fuzz param type: {param}"), } } @@ -85,25 +126,31 @@ pub fn fuzz_param( /// Works with ABI Encoder v2 tuples. pub fn fuzz_param_from_state( param: &DynSolType, - arc_state: EvmFuzzState, + state: &EvmFuzzState, ) -> BoxedStrategy { - // These are to comply with lifetime requirements - let state_len = arc_state.read().values().len(); + let no_zksync_reserved_addresses = state.dictionary_read().no_zksync_reserved_addresses(); - // Select a value from the state - let st = arc_state.clone(); - let no_zksync_reserved_addresses = st.read().no_zksync_reserved_addresses(); - - let value = any::() - .prop_map(move |index| index.index(state_len)) - .prop_map(move |index| *st.read().values().iter().nth(index).unwrap()); - let param = param.to_owned(); + // Value strategy that uses the state. + let value = || { + let state = state.clone(); + let param = param.clone(); + // Generate a bias and use it to pick samples or non-persistent values (50 / 50). + // Use `Index` instead of `Selector` when selecting a value to avoid iterating over the + // entire dictionary. + any::<(bool, prop::sample::Index)>().prop_map(move |(bias, index)| { + let state = state.dictionary_read(); + let values = if bias { state.samples(¶m) } else { None } + .unwrap_or_else(|| state.values()) + .as_slice(); + values[index.index(values.len())] + }) + }; // Convert the value based on the parameter type - match param { - DynSolType::Address => value + match *param { + DynSolType::Address => value() .prop_map(move |value| { - let addr = Address::from_word(value.into()); + let addr = Address::from_word(value); if no_zksync_reserved_addresses { DynSolValue::Address(foundry_zksync_core::to_safe_address(addr)) } else { @@ -111,88 +158,82 @@ pub fn fuzz_param_from_state( } }) .boxed(), - DynSolType::FixedBytes(size) => value - .prop_map(move |v| { - let mut buf: [u8; 32] = [0; 32]; - - for b in v[..size].iter().enumerate() { - buf[b.0] = *b.1 - } - - let mut unstructured_v = Unstructured::new(v.as_slice()); - DynSolValue::arbitrary_from_type(¶m, &mut unstructured_v) - .unwrap_or(DynSolValue::FixedBytes(FixedBytes::from_slice(&buf), size)) + DynSolType::Function => value() + .prop_map(move |value| { + DynSolValue::Function(alloy_primitives::Function::from_word(value)) }) .boxed(), - DynSolType::Function | DynSolType::Bool => DynSolValue::type_strategy(¶m).boxed(), - DynSolType::String => DynSolValue::type_strategy(¶m) + DynSolType::FixedBytes(size @ 1..=32) => value() + .prop_map(move |mut v| { + v[size..].fill(0); + DynSolValue::FixedBytes(B256::from(v), size) + }) + .boxed(), + DynSolType::Bool => DynSolValue::type_strategy(param).boxed(), + DynSolType::String => DynSolValue::type_strategy(param) .prop_map(move |value| { DynSolValue::String( - String::from_utf8_lossy(value.as_str().unwrap().as_bytes()) - .trim() - .trim_end_matches('\0') - .to_string(), + value.as_str().unwrap().trim().trim_end_matches('\0').to_string(), ) }) .boxed(), - DynSolType::Int(n) => match n / 8 { - 32 => value - .prop_map(move |value| { - DynSolValue::Int(I256::from_raw(U256::from_be_bytes(value)), 256) - }) + DynSolType::Bytes => { + value().prop_map(move |value| DynSolValue::Bytes(value.0.into())).boxed() + } + DynSolType::Int(n @ 8..=256) => match n / 8 { + 32 => value() + .prop_map(move |value| DynSolValue::Int(I256::from_raw(value.into()), 256)) .boxed(), - y @ 1..=31 => value + 1..=31 => value() .prop_map(move |value| { // Generate a uintN in the correct range, then shift it to the range of intN // by subtracting 2^(N-1) - let uint = - U256::from_be_bytes(value) % U256::from(2usize).pow(U256::from(y * 8)); - let max_int_plus1 = U256::from(2usize).pow(U256::from(y * 8 - 1)); - let num = I256::from_raw(uint.overflowing_sub(max_int_plus1).0); - DynSolValue::Int(num, y * 8) + let uint = U256::from_be_bytes(value.0) % U256::from(1).wrapping_shl(n); + let max_int_plus1 = U256::from(1).wrapping_shl(n - 1); + let num = I256::from_raw(uint.wrapping_sub(max_int_plus1)); + DynSolValue::Int(num, n) }) .boxed(), - _ => panic!("unsupported solidity type int{n}"), + _ => unreachable!(), }, - DynSolType::Uint(n) => match n / 8 { - 32 => value - .prop_map(move |value| DynSolValue::Uint(U256::from_be_bytes(value), 256)) + DynSolType::Uint(n @ 8..=256) => match n / 8 { + 32 => value() + .prop_map(move |value| DynSolValue::Uint(U256::from_be_bytes(value.0), 256)) .boxed(), - y @ 1..=31 => value + 1..=31 => value() .prop_map(move |value| { - DynSolValue::Uint( - U256::from_be_bytes(value) % U256::from(2).pow(U256::from(y * 8)), - y * 8, - ) + let uint = U256::from_be_bytes(value.0) % U256::from(1).wrapping_shl(n); + DynSolValue::Uint(uint, n) }) .boxed(), - _ => panic!("unsupported solidity type uint{n}"), + _ => unreachable!(), }, - DynSolType::Tuple(params) => params + DynSolType::Tuple(ref params) => params .iter() - .map(|p| fuzz_param_from_state(p, arc_state.clone())) + .map(|p| fuzz_param_from_state(p, state)) .collect::>() .prop_map(DynSolValue::Tuple) .boxed(), - DynSolType::Bytes => value.prop_map(move |value| DynSolValue::Bytes(value.into())).boxed(), - DynSolType::FixedArray(param, size) => { - let fixed_size = size; - proptest::collection::vec(fuzz_param_from_state(¶m, arc_state), fixed_size) + DynSolType::FixedArray(ref param, size) => { + proptest::collection::vec(fuzz_param_from_state(param, state), size) .prop_map(DynSolValue::FixedArray) .boxed() } - DynSolType::Array(param) => { - proptest::collection::vec(fuzz_param_from_state(¶m, arc_state), 0..MAX_ARRAY_LEN) + DynSolType::Array(ref param) => { + proptest::collection::vec(fuzz_param_from_state(param, state), 0..MAX_ARRAY_LEN) .prop_map(DynSolValue::Array) .boxed() } - DynSolType::CustomStruct { .. } => panic!("unsupported type"), + _ => panic!("unsupported fuzz param type: {param}"), } } #[cfg(test)] mod tests { - use crate::strategies::{build_initial_state, fuzz_calldata, fuzz_calldata_from_state}; + use crate::{ + strategies::{fuzz_calldata, fuzz_calldata_from_state, EvmFuzzState}, + FuzzFixtures, + }; use foundry_common::abi::get_func; use foundry_config::FuzzDictionaryConfig; use revm::db::{CacheDB, EmptyDB}; @@ -202,11 +243,11 @@ mod tests { let f = "testArray(uint64[2] calldata values)"; let func = get_func(f).unwrap(); let db = CacheDB::new(EmptyDB::default()); - let state = build_initial_state(&db, &FuzzDictionaryConfig::default(), false); - let strat = proptest::strategy::Union::new_weighted(vec![ - (60, fuzz_calldata(func.clone(), false)), - (40, fuzz_calldata_from_state(func, state)), - ]); + let state = EvmFuzzState::new(&db, FuzzDictionaryConfig::default(), false); + let strat = proptest::prop_oneof![ + 60 => fuzz_calldata(func.clone(), &FuzzFixtures::default(), false), + 40 => fuzz_calldata_from_state(func, &state), + ]; let cfg = proptest::test_runner::Config { failure_persistence: None, ..Default::default() }; let mut runner = proptest::test_runner::TestRunner::new(cfg); let _ = runner.run(&strat, |_| Ok(())); diff --git a/crates/evm/fuzz/src/strategies/state.rs b/crates/evm/fuzz/src/strategies/state.rs index 345ef7d08..782e8a27b 100644 --- a/crates/evm/fuzz/src/strategies/state.rs +++ b/crates/evm/fuzz/src/strategies/state.rs @@ -1,34 +1,123 @@ -use super::fuzz_param_from_state; -use crate::invariant::{ArtifactFilters, FuzzRunIdentifiedContracts}; -use alloy_dyn_abi::{DynSolType, JsonAbiExt}; -use alloy_json_abi::Function; +use crate::invariant::{BasicTxDetails, FuzzRunIdentifiedContracts}; +use alloy_dyn_abi::{DynSolType, DynSolValue, EventExt, FunctionExt}; +use alloy_json_abi::{Function, JsonAbi}; use alloy_primitives::{Address, Bytes, Log, B256, U256}; -use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact}; use foundry_config::FuzzDictionaryConfig; use foundry_evm_core::utils::StateChangeset; -use hashbrown::HashSet; -use parking_lot::RwLock; -use proptest::prelude::{BoxedStrategy, Strategy}; +use indexmap::IndexSet; +use parking_lot::{lock_api::RwLockReadGuard, RawRwLock, RwLock}; use revm::{ - db::{CacheDB, DatabaseRef}, - interpreter::opcode::{self, spec_opcode_gas}, - primitives::SpecId, + db::{CacheDB, DatabaseRef, DbAccount}, + interpreter::opcode, + primitives::AccountInfo, }; -use std::{fmt, io::Write, str::FromStr, sync::Arc}; +use std::{ + collections::{BTreeMap, HashMap}, + fmt, + sync::Arc, +}; + +type AIndexSet = IndexSet>; + +/// The maximum number of bytes we will look at in bytecodes to find push bytes (24 KiB). +/// +/// This is to limit the performance impact of fuzz tests that might deploy arbitrarily sized +/// bytecode (as is the case with Solmate). +const PUSH_BYTE_ANALYSIS_LIMIT: usize = 24 * 1024; /// A set of arbitrary 32 byte data from the VM used to generate values for the strategy. /// /// Wrapped in a shareable container. -pub type EvmFuzzState = Arc>; +#[derive(Clone, Debug)] +pub struct EvmFuzzState { + inner: Arc>, +} +impl EvmFuzzState { + pub fn new( + db: &CacheDB, + config: FuzzDictionaryConfig, + no_zksync_reserved_addresses: bool, + ) -> Self { + // Sort accounts to ensure deterministic dictionary generation from the same setUp state. + let mut accs = db.accounts.iter().collect::>(); + accs.sort_by_key(|(address, _)| *address); + + // Create fuzz dictionary and insert values from db state. + let mut dictionary = FuzzDictionary::new(config, no_zksync_reserved_addresses); + dictionary.insert_db_values(accs); + Self { inner: Arc::new(RwLock::new(dictionary)) } + } + + pub fn collect_values(&self, values: impl IntoIterator) { + let mut dict = self.inner.write(); + for value in values { + dict.insert_value(value); + } + } + + /// Collects state changes from a [StateChangeset] and logs into an [EvmFuzzState] according to + /// the given [FuzzDictionaryConfig]. + pub fn collect_values_from_call( + &self, + fuzzed_contracts: &FuzzRunIdentifiedContracts, + tx: &BasicTxDetails, + result: &Bytes, + logs: &[Log], + state_changeset: &StateChangeset, + run_depth: u32, + ) { + let mut dict = self.inner.write(); + { + let targets = fuzzed_contracts.targets.lock(); + let (target_abi, target_function) = targets.fuzzed_artifacts(tx); + dict.insert_logs_values(target_abi, logs, run_depth); + dict.insert_result_values(target_function, result, run_depth); + } + dict.insert_new_state_values(state_changeset); + } + + /// Removes all newly added entries from the dictionary. + /// + /// Should be called between fuzz/invariant runs to avoid accumulating data derived from fuzz + /// inputs. + pub fn revert(&self) { + self.inner.write().revert(); + } + + pub fn dictionary_read(&self) -> RwLockReadGuard<'_, RawRwLock, FuzzDictionary> { + self.inner.read() + } + + /// Logs stats about the current state. + pub fn log_stats(&self) { + self.inner.read().log_stats(); + } +} + +// We're using `IndexSet` to have a stable element order when restoring persisted state, as well as +// for performance when iterating over the sets. #[derive(Default)] pub struct FuzzDictionary { /// Collected state values. - state_values: HashSet<[u8; 32]>, + state_values: AIndexSet, /// Addresses that already had their PUSH bytes collected. - addresses: HashSet
, + addresses: AIndexSet
, + /// Configuration for the dictionary. + config: FuzzDictionaryConfig, + /// Number of state values initially collected from db. + /// Used to revert new collected values at the end of each run. + db_state_values: usize, + /// Number of address values initially collected from db. + /// Used to revert new collected addresses at the end of each run. + db_addresses: usize, + /// Sample typed values that are collected from call result and used across invariant runs. + sample_values: HashMap>, /// Avoid addresses less than 2^16 as they are reserved in zkSync space. no_zksync_reserved_addresses: bool, + + misses: usize, + hits: usize, } impl fmt::Debug for FuzzDictionary { @@ -42,248 +131,266 @@ impl fmt::Debug for FuzzDictionary { } impl FuzzDictionary { - #[inline] - pub fn values(&self) -> &HashSet<[u8; 32]> { - &self.state_values + pub fn new(config: FuzzDictionaryConfig, no_zksync_reserved_addresses: bool) -> Self { + let mut dictionary = Self { config, no_zksync_reserved_addresses, ..Default::default() }; + dictionary.prefill(); + dictionary } - #[inline] - pub fn values_mut(&mut self) -> &mut HashSet<[u8; 32]> { - &mut self.state_values + /// Insert common values into the dictionary at initialization. + fn prefill(&mut self) { + self.insert_value(B256::ZERO); } - #[inline] - pub fn addresses(&mut self) -> &HashSet
{ - &self.addresses - } + /// Insert values from initial db state into fuzz dictionary. + /// These values are persisted across invariant runs. + fn insert_db_values(&mut self, db_state: Vec<(&Address, &DbAccount)>) { + for (address, account) in db_state { + // Insert basic account information + self.insert_value(address.into_word()); + // Insert push bytes + self.insert_push_bytes_values(address, &account.info); + // Insert storage values. + if self.config.include_storage { + // Sort storage values before inserting to ensure deterministic dictionary. + let values = account.storage.iter().collect::>(); + for (slot, value) in values { + self.insert_storage_value(slot, value); + } + } + } - #[inline] - pub fn addresses_mut(&mut self) -> &mut HashSet
{ - &mut self.addresses - } + // We need at least some state data if DB is empty, + // otherwise we can't select random data for state fuzzing. + if self.values().is_empty() { + // Prefill with a random address. + self.insert_value(Address::random().into_word()); + } - #[inline] - pub fn no_zksync_reserved_addresses(&self) -> bool { - self.no_zksync_reserved_addresses + // Record number of values and addresses inserted from db to be used for reverting at the + // end of each run. + self.db_state_values = self.state_values.len(); + self.db_addresses = self.addresses.len(); } -} -/// Given a function and some state, it returns a strategy which generated valid calldata for the -/// given function's input types, based on state taken from the EVM. -pub fn fuzz_calldata_from_state(func: Function, state: EvmFuzzState) -> BoxedStrategy { - let strats = func - .inputs - .iter() - .map(|input| { - fuzz_param_from_state( - &DynSolType::from_str(&input.selector_type()).unwrap(), - state.clone(), - ) - }) - .collect::>(); - - strats - .prop_map(move |tokens| { - func.abi_encode_input(&tokens) - .unwrap_or_else(|_| { - panic!( - "Fuzzer generated invalid tokens for function `{}` with inputs {:?}: {:?}", - func.name, func.inputs, tokens - ) - }) - .into() - }) - .no_shrink() - .boxed() -} + /// Insert values collected from call result into fuzz dictionary. + fn insert_result_values( + &mut self, + function: Option<&Function>, + result: &Bytes, + run_depth: u32, + ) { + if let Some(function) = function { + if !function.outputs.is_empty() { + // Decode result and collect samples to be used in subsequent fuzz runs. + if let Ok(decoded_result) = function.abi_decode_output(result, false) { + self.insert_sample_values(decoded_result, run_depth); + } + } + } + } -/// Builds the initial [EvmFuzzState] from a database. -pub fn build_initial_state( - db: &CacheDB, - config: &FuzzDictionaryConfig, - no_zksync_reserved_addresses: bool, -) -> EvmFuzzState { - let mut state = FuzzDictionary { no_zksync_reserved_addresses, ..Default::default() }; - - for (address, account) in db.accounts.iter() { - let address: Address = *address; - let val = address.into_word().into(); - // Insert basic account information - // state.values_mut().insert(address.into_word().into()); - state.values_mut().insert(val); - - // Insert push bytes - if config.include_push_bytes { - if let Some(code) = &account.info.code { - if state.addresses_mut().insert(address) { - for push_byte in collect_push_bytes(code.bytes()) { - state.values_mut().insert(push_byte); + /// Insert values from call log topics and data into fuzz dictionary. + fn insert_logs_values(&mut self, abi: Option<&JsonAbi>, logs: &[Log], run_depth: u32) { + let mut samples = Vec::new(); + // Decode logs with known events and collect samples from indexed fields and event body. + for log in logs { + let mut log_decoded = false; + // Try to decode log with events from contract abi. + if let Some(abi) = abi { + for event in abi.events() { + if let Ok(decoded_event) = event.decode_log(log, false) { + samples.extend(decoded_event.indexed); + samples.extend(decoded_event.body); + log_decoded = true; + break; } } } - } - if config.include_storage { - // Insert storage - for (slot, value) in &account.storage { - state.values_mut().insert(B256::from(*slot).0); - state.values_mut().insert(B256::from(*value).0); - // also add the value below and above the storage value to the dictionary. - if *value != U256::ZERO { - let below_value = value - U256::from(1); - state.values_mut().insert(B256::from(below_value).0); + // If we weren't able to decode event then we insert raw data in fuzz dictionary. + if !log_decoded { + for &topic in log.topics() { + self.insert_value(topic); + } + let chunks = log.data.data.chunks_exact(32); + let rem = chunks.remainder(); + for chunk in chunks { + self.insert_value(chunk.try_into().unwrap()); } - if *value != U256::MAX { - let above_value = value + U256::from(1); - state.values_mut().insert(B256::from(above_value).0); + if !rem.is_empty() { + self.insert_value(B256::right_padding_from(rem)); } } } - } - // need at least some state data if db is empty otherwise we can't select random data for state - // fuzzing - if state.values().is_empty() { - // prefill with a random addresses - state.values_mut().insert(Address::random().into_word().into()); + // Insert samples collected from current call in fuzz dictionary. + self.insert_sample_values(samples, run_depth); } - Arc::new(RwLock::new(state)) -} + /// Insert values from call state changeset into fuzz dictionary. + /// These values are removed at the end of current run. + fn insert_new_state_values(&mut self, state_changeset: &StateChangeset) { + for (address, account) in state_changeset { + // Insert basic account information. + self.insert_value(address.into_word()); + // Insert push bytes. + self.insert_push_bytes_values(address, &account.info); + // Insert storage values. + if self.config.include_storage { + for (slot, value) in &account.storage { + self.insert_storage_value(slot, &value.present_value); + } + } + } + } -/// Collects state changes from a [StateChangeset] and logs into an [EvmFuzzState] according to the -/// given [FuzzDictionaryConfig]. -pub fn collect_state_from_call( - logs: &[Log], - state_changeset: &StateChangeset, - state: EvmFuzzState, - config: &FuzzDictionaryConfig, -) { - let mut state = state.write(); - - for (address, account) in state_changeset { - // Insert basic account information - state.values_mut().insert(address.into_word().into()); - - if config.include_push_bytes && state.addresses.len() < config.max_fuzz_dictionary_addresses - { + /// Insert values from push bytes into fuzz dictionary. + /// Values are collected only once for a given address. + /// If values are newly collected then they are removed at the end of current run. + fn insert_push_bytes_values(&mut self, address: &Address, account_info: &AccountInfo) { + if self.config.include_push_bytes && !self.addresses.contains(address) { // Insert push bytes - if let Some(code) = &account.info.code { - if state.addresses_mut().insert(*address) { - for push_byte in collect_push_bytes(code.bytes()) { - state.values_mut().insert(push_byte); - } - } + if let Some(code) = &account_info.code { + self.insert_address(*address); + self.collect_push_bytes(code.bytes_slice()); } } + } - if config.include_storage && state.state_values.len() < config.max_fuzz_dictionary_values { - // Insert storage - for (slot, value) in &account.storage { - let value = value.present_value; - state.values_mut().insert(B256::from(*slot).0); - state.values_mut().insert(B256::from(value).0); - // also add the value below and above the storage value to the dictionary. - if value != U256::ZERO { - let below_value = value - U256::from(1); - state.values_mut().insert(B256::from(below_value).0); + fn collect_push_bytes(&mut self, code: &[u8]) { + let mut i = 0; + let len = code.len().min(PUSH_BYTE_ANALYSIS_LIMIT); + while i < len { + let op = code[i]; + if (opcode::PUSH1..=opcode::PUSH32).contains(&op) { + let push_size = (op - opcode::PUSH1 + 1) as usize; + let push_start = i + 1; + let push_end = push_start + push_size; + // As a precaution, if a fuzz test deploys malformed bytecode (such as using + // `CREATE2`) this will terminate the loop early. + if push_start > code.len() || push_end > code.len() { + break; } - if value != U256::MAX { - let above_value = value + U256::from(1); - state.values_mut().insert(B256::from(above_value).0); + + let push_value = U256::try_from_be_slice(&code[push_start..push_end]).unwrap(); + if push_value != U256::ZERO { + // Never add 0 to the dictionary as it's always present. + self.insert_value(push_value.into()); + + // Also add the value below and above the push value to the dictionary. + self.insert_value((push_value - U256::from(1)).into()); + + if push_value != U256::MAX { + self.insert_value((push_value + U256::from(1)).into()); + } } + + i += push_size; } - } else { - return; + i += 1; } + } - // Insert log topics and data - for log in logs { - log.data.topics().iter().for_each(|topic| { - state.values_mut().insert(topic.0); - }); - log.data.data.chunks(32).for_each(|chunk| { - let mut buffer: [u8; 32] = [0; 32]; - let _ = (&mut buffer[..]) - .write(chunk) - .expect("log data chunk was larger than 32 bytes"); - state.values_mut().insert(buffer); - }); + /// Insert values from single storage slot and storage value into fuzz dictionary. + /// If storage values are newly collected then they are removed at the end of current run. + fn insert_storage_value(&mut self, storage_slot: &U256, storage_value: &U256) { + self.insert_value(B256::from(*storage_slot)); + self.insert_value(B256::from(*storage_value)); + // also add the value below and above the storage value to the dictionary. + if *storage_value != U256::ZERO { + let below_value = storage_value - U256::from(1); + self.insert_value(B256::from(below_value)); + } + if *storage_value != U256::MAX { + let above_value = storage_value + U256::from(1); + self.insert_value(B256::from(above_value)); } } -} -/// The maximum number of bytes we will look at in bytecodes to find push bytes (24 KiB). -/// -/// This is to limit the performance impact of fuzz tests that might deploy arbitrarily sized -/// bytecode (as is the case with Solmate). -const PUSH_BYTE_ANALYSIS_LIMIT: usize = 24 * 1024; - -/// Collects all push bytes from the given bytecode. -fn collect_push_bytes(code: &[u8]) -> Vec<[u8; 32]> { - let mut bytes: Vec<[u8; 32]> = Vec::new(); - // We use [SpecId::LATEST] since we do not really care what spec it is - we are not interested - // in gas costs. - let opcode_infos = spec_opcode_gas(SpecId::LATEST); - let mut i = 0; - while i < code.len().min(PUSH_BYTE_ANALYSIS_LIMIT) { - let op = code[i]; - if opcode_infos[op as usize].is_push() { - let push_size = (op - opcode::PUSH1 + 1) as usize; - let push_start = i + 1; - let push_end = push_start + push_size; - // As a precaution, if a fuzz test deploys malformed bytecode (such as using `CREATE2`) - // this will terminate the loop early. - if push_start > code.len() || push_end > code.len() { - return bytes; - } - - let push_value = U256::try_from_be_slice(&code[push_start..push_end]).unwrap(); - bytes.push(push_value.to_be_bytes()); - // also add the value below and above the push value to the dictionary. - if push_value != U256::ZERO { - bytes.push((push_value - U256::from(1)).to_be_bytes()); - } - if push_value != U256::MAX { - bytes.push((push_value + U256::from(1)).to_be_bytes()); - } + /// Insert address into fuzz dictionary. + /// If address is newly collected then it is removed by index at the end of current run. + fn insert_address(&mut self, address: Address) { + if self.addresses.len() < self.config.max_fuzz_dictionary_addresses { + self.addresses.insert(address); + } + } - i += push_size; + /// Insert raw value into fuzz dictionary. + /// If value is newly collected then it is removed by index at the end of current run. + fn insert_value(&mut self, value: B256) { + if self.state_values.len() < self.config.max_fuzz_dictionary_values { + let new_value = self.state_values.insert(value); + let counter = if new_value { &mut self.misses } else { &mut self.hits }; + *counter += 1; } - i += 1; } - bytes -} -/// Collects all created contracts from a StateChangeset which haven't been discovered yet. Stores -/// them at `targeted_contracts` and `created_contracts`. -pub fn collect_created_contracts( - state_changeset: &StateChangeset, - project_contracts: &ContractsByArtifact, - setup_contracts: &ContractsByAddress, - artifact_filters: &ArtifactFilters, - targeted_contracts: FuzzRunIdentifiedContracts, - created_contracts: &mut Vec
, -) -> eyre::Result<()> { - let mut writable_targeted = targeted_contracts.lock(); - for (address, account) in state_changeset { - if !setup_contracts.contains_key(address) { - if let (true, Some(code)) = (&account.is_touched(), &account.info.code) { - if !code.is_empty() { - if let Some((artifact, (abi, _))) = - project_contracts.find_by_code(&code.original_bytes()) - { - if let Some(functions) = - artifact_filters.get_targeted_functions(artifact, abi)? - { - created_contracts.push(*address); - writable_targeted - .insert(*address, (artifact.name.clone(), abi.clone(), functions)); - } + /// Insert sample values that are reused across multiple runs. + /// The number of samples is limited to invariant run depth. + /// If collected samples limit is reached then values are inserted as regular values. + pub fn insert_sample_values( + &mut self, + sample_values: impl IntoIterator, + limit: u32, + ) { + for sample in sample_values { + if let (Some(sample_type), Some(sample_value)) = (sample.as_type(), sample.as_word()) { + if let Some(values) = self.sample_values.get_mut(&sample_type) { + if values.len() < limit as usize { + values.insert(sample_value); + } else { + // Insert as state value (will be removed at the end of the run). + self.insert_value(sample_value); } + } else { + self.sample_values.entry(sample_type).or_default().insert(sample_value); } } } } - Ok(()) + + pub fn values(&self) -> &AIndexSet { + &self.state_values + } + + pub fn len(&self) -> usize { + self.state_values.len() + } + + pub fn is_empty(&self) -> bool { + self.state_values.is_empty() + } + + #[inline] + pub fn samples(&self, param_type: &DynSolType) -> Option<&AIndexSet> { + self.sample_values.get(param_type) + } + + #[inline] + pub fn addresses(&self) -> &AIndexSet
{ + &self.addresses + } + + #[inline] + pub fn no_zksync_reserved_addresses(&self) -> bool { + self.no_zksync_reserved_addresses + } + + /// Revert values and addresses collected during the run by truncating to initial db len. + pub fn revert(&mut self) { + self.state_values.truncate(self.db_state_values); + self.addresses.truncate(self.db_addresses); + } + + pub fn log_stats(&self) { + trace!( + addresses.len = self.addresses.len(), + sample.len = self.sample_values.len(), + state.len = self.state_values.len(), + state.misses = self.misses, + state.hits = self.hits, + "FuzzDictionary stats", + ); + } } diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index e1d745526..af133efa0 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -1,3 +1,4 @@ +use alloy_dyn_abi::{DynSolType, DynSolValue}; use alloy_primitives::U256; use proptest::{ strategy::{NewTree, Strategy, ValueTree}, @@ -6,7 +7,6 @@ use proptest::{ use rand::Rng; /// Value tree for unsigned ints (up to uint256). -/// This is very similar to [proptest::BinarySearch] pub struct UintValueTree { /// Lower base lo: U256, @@ -68,15 +68,23 @@ impl ValueTree for UintValueTree { /// Value tree for unsigned ints (up to uint256). /// The strategy combines 3 different strategies, each assigned a specific weight: /// 1. Generate purely random value in a range. This will first choose bit size uniformly (up `bits` -/// param). Then generate a value for this bit size. +/// param). Then generate a value for this bit size. /// 2. Generate a random value around the edges (+/- 3 around 0 and max possible value) /// 3. Generate a value from a predefined fixtures set +/// +/// To define uint fixtures: +/// - return an array of possible values for a parameter named `amount` declare a function `function +/// fixture_amount() public returns (uint32[] memory)`. +/// - use `amount` named parameter in fuzzed test in order to include fixtures in fuzzed values +/// `function testFuzz_uint32(uint32 amount)`. +/// +/// If fixture is not a valid uint type then error is raised and random value generated. #[derive(Debug)] pub struct UintStrategy { /// Bit size of uint (e.g. 256) bits: usize, /// A set of fixtures to be generated - fixtures: Vec, + fixtures: Vec, /// The weight for edge cases (+/- 3 around 0 and max possible value) edge_weight: usize, /// The weight for fixtures @@ -90,10 +98,10 @@ impl UintStrategy { /// #Arguments /// * `bits` - Size of uint in bits /// * `fixtures` - A set of fixed values to be generated (according to fixtures weight) - pub fn new(bits: usize, fixtures: Vec) -> Self { + pub fn new(bits: usize, fixtures: Option<&[DynSolValue]>) -> Self { Self { bits, - fixtures, + fixtures: Vec::from(fixtures.unwrap_or_default()), edge_weight: 10usize, fixtures_weight: 40usize, random_weight: 50usize, @@ -105,19 +113,27 @@ impl UintStrategy { // Choose if we want values around 0 or max let is_min = rng.gen_bool(0.5); let offset = U256::from(rng.gen_range(0..4)); - let max = - if self.bits < 256 { (U256::from(1) << self.bits) - U256::from(1) } else { U256::MAX }; - let start = if is_min { offset } else { max.saturating_sub(offset) }; + let start = if is_min { offset } else { self.type_max().saturating_sub(offset) }; Ok(UintValueTree::new(start, false)) } fn generate_fixtures_tree(&self, runner: &mut TestRunner) -> NewTree { - // generate edge cases if there's no fixtures + // generate random cases if there's no fixtures if self.fixtures.is_empty() { - return self.generate_edge_tree(runner) + return self.generate_random_tree(runner) } - let idx = runner.rng().gen_range(0..self.fixtures.len()); - Ok(UintValueTree::new(self.fixtures[idx], false)) + + // Generate value tree from fixture. + let fixture = &self.fixtures[runner.rng().gen_range(0..self.fixtures.len())]; + if let Some(uint_fixture) = fixture.as_uint() { + if uint_fixture.1 == self.bits { + return Ok(UintValueTree::new(uint_fixture.0, false)); + } + } + + // If fixture is not a valid type, raise error and generate random value. + error!("{:?} is not a valid {} fixture", fixture, DynSolType::Uint(self.bits)); + self.generate_random_tree(runner) } fn generate_random_tree(&self, runner: &mut TestRunner) -> NewTree { @@ -151,6 +167,14 @@ impl UintStrategy { Ok(UintValueTree::new(start, false)) } + + fn type_max(&self) -> U256 { + if self.bits < 256 { + (U256::from(1) << self.bits) - U256::from(1) + } else { + U256::MAX + } + } } impl Strategy for UintStrategy { @@ -167,3 +191,19 @@ impl Strategy for UintStrategy { } } } + +#[cfg(test)] +mod tests { + use crate::strategies::uint::UintValueTree; + use alloy_primitives::U256; + use proptest::strategy::ValueTree; + + #[test] + fn test_uint_tree_complicate_max() { + let mut uint_tree = UintValueTree::new(U256::MAX, false); + assert_eq!(uint_tree.hi, U256::MAX); + assert_eq!(uint_tree.curr, U256::MAX); + uint_tree.complicate(); + assert_eq!(uint_tree.lo, U256::MIN); + } +} diff --git a/crates/evm/traces/Cargo.toml b/crates/evm/traces/Cargo.toml index cc5bd7ef8..9f1c15c13 100644 --- a/crates/evm/traces/Cargo.toml +++ b/crates/evm/traces/Cargo.toml @@ -10,6 +10,9 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [dependencies] foundry-block-explorers.workspace = true foundry-common.workspace = true @@ -20,20 +23,26 @@ foundry-evm-core.workspace = true alloy-dyn-abi = { workspace = true, features = ["arbitrary", "eip712"] } alloy-json-abi.workspace = true -alloy-primitives = { workspace = true, features = ["serde", "getrandom", "arbitrary", "rlp"] } +alloy-primitives = { workspace = true, features = [ + "serde", + "getrandom", + "arbitrary", + "rlp", +] } alloy-sol-types.workspace = true revm-inspectors.workspace = true -eyre = "0.6" -futures = "0.3" -hashbrown = "0.14" +eyre .workspace = true +futures.workspace = true hex.workspace = true itertools.workspace = true -once_cell = "1" -serde = "1" -tokio = { version = "1", features = ["time", "macros"] } -tracing = "0.1" -yansi = "0.5" +once_cell.workspace = true +serde.workspace = true +tokio = { workspace = true, features = ["time", "macros"] } +tracing.workspace = true +yansi.workspace = true +rustc-hash.workspace = true +tempfile.workspace = true [dev-dependencies] -tempfile = "3" +tempfile.workspace = true diff --git a/crates/evm/traces/src/decoder/mod.rs b/crates/evm/traces/src/decoder/mod.rs index 86d5ab53d..1985fc2cd 100644 --- a/crates/evm/traces/src/decoder/mod.rs +++ b/crates/evm/traces/src/decoder/mod.rs @@ -9,10 +9,8 @@ use alloy_json_abi::{Error, Event, Function, JsonAbi}; use alloy_primitives::{Address, LogData, Selector, B256}; use foundry_cheatcodes_spec::Vm; use foundry_common::{ - abi::get_indexed_event, - console::{Console, HardhatConsole, HARDHAT_CONSOLE_SELECTOR_PATCHES}, - fmt::format_token, - SELECTOR_LEN, + abi::get_indexed_event, fmt::format_token, Console, ContractsByArtifact, HardhatConsole, + HARDHAT_CONSOLE_SELECTOR_PATCHES, SELECTOR_LEN, }; use foundry_evm_core::{ constants::{ @@ -23,6 +21,7 @@ use foundry_evm_core::{ }; use itertools::Itertools; use once_cell::sync::OnceCell; +use rustc_hash::FxHashMap; use std::collections::{hash_map::Entry, BTreeMap, HashMap}; mod precompiles; @@ -55,17 +54,22 @@ impl CallTraceDecoderBuilder { self } - /// Add known contracts to the decoder from a `LocalTraceIdentifier`. + /// Add known contracts to the decoder. #[inline] - pub fn with_local_identifier_abis(mut self, identifier: &LocalTraceIdentifier<'_>) -> Self { - let contracts = identifier.contracts(); - trace!(target: "evm::traces", len=contracts.len(), "collecting local identifier ABIs"); - for (abi, _) in contracts.values() { - self.decoder.collect_abi(abi, None); + pub fn with_known_contracts(mut self, contracts: &ContractsByArtifact) -> Self { + trace!(target: "evm::traces", len=contracts.len(), "collecting known contract ABIs"); + for contract in contracts.values() { + self.decoder.collect_abi(&contract.abi, None); } self } + /// Add known contracts to the decoder from a `LocalTraceIdentifier`. + #[inline] + pub fn with_local_identifier_abis(self, identifier: &LocalTraceIdentifier<'_>) -> Self { + self.with_known_contracts(identifier.contracts()) + } + /// Sets the verbosity level of the decoder. #[inline] pub fn with_verbosity(mut self, level: u8) -> Self { @@ -106,7 +110,7 @@ pub struct CallTraceDecoder { pub receive_contracts: Vec
, /// All known functions. - pub functions: HashMap>, + pub functions: FxHashMap>, /// All known events. pub events: BTreeMap<(B256, usize), Vec>, /// Revert decoder. Contains all known custom errors. @@ -187,7 +191,7 @@ impl CallTraceDecoder { let default_labels = &Self::new().labels; if self.labels.len() > default_labels.len() { - self.labels = default_labels.clone(); + self.labels.clone_from(default_labels); } self.receive_contracts.clear(); @@ -197,7 +201,7 @@ impl CallTraceDecoder { /// /// Unknown contracts are contracts that either lack a label or an ABI. pub fn identify(&mut self, trace: &CallTraceArena, identifier: &mut impl TraceIdentifier) { - self.collect_identities(identifier.identify_addresses(self.addresses(trace))); + self.collect_identities(identifier.identify_addresses(self.trace_addresses(trace))); } /// Adds a single event to the decoder. @@ -227,21 +231,21 @@ impl CallTraceDecoder { self.revert_decoder.push_error(error); } - fn addresses<'a>( + /// Returns an iterator over the trace addresses. + pub fn trace_addresses<'a>( &'a self, arena: &'a CallTraceArena, - ) -> impl Iterator)> + 'a { + ) -> impl Iterator)> + Clone + 'a { arena .nodes() .iter() .map(|node| { - ( - &node.trace.address, - node.trace.kind.is_any_create().then_some(&node.trace.output[..]), - ) + let address = &node.trace.address; + let output = node.trace.kind.is_any_create().then_some(&node.trace.output[..]); + (address, output) }) - .filter(|(address, _)| { - !self.labels.contains_key(*address) || !self.contracts.contains_key(*address) + .filter(|&(address, _)| { + !self.labels.contains_key(address) || !self.contracts.contains_key(address) }) } @@ -308,7 +312,8 @@ impl CallTraceDecoder { if trace.address == DEFAULT_CREATE2_DEPLOYER { return DecodedCallTrace { label, - return_data: None, + return_data: (!trace.status.is_ok()) + .then(|| self.revert_decoder.decode(&trace.output, Some(trace.status))), contract, func: Some(DecodedCallData { signature: "create2".to_string(), args: vec![] }), }; @@ -434,9 +439,12 @@ impl CallTraceDecoder { "parseJsonBytes32" | "parseJsonBytes32Array" | "writeJson" | - "keyExists" | + // `keyExists` is being deprecated in favor of `keyExistsJson`. It will be removed in future versions. + "keyExists" | + "keyExistsJson" | "serializeBool" | "serializeUint" | + "serializeUintToHex" | "serializeInt" | "serializeAddress" | "serializeBytes32" | @@ -446,12 +454,31 @@ impl CallTraceDecoder { None } else { let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..], false).ok()?; - let token = - if func.name.as_str() == "parseJson" || func.name.as_str() == "keyExists" { - "" - } else { - "" - }; + let token = if func.name.as_str() == "parseJson" || + // `keyExists` is being deprecated in favor of `keyExistsJson`. It will be removed in future versions. + func.name.as_str() == "keyExists" || + func.name.as_str() == "keyExistsJson" + { + "" + } else { + "" + }; + decoded[0] = DynSolValue::String(token.to_string()); + Some(decoded.iter().map(format_token).collect()) + } + } + s if s.contains("Toml") => { + if self.verbosity >= 5 { + None + } else { + let mut decoded = func.abi_decode_input(&data[SELECTOR_LEN..], false).ok()?; + let token = if func.name.as_str() == "parseToml" || + func.name.as_str() == "keyExistsToml" + { + "" + } else { + "" + }; decoded[0] = DynSolValue::String(token.to_string()); Some(decoded.iter().map(format_token).collect()) } @@ -497,6 +524,7 @@ impl CallTraceDecoder { match func.name.as_str() { s if s.starts_with("env") => Some(""), "createWallet" | "deriveKey" => Some(""), + "promptSecret" | "promptSecretUint" => Some(""), "parseJson" if self.verbosity < 5 => Some(""), "readFile" if self.verbosity < 5 => Some(""), _ => None, @@ -671,13 +699,13 @@ mod tests { for (function_signature, data, expected) in cheatcode_input_test_cases { let function = Function::parse(function_signature).unwrap(); let result = decoder.decode_cheatcode_inputs(&function, &data); - assert_eq!(result, expected, "Input case failed for: {}", function_signature); + assert_eq!(result, expected, "Input case failed for: {function_signature}"); } for (function_signature, expected) in cheatcode_output_test_cases { let function = Function::parse(function_signature).unwrap(); let result = Some(decoder.decode_cheatcode_outputs(&function).unwrap_or_default()); - assert_eq!(result, expected, "Output case failed for: {}", function_signature); + assert_eq!(result, expected, "Output case failed for: {function_signature}"); } } } diff --git a/crates/evm/traces/src/identifier/etherscan.rs b/crates/evm/traces/src/identifier/etherscan.rs index 43552f12b..87d7e9c92 100644 --- a/crates/evm/traces/src/identifier/etherscan.rs +++ b/crates/evm/traces/src/identifier/etherscan.rs @@ -4,14 +4,12 @@ use foundry_block_explorers::{ contract::{ContractMetadata, Metadata}, errors::EtherscanError, }; -use foundry_common::compile::{self, ContractSources}; +use foundry_common::compile::{etherscan_project, ContractSources}; use foundry_config::{Chain, Config}; -use foundry_evm_core::utils::RuntimeOrHandle; use futures::{ future::{join_all, Future}, stream::{FuturesUnordered, Stream, StreamExt}, task::{Context, Poll}, - TryFutureExt, }; use std::{ borrow::Cow, @@ -25,10 +23,9 @@ use std::{ use tokio::time::{Duration, Interval}; /// A trace identifier that tries to identify addresses using Etherscan. -#[derive(Default)] pub struct EtherscanIdentifier { /// The Etherscan client - client: Option>, + client: Arc, /// Tracks whether the API key provides was marked as invalid /// /// After the first [EtherscanError::InvalidApiKey] this will get set to true, so we can @@ -40,54 +37,56 @@ pub struct EtherscanIdentifier { impl EtherscanIdentifier { /// Creates a new Etherscan identifier with the given client - pub fn new(config: &Config, chain: Option) -> eyre::Result { + pub fn new(config: &Config, chain: Option) -> eyre::Result> { + // In offline mode, don't use Etherscan. if config.offline { - // offline mode, don't use etherscan - return Ok(Default::default()) - } - if let Some(config) = config.get_etherscan_config_with_chain(chain)? { - trace!(target: "etherscanidentifier", chain=?config.chain, url=?config.api_url, "using etherscan identifier"); - Ok(Self { - client: Some(Arc::new(config.into_client()?)), - invalid_api_key: Arc::new(Default::default()), - contracts: BTreeMap::new(), - sources: BTreeMap::new(), - }) - } else { - Ok(Default::default()) + return Ok(None); } + let Some(config) = config.get_etherscan_config_with_chain(chain)? else { + return Ok(None); + }; + trace!(target: "traces::etherscan", chain=?config.chain, url=?config.api_url, "using etherscan identifier"); + Ok(Some(Self { + client: Arc::new(config.into_client()?), + invalid_api_key: Arc::new(AtomicBool::new(false)), + contracts: BTreeMap::new(), + sources: BTreeMap::new(), + })) } /// Goes over the list of contracts we have pulled from the traces, clones their source from /// Etherscan and compiles them locally, for usage in the debugger. pub async fn get_compiled_contracts(&self) -> eyre::Result { // TODO: Add caching so we dont double-fetch contracts. - let contracts_iter = self + let outputs_fut = self .contracts .iter() // filter out vyper files - .filter(|(_, metadata)| !metadata.is_vyper()); - - let outputs_fut = contracts_iter - .clone() - .map(|(address, metadata)| { + .filter(|(_, metadata)| !metadata.is_vyper()) + .map(|(address, metadata)| async move { println!("Compiling: {} {address}", metadata.contract_name); - let err_msg = - format!("Failed to compile contract {} from {address}", metadata.contract_name); - compile::compile_from_source(metadata).map_err(move |err| err.wrap_err(err_msg)) + let root = tempfile::tempdir()?; + let root_path = root.path(); + let project = etherscan_project(metadata, root_path)?; + let output = project.compile()?; + + if output.has_compiler_errors() { + eyre::bail!("{output}") + } + + Ok((project, output, root)) }) .collect::>(); // poll all the futures concurrently - let artifacts = join_all(outputs_fut).await; + let outputs = join_all(outputs_fut).await; let mut sources: ContractSources = Default::default(); // construct the map - for (results, (_, metadata)) in artifacts.into_iter().zip(contracts_iter) { - // get the inner type - let (artifact_id, file_id, bytecode) = results?; - sources.insert(&artifact_id, file_id, metadata.source_code(), bytecode); + for res in outputs { + let (project, output, _) = res?; + sources.insert(&output, project.root(), None)?; } Ok(sources) @@ -101,46 +100,56 @@ impl TraceIdentifier for EtherscanIdentifier { { trace!(target: "evm::traces", "identify {:?} addresses", addresses.size_hint().1); - let Some(client) = self.client.clone() else { - // no client was configured - return Vec::new() - }; - if self.invalid_api_key.load(Ordering::Relaxed) { // api key was marked as invalid return Vec::new() } + let mut identities = Vec::new(); let mut fetcher = EtherscanFetcher::new( - client, + self.client.clone(), Duration::from_secs(1), 5, Arc::clone(&self.invalid_api_key), ); for (addr, _) in addresses { - if !self.contracts.contains_key(addr) { - fetcher.push(*addr); - } - } - - let fut = fetcher - .map(|(address, metadata)| { + if let Some(metadata) = self.contracts.get(addr) { let label = metadata.contract_name.clone(); let abi = metadata.abi().ok().map(Cow::Owned); - self.contracts.insert(address, metadata); - AddressIdentity { - address, + identities.push(AddressIdentity { + address: *addr, label: Some(label.clone()), contract: Some(label), abi, artifact_id: None, - } - }) - .collect(); + }); + } else { + fetcher.push(*addr); + } + } + + let fetched_identities = foundry_common::block_on( + fetcher + .map(|(address, metadata)| { + let label = metadata.contract_name.clone(); + let abi = metadata.abi().ok().map(Cow::Owned); + self.contracts.insert(address, metadata); - RuntimeOrHandle::new().block_on(fut) + AddressIdentity { + address, + label: Some(label.clone()), + contract: Some(label), + abi, + artifact_id: None, + } + }) + .collect::>>(), + ); + + identities.extend(fetched_identities); + identities } } @@ -191,16 +200,13 @@ impl EtherscanFetcher { fn queue_next_reqs(&mut self) { while self.in_progress.len() < self.concurrency { - if let Some(addr) = self.queue.pop() { - let client = Arc::clone(&self.client); - trace!(target: "etherscanidentifier", "fetching info for {:?}", addr); - self.in_progress.push(Box::pin(async move { - let res = client.contract_source_code(addr).await; - (addr, res) - })); - } else { - break - } + let Some(addr) = self.queue.pop() else { break }; + let client = Arc::clone(&self.client); + self.in_progress.push(Box::pin(async move { + trace!(target: "traces::etherscan", ?addr, "fetching info"); + let res = client.contract_source_code(addr).await; + (addr, res) + })); } } } @@ -234,24 +240,24 @@ impl Stream for EtherscanFetcher { } } Err(EtherscanError::RateLimitExceeded) => { - warn!(target: "etherscanidentifier", "rate limit exceeded on attempt"); + warn!(target: "traces::etherscan", "rate limit exceeded on attempt"); pin.backoff = Some(tokio::time::interval(pin.timeout)); pin.queue.push(addr); } Err(EtherscanError::InvalidApiKey) => { - warn!(target: "etherscanidentifier", "invalid api key"); + warn!(target: "traces::etherscan", "invalid api key"); // mark key as invalid pin.invalid_api_key.store(true, Ordering::Relaxed); return Poll::Ready(None) } Err(EtherscanError::BlockedByCloudflare) => { - warn!(target: "etherscanidentifier", "blocked by cloudflare"); + warn!(target: "traces::etherscan", "blocked by cloudflare"); // mark key as invalid pin.invalid_api_key.store(true, Ordering::Relaxed); return Poll::Ready(None) } Err(err) => { - warn!(target: "etherscanidentifier", "could not get etherscan info: {:?}", err); + warn!(target: "traces::etherscan", "could not get etherscan info: {:?}", err); } } } diff --git a/crates/evm/traces/src/identifier/local.rs b/crates/evm/traces/src/identifier/local.rs index 193a8c526..d21022d96 100644 --- a/crates/evm/traces/src/identifier/local.rs +++ b/crates/evm/traces/src/identifier/local.rs @@ -1,26 +1,31 @@ use super::{AddressIdentity, TraceIdentifier}; use alloy_json_abi::JsonAbi; -use alloy_primitives::Address; +use alloy_primitives::{Address, Bytes}; use foundry_common::contracts::{bytecode_diff_score, ContractsByArtifact}; use foundry_compilers::ArtifactId; -use std::borrow::Cow; +use std::{borrow::Cow, collections::HashMap}; /// A trace identifier that tries to identify addresses using local contracts. pub struct LocalTraceIdentifier<'a> { /// Known contracts to search through. known_contracts: &'a ContractsByArtifact, - /// Vector of pairs of artifact ID and the code length of the given artifact. + /// Vector of pairs of artifact ID and the runtime code length of the given artifact. ordered_ids: Vec<(&'a ArtifactId, usize)>, + /// Deployments generated during the setup + pub deployments: HashMap, } impl<'a> LocalTraceIdentifier<'a> { /// Creates a new local trace identifier. #[inline] pub fn new(known_contracts: &'a ContractsByArtifact) -> Self { - let mut ordered_ids = - known_contracts.iter().map(|(id, contract)| (id, contract.1.len())).collect::>(); + let mut ordered_ids = known_contracts + .iter() + .filter_map(|(id, contract)| Some((id, contract.deployed_bytecode()?))) + .map(|(id, bytecode)| (id, bytecode.len())) + .collect::>(); ordered_ids.sort_by_key(|(_, len)| *len); - Self { known_contracts, ordered_ids } + Self { known_contracts, ordered_ids, deployments: HashMap::new() } } /// Returns the known contracts. @@ -37,15 +42,17 @@ impl<'a> LocalTraceIdentifier<'a> { let mut min_score_id = None; let mut check = |id| { - let (abi, known_code) = self.known_contracts.get(id)?; - let score = bytecode_diff_score(known_code, code); - if score == 0.0 { - trace!(target: "evm::traces", "found exact match"); - return Some((id, abi)); - } - if score < min_score { - min_score = score; - min_score_id = Some((id, abi)); + let contract = self.known_contracts.get(id)?; + if let Some(deployed_bytecode) = contract.deployed_bytecode() { + let score = bytecode_diff_score(deployed_bytecode, code); + if score == 0.0 { + trace!(target: "evm::traces", "found exact match"); + return Some((id, &contract.abi)); + } + if score < min_score { + min_score = score; + min_score_id = Some((id, &contract.abi)); + } } None }; @@ -90,7 +97,7 @@ impl<'a> LocalTraceIdentifier<'a> { /// artifact with a greater code length if the exact code length is not found. fn find_index(&self, len: usize) -> usize { let (Ok(mut idx) | Err(mut idx)) = - self.ordered_ids.binary_search_by(|(_, probe)| probe.cmp(&len)); + self.ordered_ids.binary_search_by_key(&len, |(_, probe)| *probe); // In case of multiple artifacts with the same code length, we need to find the first one. while idx > 0 && self.ordered_ids[idx - 1].1 == len { @@ -111,9 +118,10 @@ impl TraceIdentifier for LocalTraceIdentifier<'_> { addresses .filter_map(|(address, code)| { let _span = trace_span!(target: "evm::traces", "identify", %address).entered(); - trace!(target: "evm::traces", "identifying"); - let (id, abi) = self.identify_code(code?)?; + let (id, abi) = self.identify_code(code?).or_else(|| { + self.deployments.get(address).and_then(|bytes| self.identify_code(bytes)) + })?; trace!(target: "evm::traces", id=%id.identifier(), "identified"); Some(AddressIdentity { diff --git a/crates/evm/traces/src/identifier/mod.rs b/crates/evm/traces/src/identifier/mod.rs index 6d86b072a..a16b108d8 100644 --- a/crates/evm/traces/src/identifier/mod.rs +++ b/crates/evm/traces/src/identifier/mod.rs @@ -1,6 +1,8 @@ use alloy_json_abi::JsonAbi; use alloy_primitives::Address; +use foundry_common::ContractsByArtifact; use foundry_compilers::ArtifactId; +use foundry_config::{Chain, Config}; use std::borrow::Cow; mod local; @@ -33,5 +35,59 @@ pub trait TraceIdentifier { /// Attempts to identify an address in one or more call traces. fn identify_addresses<'a, A>(&mut self, addresses: A) -> Vec> where - A: Iterator)>; + A: Iterator)> + Clone; +} + +/// A collection of trace identifiers. +pub struct TraceIdentifiers<'a> { + /// The local trace identifier. + pub local: Option>, + /// The optional Etherscan trace identifier. + pub etherscan: Option, +} + +impl Default for TraceIdentifiers<'_> { + fn default() -> Self { + Self::new() + } +} + +impl TraceIdentifier for TraceIdentifiers<'_> { + fn identify_addresses<'a, A>(&mut self, addresses: A) -> Vec> + where + A: Iterator)> + Clone, + { + let mut identities = Vec::new(); + if let Some(local) = &mut self.local { + identities.extend(local.identify_addresses(addresses.clone())); + } + if let Some(etherscan) = &mut self.etherscan { + identities.extend(etherscan.identify_addresses(addresses)); + } + identities + } +} + +impl<'a> TraceIdentifiers<'a> { + /// Creates a new, empty instance. + pub const fn new() -> Self { + Self { local: None, etherscan: None } + } + + /// Sets the local identifier. + pub fn with_local(mut self, known_contracts: &'a ContractsByArtifact) -> Self { + self.local = Some(LocalTraceIdentifier::new(known_contracts)); + self + } + + /// Sets the etherscan identifier. + pub fn with_etherscan(mut self, config: &Config, chain: Option) -> eyre::Result { + self.etherscan = EtherscanIdentifier::new(config, chain)?; + Ok(self) + } + + /// Returns `true` if there are no set identifiers. + pub fn is_empty(&self) -> bool { + self.local.is_none() && self.etherscan.is_none() + } } diff --git a/crates/evm/traces/src/identifier/signatures.rs b/crates/evm/traces/src/identifier/signatures.rs index b1f3124cd..cd69cf947 100644 --- a/crates/evm/traces/src/identifier/signatures.rs +++ b/crates/evm/traces/src/identifier/signatures.rs @@ -2,11 +2,14 @@ use alloy_json_abi::{Event, Function}; use foundry_common::{ abi::{get_event, get_func}, fs, - selectors::{SelectorType, SignEthClient}, + selectors::{OpenChainClient, SelectorType}, }; -use hashbrown::HashSet; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, path::PathBuf, sync::Arc}; +use std::{ + collections::{BTreeMap, HashSet}, + path::PathBuf, + sync::Arc, +}; use tokio::sync::RwLock; pub type SingleSignaturesIdentifier = Arc>; @@ -28,7 +31,7 @@ pub struct SignaturesIdentifier { /// Selectors that were unavailable during the session. unavailable: HashSet, /// The API client to fetch signatures from - sign_eth_api: SignEthClient, + sign_eth_api: OpenChainClient, /// whether traces should be decoded via `sign_eth_api` offline: bool, } @@ -39,7 +42,7 @@ impl SignaturesIdentifier { cache_path: Option, offline: bool, ) -> eyre::Result { - let sign_eth_api = SignEthClient::new()?; + let sign_eth_api = OpenChainClient::new()?; let identifier = if let Some(cache_path) = cache_path { let path = cache_path.join("signatures"); diff --git a/crates/evm/traces/src/lib.rs b/crates/evm/traces/src/lib.rs index d7d55696f..1eb2af762 100644 --- a/crates/evm/traces/src/lib.rs +++ b/crates/evm/traces/src/lib.rs @@ -2,24 +2,25 @@ //! //! EVM trace identifying and decoding. -#![warn(unreachable_pub, unused_crate_dependencies, rust_2018_idioms)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #[macro_use] extern crate tracing; -use alloy_primitives::LogData; +use alloy_primitives::{Address, Bytes, LogData}; use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact}; use foundry_evm_core::constants::CHEATCODE_ADDRESS; use futures::{future::BoxFuture, FutureExt}; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, fmt::Write}; +use std::{collections::HashMap, fmt::Write}; use yansi::{Color, Paint}; /// Call trace address identifiers. /// /// Identifiers figure out what ABIs and labels belong to all the addresses of the trace. pub mod identifier; -use identifier::LocalTraceIdentifier; +use identifier::{LocalTraceIdentifier, TraceIdentifier}; mod decoder; pub use decoder::{CallTraceDecoder, CallTraceDecoderBuilder}; @@ -86,7 +87,7 @@ pub async fn render_trace_arena( // Display trace header let (trace, return_data) = render_trace(&node.trace, decoder).await?; - writeln!(s, "{left}{}", trace)?; + writeln!(s, "{left}{trace}")?; // Display logs and subcalls let left_prefix = format!("{child}{BRANCH}"); @@ -122,23 +123,21 @@ pub async fn render_trace_arena( // Display trace return data let color = trace_color(&node.trace); - write!(s, "{child}{EDGE}{}", color.paint(RETURN))?; - if node.trace.kind.is_any_create() { - match &return_data { - None => { - writeln!(s, "{} bytes of code", node.trace.output.len())?; - } - Some(val) => { - writeln!(s, "{val}")?; - } + write!( + s, + "{child}{EDGE}{}{}", + RETURN.fg(color), + format!("[{:?}] ", node.trace.status).fg(color) + )?; + match return_data { + Some(val) => write!(s, "{val}"), + None if node.trace.kind.is_any_create() => { + write!(s, "{} bytes of code", node.trace.output.len()) } - } else { - match &return_data { - None if node.trace.output.is_empty() => writeln!(s, "()")?, - None => writeln!(s, "{}", node.trace.output)?, - Some(val) => writeln!(s, "{val}")?, - } - } + None if node.trace.output.is_empty() => Ok(()), + None => write!(s, "{}", node.trace.output), + }?; + writeln!(s)?; Ok(()) } @@ -166,8 +165,8 @@ pub async fn render_trace( write!( &mut s, "{}{} {}@{}", - Paint::yellow(CALL), - Paint::yellow("new"), + CALL.yellow(), + "new".yellow(), decoded.label.as_deref().unwrap_or(""), address )?; @@ -194,20 +193,21 @@ pub async fn render_trace( CallKind::CallCode => " [callcode]", CallKind::DelegateCall => " [delegatecall]", CallKind::Create | CallKind::Create2 => unreachable!(), + CallKind::AuthCall => " [authcall]", }; let color = trace_color(trace); write!( &mut s, "{addr}::{func_name}{opt_value}({inputs}){action}", - addr = color.paint(decoded.label.as_deref().unwrap_or(&address)), - func_name = color.paint(func_name), + addr = decoded.label.as_deref().unwrap_or(&address).fg(color), + func_name = func_name.fg(color), opt_value = if trace.value.is_zero() { String::new() } else { format!("{{value: {}}}", trace.value) }, - action = Paint::yellow(action), + action = action.yellow(), )?; } @@ -229,11 +229,11 @@ async fn render_trace_log( s, "{:>13}: {}", if i == 0 { "emit topic 0".to_string() } else { format!("topic {i}") }, - Paint::cyan(format!("{topic:?}")) + format!("{topic:?}").cyan() )?; } - write!(s, " data: {}", Paint::cyan(hex::encode_prefixed(&log.data)))?; + write!(s, " data: {}", hex::encode_prefixed(&log.data).cyan())?; } DecodedCallLog::Decoded(name, params) => { let params = params @@ -242,7 +242,7 @@ async fn render_trace_log( .collect::>() .join(", "); - write!(s, "emit {}({params})", Paint::cyan(name.clone()))?; + write!(s, "emit {}({params})", name.clone().cyan())?; } } @@ -295,25 +295,25 @@ fn trace_color(trace: &CallTrace) -> Color { } /// Given a list of traces and artifacts, it returns a map connecting address to abi -pub fn load_contracts( - traces: Traces, - known_contracts: Option<&ContractsByArtifact>, +pub fn load_contracts<'a>( + traces: impl IntoIterator, + known_contracts: &ContractsByArtifact, + deployments: &HashMap, ) -> ContractsByAddress { - let Some(contracts) = known_contracts else { return BTreeMap::new() }; - let mut local_identifier = LocalTraceIdentifier::new(contracts); - let mut decoder = CallTraceDecoderBuilder::new().build(); - for (_, trace) in &traces { - decoder.identify(trace, &mut local_identifier); - } - - decoder - .contracts - .iter() - .filter_map(|(addr, name)| { - if let Ok(Some((_, (abi, _)))) = contracts.find_by_name_or_identifier(name) { - return Some((*addr, (name.clone(), abi.clone()))); + let mut local_identifier = LocalTraceIdentifier::new(known_contracts); + local_identifier.deployments.clone_from(deployments); + let decoder = CallTraceDecoder::new(); + let mut contracts = ContractsByAddress::new(); + for trace in traces { + let identified_addresses = + local_identifier.identify_addresses(decoder.trace_addresses(trace)); + for address in identified_addresses { + let contract = address.contract; + let abi = address.abi; + if let (Some(contract), Some(abi)) = (contract, abi) { + contracts.insert(address.address, (contract, abi.into_owned())); } - None - }) - .collect() + } + } + contracts } diff --git a/crates/fmt/Cargo.toml b/crates/fmt/Cargo.toml index 0f49a10a1..0bc3e06a6 100644 --- a/crates/fmt/Cargo.toml +++ b/crates/fmt/Cargo.toml @@ -9,19 +9,22 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [dependencies] foundry-config.workspace = true alloy-primitives.workspace = true -ariadne = "0.3" +ariadne = "0.4" itertools.workspace = true solang-parser.workspace = true -thiserror = "1" +thiserror.workspace = true tracing.workspace = true [dev-dependencies] itertools.workspace = true -pretty_assertions.workspace = true +similar-asserts.workspace = true toml.workspace = true tracing-subscriber = { workspace = true, features = ["env-filter"] } diff --git a/crates/fmt/src/buffer.rs b/crates/fmt/src/buffer.rs index 11c0838ec..8d62a70f9 100644 --- a/crates/fmt/src/buffer.rs +++ b/crates/fmt/src/buffer.rs @@ -22,16 +22,16 @@ enum WriteState { impl WriteState { fn comment_state(&self) -> CommentState { match self { - WriteState::LineStart(state) => *state, - WriteState::WriteTokens(state) => *state, - WriteState::WriteString(_) => CommentState::None, + Self::LineStart(state) => *state, + Self::WriteTokens(state) => *state, + Self::WriteString(_) => CommentState::None, } } } impl Default for WriteState { fn default() -> Self { - WriteState::LineStart(CommentState::default()) + Self::LineStart(CommentState::default()) } } diff --git a/crates/fmt/src/chunk.rs b/crates/fmt/src/chunk.rs index badce88db..7d9ce25c7 100644 --- a/crates/fmt/src/chunk.rs +++ b/crates/fmt/src/chunk.rs @@ -12,13 +12,13 @@ pub struct Chunk { impl From for Chunk { fn from(string: String) -> Self { - Chunk { content: string, ..Default::default() } + Self { content: string, ..Default::default() } } } impl From<&str> for Chunk { fn from(string: &str) -> Self { - Chunk { content: string.to_owned(), ..Default::default() } + Self { content: string.to_owned(), ..Default::default() } } } @@ -37,7 +37,7 @@ impl SurroundingChunk { before: Option, next: Option, ) -> Self { - SurroundingChunk { before, next, content: format!("{content}"), spaced: None } + Self { before, next, content: format!("{content}"), spaced: None } } pub fn spaced(mut self) -> Self { diff --git a/crates/fmt/src/comments.rs b/crates/fmt/src/comments.rs index 03f4e4181..5b9d7fb27 100644 --- a/crates/fmt/src/comments.rs +++ b/crates/fmt/src/comments.rs @@ -72,11 +72,7 @@ impl CommentWithMetadata { } /// Construct a comment with metadata by analyzing its surrounding source code - fn from_comment_and_src( - comment: Comment, - src: &str, - last_comment: Option<&CommentWithMetadata>, - ) -> Self { + fn from_comment_and_src(comment: Comment, src: &str, last_comment: Option<&Self>) -> Self { let src_before = &src[..comment.loc().start()]; if src_before.is_empty() { return Self::new(comment, CommentPosition::Prefix, false, 0) @@ -429,10 +425,10 @@ impl<'a> Iterator for NonCommentChars<'a> { /// Helpers for iterating over comment containing strings pub trait CommentStringExt { - fn comment_state_char_indices(&self) -> CommentStateCharIndices; + fn comment_state_char_indices(&self) -> CommentStateCharIndices<'_>; #[inline] - fn non_comment_chars(&self) -> NonCommentChars { + fn non_comment_chars(&self) -> NonCommentChars<'_> { NonCommentChars(self.comment_state_char_indices()) } @@ -447,14 +443,14 @@ where T: AsRef, { #[inline] - fn comment_state_char_indices(&self) -> CommentStateCharIndices { + fn comment_state_char_indices(&self) -> CommentStateCharIndices<'_> { CommentStateCharIndices::new(self.as_ref()) } } impl CommentStringExt for str { #[inline] - fn comment_state_char_indices(&self) -> CommentStateCharIndices { + fn comment_state_char_indices(&self) -> CommentStateCharIndices<'_> { CommentStateCharIndices::new(self) } } diff --git a/crates/fmt/src/formatter.rs b/crates/fmt/src/formatter.rs index 00beb4d8c..94052d9a9 100644 --- a/crates/fmt/src/formatter.rs +++ b/crates/fmt/src/formatter.rs @@ -6,6 +6,7 @@ use crate::{ comments::{ CommentPosition, CommentState, CommentStringExt, CommentType, CommentWithMetadata, Comments, }, + format_diagnostics_report, helpers::import_path_string, macros::*, solang_ext::{pt::*, *}, @@ -16,7 +17,8 @@ use crate::{ use alloy_primitives::Address; use foundry_config::fmt::{HexUnderscore, MultilineFuncHeaderStyle, SingleLineBlockStyle}; use itertools::{Either, Itertools}; -use std::{fmt::Write, str::FromStr}; +use solang_parser::diagnostics::Diagnostic; +use std::{fmt::Write, path::PathBuf, str::FromStr}; use thiserror::Error; type Result = std::result::Result; @@ -28,8 +30,11 @@ pub enum FormatterError { #[error(transparent)] Fmt(#[from] std::fmt::Error), /// Encountered invalid parse tree item. - #[error("Encountered invalid parse tree item at {0:?}")] + #[error("encountered invalid parse tree item at {0:?}")] InvalidParsedItem(Loc), + /// Failed to parse the source code + #[error("failed to parse file:\n{}", format_diagnostics_report(_0, _1.as_deref(), _2))] + Parse(String, Option, Vec), /// All other errors #[error(transparent)] Custom(Box), @@ -39,6 +44,7 @@ impl FormatterError { fn fmt() -> Self { Self::Fmt(std::fmt::Error) } + fn custom(err: impl std::error::Error + Send + Sync + 'static) -> Self { Self::Custom(Box::new(err)) } @@ -393,7 +399,7 @@ impl<'a, W: Write> Formatter<'a, W> { Ok(out) } - /// Transform [Visitable] items to a list of chunks and then sort those chunks by [AttrSortKey] + /// Transform [Visitable] items to a list of chunks and then sort those chunks. fn items_to_chunks_sorted<'b>( &mut self, next_byte_offset: Option, @@ -585,8 +591,8 @@ impl<'a, W: Write> Formatter<'a, W> { Ok(false) } - /// Write a raw comment. This is like [`write_comment`] but won't do any formatting or worry - /// about whitespace behind the comment + /// Write a raw comment. This is like [`write_comment`](Self::write_comment) but won't do any + /// formatting or worry about whitespace behind the comment. fn write_raw_comment(&mut self, comment: &CommentWithMetadata) -> Result<()> { self.write_raw(&comment.comment)?; if comment.is_line() { @@ -820,7 +826,7 @@ impl<'a, W: Write> Formatter<'a, W> { Ok(self.transact(fun)?.buffer) } - /// Turn a chunk and its surrounding comments into a a string + /// Turn a chunk and its surrounding comments into a string fn chunk_to_string(&mut self, chunk: &Chunk) -> Result { self.simulate_to_string(|fmt| fmt.write_chunk(chunk)) } @@ -895,7 +901,7 @@ impl<'a, W: Write> Formatter<'a, W> { write_chunk!(fmt, "{}", stringified.trim_start()) })?; if !last.content.trim_start().is_empty() { - self.write_whitespace_separator(true)?; + self.indented(1, |fmt| fmt.write_whitespace_separator(true))?; } let last_chunk = self.chunk_at(last.loc_before(), last.loc_next(), last.spaced, &last.content); @@ -1255,7 +1261,8 @@ impl<'a, W: Write> Formatter<'a, W> { /// Visit the yul string with an optional identifier. /// If the identifier is present, write the value in the format `:`. - /// Ref: https://docs.soliditylang.org/en/v0.8.15/yul.html#variable-declarations + /// + /// Ref: fn visit_yul_string_with_ident( &mut self, loc: Loc, diff --git a/crates/fmt/src/helpers.rs b/crates/fmt/src/helpers.rs index 8d472e3bb..7f05a9c09 100644 --- a/crates/fmt/src/helpers.rs +++ b/crates/fmt/src/helpers.rs @@ -10,20 +10,31 @@ use std::{fmt::Write, path::Path}; /// Result of parsing the source code #[derive(Debug)] pub struct Parsed<'a> { - /// The original source code + /// The original source code. pub src: &'a str, - /// The Parse Tree via [`solang`] + /// The parse tree. pub pt: SourceUnit, - /// Parsed comments + /// Parsed comments. pub comments: Comments, - /// Parsed inline config + /// Parsed inline config. pub inline_config: InlineConfig, - /// Invalid inline config items parsed + /// Invalid inline config items parsed. pub invalid_inline_config_items: Vec<(Loc, InvalidInlineConfigItem)>, } -/// Parse source code -pub fn parse(src: &str) -> Result> { +/// Parse source code. +pub fn parse(src: &str) -> Result, FormatterError> { + parse_raw(src).map_err(|diag| FormatterError::Parse(src.to_string(), None, diag)) +} + +/// Parse source code with a path for diagnostics. +pub fn parse2<'s>(src: &'s str, path: Option<&Path>) -> Result, FormatterError> { + parse_raw(src) + .map_err(|diag| FormatterError::Parse(src.to_string(), path.map(ToOwned::to_owned), diag)) +} + +/// Parse source code, returning a list of diagnostics on failure. +pub fn parse_raw(src: &str) -> Result, Vec> { let (pt, comments) = solang_parser::parse(src, 0)?; let comments = Comments::new(comments, src); let (inline_config_items, invalid_inline_config_items): (Vec<_>, Vec<_>) = @@ -35,7 +46,7 @@ pub fn parse(src: &str) -> Result> { /// Format parsed code pub fn format_to( writer: W, - mut parsed: Parsed, + mut parsed: Parsed<'_>, config: FormatterConfig, ) -> Result<(), FormatterError> { trace!(?parsed, ?config, "Formatting"); @@ -46,10 +57,7 @@ pub fn format_to( /// Parse and format a string with default settings pub fn format(src: &str) -> Result { - let parsed = parse(src).map_err(|err| { - debug!(?err, "Parse error"); - FormatterError::Fmt(std::fmt::Error) - })?; + let parsed = parse(src)?; let mut output = String::new(); format_to(&mut output, parsed, FormatterConfig::default())?; @@ -75,14 +83,19 @@ pub fn offset_to_line_column(content: &str, start: usize) -> (usize, usize) { unreachable!("content.len() > start") } -/// Print the report of parser's diagnostics -pub fn print_diagnostics_report( +/// Formats parser diagnostics +pub fn format_diagnostics_report( content: &str, path: Option<&Path>, - diagnostics: Vec, -) -> std::io::Result<()> { + diagnostics: &[Diagnostic], +) -> String { + if diagnostics.is_empty() { + return String::new(); + } + let filename = path.map(|p| p.file_name().unwrap().to_string_lossy().to_string()).unwrap_or_default(); + let mut s = Vec::new(); for diag in diagnostics { let (start, end) = (diag.loc.start(), diag.loc.end()); let mut report = Report::build(ReportKind::Error, &filename, start) @@ -90,16 +103,16 @@ pub fn print_diagnostics_report( .with_label( Label::new((&filename, start..end)) .with_color(Color::Red) - .with_message(format!("{}", diag.message.fg(Color::Red))), + .with_message(format!("{}", diag.message.as_str().fg(Color::Red))), ); - for note in diag.notes { - report = report.with_note(note.message); + for note in &diag.notes { + report = report.with_note(¬e.message); } - report.finish().print((&filename, Source::from(content)))?; + report.finish().write((&filename, Source::from(content)), &mut s).unwrap(); } - Ok(()) + String::from_utf8(s).unwrap() } pub fn import_path_string(path: &ImportPath) -> String { diff --git a/crates/fmt/src/inline_config.rs b/crates/fmt/src/inline_config.rs index 702669fe5..9f0e54806 100644 --- a/crates/fmt/src/inline_config.rs +++ b/crates/fmt/src/inline_config.rs @@ -23,11 +23,11 @@ impl FromStr for InlineConfigItem { type Err = InvalidInlineConfigItem; fn from_str(s: &str) -> Result { Ok(match s { - "disable-next-item" => InlineConfigItem::DisableNextItem, - "disable-line" => InlineConfigItem::DisableLine, - "disable-next-line" => InlineConfigItem::DisableNextLine, - "disable-start" => InlineConfigItem::DisableStart, - "disable-end" => InlineConfigItem::DisableEnd, + "disable-next-item" => Self::DisableNextItem, + "disable-line" => Self::DisableLine, + "disable-next-line" => Self::DisableNextLine, + "disable-start" => Self::DisableStart, + "disable-end" => Self::DisableEnd, s => return Err(InvalidInlineConfigItem(s.into())), }) } @@ -61,8 +61,10 @@ impl DisabledRange { /// An inline config. Keeps track of disabled ranges. /// /// This is a list of Inline Config items for locations in a source file. This is -/// usually acquired by parsing the comments for an `forgefmt:` items. See -/// [`Comments::parse_inline_config_items`] for details. +/// usually acquired by parsing the comments for an `forgefmt:` items. +/// +/// See [`Comments::parse_inline_config_items`](crate::Comments::parse_inline_config_items) for +/// details. #[derive(Debug, Default)] pub struct InlineConfig { disabled_ranges: Vec, diff --git a/crates/fmt/src/lib.rs b/crates/fmt/src/lib.rs index 958d74647..006b4db02 100644 --- a/crates/fmt/src/lib.rs +++ b/crates/fmt/src/lib.rs @@ -1,5 +1,6 @@ #![doc = include_str!("../README.md")] #![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #[macro_use] extern crate tracing; @@ -20,7 +21,7 @@ pub use foundry_config::fmt::*; pub use comments::Comments; pub use formatter::{Formatter, FormatterError}; pub use helpers::{ - format, format_to, offset_to_line_column, parse, print_diagnostics_report, Parsed, + format, format_diagnostics_report, format_to, offset_to_line_column, parse, parse2, Parsed, }; pub use inline_config::InlineConfig; pub use visit::{Visitable, Visitor}; diff --git a/crates/fmt/src/solang_ext/ast_eq.rs b/crates/fmt/src/solang_ext/ast_eq.rs index e31fc2b41..2640008e2 100644 --- a/crates/fmt/src/solang_ext/ast_eq.rs +++ b/crates/fmt/src/solang_ext/ast_eq.rs @@ -225,13 +225,13 @@ macro_rules! wrap_in_box { impl AstEq for Statement { fn ast_eq(&self, other: &Self) -> bool { match self { - Statement::If(loc, expr, stmt1, stmt2) => { + Self::If(loc, expr, stmt1, stmt2) => { #[allow(clippy::borrowed_box)] - let wrap_if = |stmt1: &Box, stmt2: &Option>| { + let wrap_if = |stmt1: &Box, stmt2: &Option>| { ( wrap_in_box!(stmt1, *loc), stmt2.as_ref().map(|stmt2| { - if matches!(**stmt2, Statement::If(..)) { + if matches!(**stmt2, Self::If(..)) { stmt2.clone() } else { wrap_in_box!(stmt2, *loc) @@ -241,7 +241,7 @@ impl AstEq for Statement { }; let (stmt1, stmt2) = wrap_if(stmt1, stmt2); let left = (loc, expr, &stmt1, &stmt2); - if let Statement::If(loc, expr, stmt1, stmt2) = other { + if let Self::If(loc, expr, stmt1, stmt2) = other { let (stmt1, stmt2) = wrap_if(stmt1, stmt2); let right = (loc, expr, &stmt1, &stmt2); left.ast_eq(&right) @@ -249,10 +249,10 @@ impl AstEq for Statement { false } } - Statement::While(loc, expr, stmt1) => { + Self::While(loc, expr, stmt1) => { let stmt1 = wrap_in_box!(stmt1, *loc); let left = (loc, expr, &stmt1); - if let Statement::While(loc, expr, stmt1) = other { + if let Self::While(loc, expr, stmt1) = other { let stmt1 = wrap_in_box!(stmt1, *loc); let right = (loc, expr, &stmt1); left.ast_eq(&right) @@ -260,10 +260,10 @@ impl AstEq for Statement { false } } - Statement::DoWhile(loc, stmt1, expr) => { + Self::DoWhile(loc, stmt1, expr) => { let stmt1 = wrap_in_box!(stmt1, *loc); let left = (loc, &stmt1, expr); - if let Statement::DoWhile(loc, stmt1, expr) = other { + if let Self::DoWhile(loc, stmt1, expr) = other { let stmt1 = wrap_in_box!(stmt1, *loc); let right = (loc, &stmt1, expr); left.ast_eq(&right) @@ -271,10 +271,10 @@ impl AstEq for Statement { false } } - Statement::For(loc, stmt1, expr, stmt2, stmt3) => { + Self::For(loc, stmt1, expr, stmt2, stmt3) => { let stmt3 = stmt3.as_ref().map(|stmt3| wrap_in_box!(stmt3, *loc)); let left = (loc, stmt1, expr, stmt2, &stmt3); - if let Statement::For(loc, stmt1, expr, stmt2, stmt3) = other { + if let Self::For(loc, stmt1, expr, stmt2, stmt3) = other { let stmt3 = stmt3.as_ref().map(|stmt3| wrap_in_box!(stmt3, *loc)); let right = (loc, stmt1, expr, stmt2, &stmt3); left.ast_eq(&right) @@ -282,11 +282,11 @@ impl AstEq for Statement { false } } - Statement::Try(loc, expr, returns, catch) => { + Self::Try(loc, expr, returns, catch) => { let left_returns = returns.as_ref().map(|(params, stmt)| (filter_params(params), stmt)); let left = (loc, expr, left_returns, catch); - if let Statement::Try(loc, expr, returns, catch) = other { + if let Self::Try(loc, expr, returns, catch) = other { let right_returns = returns.as_ref().map(|(params, stmt)| (filter_params(params), stmt)); let right = (loc, expr, right_returns, catch); diff --git a/crates/fmt/src/string.rs b/crates/fmt/src/string.rs index 6ffc0b959..1dbc2f2f6 100644 --- a/crates/fmt/src/string.rs +++ b/crates/fmt/src/string.rs @@ -65,7 +65,7 @@ impl<'a> Iterator for QuoteStateCharIndices<'a> { } } -/// An iterator over the the indices of quoted string locations +/// An iterator over the indices of quoted string locations pub struct QuotedRanges<'a>(QuoteStateCharIndices<'a>); impl<'a> QuotedRanges<'a> { @@ -100,12 +100,14 @@ impl<'a> Iterator for QuotedRanges<'a> { /// Helpers for iterating over quoted strings pub trait QuotedStringExt { - /// Get an iterator of characters, indices and their quoted string state - fn quote_state_char_indices(&self) -> QuoteStateCharIndices; - /// Get an iterator of quoted string ranges - fn quoted_ranges(&self) -> QuotedRanges { + /// Returns an iterator of characters, indices and their quoted string state. + fn quote_state_char_indices(&self) -> QuoteStateCharIndices<'_>; + + /// Returns an iterator of quoted string ranges. + fn quoted_ranges(&self) -> QuotedRanges<'_> { QuotedRanges(self.quote_state_char_indices()) } + /// Check to see if a string is quoted. This will return true if the first character /// is a quote and the last character is a quote with no non-quoted sections in between. fn is_quoted(&self) -> bool { @@ -126,13 +128,13 @@ impl QuotedStringExt for T where T: AsRef, { - fn quote_state_char_indices(&self) -> QuoteStateCharIndices { + fn quote_state_char_indices(&self) -> QuoteStateCharIndices<'_> { QuoteStateCharIndices::new(self.as_ref()) } } impl QuotedStringExt for str { - fn quote_state_char_indices(&self) -> QuoteStateCharIndices { + fn quote_state_char_indices(&self) -> QuoteStateCharIndices<'_> { QuoteStateCharIndices::new(self) } } @@ -140,7 +142,7 @@ impl QuotedStringExt for str { #[cfg(test)] mod tests { use super::*; - use pretty_assertions::assert_eq; + use similar_asserts::assert_eq; #[test] fn quote_state_char_indices() { diff --git a/crates/fmt/src/visit.rs b/crates/fmt/src/visit.rs index da7f3ca37..ef9273a30 100644 --- a/crates/fmt/src/visit.rs +++ b/crates/fmt/src/visit.rs @@ -456,19 +456,19 @@ impl Visitable for SourceUnitPart { V: Visitor, { match self { - SourceUnitPart::ContractDefinition(contract) => v.visit_contract(contract), - SourceUnitPart::PragmaDirective(loc, ident, str) => v.visit_pragma(*loc, ident, str), - SourceUnitPart::ImportDirective(import) => import.visit(v), - SourceUnitPart::EnumDefinition(enumeration) => v.visit_enum(enumeration), - SourceUnitPart::StructDefinition(structure) => v.visit_struct(structure), - SourceUnitPart::EventDefinition(event) => v.visit_event(event), - SourceUnitPart::ErrorDefinition(error) => v.visit_error(error), - SourceUnitPart::FunctionDefinition(function) => v.visit_function(function), - SourceUnitPart::VariableDefinition(variable) => v.visit_var_definition(variable), - SourceUnitPart::TypeDefinition(def) => v.visit_type_definition(def), - SourceUnitPart::StraySemicolon(_) => v.visit_stray_semicolon(), - SourceUnitPart::Using(using) => v.visit_using(using), - SourceUnitPart::Annotation(annotation) => v.visit_annotation(annotation), + Self::ContractDefinition(contract) => v.visit_contract(contract), + Self::PragmaDirective(loc, ident, str) => v.visit_pragma(*loc, ident, str), + Self::ImportDirective(import) => import.visit(v), + Self::EnumDefinition(enumeration) => v.visit_enum(enumeration), + Self::StructDefinition(structure) => v.visit_struct(structure), + Self::EventDefinition(event) => v.visit_event(event), + Self::ErrorDefinition(error) => v.visit_error(error), + Self::FunctionDefinition(function) => v.visit_function(function), + Self::VariableDefinition(variable) => v.visit_var_definition(variable), + Self::TypeDefinition(def) => v.visit_type_definition(def), + Self::StraySemicolon(_) => v.visit_stray_semicolon(), + Self::Using(using) => v.visit_using(using), + Self::Annotation(annotation) => v.visit_annotation(annotation), } } } @@ -479,11 +479,11 @@ impl Visitable for Import { V: Visitor, { match self { - Import::Plain(import, loc) => v.visit_import_plain(*loc, import), - Import::GlobalSymbol(global, import_as, loc) => { + Self::Plain(import, loc) => v.visit_import_plain(*loc, import), + Self::GlobalSymbol(global, import_as, loc) => { v.visit_import_global(*loc, global, import_as) } - Import::Rename(from, imports, loc) => v.visit_import_renames(*loc, imports, from), + Self::Rename(from, imports, loc) => v.visit_import_renames(*loc, imports, from), } } } @@ -494,16 +494,16 @@ impl Visitable for ContractPart { V: Visitor, { match self { - ContractPart::StructDefinition(structure) => v.visit_struct(structure), - ContractPart::EventDefinition(event) => v.visit_event(event), - ContractPart::ErrorDefinition(error) => v.visit_error(error), - ContractPart::EnumDefinition(enumeration) => v.visit_enum(enumeration), - ContractPart::VariableDefinition(variable) => v.visit_var_definition(variable), - ContractPart::FunctionDefinition(function) => v.visit_function(function), - ContractPart::TypeDefinition(def) => v.visit_type_definition(def), - ContractPart::StraySemicolon(_) => v.visit_stray_semicolon(), - ContractPart::Using(using) => v.visit_using(using), - ContractPart::Annotation(annotation) => v.visit_annotation(annotation), + Self::StructDefinition(structure) => v.visit_struct(structure), + Self::EventDefinition(event) => v.visit_event(event), + Self::ErrorDefinition(error) => v.visit_error(error), + Self::EnumDefinition(enumeration) => v.visit_enum(enumeration), + Self::VariableDefinition(variable) => v.visit_var_definition(variable), + Self::FunctionDefinition(function) => v.visit_function(function), + Self::TypeDefinition(def) => v.visit_type_definition(def), + Self::StraySemicolon(_) => v.visit_stray_semicolon(), + Self::Using(using) => v.visit_using(using), + Self::Annotation(annotation) => v.visit_annotation(annotation), } } } @@ -514,40 +514,34 @@ impl Visitable for Statement { V: Visitor, { match self { - Statement::Block { loc, unchecked, statements } => { + Self::Block { loc, unchecked, statements } => { v.visit_block(*loc, *unchecked, statements) } - Statement::Assembly { loc, dialect, block, flags } => { + Self::Assembly { loc, dialect, block, flags } => { v.visit_assembly(*loc, dialect, block, flags) } - Statement::Args(loc, args) => v.visit_args(*loc, args), - Statement::If(loc, cond, if_branch, else_branch) => { + Self::Args(loc, args) => v.visit_args(*loc, args), + Self::If(loc, cond, if_branch, else_branch) => { v.visit_if(*loc, cond, if_branch, else_branch, true) } - Statement::While(loc, cond, body) => v.visit_while(*loc, cond, body), - Statement::Expression(loc, expr) => { + Self::While(loc, cond, body) => v.visit_while(*loc, cond, body), + Self::Expression(loc, expr) => { v.visit_expr(*loc, expr)?; v.visit_stray_semicolon() } - Statement::VariableDefinition(loc, declaration, expr) => { + Self::VariableDefinition(loc, declaration, expr) => { v.visit_var_definition_stmt(*loc, declaration, expr) } - Statement::For(loc, init, cond, update, body) => { - v.visit_for(*loc, init, cond, update, body) - } - Statement::DoWhile(loc, body, cond) => v.visit_do_while(*loc, body, cond), - Statement::Continue(loc) => v.visit_continue(*loc, true), - Statement::Break(loc) => v.visit_break(*loc, true), - Statement::Return(loc, expr) => v.visit_return(*loc, expr), - Statement::Revert(loc, error, args) => v.visit_revert(*loc, error, args), - Statement::RevertNamedArgs(loc, error, args) => { - v.visit_revert_named_args(*loc, error, args) - } - Statement::Emit(loc, event) => v.visit_emit(*loc, event), - Statement::Try(loc, expr, returns, clauses) => { - v.visit_try(*loc, expr, returns, clauses) - } - Statement::Error(loc) => v.visit_parser_error(*loc), + Self::For(loc, init, cond, update, body) => v.visit_for(*loc, init, cond, update, body), + Self::DoWhile(loc, body, cond) => v.visit_do_while(*loc, body, cond), + Self::Continue(loc) => v.visit_continue(*loc, true), + Self::Break(loc) => v.visit_break(*loc, true), + Self::Return(loc, expr) => v.visit_return(*loc, expr), + Self::Revert(loc, error, args) => v.visit_revert(*loc, error, args), + Self::RevertNamedArgs(loc, error, args) => v.visit_revert_named_args(*loc, error, args), + Self::Emit(loc, event) => v.visit_emit(*loc, event), + Self::Try(loc, expr, returns, clauses) => v.visit_try(*loc, expr, returns, clauses), + Self::Error(loc) => v.visit_parser_error(*loc), } } } @@ -603,24 +597,20 @@ impl Visitable for YulStatement { V: Visitor, { match self { - YulStatement::Assign(loc, exprs, expr) => { - v.visit_yul_assignment(*loc, exprs, &mut Some(expr)) - } - YulStatement::Block(block) => { - v.visit_yul_block(block.loc, block.statements.as_mut(), false) - } - YulStatement::Break(loc) => v.visit_break(*loc, false), - YulStatement::Continue(loc) => v.visit_continue(*loc, false), - YulStatement::For(stmt) => v.visit_yul_for(stmt), - YulStatement::FunctionCall(stmt) => v.visit_yul_function_call(stmt), - YulStatement::FunctionDefinition(stmt) => v.visit_yul_fun_def(stmt), - YulStatement::If(loc, expr, block) => v.visit_yul_if(*loc, expr, block), - YulStatement::Leave(loc) => v.visit_yul_leave(*loc), - YulStatement::Switch(stmt) => v.visit_yul_switch(stmt), - YulStatement::VariableDeclaration(loc, idents, expr) => { + Self::Assign(loc, exprs, expr) => v.visit_yul_assignment(*loc, exprs, &mut Some(expr)), + Self::Block(block) => v.visit_yul_block(block.loc, block.statements.as_mut(), false), + Self::Break(loc) => v.visit_break(*loc, false), + Self::Continue(loc) => v.visit_continue(*loc, false), + Self::For(stmt) => v.visit_yul_for(stmt), + Self::FunctionCall(stmt) => v.visit_yul_function_call(stmt), + Self::FunctionDefinition(stmt) => v.visit_yul_fun_def(stmt), + Self::If(loc, expr, block) => v.visit_yul_if(*loc, expr, block), + Self::Leave(loc) => v.visit_yul_leave(*loc), + Self::Switch(stmt) => v.visit_yul_switch(stmt), + Self::VariableDeclaration(loc, idents, expr) => { v.visit_yul_var_declaration(*loc, idents, expr) } - YulStatement::Error(loc) => v.visit_parser_error(*loc), + Self::Error(loc) => v.visit_parser_error(*loc), } } } diff --git a/crates/fmt/testdata/Repros/fmt.sol b/crates/fmt/testdata/Repros/fmt.sol index 8439563ab..dc1ac24eb 100644 --- a/crates/fmt/testdata/Repros/fmt.sol +++ b/crates/fmt/testdata/Repros/fmt.sol @@ -5,3 +5,15 @@ function errorIdentifier() { bytes memory error = bytes(""); if (error.length > 0) {} } + +// https://github.com/foundry-rs/foundry/issues/7549 +function one() external { + this.other({ + data: abi.encodeCall( + this.other, + ( + "bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla" + ) + ) + }); +} diff --git a/crates/fmt/testdata/Repros/original.sol b/crates/fmt/testdata/Repros/original.sol index 8439563ab..cee4fc97a 100644 --- a/crates/fmt/testdata/Repros/original.sol +++ b/crates/fmt/testdata/Repros/original.sol @@ -5,3 +5,15 @@ function errorIdentifier() { bytes memory error = bytes(""); if (error.length > 0) {} } + +// https://github.com/foundry-rs/foundry/issues/7549 +function one() external { + this.other({ + data: abi.encodeCall( + this.other, + ( + "bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla bla" + ) + ) + }); +} diff --git a/crates/fmt/tests/formatter.rs b/crates/fmt/tests/formatter.rs index 18b72a545..ba1d9216d 100644 --- a/crates/fmt/tests/formatter.rs +++ b/crates/fmt/tests/formatter.rs @@ -94,7 +94,7 @@ fn test_formatter( struct PrettyString(String); impl PartialEq for PrettyString { - fn eq(&self, other: &PrettyString) -> bool { + fn eq(&self, other: &Self) -> bool { self.0.lines().eq(other.0.lines()) } } @@ -111,7 +111,7 @@ fn test_formatter( let expected_parsed = parse(expected_source).unwrap(); if !test_config.skip_compare_ast_eq && !source_parsed.pt.ast_eq(&expected_parsed.pt) { - pretty_assertions::assert_eq!( + similar_asserts::assert_eq!( source_parsed.pt, expected_parsed.pt, "(formatted Parse Tree == expected Parse Tree) in {}", @@ -127,7 +127,7 @@ fn test_formatter( let source_formatted = PrettyString(source_formatted); - pretty_assertions::assert_eq!( + similar_asserts::assert_eq!( source_formatted, expected, "(formatted == expected) in {}", @@ -140,7 +140,7 @@ fn test_formatter( let expected_formatted = PrettyString(expected_formatted); - pretty_assertions::assert_eq!( + similar_asserts::assert_eq!( expected_formatted, expected, "(formatted == expected) in {}", diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index 6670bf889..2efe93c41 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -10,12 +10,19 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [[bin]] name = "forge" path = "bin/main.rs" [build-dependencies] -vergen = { workspace = true, default-features = false, features = ["build", "git", "gitcl"] } +vergen = { workspace = true, default-features = false, features = [ + "build", + "git", + "gitcl", +] } [dependencies] globset = "0.4" @@ -27,87 +34,121 @@ foundry-compilers = { workspace = true, features = ["full"] } foundry-config.workspace = true foundry-evm.workspace = true foundry-wallets.workspace = true +foundry-linking.workspace = true foundry-zksync-core.workspace = true foundry-zksync-compiler.workspace = true -ethers-contract.workspace = true -ethers-core.workspace = true -ethers-middleware.workspace = true -ethers-providers.workspace = true -ethers-signers.workspace = true +ethers-contract-abigen = { workspace = true, features = ["providers"] } revm-inspectors.workspace = true comfy-table = "7" eyre.workspace = true proptest = "1" -rayon = "1" +rayon.workspace = true serde.workspace = true tracing.workspace = true -yansi = "0.5" +yansi.workspace = true +humantime-serde = "1.1.1" # bin forge-doc.workspace = true forge-fmt.workspace = true +forge-verify.workspace = true +forge-script.workspace = true +forge-sol-macro-gen.workspace = true foundry-cli.workspace = true foundry-debugger.workspace = true +alloy-chains.workspace = true +alloy-consensus.workspace = true alloy-dyn-abi.workspace = true alloy-json-abi.workspace = true +alloy-network.workspace = true alloy-primitives = { workspace = true, features = ["serde"] } +alloy-provider = { workspace = true, features = ["reqwest", "ws", "ipc"] } alloy-rpc-types.workspace = true +alloy-serde.workspace = true +alloy-signer.workspace = true +alloy-sol-macro-expander = { workspace = true, features = ["json"] } +alloy-sol-macro-input.workspace = true +alloy-transport.workspace = true -async-trait = "0.1" +async-trait.workspace = true clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } clap_complete = "4" clap_complete_fig = "4" dialoguer = { version = "0.11", default-features = false } -dunce = "1" -futures = "0.3" +dunce.workspace = true +futures.workspace = true hex.workspace = true indicatif = "0.17" itertools.workspace = true -once_cell = "1" -parking_lot = "0.12" +once_cell.workspace = true +parking_lot.workspace = true regex = { version = "1", default-features = false } -reqwest = { version = "0.11", default-features = false, features = ["json"] } -semver = "1" +reqwest = { workspace = true, features = ["json"] } +semver.workspace = true serde_json.workspace = true similar = { version = "2", features = ["inline"] } solang-parser.workspace = true strum = { workspace = true, features = ["derive"] } -thiserror = "1" -tokio = { version = "1", features = ["time"] } -watchexec = "2.3.2" +thiserror.workspace = true +tokio = { workspace = true, features = ["time"] } +toml = { version = "0.8", features = ["preserve_order"] } +toml_edit = "0.22.4" +watchexec = "4.1" +watchexec-events = "3.0" +watchexec-signals = "3.0" +clearscreen = "3.0" evm-disassembler.workspace = true +rustc-hash.workspace = true # doc server axum = { workspace = true, features = ["ws"] } hyper.workspace = true tower-http = { workspace = true, features = ["fs"] } -opener = "0.6" +opener = "0.7" + +# soldeer +soldeer.workspace = true # zk zksync-web3-rs = { workspace = true } zksync_types = { workspace = true } +[target.'cfg(unix)'.dependencies] +tikv-jemallocator = { workspace = true, optional = true } + [dev-dependencies] anvil.workspace = true foundry-test-utils.workspace = true +mockall = "0.12" criterion = "0.5" paste = "1.0" path-slash = "0.2" -pretty_assertions.workspace = true -svm = { package = "svm-rs", version = "0.3", default-features = false, features = ["rustls"] } -tempfile = "3" +similar-asserts.workspace = true +svm = { package = "svm-rs", version = "0.5", default-features = false, features = [ + "rustls", +] } +tempfile.workspace = true tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } +alloy-signer-local.workspace = true + [features] -default = ["rustls"] -rustls = ["foundry-cli/rustls", "foundry-wallets/rustls", "reqwest/rustls-tls", "reqwest/rustls-tls-native-roots"] -openssl = ["foundry-cli/openssl", "reqwest/default-tls", "foundry-wallets/openssl"] +default = ["rustls", "jemalloc"] +rustls = [ + "foundry-cli/rustls", + "foundry-wallets/rustls", + "reqwest/rustls-tls", + "reqwest/rustls-tls-native-roots", +] +openssl = ["foundry-cli/openssl", "reqwest/default-tls"] asm-keccak = ["alloy-primitives/asm-keccak"] +jemalloc = ["dep:tikv-jemallocator"] +aws-kms = ["foundry-wallets/aws-kms"] [[bench]] name = "test" diff --git a/crates/forge/README.md b/crates/forge/README.md index 5ac9d5747..d4cfeede2 100644 --- a/crates/forge/README.md +++ b/crates/forge/README.md @@ -386,7 +386,7 @@ Logs: If you are working in a repo with NPM-style imports, like -``` +```solidity import "@openzeppelin/contracts/access/Ownable.sol"; ``` @@ -398,7 +398,7 @@ For example, if you have `@openzeppelin` imports, you would 2. Create a remappings file: `touch remappings.txt` 3. Add this line to `remappings.txt` -``` +```text @openzeppelin/=lib/openzeppelin-contracts/ ``` diff --git a/crates/forge/benches/test.rs b/crates/forge/benches/test.rs index 7650077b1..7646a3c21 100644 --- a/crates/forge/benches/test.rs +++ b/crates/forge/benches/test.rs @@ -15,7 +15,7 @@ fn forge_test_benchmark(c: &mut Criterion) { let mut cmd = prj.forge_command(); cmd.arg("test"); b.iter(|| { - cmd.ensure_execute_success().unwrap(); + cmd.print_output(); }); }); } diff --git a/crates/forge/bin/cmd/bind.rs b/crates/forge/bin/cmd/bind.rs index 93d188260..f9e9a22a4 100644 --- a/crates/forge/bin/cmd/bind.rs +++ b/crates/forge/bin/cmd/bind.rs @@ -1,9 +1,13 @@ use clap::{Parser, ValueHint}; -use ethers_contract::{Abigen, ContractFilter, ExcludeContracts, MultiAbigen, SelectContracts}; +use ethers_contract_abigen::{ + Abigen, ContractFilter, ExcludeContracts, MultiAbigen, SelectContracts, +}; use eyre::{Result, WrapErr}; +use forge_sol_macro_gen::{MultiSolMacroGen, SolMacroGen}; use foundry_cli::{opts::CoreBuildArgs, utils::LoadConfig}; use foundry_common::{compile::ProjectCompiler, fs::json_files}; use foundry_config::impl_figment_convert; +use regex::Regex; use std::{ fs, path::{Path, PathBuf}, @@ -18,7 +22,7 @@ const DEFAULT_CRATE_VERSION: &str = "0.1.0"; #[derive(Clone, Debug, Parser)] pub struct BindArgs { /// Path to where the contract artifacts are stored. - #[clap( + #[arg( long = "bindings-path", short, value_hint = ValueHint::DirPath, @@ -27,146 +31,206 @@ pub struct BindArgs { pub bindings: Option, /// Create bindings only for contracts whose names match the specified filter(s) - #[clap(long)] + #[arg(long)] pub select: Vec, - /// Create bindings only for contracts whose names do not match the specified filter(s) - #[clap(long, conflicts_with = "select")] - pub skip: Vec, - /// Explicitly generate bindings for all contracts /// /// By default all contracts ending with `Test` or `Script` are excluded. - #[clap(long, conflicts_with_all = &["select", "skip"])] + #[arg(long, conflicts_with_all = &["select", "skip"])] pub select_all: bool, /// The name of the Rust crate to generate. /// /// This should be a valid crates.io crate name, /// however, this is not currently validated by this command. - #[clap(long, default_value = DEFAULT_CRATE_NAME, value_name = "NAME")] + #[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, this is not currently validated by this command. - #[clap(long, default_value = DEFAULT_CRATE_VERSION, value_name = "VERSION")] + #[arg(long, default_value = DEFAULT_CRATE_VERSION, value_name = "VERSION")] crate_version: String, /// Generate the bindings as a module instead of a crate. - #[clap(long)] + #[arg(long)] module: bool, /// Overwrite existing generated bindings. /// /// By default, the command will check that the bindings are correct, and then exit. If /// --overwrite is passed, it will instead delete and overwrite the bindings. - #[clap(long)] + #[arg(long)] overwrite: bool, /// Generate bindings as a single file. - #[clap(long)] + #[arg(long)] single_file: bool, /// Skip Cargo.toml consistency checks. - #[clap(long)] + #[arg(long)] skip_cargo_toml: bool, /// Skips running forge build before generating binding - #[clap(long)] + #[arg(long)] skip_build: bool, /// Don't add any additional derives to generated bindings - #[clap(long)] + #[arg(long)] skip_extra_derives: bool, - #[clap(flatten)] + /// Generate bindings for the `alloy` library, instead of `ethers`. + #[arg(long, conflicts_with = "ethers")] + alloy: bool, + + /// Specify the alloy version. + #[arg(long, value_name = "ALLOY_VERSION")] + alloy_version: Option, + + /// Generate bindings for the `ethers` library, instead of `alloy` (default, deprecated). + #[arg(long)] + ethers: bool, + + #[command(flatten)] build_args: CoreBuildArgs, } impl BindArgs { pub fn run(self) -> Result<()> { if !self.skip_build { - // run `forge build` let project = self.build_args.project()?; let _ = ProjectCompiler::new().compile(&project)?; } - let artifacts = self.try_load_config_emit_warnings()?.out; - - if !self.overwrite && self.bindings_exist(&artifacts) { - println!("Bindings found. Checking for consistency."); - return self.check_existing_bindings(&artifacts) + if !self.alloy { + eprintln!( + "Warning: `--ethers` (default) bindings are deprecated and will be removed in the future. \ + Consider using `--alloy` instead." + ); } - if self.overwrite && self.bindings_exist(&artifacts) { + let config = self.try_load_config_emit_warnings()?; + let artifacts = config.out; + let bindings_root = self.bindings.clone().unwrap_or_else(|| artifacts.join("bindings")); + + if bindings_root.exists() { + if !self.overwrite { + println!("Bindings found. Checking for consistency."); + return self.check_existing_bindings(&artifacts, &bindings_root); + } + trace!(?artifacts, "Removing existing bindings"); - fs::remove_dir_all(self.bindings_root(&artifacts))?; + fs::remove_dir_all(&bindings_root)?; } - self.generate_bindings(&artifacts)?; + self.generate_bindings(&artifacts, &bindings_root)?; - println!( - "Bindings have been output to {}", - self.bindings_root(&artifacts).to_str().unwrap() - ); + println!("Bindings have been generated to {}", bindings_root.display()); Ok(()) } - /// Get the path to the root of the autogenerated crate - fn bindings_root(&self, artifacts: impl AsRef) -> PathBuf { - self.bindings.clone().unwrap_or_else(|| artifacts.as_ref().join("bindings")) - } - - /// `true` if the bindings root already exists - fn bindings_exist(&self, artifacts: impl AsRef) -> bool { - self.bindings_root(artifacts).is_dir() - } - /// Returns the filter to use for `MultiAbigen` - fn get_filter(&self) -> ContractFilter { + fn get_filter(&self) -> Result { if self.select_all { - return ContractFilter::All + return Ok(ContractFilter::All) } if !self.select.is_empty() { - return SelectContracts::default().extend_regex(self.select.clone()).into() + return Ok(SelectContracts::default().extend_regex(self.select.clone()).into()) } - if !self.skip.is_empty() { - return ExcludeContracts::default().extend_regex(self.skip.clone()).into() + if let Some(skip) = self.build_args.skip.as_ref().filter(|s| !s.is_empty()) { + return Ok(ExcludeContracts::default() + .extend_regex( + skip.clone() + .into_iter() + .map(|s| Regex::new(s.file_pattern())) + .collect::, _>>()?, + ) + .into()) } // This excludes all Test/Script and forge-std contracts - ExcludeContracts::default() + Ok(ExcludeContracts::default() .extend_pattern([ ".*Test.*", ".*Script", "console[2]?", "CommonBase", "Components", - "[Ss]td(Chains|Math|Error|Json|Utils|Cheats|Style|Invariant|Assertions|Storage(Safe)?)", + "[Ss]td(Chains|Math|Error|Json|Utils|Cheats|Style|Invariant|Assertions|Toml|Storage(Safe)?)", "[Vv]m.*", ]) .extend_names(["IMulticall3"]) - .into() + .into()) } - /// Instantiate the multi-abigen - fn get_multi(&self, artifacts: impl AsRef) -> Result { - let abigens = json_files(artifacts.as_ref()) - .into_iter() + fn get_alloy_filter(&self) -> Result { + if self.select_all { + // Select all json files + return Ok(Filter::All); + } + if !self.select.is_empty() { + // Return json files that match the select regex + return Ok(Filter::Select(self.select.clone())); + } + + if let Some(skip) = self.build_args.skip.as_ref().filter(|s| !s.is_empty()) { + return Ok(Filter::Skip( + skip.clone() + .into_iter() + .map(|s| Regex::new(s.file_pattern())) + .collect::, _>>()?, + )); + } + + // Exclude defaults + Ok(Filter::skip_default()) + } + + /// Returns an iterator over the JSON files and the contract name in the `artifacts` directory. + fn get_json_files(&self, artifacts: &Path) -> Result> { + let filter = self.get_filter()?; + let alloy_filter = self.get_alloy_filter()?; + let is_alloy = self.alloy; + Ok(json_files(artifacts) .filter_map(|path| { - // we don't want `.metadata.json files - let stem = path.file_stem()?; - if stem.to_str()?.ends_with(".metadata") { - None - } else { - Some(path) + // Ignore the build info JSON. + if path.to_str()?.contains("build-info") { + return None; + } + + // We don't want `.metadata.json` files. + let stem = path.file_stem()?.to_str()?; + if stem.ends_with(".metadata") { + return None; } + + let name = stem.split('.').next().unwrap(); + + // Best effort identifier cleanup. + let name = name.replace(char::is_whitespace, "").replace('-', "_"); + + Some((name, path)) }) - .map(|path| { + .filter( + move |(name, _path)| { + if is_alloy { + alloy_filter.is_match(name) + } else { + filter.is_match(name) + } + }, + )) + } + + /// Instantiate the multi-abigen + fn get_multi(&self, artifacts: &Path) -> Result { + let abigens = self + .get_json_files(artifacts)? + .map(|(name, path)| { trace!(?path, "parsing Abigen from file"); - let abi = Abigen::from_file(&path) - .wrap_err_with(|| format!("failed to parse Abigen from file: {:?}", path)); + let abi = Abigen::new(name, path.to_str().unwrap()) + .wrap_err_with(|| format!("failed to parse Abigen from file: {path:?}")); if !self.skip_extra_derives { abi?.add_derive("serde::Serialize")?.add_derive("serde::Deserialize") } else { @@ -174,27 +238,48 @@ impl BindArgs { } }) .collect::, _>>()?; - let multi = MultiAbigen::from_abigens(abigens).with_filter(self.get_filter()); - - eyre::ensure!( - !multi.is_empty(), - r#" -No contract artifacts found. Hint: Have you built your contracts yet? `forge bind` does not currently invoke `forge build`, although this is planned for future versions. - "# - ); + let multi = MultiAbigen::from_abigens(abigens); + eyre::ensure!(!multi.is_empty(), "No contract artifacts found"); + Ok(multi) + } + + fn get_solmacrogen(&self, artifacts: &Path) -> Result { + let mut dup = std::collections::HashSet::::new(); + let instances = self + .get_json_files(artifacts)? + .filter_map(|(name, path)| { + trace!(?path, "parsing SolMacroGen from file"); + if dup.insert(name.clone()) { + Some(SolMacroGen::new(path, name)) + } else { + None + } + }) + .collect::>(); + + let multi = MultiSolMacroGen::new(artifacts, instances); + eyre::ensure!(!multi.instances.is_empty(), "No contract artifacts found"); Ok(multi) } /// Check that the existing bindings match the expected abigen output - fn check_existing_bindings(&self, artifacts: impl AsRef) -> Result<()> { - let bindings = self.get_multi(&artifacts)?.build()?; + fn check_existing_bindings(&self, artifacts: &Path, bindings_root: &Path) -> Result<()> { + if !self.alloy { + return self.check_ethers(artifacts, bindings_root); + } + + self.check_alloy(artifacts, bindings_root) + } + + fn check_ethers(&self, artifacts: &Path, bindings_root: &Path) -> Result<()> { + let bindings = self.get_multi(artifacts)?.build()?; println!("Checking bindings for {} contracts.", bindings.len()); if !self.module { bindings .ensure_consistent_crate( &self.crate_name, &self.crate_version, - self.bindings_root(&artifacts), + bindings_root, self.single_file, !self.skip_cargo_toml, ) @@ -206,15 +291,40 @@ No contract artifacts found. Hint: Have you built your contracts yet? `forge bin } })?; } else { - bindings.ensure_consistent_module(self.bindings_root(&artifacts), self.single_file)?; + bindings.ensure_consistent_module(bindings_root, self.single_file)?; } println!("OK."); Ok(()) } + fn check_alloy(&self, artifacts: &Path, bindings_root: &Path) -> Result<()> { + let mut bindings = self.get_solmacrogen(artifacts)?; + bindings.generate_bindings()?; + println!("Checking bindings for {} contracts", bindings.instances.len()); + bindings.check_consistency( + &self.crate_name, + &self.crate_version, + bindings_root, + self.single_file, + !self.skip_cargo_toml, + self.module, + self.alloy_version.clone(), + )?; + println!("OK."); + Ok(()) + } + /// Generate the bindings - fn generate_bindings(&self, artifacts: impl AsRef) -> Result<()> { - let mut bindings = self.get_multi(&artifacts)?.build()?; + fn generate_bindings(&self, artifacts: &Path, bindings_root: &Path) -> Result<()> { + if !self.alloy { + return self.generate_ethers(artifacts, bindings_root); + } + + self.generate_alloy(artifacts, bindings_root) + } + + fn generate_ethers(&self, artifacts: &Path, bindings_root: &Path) -> Result<()> { + let mut bindings = self.get_multi(artifacts)?.build()?; println!("Generating bindings for {} contracts", bindings.len()); if !self.module { trace!(single_file = self.single_file, "generating crate"); @@ -224,12 +334,67 @@ No contract artifacts found. Hint: Have you built your contracts yet? `forge bin bindings.write_to_crate( &self.crate_name, &self.crate_version, - self.bindings_root(&artifacts), + bindings_root, self.single_file, ) } else { trace!(single_file = self.single_file, "generating module"); - bindings.write_to_module(self.bindings_root(&artifacts), self.single_file) + bindings.write_to_module(bindings_root, self.single_file) } } + + fn generate_alloy(&self, artifacts: &Path, bindings_root: &Path) -> Result<()> { + let mut solmacrogen = self.get_solmacrogen(artifacts)?; + println!("Generating bindings for {} contracts", solmacrogen.instances.len()); + + if !self.module { + trace!(single_file = self.single_file, "generating crate"); + solmacrogen.write_to_crate( + &self.crate_name, + &self.crate_version, + bindings_root, + self.single_file, + self.alloy_version.clone(), + )?; + } else { + trace!(single_file = self.single_file, "generating module"); + solmacrogen.write_to_module(bindings_root, self.single_file)?; + } + + Ok(()) + } +} + +pub enum Filter { + All, + Select(Vec), + Skip(Vec), +} + +impl Filter { + pub fn is_match(&self, name: &str) -> bool { + match self { + Self::All => true, + Self::Select(regexes) => regexes.iter().any(|regex| regex.is_match(name)), + Self::Skip(regexes) => !regexes.iter().any(|regex| regex.is_match(name)), + } + } + + pub fn skip_default() -> Self { + let skip = [ + ".*Test.*", + ".*Script", + "console[2]?", + "CommonBase", + "Components", + "[Ss]td(Chains|Math|Error|Json|Utils|Cheats|Style|Invariant|Assertions|Toml|Storage(Safe)?)", + "[Vv]m.*", + "IMulticall3", + ] + .iter() + .map(|pattern| regex::Regex::new(pattern).unwrap()) + .collect::>(); + + Self::Skip(skip) + } } diff --git a/crates/forge/bin/cmd/build.rs b/crates/forge/bin/cmd/build.rs index 6155dc25a..cd691c756 100644 --- a/crates/forge/bin/cmd/build.rs +++ b/crates/forge/bin/cmd/build.rs @@ -2,8 +2,12 @@ use super::{install, watch::WatchArgs}; use clap::Parser; use eyre::Result; use foundry_cli::{opts::CoreBuildArgs, utils::LoadConfig}; -use foundry_common::compile::{ProjectCompiler, SkipBuildFilter, SkipBuildFilters}; -use foundry_compilers::Project; +use foundry_common::compile::ProjectCompiler; +use foundry_compilers::{ + compilers::{multi::MultiCompilerLanguage, Language}, + utils::source_files_iter, + Project, +}; use foundry_config::{ figment::{ self, @@ -14,7 +18,7 @@ use foundry_config::{ Config, }; use serde::Serialize; -use watchexec::config::{InitConfig, RuntimeConfig}; +use std::path::PathBuf; foundry_config::merge_impl_figment_convert!(BuildArgs, args); @@ -40,36 +44,33 @@ foundry_config::merge_impl_figment_convert!(BuildArgs, args); /// Some arguments are marked as `#[serde(skip)]` and require manual processing in /// `figment::Provider` implementation #[derive(Clone, Debug, Default, Serialize, Parser)] -#[clap(next_help_heading = "Build options", about = None, long_about = None)] // override doc +#[command(next_help_heading = "Build options", about = None, long_about = None)] // override doc pub struct BuildArgs { + /// Build source files from specified paths. + #[serde(skip)] + pub paths: Option>, + /// Print compiled contract names. - #[clap(long)] + #[arg(long)] #[serde(skip)] pub names: bool, /// Print compiled contract sizes. - #[clap(long)] + #[arg(long)] #[serde(skip)] pub sizes: bool, - /// Skip building files whose names contain the given filter. - /// - /// `test` and `script` are aliases for `.t.sol` and `.s.sol`. - #[clap(long, num_args(1..))] - #[serde(skip)] - pub skip: Option>, - - #[clap(flatten)] + #[command(flatten)] #[serde(flatten)] pub args: CoreBuildArgs, - #[clap(flatten)] + #[command(flatten)] #[serde(skip)] pub watch: WatchArgs, /// Output the compilation errors in the json format. /// This is useful when you want to use the output in other tools. - #[clap(long, conflicts_with = "silent")] + #[arg(long, conflicts_with = "silent")] #[serde(skip)] pub format_json: bool, } @@ -77,40 +78,45 @@ pub struct BuildArgs { impl BuildArgs { pub fn run(self) -> Result<()> { let mut config = self.try_load_config_emit_warnings()?; - let mut project = config.project()?; if install::install_missing_dependencies(&mut config, self.args.silent) && config.auto_detect_remappings { // need to re-configure here to also catch additional remappings config = self.load_config(); - project = config.project()?; } if !config.zksync.should_compile() { - let mut compiler = ProjectCompiler::new() + let project = config.project()?; + + // Collect sources to compile if build subdirectories specified. + let mut files = vec![]; + if let Some(paths) = self.paths { + for path in paths { + files.extend(source_files_iter(path, MultiCompilerLanguage::FILE_EXTENSIONS)); + } + } + + let compiler = ProjectCompiler::new() .print_names(self.names) .print_sizes(self.sizes) .quiet(self.format_json) .bail(!self.format_json); - if let Some(skip) = self.skip { - if !skip.is_empty() { - compiler = compiler.filter(Box::new(SkipBuildFilters::new(skip)?)); - } - } let output = compiler.compile(&project)?; if self.format_json { println!("{}", serde_json::to_string_pretty(&output.output())?); } } else { + let zk_project = foundry_zksync_compiler::create_project(&config, config.cache, false)?; let zk_compiler = ProjectCompiler::new() .print_names(self.names) .print_sizes(self.sizes) .quiet(self.format_json) .bail(!self.format_json); - let zk_output = zk_compiler.zksync_compile(&project)?; + let zk_output = + zk_compiler.zksync_compile(&zk_project, config.zksync.avoid_contracts())?; if self.format_json { println!("{}", serde_json::to_string_pretty(&zk_output.output())?); } @@ -135,11 +141,11 @@ impl BuildArgs { /// Returns the [`watchexec::InitConfig`] and [`watchexec::RuntimeConfig`] necessary to /// bootstrap a new [`watchexe::Watchexec`] loop. - pub(crate) fn watchexec_config(&self) -> Result<(InitConfig, RuntimeConfig)> { + pub(crate) fn watchexec_config(&self) -> Result { // use the path arguments or if none where provided the `src` dir self.watch.watchexec_config(|| { let config = Config::from(self); - vec![config.src, config.test, config.script] + [config.src, config.test, config.script] }) } } @@ -170,21 +176,22 @@ impl Provider for BuildArgs { #[cfg(test)] mod tests { use super::*; + use foundry_config::filter::SkipBuildFilter; #[test] fn can_parse_build_filters() { let args: BuildArgs = BuildArgs::parse_from(["foundry-cli", "--skip", "tests"]); - assert_eq!(args.skip, Some(vec![SkipBuildFilter::Tests])); + assert_eq!(args.args.skip, Some(vec![SkipBuildFilter::Tests])); let args: BuildArgs = BuildArgs::parse_from(["foundry-cli", "--skip", "scripts"]); - assert_eq!(args.skip, Some(vec![SkipBuildFilter::Scripts])); + assert_eq!(args.args.skip, Some(vec![SkipBuildFilter::Scripts])); let args: BuildArgs = BuildArgs::parse_from(["foundry-cli", "--skip", "tests", "--skip", "scripts"]); - assert_eq!(args.skip, Some(vec![SkipBuildFilter::Tests, SkipBuildFilter::Scripts])); + assert_eq!(args.args.skip, Some(vec![SkipBuildFilter::Tests, SkipBuildFilter::Scripts])); let args: BuildArgs = BuildArgs::parse_from(["foundry-cli", "--skip", "tests", "scripts"]); - assert_eq!(args.skip, Some(vec![SkipBuildFilter::Tests, SkipBuildFilter::Scripts])); + assert_eq!(args.args.skip, Some(vec![SkipBuildFilter::Tests, SkipBuildFilter::Scripts])); } #[test] diff --git a/crates/forge/bin/cmd/cache.rs b/crates/forge/bin/cmd/cache.rs index 4ae2056c4..1adaf4d26 100644 --- a/crates/forge/bin/cmd/cache.rs +++ b/crates/forge/bin/cmd/cache.rs @@ -11,7 +11,7 @@ use strum::VariantNames; /// CLI arguments for `forge cache`. #[derive(Debug, Parser)] pub struct CacheArgs { - #[clap(subcommand)] + #[command(subcommand)] pub sub: CacheSubcommands, } @@ -26,12 +26,12 @@ pub enum CacheSubcommands { /// CLI arguments for `forge clean`. #[derive(Debug, Parser)] -#[clap(group = clap::ArgGroup::new("etherscan-blocks").multiple(false))] +#[command(group = clap::ArgGroup::new("etherscan-blocks").multiple(false))] pub struct CleanArgs { /// The chains to clean the cache for. /// /// Can also be "all" to clean all chains. - #[clap( + #[arg( env = "CHAIN", default_value = "all", value_parser = ChainOrAllValueParser::default(), @@ -39,24 +39,23 @@ pub struct CleanArgs { chains: Vec, /// The blocks to clean the cache for. - #[clap( + #[arg( short, long, num_args(1..), - use_value_delimiter(true), value_delimiter(','), group = "etherscan-blocks" )] blocks: Vec, /// Whether to clean the Etherscan cache. - #[clap(long, group = "etherscan-blocks")] + #[arg(long, group = "etherscan-blocks")] etherscan: bool, } impl CleanArgs { pub fn run(self) -> Result<()> { - let CleanArgs { chains, blocks, etherscan } = self; + let Self { chains, blocks, etherscan } = self; for chain_or_all in chains { match chain_or_all { @@ -82,7 +81,7 @@ pub struct LsArgs { /// The chains to list the cache for. /// /// Can also be "all" to list all chains. - #[clap( + #[arg( env = "CHAIN", default_value = "all", value_parser = ChainOrAllValueParser::default(), @@ -92,7 +91,7 @@ pub struct LsArgs { impl LsArgs { pub fn run(self) -> Result<()> { - let LsArgs { chains } = self; + let Self { chains } = self; let mut cache = Cache::default(); for chain_or_all in chains { match chain_or_all { @@ -118,9 +117,9 @@ impl FromStr for ChainOrAll { fn from_str(s: &str) -> Result { if let Ok(chain) = NamedChain::from_str(s) { - Ok(ChainOrAll::NamedChain(chain)) + Ok(Self::NamedChain(chain)) } else if s == "all" { - Ok(ChainOrAll::All) + Ok(Self::All) } else { Err(format!("Expected known chain or all, found: {s}")) } @@ -151,7 +150,7 @@ pub struct ChainOrAllValueParser { impl Default for ChainOrAllValueParser { fn default() -> Self { - ChainOrAllValueParser { inner: possible_chains() } + Self { inner: possible_chains() } } } diff --git a/crates/forge/bin/cmd/clone.rs b/crates/forge/bin/cmd/clone.rs new file mode 100644 index 000000000..97f6383e3 --- /dev/null +++ b/crates/forge/bin/cmd/clone.rs @@ -0,0 +1,801 @@ +use super::{init::InitArgs, install::DependencyInstallOpts}; +use alloy_primitives::{Address, Bytes, ChainId, TxHash}; +use clap::{Parser, ValueHint}; +use eyre::Result; +use foundry_block_explorers::{ + contract::{ContractCreationData, ContractMetadata, Metadata}, + errors::EtherscanError, + Client, +}; +use foundry_cli::{opts::EtherscanOpts, p_println, utils::Git}; +use foundry_common::{compile::ProjectCompiler, fs}; +use foundry_compilers::{ + artifacts::{ + output_selection::ContractOutputSelection, + remappings::{RelativeRemapping, Remapping}, + ConfigurableContractArtifact, Settings, StorageLayout, + }, + compilers::solc::Solc, + ProjectCompileOutput, ProjectPathsConfig, +}; +use foundry_config::{Chain, Config}; +use std::{ + fs::read_dir, + path::{Path, PathBuf}, + time::Duration, +}; + +/// CloneMetadata stores the metadata that are not included by `foundry.toml` but necessary for a +/// cloned contract. The metadata can be serialized to a metadata file in the cloned project root. +#[derive(Debug, Clone, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloneMetadata { + /// The path to the source file that contains the contract declaration. + /// The path is relative to the root directory of the project. + pub path: PathBuf, + /// The name of the contract in the file. + pub target_contract: String, + /// The address of the contract on the blockchain. + pub address: Address, + /// The chain id. + pub chain_id: ChainId, + /// The transaction hash of the creation transaction. + pub creation_transaction: TxHash, + /// The address of the deployer, i.e., sender of the creation transaction. + pub deployer: Address, + /// The constructor arguments of the contract on chain. + pub constructor_arguments: Bytes, + /// The storage layout of the contract on chain. + pub storage_layout: StorageLayout, +} + +/// CLI arguments for `forge clone`. +/// +/// `forge clone` clones an on-chain contract from block explorers (e.g., Etherscan) in the +/// following steps: +/// 1. Fetch the contract source code from the block explorer. +/// 2. Initialize a empty foundry project at the `root` directory specified in `CloneArgs`. +/// 3. Dump the contract sources to the source directory. +/// 4. Update the `foundry.toml` configuration file with the compiler settings from Etherscan. +/// 5. Try compile the cloned contract, so that we can get the original storage layout. This +/// original storage layout is preserved in the `CloneMetadata` so that if the user later +/// modifies the contract, it is possible to quickly check the storage layout compatibility with +/// the original on-chain contract. +/// 6. Dump the `CloneMetadata` to the root directory of the cloned project as `.clone.meta` file. +#[derive(Clone, Debug, Parser)] +pub struct CloneArgs { + /// The contract address to clone. + pub address: Address, + + /// The root directory of the cloned project. + #[arg(value_hint = ValueHint::DirPath, default_value = ".", value_name = "PATH")] + pub root: PathBuf, + + /// Do not generate the remappings.txt file. Instead, keep the remappings in the configuration. + #[arg(long)] + pub no_remappings_txt: bool, + + /// Keep the original directory structure collected from Etherscan. + /// + /// If this flag is set, the directory structure of the cloned project will be kept as is. + /// By default, the directory structure is re-orgnized to increase the readability, but may + /// risk some compilation failures. + #[arg(long)] + pub keep_directory_structure: bool, + + #[command(flatten)] + pub etherscan: EtherscanOpts, + + #[command(flatten)] + pub opts: DependencyInstallOpts, +} + +impl CloneArgs { + pub async fn run(self) -> Result<()> { + let Self { address, root, opts, etherscan, no_remappings_txt, keep_directory_structure } = + self; + + // step 0. get the chain and api key from the config + let config = Config::from(ðerscan); + let chain = config.chain.unwrap_or_default(); + let etherscan_api_key = config.get_etherscan_api_key(Some(chain)).unwrap_or_default(); + let client = Client::new(chain, etherscan_api_key.clone())?; + + // step 1. get the metadata from client + p_println!(!opts.quiet => "Downloading the source code of {} from Etherscan...", address); + let meta = Self::collect_metadata_from_client(address, &client).await?; + + // step 2. initialize an empty project + Self::init_an_empty_project(&root, opts)?; + // canonicalize the root path + // note that at this point, the root directory must have been created + let root = dunce::canonicalize(&root)?; + + // step 3. parse the metadata + Self::parse_metadata(&meta, chain, &root, no_remappings_txt, keep_directory_structure) + .await?; + + // step 4. collect the compilation metadata + // if the etherscan api key is not set, we need to wait for 3 seconds between calls + p_println!(!opts.quiet => "Collecting the creation information of {} from Etherscan...", address); + if etherscan_api_key.is_empty() { + p_println!(!opts.quiet => "Waiting for 5 seconds to avoid rate limit..."); + tokio::time::sleep(Duration::from_secs(5)).await; + } + Self::collect_compilation_metadata(&meta, chain, address, &root, &client, opts.quiet) + .await?; + + // step 5. git add and commit the changes if needed + if !opts.no_commit { + let git = Git::new(&root).quiet(opts.quiet); + git.add(Some("--all"))?; + let msg = format!("chore: forge clone {address}"); + git.commit(&msg)?; + } + + Ok(()) + } + + /// Collect the metadata of the contract from the block explorer. + /// + /// * `address` - the address of the contract to be cloned. + /// * `client` - the client of the block explorer. + pub(crate) async fn collect_metadata_from_client( + address: Address, + client: &C, + ) -> Result { + let mut meta = client.contract_source_code(address).await?; + eyre::ensure!(meta.items.len() == 1, "contract not found or ill-formed"); + let meta = meta.items.remove(0); + eyre::ensure!(!meta.is_vyper(), "Vyper contracts are not supported"); + Ok(meta) + } + + /// Initialize an empty project at the root directory. + /// + /// * `root` - the root directory of the project. + /// * `enable_git` - whether to enable git for the project. + /// * `quiet` - whether to print messages. + pub(crate) fn init_an_empty_project(root: &Path, opts: DependencyInstallOpts) -> Result<()> { + // let's try to init the project with default init args + let init_args = InitArgs { root: root.to_path_buf(), opts, ..Default::default() }; + init_args.run().map_err(|e| eyre::eyre!("Project init error: {:?}", e))?; + + // remove the unnecessary example contracts + // XXX (ZZ): this is a temporary solution until we have a proper way to remove contracts, + // e.g., add a field in the InitArgs to control the example contract generation + fs::remove_file(root.join("src/Counter.sol"))?; + fs::remove_file(root.join("test/Counter.t.sol"))?; + fs::remove_file(root.join("script/Counter.s.sol"))?; + + Ok(()) + } + + /// Collect the compilation metadata of the cloned contract. + /// This function compiles the cloned contract and collects the compilation metadata. + /// + /// * `meta` - the metadata of the contract (from Etherscan). + /// * `chain` - the chain where the contract to be cloned locates. + /// * `address` - the address of the contract to be cloned. + /// * `root` - the root directory of the cloned project. + /// * `client` - the client of the block explorer. + pub(crate) async fn collect_compilation_metadata( + meta: &Metadata, + chain: Chain, + address: Address, + root: &PathBuf, + client: &C, + quiet: bool, + ) -> Result<()> { + // compile the cloned contract + let compile_output = compile_project(root, quiet)?; + let (main_file, main_artifact) = find_main_contract(&compile_output, &meta.contract_name)?; + let main_file = main_file.strip_prefix(root)?.to_path_buf(); + let storage_layout = + main_artifact.storage_layout.to_owned().expect("storage layout not found"); + + // dump the metadata to the root directory + let creation_tx = client.contract_creation_data(address).await?; + let clone_meta = CloneMetadata { + path: main_file, + target_contract: meta.contract_name.clone(), + address, + chain_id: chain.id(), + creation_transaction: creation_tx.transaction_hash, + deployer: creation_tx.contract_creator, + constructor_arguments: meta.constructor_arguments.clone(), + storage_layout, + }; + let metadata_content = serde_json::to_string(&clone_meta)?; + let metadata_file = root.join(".clone.meta"); + fs::write(&metadata_file, metadata_content)?; + let mut perms = std::fs::metadata(&metadata_file)?.permissions(); + perms.set_readonly(true); + std::fs::set_permissions(&metadata_file, perms)?; + + Ok(()) + } + + /// Download and parse the source code from Etherscan. + /// + /// * `chain` - the chain where the contract to be cloned locates. + /// * `address` - the address of the contract to be cloned. + /// * `root` - the root directory to clone the contract into as a foundry project. + /// * `client` - the client of the block explorer. + /// * `no_remappings_txt` - whether to generate the remappings.txt file. + pub(crate) async fn parse_metadata( + meta: &Metadata, + chain: Chain, + root: &PathBuf, + no_remappings_txt: bool, + keep_directory_structure: bool, + ) -> Result<()> { + // dump sources and update the remapping in configuration + let remappings = dump_sources(meta, root, keep_directory_structure)?; + Config::update_at(root, |config, doc| { + let profile = config.profile.as_str().as_str(); + + // update the remappings in the configuration + let mut remapping_array = toml_edit::Array::new(); + for r in remappings { + remapping_array.push(r.to_string()); + } + doc[Config::PROFILE_SECTION][profile]["remappings"] = toml_edit::value(remapping_array); + + // make sure auto_detect_remappings is false (it is very important because cloned + // project may not follow the common remappings) + doc[Config::PROFILE_SECTION][profile]["auto_detect_remappings"] = + toml_edit::value(false); + true + })?; + + // update configuration + Config::update_at(root, |config, doc| { + update_config_by_metadata(config, doc, meta, chain).is_ok() + })?; + + // write remappings to remappings.txt if necessary + if !no_remappings_txt { + let remappings_txt = root.join("remappings.txt"); + eyre::ensure!( + !remappings_txt.exists(), + "remappings.txt already exists, please remove it first" + ); + + Config::update_at(root, |config, doc| { + let remappings_txt_content = + config.remappings.iter().map(|r| r.to_string()).collect::>().join("\n"); + if fs::write(&remappings_txt, remappings_txt_content).is_err() { + return false + } + + let profile = config.profile.as_str().as_str(); + if let Some(elem) = doc[Config::PROFILE_SECTION][profile].as_table_mut() { + elem.remove_entry("remappings"); + true + } else { + false + } + })?; + } + + Ok(()) + } +} + +/// Update the configuration file with the metadata. +/// This function will update the configuration file with the metadata from the contract. +/// It will update the following fields: +/// - `auto_detect_solc` to `false` +/// - `solc_version` to the value from the metadata +/// - `evm_version` to the value from the metadata +/// - `via_ir` to the value from the metadata +/// - `libraries` to the value from the metadata +/// - `metadata` to the value from the metadata +/// - `cbor_metadata`, `use_literal_content`, and `bytecode_hash` +/// - `optimizer` to the value from the metadata +/// - `optimizer_runs` to the value from the metadata +/// - `optimizer_details` to the value from the metadata +/// - `yul_details`, `yul`, etc. +/// - `simpleCounterForLoopUncheckedIncrement` is ignored for now +/// - `remappings` and `stop_after` are pre-validated to be empty and None, respectively +/// - `model_checker`, `debug`, and `output_selection` are ignored for now +/// +/// Detailed information can be found from the following link: +/// - https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options +/// - https://docs.soliditylang.org/en/latest/using-the-compiler.html#compiler-input-and-output-json-description +fn update_config_by_metadata( + config: &Config, + doc: &mut toml_edit::DocumentMut, + meta: &Metadata, + chain: Chain, +) -> Result<()> { + let profile = config.profile.as_str().as_str(); + + // macro to update the config if the value exists + macro_rules! update_if_needed { + ([$($key:expr),+], $value:expr) => { + { + if let Some(value) = $value { + let mut current = &mut doc[Config::PROFILE_SECTION][profile]; + $( + if let Some(nested_doc) = current.get_mut(&$key) { + current = nested_doc; + } else { + return Err(eyre::eyre!("cannot find the key: {}", $key)); + } + )+ + *current = toml_edit::value(value); + } + } + }; + } + + // update the chain id + doc[Config::PROFILE_SECTION][profile]["chain_id"] = toml_edit::value(chain.id() as i64); + + // disable auto detect solc and set the solc version + doc[Config::PROFILE_SECTION][profile]["auto_detect_solc"] = toml_edit::value(false); + let version = meta.compiler_version()?; + doc[Config::PROFILE_SECTION][profile]["solc_version"] = + toml_edit::value(format!("{}.{}.{}", version.major, version.minor, version.patch)); + + // get optimizer settings + // we ignore `model_checker`, `debug`, and `output_selection` for now, + // it seems they do not have impacts on the actual compilation + let Settings { optimizer, libraries, evm_version, via_ir, stop_after, metadata, .. } = + meta.settings()?; + eyre::ensure!(stop_after.is_none(), "stop_after should be None"); + + update_if_needed!(["evm_version"], evm_version.map(|v| v.to_string())); + update_if_needed!(["via_ir"], via_ir); + + // update metadata if needed + if let Some(metadata) = metadata { + update_if_needed!(["cbor_metadata"], metadata.cbor_metadata); + update_if_needed!(["use_literal_content"], metadata.use_literal_content); + update_if_needed!(["bytecode_hash"], metadata.bytecode_hash.map(|v| v.to_string())); + } + + // update optimizer settings if needed + update_if_needed!(["optimizer"], optimizer.enabled); + update_if_needed!(["optimizer_runs"], optimizer.runs.map(|v| v as i64)); + // update optimizer details if needed + if let Some(detail) = optimizer.details { + doc[Config::PROFILE_SECTION][profile]["optimizer_details"] = toml_edit::table(); + + update_if_needed!(["optimizer_details", "peephole"], detail.peephole); + update_if_needed!(["optimizer_details", "inliner"], detail.inliner); + update_if_needed!(["optimizer_details", "jumpdestRemover"], detail.jumpdest_remover); + update_if_needed!(["optimizer_details", "orderLiterals"], detail.order_literals); + update_if_needed!(["optimizer_details", "deduplicate"], detail.deduplicate); + update_if_needed!(["optimizer_details", "cse"], detail.cse); + update_if_needed!(["optimizer_details", "constantOptimizer"], detail.constant_optimizer); + update_if_needed!( + ["optimizer_details", "simpleCounterForLoopUncheckedIncrement"], + detail.simple_counter_for_loop_unchecked_increment + ); + update_if_needed!(["optimizer_details", "yul"], detail.yul); + + if let Some(yul_detail) = detail.yul_details { + doc[Config::PROFILE_SECTION][profile]["optimizer_details"]["yulDetails"] = + toml_edit::table(); + update_if_needed!( + ["optimizer_details", "yulDetails", "stackAllocation"], + yul_detail.stack_allocation + ); + update_if_needed!( + ["optimizer_details", "yulDetails", "optimizerSteps"], + yul_detail.optimizer_steps + ); + } + } + + // apply remapping on libraries + let path_config: ProjectPathsConfig = config.project_paths(); + let libraries = libraries + .apply(|libs| path_config.apply_lib_remappings(libs)) + .with_stripped_file_prefixes(&path_config.root); + + // update libraries + let mut lib_array = toml_edit::Array::new(); + for (path_to_lib, info) in libraries.libs { + for (lib_name, address) in info { + lib_array.push(format!("{}:{}:{}", path_to_lib.to_str().unwrap(), lib_name, address)); + } + } + doc[Config::PROFILE_SECTION][profile]["libraries"] = toml_edit::value(lib_array); + + Ok(()) +} + +/// Dump the contract sources to the root directory. +/// The sources are dumped to the `src` directory. +/// IO errors may be returned. +/// A list of remappings is returned +fn dump_sources(meta: &Metadata, root: &PathBuf, no_reorg: bool) -> Result> { + // get config + let path_config = ProjectPathsConfig::builder().build_with_root::(root); + // we will canonicalize the sources directory later + let src_dir = &path_config.sources; + let lib_dir = &path_config.libraries[0]; + let contract_name = &meta.contract_name; + let source_tree = meta.source_tree(); + + // then we move the sources to the correct directories + // we will first load existing remappings if necessary + // make sure this happens before dumping sources + let mut remappings: Vec = Remapping::find_many(root); + + // first we dump the sources to a temporary directory + let tmp_dump_dir = root.join("raw_sources"); + source_tree + .write_to(&tmp_dump_dir) + .map_err(|e| eyre::eyre!("failed to dump sources: {}", e))?; + + // check whether we need to re-organize directories in the original sources, since we do not + // want to put all the sources in the `src` directory if the original directory structure is + // well organized, e.g., a standard foundry project containing `src` and `lib` + // + // * if the user wants to keep the original directory structure, we should not re-organize. + // * if there is any other directory other than `src`, `contracts`, `lib`, `hardhat`, + // `forge-std`, + // or not started with `@`, we should not re-organize. + let to_reorg = !no_reorg && + std::fs::read_dir(tmp_dump_dir.join(contract_name))?.all(|e| { + let Ok(e) = e else { return false }; + let folder_name = e.file_name(); + folder_name == "src" || + folder_name == "lib" || + folder_name == "contracts" || + folder_name == "hardhat" || + folder_name == "forge-std" || + folder_name.to_string_lossy().starts_with('@') + }); + + // ensure `src` and `lib` directories exist + eyre::ensure!(Path::exists(&root.join(src_dir)), "`src` directory must exists"); + eyre::ensure!(Path::exists(&root.join(lib_dir)), "`lib` directory must exists"); + + // move source files + for entry in std::fs::read_dir(tmp_dump_dir.join(contract_name))? { + let entry = entry?; + let folder_name = entry.file_name(); + // special handling when we need to re-organize the directories: we flatten them. + if to_reorg { + if folder_name == "contracts" || folder_name == "src" || folder_name == "lib" { + // move all sub folders in contracts to src or lib + let new_dir = if folder_name == "lib" { lib_dir } else { src_dir }; + for e in read_dir(entry.path())? { + let e = e?; + let dest = new_dir.join(e.file_name()); + eyre::ensure!(!Path::exists(&dest), "destination already exists: {:?}", dest); + std::fs::rename(e.path(), &dest)?; + remappings.push(Remapping { + context: None, + name: format!( + "{}/{}", + folder_name.to_string_lossy(), + e.file_name().to_string_lossy() + ), + path: dest.to_string_lossy().to_string(), + }); + } + } else { + assert!( + folder_name == "hardhat" || + folder_name == "forge-std" || + folder_name.to_string_lossy().starts_with('@') + ); + // move these other folders to lib + let dest = lib_dir.join(&folder_name); + if folder_name == "forge-std" { + // let's use the provided forge-std directory + std::fs::remove_dir_all(&dest)?; + } + eyre::ensure!(!Path::exists(&dest), "destination already exists: {:?}", dest); + std::fs::rename(entry.path(), &dest)?; + remappings.push(Remapping { + context: None, + name: folder_name.to_string_lossy().to_string(), + path: dest.to_string_lossy().to_string(), + }); + } + } else { + // directly move the all folders into src + let dest = src_dir.join(&folder_name); + eyre::ensure!(!Path::exists(&dest), "destination already exists: {:?}", dest); + std::fs::rename(entry.path(), &dest)?; + if folder_name != "src" { + remappings.push(Remapping { + context: None, + name: folder_name.to_string_lossy().to_string(), + path: dest.to_string_lossy().to_string(), + }); + } + } + } + + // remove the temporary directory + std::fs::remove_dir_all(tmp_dump_dir)?; + + // add remappings in the metedata + for mut r in meta.settings()?.remappings { + if to_reorg { + // we should update its remapped path in the same way as we dump sources + // i.e., remove prefix `contracts` (if any) and add prefix `src` + let new_path = if r.path.starts_with("contracts") { + PathBuf::from("src").join(PathBuf::from(&r.path).strip_prefix("contracts")?) + } else if r.path.starts_with('@') || + r.path.starts_with("hardhat/") || + r.path.starts_with("forge-std/") + { + PathBuf::from("lib").join(PathBuf::from(&r.path)) + } else { + PathBuf::from(&r.path) + }; + r.path = new_path.to_string_lossy().to_string(); + remappings.push(r); + } else { + remappings.push(r); + } + } + + Ok(remappings.into_iter().map(|r| r.into_relative(root)).collect()) +} + +/// Compile the project in the root directory, and return the compilation result. +pub fn compile_project(root: &PathBuf, quiet: bool) -> Result { + let mut config = Config::load_with_root(root).sanitized(); + config.extra_output.push(ContractOutputSelection::StorageLayout); + let project = config.project()?; + let compiler = ProjectCompiler::new().quiet_if(quiet); + compiler.compile(&project) +} + +/// Find the artifact of the contract with the specified name. +/// This function returns the path to the source file and the artifact. +pub fn find_main_contract<'a>( + compile_output: &'a ProjectCompileOutput, + contract: &str, +) -> Result<(PathBuf, &'a ConfigurableContractArtifact)> { + let mut rv = None; + for (f, c, a) in compile_output.artifacts_with_files() { + if contract == c { + // it is possible that we have multiple contracts with the same name + // in different files + // instead of throwing an error, we should handle this case in the future + if rv.is_some() { + return Err(eyre::eyre!("multiple contracts with the same name found")); + } + rv = Some((PathBuf::from(f), a)); + } + } + rv.ok_or(eyre::eyre!("contract not found")) +} + +#[cfg(test)] +use mockall::automock; +/// EtherscanClient is a trait that defines the methods to interact with Etherscan. +/// It is defined as a wrapper of the `foundry_block_explorers::Client` to allow mocking. +#[cfg_attr(test, automock)] +pub(crate) trait EtherscanClient { + async fn contract_source_code( + &self, + address: Address, + ) -> std::result::Result; + async fn contract_creation_data( + &self, + address: Address, + ) -> std::result::Result; +} + +impl EtherscanClient for Client { + #[inline] + async fn contract_source_code( + &self, + address: Address, + ) -> std::result::Result { + self.contract_source_code(address).await + } + + #[inline] + async fn contract_creation_data( + &self, + address: Address, + ) -> std::result::Result { + self.contract_creation_data(address).await + } +} + +#[cfg(test)] +mod tests { + use super::*; + use foundry_compilers::Artifact; + use foundry_test_utils::rpc::next_etherscan_api_key; + use hex::ToHex; + use std::collections::BTreeMap; + + fn assert_successful_compilation(root: &PathBuf) -> ProjectCompileOutput { + println!("project_root: {root:#?}"); + compile_project(root, false).expect("compilation failure") + } + + fn assert_compilation_result( + compiled: ProjectCompileOutput, + contract_name: &str, + stripped_creation_code: &str, + ) { + compiled.compiled_contracts_by_compiler_version().iter().for_each(|(_, contracts)| { + contracts.iter().for_each(|(name, contract)| { + if name == contract_name { + let compiled_creation_code = + contract.get_bytecode_object().expect("creation code not found"); + let compiled_creation_code: String = compiled_creation_code.encode_hex(); + assert!( + compiled_creation_code.starts_with(stripped_creation_code), + "inconsistent creation code" + ); + } + }); + }); + } + + fn mock_etherscan(address: Address) -> impl super::EtherscanClient { + // load mock data + let mut mocked_data = BTreeMap::new(); + let data_folder = + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../testdata/etherscan"); + // iterate each sub folder + for entry in std::fs::read_dir(data_folder).expect("failed to read test data folder") { + let entry = entry.expect("failed to read test data entry"); + let addr: Address = entry.file_name().to_string_lossy().parse().unwrap(); + let contract_data_dir = entry.path(); + // the metadata.json file contains the metadata of the contract + let metadata_file = contract_data_dir.join("metadata.json"); + let metadata: ContractMetadata = + serde_json::from_str(&std::fs::read_to_string(metadata_file).unwrap()) + .expect("failed to parse metadata.json"); + // the creation_data.json file contains the creation data of the contract + let creation_data_file = contract_data_dir.join("creation_data.json"); + let creation_data: ContractCreationData = + serde_json::from_str(&std::fs::read_to_string(creation_data_file).unwrap()) + .expect("failed to parse creation_data.json"); + // insert the data to the map + mocked_data.insert(addr, (metadata, creation_data)); + } + + let (metadata, creation_data) = mocked_data.get(&address).unwrap(); + let metadata = metadata.clone(); + let creation_data = *creation_data; + let mut mocked_client = super::MockEtherscanClient::new(); + mocked_client + .expect_contract_source_code() + .times(1) + .returning(move |_| Ok(metadata.clone())); + mocked_client + .expect_contract_creation_data() + .times(1) + .returning(move |_| Ok(creation_data)); + mocked_client + } + + /// Fetch the metadata and creation data from Etherscan and dump them to the testdata folder. + #[tokio::test(flavor = "multi_thread")] + #[ignore = "this test is used to dump mock data from Etherscan"] + async fn test_dump_mock_data() { + let address: Address = "0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F".parse().unwrap(); + let data_folder = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../../testdata/etherscan") + .join(address.to_string()); + // create folder if not exists + std::fs::create_dir_all(&data_folder).unwrap(); + // create metadata.json and creation_data.json + let client = Client::new(Chain::mainnet(), next_etherscan_api_key()).unwrap(); + let meta = client.contract_source_code(address).await.unwrap(); + // dump json + let json = serde_json::to_string_pretty(&meta).unwrap(); + // write to metadata.json + std::fs::write(data_folder.join("metadata.json"), json).unwrap(); + let creation_data = client.contract_creation_data(address).await.unwrap(); + // dump json + let json = serde_json::to_string_pretty(&creation_data).unwrap(); + // write to creation_data.json + std::fs::write(data_folder.join("creation_data.json"), json).unwrap(); + } + + /// Run the clone command with the specified contract address and assert the compilation. + async fn one_test_case(address: Address, check_compilation_result: bool) { + let mut project_root = tempfile::tempdir().unwrap().path().to_path_buf(); + let client = mock_etherscan(address); + let meta = CloneArgs::collect_metadata_from_client(address, &client).await.unwrap(); + CloneArgs::init_an_empty_project(&project_root, DependencyInstallOpts::default()).unwrap(); + project_root = dunce::canonicalize(&project_root).unwrap(); + CloneArgs::parse_metadata(&meta, Chain::mainnet(), &project_root, false, false) + .await + .unwrap(); + CloneArgs::collect_compilation_metadata( + &meta, + Chain::mainnet(), + address, + &project_root, + &client, + false, + ) + .await + .unwrap(); + let rv = assert_successful_compilation(&project_root); + if check_compilation_result { + let (contract_name, stripped_creation_code) = + pick_creation_info(&address.to_string()).expect("creation code not found"); + assert_compilation_result(rv, contract_name, stripped_creation_code); + } + std::fs::remove_dir_all(project_root).unwrap(); + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_clone_single_file_contract() { + let address = "0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193".parse().unwrap(); + one_test_case(address, true).await + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_clone_contract_with_optimization_details() { + let address = "0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545".parse().unwrap(); + one_test_case(address, true).await + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_clone_contract_with_libraries() { + let address = "0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec".parse().unwrap(); + one_test_case(address, true).await + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_clone_contract_with_metadata() { + let address = "0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05".parse().unwrap(); + one_test_case(address, true).await + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_clone_contract_with_relative_import() { + let address = "0x3a23F943181408EAC424116Af7b7790c94Cb97a5".parse().unwrap(); + one_test_case(address, false).await + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_clone_contract_with_original_remappings() { + let address = "0x9ab6b21cdf116f611110b048987e58894786c244".parse().unwrap(); + one_test_case(address, false).await + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_clone_contract_with_relative_import2() { + let address = "0x044b75f554b886A065b9567891e45c79542d7357".parse().unwrap(); + one_test_case(address, false).await + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_clone_contract_with_nested_src() { + let address = "0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F".parse().unwrap(); + one_test_case(address, false).await + } + + fn pick_creation_info(address: &str) -> Option<(&'static str, &'static str)> { + for (addr, contract_name, creation_code) in CREATION_ARRAY.iter() { + if address == *addr { + return Some((contract_name, creation_code)); + } + } + + None + } + + // remember to remove CBOR metadata from the creation code + const CREATION_ARRAY: [(&str, &str, &str); 4] = [ + ("0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193", "BearXNFTStaking", "608060405234801561001057600080fd5b50613000806100206000396000f3fe608060405234801561001057600080fd5b50600436106102265760003560e01c80638129fc1c11610130578063bca35a71116100b8578063dada55011161007c578063dada550114610458578063f2fde38b1461046b578063f83d08ba1461047e578063fbb0022714610486578063fccd7f721461048e57600080fd5b8063bca35a71146103fa578063bf9befb11461040d578063c89d5b8b14610416578063d5d423001461041e578063d976e09f1461042657600080fd5b8063b1c92f95116100ff578063b1c92f95146103c5578063b549445c146103ce578063b81f8e89146103d6578063b9ade5b7146103de578063ba0848db146103e757600080fd5b80638129fc1c146103905780638da5cb5b14610398578063aaed083b146103a9578063b10dcc93146103b257600080fd5b8063367c164e116101b35780635923489b116101825780635923489b146103245780636e2751211461034f578063706ce3e114610362578063715018a614610375578063760a2e8a1461037d57600080fd5b8063367c164e146102bd57806338ff8a85146102d05780633a17f4f0146102f1578063426233601461030457600080fd5b8063206635e7116101fa578063206635e71461026d5780632afe761a146102805780632bd30f1114610289578063305f839a146102ab57806333ddacd1146102b457600080fd5b8062944f621461022b5780630d00368b146102405780630e8feed41461025c578063120957fd14610264575b600080fd5b61023e610239366004612aa4565b6104bc565b005b61024960735481565b6040519081526020015b60405180910390f35b61023e61053a565b610249606d5481565b61023e61027b366004612b2c565b61057e565b610249606f5481565b60785461029b90610100900460ff1681565b6040519015158152602001610253565b61024960715481565b61024960765481565b61023e6102cb366004612bc2565b6105d1565b6102e36102de366004612aa4565b610829565b604051610253929190612c16565b61023e6102ff366004612aa4565b6109e1565b610317610312366004612aa4565b610a56565b6040516102539190612c2f565b606a54610337906001600160a01b031681565b6040516001600160a01b039091168152602001610253565b6102e361035d366004612aa4565b610b4c565b606b54610337906001600160a01b031681565b61023e610cf8565b61029b61038b366004612aa4565b610d2e565b61023e610dc2565b6033546001600160a01b0316610337565b61024960705481565b61023e6103c0366004612b2c565b610fc0565b610249606e5481565b61023e611236565b61023e6112bb565b61024960725481565b6102e36103f5366004612aa4565b6112ef565b61023e610408366004612aa4565b61149b565b610249606c5481565b610249611510565b606f54610249565b610439610434366004612aa4565b611594565b6040805192151583526001600160a01b03909116602083015201610253565b606954610337906001600160a01b031681565b61023e610479366004612aa4565b6115cf565b61023e611667565b61023e6116a0565b6104a161049c366004612aa4565b6116e1565b60408051938452602084019290925290820152606001610253565b6033546001600160a01b031633146104ef5760405162461bcd60e51b81526004016104e690612c42565b60405180910390fd5b606b546001600160a01b03163b6105185760405162461bcd60e51b81526004016104e690612c77565b606b80546001600160a01b0319166001600160a01b0392909216919091179055565b600061054533611594565b509050806105655760405162461bcd60e51b81526004016104e690612cae565b61056e33610d2e565b61057b5761057b33611c73565b50565b610587336116e1565b505060765560005b81518110156105cd576105bb8282815181106105ad576105ad612cda565b602002602001015133611d7b565b806105c581612d06565b91505061058f565b5050565b606a54604051636eb1769f60e11b815233600482015273871770e3e03bfaefa3597056e540a1a9c9ac7f6b602482015282916001600160a01b03169063dd62ed3e90604401602060405180830381865afa158015610633573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106579190612d21565b10156106bb5760405162461bcd60e51b815260206004820152602d60248201527f596f75206861766520746f20617070726f766520726f6f747820746f2073746160448201526c1ada5b99c818dbdb9d1c9858dd609a1b60648201526084016104e6565b606a546040516323b872dd60e01b815233600482015273871770e3e03bfaefa3597056e540a1a9c9ac7f6b6024820152604481018390526001600160a01b03909116906323b872dd906064016020604051808303816000875af1158015610726573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061074a9190612d3a565b50606a546040516326c7e79d60e21b8152600481018390526001600160a01b0390911690639b1f9e7490602401600060405180830381600087803b15801561079157600080fd5b505af11580156107a5573d6000803e3d6000fd5b5050606b546001600160a01b031691506379c650689050336107c88460056120a1565b6040516001600160e01b031960e085901b1681526001600160a01b0390921660048301526024820152604401600060405180830381600087803b15801561080e57600080fd5b505af1158015610822573d6000803e3d6000fd5b5050505050565b600060606000805b6001600160a01b0385166000908152607460205260409020548110156108bc576001600160a01b0385166000908152607460205260409020805461089791908390811061088057610880612cda565b906000526020600020906005020160000154612129565b156108aa576108a7600183612d5c565b91505b806108b481612d06565b915050610831565b5060008167ffffffffffffffff8111156108d8576108d8612ac1565b604051908082528060200260200182016040528015610901578160200160208202803683370190505b5090506000805b6001600160a01b0387166000908152607460205260409020548110156109d5576001600160a01b0387166000908152607460205260409020805461095791908390811061088057610880612cda565b156109c3576001600160a01b038716600090815260746020526040902080548290811061098657610986612cda565b9060005260206000209060050201600001548383815181106109aa576109aa612cda565b60209081029190910101526109c0826001612154565b91505b806109cd81612d06565b915050610908565b50919590945092505050565b6033546001600160a01b03163314610a0b5760405162461bcd60e51b81526004016104e690612c42565b6069546001600160a01b03163b610a345760405162461bcd60e51b81526004016104e690612c77565b606980546001600160a01b0319166001600160a01b0392909216919091179055565b6001600160a01b0381166000908152607460205260408120546060919067ffffffffffffffff811115610a8b57610a8b612ac1565b604051908082528060200260200182016040528015610ab4578160200160208202803683370190505b50905060005b6001600160a01b038416600090815260746020526040902054811015610b45576001600160a01b0384166000908152607460205260409020805482908110610b0457610b04612cda565b906000526020600020906005020160000154828281518110610b2857610b28612cda565b602090810291909101015280610b3d81612d06565b915050610aba565b5092915050565b600060606000805b6001600160a01b038516600090815260746020526040902054811015610bdf576001600160a01b03851660009081526074602052604090208054610bba919083908110610ba357610ba3612cda565b9060005260206000209060050201600001546121b3565b15610bcd57610bca826001612154565b91505b80610bd781612d06565b915050610b54565b5060008167ffffffffffffffff811115610bfb57610bfb612ac1565b604051908082528060200260200182016040528015610c24578160200160208202803683370190505b5090506000805b6001600160a01b0387166000908152607460205260409020548110156109d5576001600160a01b03871660009081526074602052604090208054610c7a919083908110610ba357610ba3612cda565b15610ce6576001600160a01b0387166000908152607460205260409020805482908110610ca957610ca9612cda565b906000526020600020906005020160000154838381518110610ccd57610ccd612cda565b6020908102919091010152610ce3826001612154565b91505b80610cf081612d06565b915050610c2b565b6033546001600160a01b03163314610d225760405162461bcd60e51b81526004016104e690612c42565b610d2c60006121d0565b565b60006001815b6001600160a01b038416600090815260746020526040902054811015610b45576001600160a01b03841660009081526074602052604081208054610d9a919084908110610d8357610d83612cda565b906000526020600020906005020160010154612222565b9050603c8111610daa5750610db0565b60009250505b80610dba81612d06565b915050610d34565b600054610100900460ff16610ddd5760005460ff1615610de1565b303b155b610e445760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b60648201526084016104e6565b600054610100900460ff16158015610e66576000805461ffff19166101011790555b610e6e61223c565b606580546001600160a01b0319908116737a250d5630b4cf539739df2c5dacb4c659f2488d1790915560668054821673c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2179055620151806067556312cc030060685560698054821673e22e1e620dffb03065cd77db0162249c0c91bf01179055606a8054821673d718ad25285d65ef4d79262a6cd3aea6a8e01023179055606b80549091167399cfdf48d0ba4885a73786148a2f89d86c7021701790556000606c5568056bc75e2d63100000606d556802b5e3af16b1880000606e55690257058e269742680000606f819055681b1ae4d6e2ef5000006070819055610bb8607181905591610f709190612d74565b610f7a9190612d8b565b607255607154606e54606d54610f909190612d74565b610f9a9190612d8b565b60735560006076556078805460ff19169055801561057b576000805461ff001916905550565b6000610fcb33611594565b50905080610feb5760405162461bcd60e51b81526004016104e690612cae565b607854610100900460ff161561102c5760405162461bcd60e51b8152602060048201526006602482015265131bd8dad95960d21b60448201526064016104e6565b600061103733610a56565b90508051835111156110775760405162461bcd60e51b81526020600482015260096024820152684964206572726f727360b81b60448201526064016104e6565b6000805b84518110156110fd5760005b83518110156110ea578381815181106110a2576110a2612cda565b60200260200101518683815181106110bc576110bc612cda565b602002602001015114156110d8576110d5836001612154565b92505b806110e281612d06565b915050611087565b50806110f581612d06565b91505061107b565b50835181141561123057835161112761111e82678ac7230489e800006120a1565b606f5490612154565b606f55611132612273565b600060768190555b855181101561122d5760695486516001600160a01b03909116906323b872dd90309033908a908690811061117057611170612cda565b60209081029190910101516040516001600160e01b031960e086901b1681526001600160a01b0393841660048201529290911660248301526044820152606401600060405180830381600087803b1580156111ca57600080fd5b505af11580156111de573d6000803e3d6000fd5b5050606c80549250905060006111f383612dad565b919050555061121b3387838151811061120e5761120e612cda565b602002602001015161229b565b8061122581612d06565b91505061113a565b50505b50505050565b60785460ff166112ac5760005b60755481101561057b576001607760006075848154811061126657611266612cda565b6000918252602080832091909101546001600160a01b031683528201929092526040019020805460ff1916911515919091179055806112a481612d06565b915050611243565b6078805460ff19166001179055565b60006112c633611594565b509050806112e65760405162461bcd60e51b81526004016104e690612cae565b61057b33612470565b600060606000805b6001600160a01b038516600090815260746020526040902054811015611382576001600160a01b0385166000908152607460205260409020805461135d91908390811061134657611346612cda565b906000526020600020906005020160000154612574565b156113705761136d600183612d5c565b91505b8061137a81612d06565b9150506112f7565b5060008167ffffffffffffffff81111561139e5761139e612ac1565b6040519080825280602002602001820160405280156113c7578160200160208202803683370190505b5090506000805b6001600160a01b0387166000908152607460205260409020548110156109d5576001600160a01b0387166000908152607460205260409020805461141d91908390811061134657611346612cda565b15611489576001600160a01b038716600090815260746020526040902080548290811061144c5761144c612cda565b90600052602060002090600502016000015483838151811061147057611470612cda565b6020908102919091010152611486826001612154565b91505b8061149381612d06565b9150506113ce565b6033546001600160a01b031633146114c55760405162461bcd60e51b81526004016104e690612c42565b606a546001600160a01b03163b6114ee5760405162461bcd60e51b81526004016104e690612c77565b606a80546001600160a01b0319166001600160a01b0392909216919091179055565b6000806064606f5460016115249190612dc4565b61152e9190612d8b565b611539906001612d5c565b606d546115469190612dc4565b90506000606d54826115589190612d74565b905060006001606d548361156c9190612d8b565b6115769190612d8b565b611581906001612dc4565b61158c906064612dc4565b949350505050565b6001600160a01b038116600090815260776020526040812054819060ff161515600114156115c457506001929050565b506000928392509050565b6033546001600160a01b031633146115f95760405162461bcd60e51b81526004016104e690612c42565b6001600160a01b03811661165e5760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b60648201526084016104e6565b61057b816121d0565b73d0d725208fd36be1561050fc1dd6a651d7ea7c89331415610d2c576078805461ff001981166101009182900460ff1615909102179055565b60006116ab33611594565b509050806116cb5760405162461bcd60e51b81526004016104e690612cae565b6116d433610d2e565b61057b5761057b336125aa565b600080808080808087816116f482610829565b509050600061170283610b4c565b50905060058110611a3b57600160005b6001600160a01b038516600090815260746020526040902054811015611a0e576001600160a01b0385166000908152607460205260408120805461177891908490811061176157611761612cda565b906000526020600020906005020160040154612222565b6001600160a01b038716600090815260746020526040902080549192506117a99184908110610ba357610ba3612cda565b806117de57506001600160a01b038616600090815260746020526040902080546117de91908490811061088057610880612cda565b80156117ea5750600181105b156117f457600092505b82801561181857506001600160a01b03861660009081526074602052604090205415155b156118715761186a8561182c83600a612dc4565b6118369190612dc4565b6118648661184585600a612dc4565b61184f9190612dc4565b60765461186490670de0b6b3a7640000612798565b90612154565b995061188e565b8261188e5760765461188b90670de0b6b3a7640000612798565b99505b6001600160a01b038616600090815260746020526040902080546118bd91908490811061088057610880612cda565b15611950576001600160a01b038616600090815260746020526040812080546119089190859081106118f1576118f1612cda565b906000526020600020906005020160020154612222565b90508061192861192182680ad78ebc5ac6200000612dc4565b8c90612154565b9a5061194761194082680ad78ebc5ac6200000612dc4565b8b90612154565b995050506119fb565b6001600160a01b0386166000908152607460205260409020805461197f919084908110610ba357610ba3612cda565b156119fb576001600160a01b038616600090815260746020526040812080546119b39190859081106118f1576118f1612cda565b905060008190506119d76002606d54846119cd9190612dc4565b6119219190612d8b565b9a506119f66002606d54836119ec9190612dc4565b6119409190612d8b565b995050505b5080611a0681612d06565b915050611712565b508515611a3557606b54606654611a32916001600160a01b039081169116886127da565b94505b50611c4f565b60005b6001600160a01b038416600090815260746020526040902054811015611c28576001600160a01b03841660009081526074602052604090208054611a8d91908390811061088057610880612cda565b15611b9f576001600160a01b03841660009081526074602052604081208054611ac191908490811061176157611761612cda565b9050611ad8611ad182600a612dc4565b8a90612154565b98506000611b1560746000886001600160a01b03166001600160a01b0316815260200190815260200160002084815481106118f1576118f1612cda565b9050611b2d611ad182680ad78ebc5ac6200000612dc4565b98506000611b8160746000896001600160a01b03166001600160a01b031681526020019081526020016000208581548110611b6a57611b6a612cda565b906000526020600020906005020160030154612222565b9050611b99611ad182680ad78ebc5ac6200000612dc4565b98505050505b6001600160a01b03841660009081526074602052604090208054611bce919083908110610ba357610ba3612cda565b15611c16576001600160a01b03841660009081526074602052604081208054611c0291908490811061176157611761612cda565b9050611c12611ad182600a612dc4565b9850505b80611c2081612d06565b915050611a3e565b508415611c4f57606b54606654611c4c916001600160a01b039081169116876127da565b93505b611c6187670de0b6b3a7640000612dc4565b9b959a50929850939650505050505050565b6000611c7e826116e1565b5091505080156105cd57606b5460405163a9059cbb60e01b81526001600160a01b038481166004830152602482018490529091169063a9059cbb906044016020604051808303816000875af1158015611cdb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611cff9190612d3a565b5060005b6001600160a01b038316600090815260746020526040902054811015611d76576001600160a01b0383166000908152607460205260409020805442919083908110611d5057611d50612cda565b600091825260209091206002600590920201015580611d6e81612d06565b915050611d03565b505050565b607054606f5410611db857607354606d6000828254611d9a9190612d74565b9091555050606f54611db490678ac7230489e80000612905565b606f555b6069546040516331a9108f60e11b8152600481018490526001600160a01b03838116921690636352211e90602401602060405180830381865afa158015611e03573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611e279190612de3565b6001600160a01b031614611e7d5760405162461bcd60e51b815260206004820152601e60248201527f596f7520617265206e6f742061206f776e6572206f6620746865206e6674000060448201526064016104e6565b60695460405163e985e9c560e01b81523360048201523060248201526001600160a01b039091169063e985e9c590604401602060405180830381865afa158015611ecb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611eef9190612d3a565b1515600114611f575760405162461bcd60e51b815260206004820152602e60248201527f596f752073686f756c6420617070726f7665206e667420746f2074686520737460448201526d185ada5b99c818dbdb9d1c9858dd60921b60648201526084016104e6565b6069546040516323b872dd60e01b81526001600160a01b03838116600483015230602483015260448201859052909116906323b872dd90606401600060405180830381600087803b158015611fab57600080fd5b505af1158015611fbf573d6000803e3d6000fd5b505050506000611fce82611594565b50905060006040518060a001604052808581526020014281526020014281526020014281526020014281525090506120126001606c5461215490919063ffffffff16565b606c556001600160a01b03831660009081526074602090815260408083208054600181810183559185529383902085516005909502019384559184015191830191909155820151600282015560608201516003820155608082015160049091015581611230576001600160a01b0383166000908152607760205260409020805460ff1916600117905550505050565b6000826120b057506000612123565b60006120bc8385612dc4565b9050826120c98583612d8b565b146121205760405162461bcd60e51b815260206004820152602160248201527f536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f6044820152607760f81b60648201526084016104e6565b90505b92915050565b60008064e8d4a510008310158015612146575064e8d4a510058311155b156121235750600192915050565b6000806121618385612d5c565b9050838110156121205760405162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f77000000000060448201526064016104e6565b600080610e7483116121c757506001612123565b50600092915050565b603380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b6067546000906122328342612d74565b6121239190612d8b565b600054610100900460ff166122635760405162461bcd60e51b81526004016104e690612e00565b61226b612947565b610d2c61296e565b61227c33612470565b61228533610d2e565b610d2c5761229233611c73565b610d2c336125aa565b60005b6001600160a01b038316600090815260746020526040902054811015612428576001600160a01b03831660009081526074602052604090208054839190839081106122eb576122eb612cda565b9060005260206000209060050201600001541415612416576001600160a01b0383166000908152607460205260409020805461232990600190612d74565b8154811061233957612339612cda565b906000526020600020906005020160746000856001600160a01b03166001600160a01b03168152602001908152602001600020828154811061237d5761237d612cda565b60009182526020808320845460059093020191825560018085015490830155600280850154908301556003808501549083015560049384015493909101929092556001600160a01b03851681526074909152604090208054806123e2576123e2612e4b565b6000828152602081206005600019909301928302018181556001810182905560028101829055600381018290556004015590555b8061242081612d06565b91505061229e565b506001600160a01b0382166000908152607460205260409020546105cd576001600160a01b038216600090815260746020526040812061246791612a3f565b6105cd8261299e565b600061247b826116e1565b509091505080156105cd57606a5460405163a9059cbb60e01b81526001600160a01b038481166004830152602482018490529091169063a9059cbb906044016020604051808303816000875af11580156124d9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124fd9190612d3a565b5060005b6001600160a01b038316600090815260746020526040902054811015611d76576001600160a01b038316600090815260746020526040902080544291908390811061254e5761254e612cda565b60009182526020909120600460059092020101558061256c81612d06565b915050612501565b60006509184e72a00682101561258c57506000919050565b6509184e72b4b38211156125a257506000919050565b506001919050565b60006125b5826116e1565b5091505080156105cd57606b5460655460405163095ea7b360e01b81526001600160a01b0391821660048201526024810184905291169063095ea7b3906044016020604051808303816000875af1158015612614573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906126389190612d3a565b5060408051600280825260608083018452926020830190803683375050606b5482519293506001600160a01b03169183915060009061267957612679612cda565b6001600160a01b0392831660209182029290920101526066548251911690829060019081106126aa576126aa612cda565b6001600160a01b03928316602091820292909201015260655460405163791ac94760e01b815291169063791ac947906126f0908590600090869089904290600401612e9a565b600060405180830381600087803b15801561270a57600080fd5b505af115801561271e573d6000803e3d6000fd5b5050505060005b6001600160a01b038416600090815260746020526040902054811015611230576001600160a01b038416600090815260746020526040902080544291908390811061277257612772612cda565b60009182526020909120600360059092020101558061279081612d06565b915050612725565b600061212083836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f0000000000008152506129d7565b6040805160028082526060808301845260009390929190602083019080368337019050509050848160008151811061281457612814612cda565b60200260200101906001600160a01b031690816001600160a01b031681525050838160018151811061284857612848612cda565b6001600160a01b03928316602091820292909201015260655460405163d06ca61f60e01b8152600092919091169063d06ca61f9061288c9087908690600401612ed6565b600060405180830381865afa1580156128a9573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526128d19190810190612eef565b905080600183516128e29190612d74565b815181106128f2576128f2612cda565b6020026020010151925050509392505050565b600061212083836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250612a0e565b600054610100900460ff16610d2c5760405162461bcd60e51b81526004016104e690612e00565b600054610100900460ff166129955760405162461bcd60e51b81526004016104e690612e00565b610d2c336121d0565b6000806129aa83611594565b915091508115611d76576001600160a01b03166000908152607760205260409020805460ff191690555050565b600081836129f85760405162461bcd60e51b81526004016104e69190612f75565b506000612a058486612d8b565b95945050505050565b60008184841115612a325760405162461bcd60e51b81526004016104e69190612f75565b506000612a058486612d74565b508054600082556005029060005260206000209081019061057b91905b80821115612a8b5760008082556001820181905560028201819055600382018190556004820155600501612a5c565b5090565b6001600160a01b038116811461057b57600080fd5b600060208284031215612ab657600080fd5b813561212081612a8f565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff81118282101715612b0057612b00612ac1565b604052919050565b600067ffffffffffffffff821115612b2257612b22612ac1565b5060051b60200190565b60006020808385031215612b3f57600080fd5b823567ffffffffffffffff811115612b5657600080fd5b8301601f81018513612b6757600080fd5b8035612b7a612b7582612b08565b612ad7565b81815260059190911b82018301908381019087831115612b9957600080fd5b928401925b82841015612bb757833582529284019290840190612b9e565b979650505050505050565b600060208284031215612bd457600080fd5b5035919050565b600081518084526020808501945080840160005b83811015612c0b57815187529582019590820190600101612bef565b509495945050505050565b82815260406020820152600061158c6040830184612bdb565b6020815260006121206020830184612bdb565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b60208082526017908201527f41646472657373206973206e6f7420636f6e7472616374000000000000000000604082015260600190565b6020808252601290820152712cb7ba9030b932903737ba1039ba30b5b2b960711b604082015260600190565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b6000600019821415612d1a57612d1a612cf0565b5060010190565b600060208284031215612d3357600080fd5b5051919050565b600060208284031215612d4c57600080fd5b8151801515811461212057600080fd5b60008219821115612d6f57612d6f612cf0565b500190565b600082821015612d8657612d86612cf0565b500390565b600082612da857634e487b7160e01b600052601260045260246000fd5b500490565b600081612dbc57612dbc612cf0565b506000190190565b6000816000190483118215151615612dde57612dde612cf0565b500290565b600060208284031215612df557600080fd5b815161212081612a8f565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b634e487b7160e01b600052603160045260246000fd5b600081518084526020808501945080840160005b83811015612c0b5781516001600160a01b031687529582019590820190600101612e75565b85815284602082015260a060408201526000612eb960a0830186612e61565b6001600160a01b0394909416606083015250608001529392505050565b82815260406020820152600061158c6040830184612e61565b60006020808385031215612f0257600080fd5b825167ffffffffffffffff811115612f1957600080fd5b8301601f81018513612f2a57600080fd5b8051612f38612b7582612b08565b81815260059190911b82018301908381019087831115612f5757600080fd5b928401925b82841015612bb757835182529284019290840190612f5c565b600060208083528351808285015260005b81811015612fa257858101830151858201604001528201612f86565b81811115612fb4576000604083870101525b50601f01601f191692909201604001939250505056fe"), + ("0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545", "GovernorCharlieDelegate", "608060405234801561001057600080fd5b50613e45806100206000396000f3fe60806040526004361061031a5760003560e01c80637b3c71d3116101ab578063d50572ee116100f7578063f0843ba811610095578063fc176c041161006f578063fc176c0414610b82578063fc4eee4214610ba2578063fc66ff1414610bb8578063fe0d94c114610bd857600080fd5b8063f0843ba814610b12578063f2b0653714610b32578063f682e04c14610b6257600080fd5b8063de7bc127116100d1578063de7bc127146109ec578063deaaa7cc14610a02578063e23a9a5214610a36578063e837159c14610afc57600080fd5b8063d50572ee146109a0578063da35c664146109b6578063ddf0b009146109cc57600080fd5b8063a6d8784a11610164578063c1a287e21161013e578063c1a287e214610933578063c4d66de81461094a578063c5a8425d1461096a578063c9fb9e871461098a57600080fd5b8063a6d8784a146108e7578063abaac6a8146108fd578063b58131b01461091d57600080fd5b80637b3c71d31461083c5780637bdbe4d01461085c5780637cae57bb14610871578063806bd5811461088757806386d37e8b146108a757806399533365146108c757600080fd5b80632fedff591161026a5780633e4f49e61161022357806350442098116101fd578063504420981461074657806356781388146107665780635c60da1b1461078657806366176743146107be57600080fd5b80633e4f49e6146106d957806340e58ee5146107065780634d6733d21461072657600080fd5b80632fedff59146105ee578063328dd9821461060e57806338bd0dda1461063e5780633932abb11461066b5780633af32abf146106815780633bccf4fd146106b957600080fd5b8063158ef93e116102d757806318b62629116102b157806318b626291461056e5780631dfb1b5a1461058457806320606b70146105a457806324bc1a64146105d857600080fd5b8063158ef93e146104f757806317977c611461052157806317ba1b8b1461054e57600080fd5b8063013cf08b1461031f57806302a251a31461042857806306fdde031461044c5780630825f38f146104a25780630ea2d98c146104b7578063140499ea146104d7575b600080fd5b34801561032b57600080fd5b506103b361033a3660046132ee565b60096020819052600091825260409091208054600182015460028301546007840154600885015495850154600a860154600b870154600c880154600d890154600e9099015497996001600160a01b0390971698959794969593949293919260ff808316936101008404821693620100009004909116918d565b604080519d8e526001600160a01b03909c1660208e01529a8c019990995260608b019790975260808a019590955260a089019390935260c088019190915260e08701521515610100860152151561012085015215156101408401526101608301526101808201526101a0015b60405180910390f35b34801561043457600080fd5b5061043e60045481565b60405190815260200161041f565b34801561045857600080fd5b506104956040518060400160405280601a81526020017f496e7465726573742050726f746f636f6c20476f7665726e6f7200000000000081525081565b60405161041f9190613363565b6104b56104b0366004613450565b610beb565b005b3480156104c357600080fd5b506104b56104d23660046132ee565b610e61565b3480156104e357600080fd5b506104b56104f23660046134d6565b610ec6565b34801561050357600080fd5b506012546105119060ff1681565b604051901515815260200161041f565b34801561052d57600080fd5b5061043e61053c3660046134d6565b600a6020526000908152604090205481565b34801561055a57600080fd5b506104b56105693660046132ee565b610f07565b34801561057a57600080fd5b5061043e600f5481565b34801561059057600080fd5b506104b561059f3660046132ee565b610f64565b3480156105b057600080fd5b5061043e7f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a86681565b3480156105e457600080fd5b5061043e60015481565b3480156105fa57600080fd5b506104b56106093660046132ee565b610fc1565b34801561061a57600080fd5b5061062e6106293660046132ee565b61101e565b60405161041f94939291906135ba565b34801561064a57600080fd5b5061043e6106593660046134d6565b600d6020526000908152604090205481565b34801561067757600080fd5b5061043e60035481565b34801561068d57600080fd5b5061051161069c3660046134d6565b6001600160a01b03166000908152600d6020526040902054421090565b3480156106c557600080fd5b506104b56106d4366004613623565b6112af565b3480156106e557600080fd5b506106f96106f43660046132ee565b611516565b60405161041f9190613687565b34801561071257600080fd5b506104b56107213660046132ee565b61169e565b34801561073257600080fd5b506104b56107413660046136af565b611b80565b34801561075257600080fd5b506104b56107613660046132ee565b611c45565b34801561077257600080fd5b506104b56107813660046136d9565b611ca2565b34801561079257600080fd5b506000546107a6906001600160a01b031681565b6040516001600160a01b03909116815260200161041f565b3480156107ca57600080fd5b506108146107d9366004613705565b601160209081526000928352604080842090915290825290205460ff808216916101008104909116906201000090046001600160601b031683565b60408051931515845260ff90921660208401526001600160601b03169082015260600161041f565b34801561084857600080fd5b506104b5610857366004613728565b611d09565b34801561086857600080fd5b5061043e600a81565b34801561087d57600080fd5b5061043e600c5481565b34801561089357600080fd5b506104b56108a23660046132ee565b611d58565b3480156108b357600080fd5b506104b56108c23660046132ee565b611db5565b3480156108d357600080fd5b506104b56108e23660046134d6565b611e12565b3480156108f357600080fd5b5061043e60155481565b34801561090957600080fd5b506104b56109183660046132ee565b611e8b565b34801561092957600080fd5b5061043e60055481565b34801561093f57600080fd5b5061043e6212750081565b34801561095657600080fd5b506104b56109653660046134d6565b611ee8565b34801561097657600080fd5b50600e546107a6906001600160a01b031681565b34801561099657600080fd5b5061043e60135481565b3480156109ac57600080fd5b5061043e60025481565b3480156109c257600080fd5b5061043e60075481565b3480156109d857600080fd5b506104b56109e73660046132ee565b611fd9565b3480156109f857600080fd5b5061043e60105481565b348015610a0e57600080fd5b5061043e7f150214d74d59b7d1e90c73fc22ef3d991dd0a76b046543d4d80ab92d2a50328f81565b348015610a4257600080fd5b50610acc610a51366004613705565b60408051606081018252600080825260208201819052918101919091525060009182526011602090815260408084206001600160a01b03939093168452918152918190208151606081018352905460ff8082161515835261010082041693820193909352620100009092046001600160601b03169082015290565b6040805182511515815260208084015160ff1690820152918101516001600160601b03169082015260600161041f565b348015610b0857600080fd5b5061043e60145481565b348015610b1e57600080fd5b506104b5610b2d3660046132ee565b61238f565b348015610b3e57600080fd5b50610511610b4d3660046132ee565b600b6020526000908152604090205460ff1681565b348015610b6e57600080fd5b5061043e610b7d3660046139b0565b6123ec565b348015610b8e57600080fd5b506104b5610b9d3660046132ee565b612a48565b348015610bae57600080fd5b5061043e60065481565b348015610bc457600080fd5b506008546107a6906001600160a01b031681565b6104b5610be63660046132ee565b612aa5565b60008585858585604051602001610c06959493929190613a91565b60408051601f1981840301815291815281516020928301206000818152600b90935291205490915060ff16610c7b5760405162461bcd60e51b81526020600482015260166024820152753a3c103430b9b713ba103132b2b71038bab2bab2b21760511b60448201526064015b60405180910390fd5b81421015610ccb5760405162461bcd60e51b815260206004820152601d60248201527f7478206861736e2774207375727061737365642074696d656c6f636b2e0000006044820152606401610c72565b610cd86212750083613af3565b421115610d165760405162461bcd60e51b815260206004820152600c60248201526b3a3c1034b99039ba30b6329760a11b6044820152606401610c72565b6000818152600b60205260409020805460ff191690558351606090610d3c575082610d68565b848051906020012084604051602001610d56929190613b0b565b60405160208183030381529060405290505b6000876001600160a01b03168783604051610d839190613b3c565b60006040518083038185875af1925050503d8060008114610dc0576040519150601f19603f3d011682016040523d82523d6000602084013e610dc5565b606091505b5050905080610e0f5760405162461bcd60e51b81526020600482015260166024820152753a3c1032bc32b1baba34b7b7103932bb32b93a32b21760511b6044820152606401610c72565b876001600160a01b0316837fa560e3198060a2f10670c1ec5b403077ea6ae93ca8de1c32b451dc1a943cd6e789898989604051610e4f9493929190613b58565b60405180910390a35050505050505050565b333014610e805760405162461bcd60e51b8152600401610c7290613b95565b600480549082905560408051828152602081018490527f7e3f7f0708a84de9203036abaa450dccc85ad5ff52f78c170f3edb55cf5e882891015b60405180910390a15050565b333014610ee55760405162461bcd60e51b8152600401610c7290613b95565b600880546001600160a01b0319166001600160a01b0392909216919091179055565b333014610f265760405162461bcd60e51b8152600401610c7290613b95565b600580549082905560408051828152602081018490527fccb45da8d5717e6c4544694297c4ba5cf151d455c9bb0ed4fc7a38411bc054619101610eba565b333014610f835760405162461bcd60e51b8152600401610c7290613b95565b600380549082905560408051828152602081018490527fc565b045403dc03c2eea82b81a0465edad9e2e7fc4d97e11421c209da93d7a939101610eba565b333014610fe05760405162461bcd60e51b8152600401610c7290613b95565b601480549082905560408051828152602081018490527f519a192fe8db9e38785eb494c69f530ddb21b9e34322f8d08fe29bd3849749889101610eba565b606080606080600060096000878152602001908152602001600020905080600301816004018260050183600601838054806020026020016040519081016040528092919081815260200182805480156110a057602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311611082575b50505050509350828054806020026020016040519081016040528092919081815260200182805480156110f257602002820191906000526020600020905b8154815260200190600101908083116110de575b5050505050925081805480602002602001604051908101604052809291908181526020016000905b828210156111c657838290600052602060002001805461113990613bcc565b80601f016020809104026020016040519081016040528092919081815260200182805461116590613bcc565b80156111b25780601f10611187576101008083540402835291602001916111b2565b820191906000526020600020905b81548152906001019060200180831161119557829003601f168201915b50505050508152602001906001019061111a565b50505050915080805480602002602001604051908101604052809291908181526020016000905b8282101561129957838290600052602060002001805461120c90613bcc565b80601f016020809104026020016040519081016040528092919081815260200182805461123890613bcc565b80156112855780601f1061125a57610100808354040283529160200191611285565b820191906000526020600020905b81548152906001019060200180831161126857829003601f168201915b5050505050815260200190600101906111ed565b5050505090509450945094509450509193509193565b604080518082018252601a81527f496e7465726573742050726f746f636f6c20476f7665726e6f7200000000000060209182015281517f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a866818301527f75a838dcd8ee5903cc7f4a5799344d0080864f57a6e9911f8bdfb4c8ddce9b5481840152466060820152306080808301919091528351808303909101815260a0820184528051908301207f150214d74d59b7d1e90c73fc22ef3d991dd0a76b046543d4d80ab92d2a50328f60c083015260e0820189905260ff8816610100808401919091528451808403909101815261012083019094528351939092019290922061190160f01b6101408401526101428301829052610162830181905290916000906101820160408051601f198184030181528282528051602091820120600080855291840180845281905260ff8a169284019290925260608301889052608083018790529092509060019060a0016020604051602081039080840390855afa15801561143c573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b03811661149f5760405162461bcd60e51b815260206004820181905260248201527f63617374566f746542795369673a20696e76616c6964207369676e61747572656044820152606401610c72565b88816001600160a01b03167fb8e138887d0aa13bab447e82de9d5c1777041ecd21ca36ba824ff1e6c07ddda48a6114d7858e8e612c90565b6040805160ff90931683526001600160601b039091166020830152606090820181905260009082015260800160405180910390a3505050505050505050565b6000816007541015801561152b575060065482115b6115775760405162461bcd60e51b815260206004820152601a60248201527f73746174653a20696e76616c69642070726f706f73616c2069640000000000006044820152606401610c72565b600082815260096020908152604080832060018101546001600160a01b03168452600d90925290912054600c82015442919091109060ff16156115be575060029392505050565b816007015443116115d3575060009392505050565b816008015443116115e8575060019392505050565b8080156115fc575081600d015482600a0154115b80611618575080158015611618575081600a0154826009015411155b80611633575080158015611633575081600d01548260090154105b15611642575060039392505050565b6002820154611655575060049392505050565b600c820154610100900460ff1615611671575060079392505050565b6212750082600201546116849190613af3565b4210611694575060069392505050565b5060059392505050565b60076116a982611516565b60078111156116ba576116ba613671565b14156117085760405162461bcd60e51b815260206004820152601d60248201527f63616e742063616e63656c2065786563757465642070726f706f73616c0000006044820152606401610c72565b600081815260096020526040902060018101546001600160a01b0316336001600160a01b0316146119755760018101546001600160a01b03166000908152600d6020526040902054421015611878576005546008546001838101546001600160a01b039283169263782d6fe1929116906117829043613c07565b6040516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160206040518083038186803b1580156117c657600080fd5b505afa1580156117da573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117fe9190613c1e565b6001600160601b03161080156118275750600e546001600160a01b0316336001600160a01b0316145b6118735760405162461bcd60e51b815260206004820152601c60248201527f63616e63656c3a2077686974656c69737465642070726f706f736572000000006044820152606401610c72565b611975565b6005546008546001838101546001600160a01b039283169263782d6fe1929116906118a39043613c07565b6040516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160206040518083038186803b1580156118e757600080fd5b505afa1580156118fb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061191f9190613c1e565b6001600160601b0316106119755760405162461bcd60e51b815260206004820181905260248201527f63616e63656c3a2070726f706f7365722061626f7665207468726573686f6c646044820152606401610c72565b600c8101805460ff1916600117905560005b6003820154811015611b5057611b3e8260030182815481106119ab576119ab613c47565b6000918252602090912001546004840180546001600160a01b0390921691849081106119d9576119d9613c47565b90600052602060002001548460050184815481106119f9576119f9613c47565b906000526020600020018054611a0e90613bcc565b80601f0160208091040260200160405190810160405280929190818152602001828054611a3a90613bcc565b8015611a875780601f10611a5c57610100808354040283529160200191611a87565b820191906000526020600020905b815481529060010190602001808311611a6a57829003601f168201915b5050505050856006018581548110611aa157611aa1613c47565b906000526020600020018054611ab690613bcc565b80601f0160208091040260200160405190810160405280929190818152602001828054611ae290613bcc565b8015611b2f5780601f10611b0457610100808354040283529160200191611b2f565b820191906000526020600020905b815481529060010190602001808311611b1257829003601f168201915b50505050508660020154612f12565b80611b4881613c5d565b915050611987565b5060405182907f789cf55be980739dad1d0699b93b58e806b51c9d96619bfa8fe0a28abaa7b30c90600090a25050565b333014611b9f5760405162461bcd60e51b8152600401610c7290613b95565b42601554611bad9190613af3565b8110611bf45760405162461bcd60e51b81526020600482015260166024820152750caf0e0d2e4c2e8d2dedc40caf0c6cacac8e640dac2f60531b6044820152606401610c72565b6001600160a01b0382166000818152600d6020908152604091829020849055815192835282018390527f4e7b7545bc5744d0e30425959f4687475774b6c7edad77d24cb51c7d967d45159101610eba565b333014611c645760405162461bcd60e51b8152600401610c7290613b95565b601080549082905560408051828152602081018490527f2a61b867418a359864adca8bb250ea65ee8bd41dbfd0279198d8e7552d4a27c29101610eba565b81337fb8e138887d0aa13bab447e82de9d5c1777041ecd21ca36ba824ff1e6c07ddda483611cd1838583612c90565b6040805160ff90931683526001600160601b039091166020830152606090820181905260009082015260800160405180910390a35050565b83337fb8e138887d0aa13bab447e82de9d5c1777041ecd21ca36ba824ff1e6c07ddda485611d38838583612c90565b8686604051611d4a9493929190613c78565b60405180910390a350505050565b333014611d775760405162461bcd60e51b8152600401610c7290613b95565b601380549082905560408051828152602081018490527f8cb5451eee8feb516cec9cd600201bbc31a30886d70c841a085a3fa69a4294d19101610eba565b333014611dd45760405162461bcd60e51b8152600401610c7290613b95565b600180549082905560408051828152602081018490527fa74554b0f53da47d07ec571d712428b3720460f54f81375fbcf78f6b5f72e7ed9101610eba565b333014611e315760405162461bcd60e51b8152600401610c7290613b95565b600e80546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527f80a07e73e552148844a9c216d9724212d609cfa54e9c1a2e97203bdd2c4ad3419101610eba565b333014611eaa5760405162461bcd60e51b8152600401610c7290613b95565b600f80549082905560408051828152602081018490527f80a384652af83fc00bfd40ef94edda7ede83e7db39931b2c889821573f314e239101610eba565b60125460ff1615611f3b5760405162461bcd60e51b815260206004820152601860248201527f616c7265616479206265656e20696e697469616c697a656400000000000000006044820152606401610c72565b600880546001600160a01b0319166001600160a01b0392909216919091179055619d8060045561335460035569d3c21bcecceda10000006005556202a300600c5560006007556a084595161401484a00000060019081556a21165458500521280000006002556119aa600f5561a8c06010556a01a784379d99db420000006013556146506014556301e133806015556012805460ff19169091179055565b6004611fe482611516565b6007811115611ff557611ff5613671565b146120425760405162461bcd60e51b815260206004820152601f60248201527f63616e206f6e6c792062652071756575656420696620737563636565646564006044820152606401610c72565b6000818152600960205260408120600e8101549091906120629042613af3565b905060005b600383015481101561234d57600b600084600301838154811061208c5761208c613c47565b6000918252602090912001546004860180546001600160a01b0390921691859081106120ba576120ba613c47565b90600052602060002001548660050185815481106120da576120da613c47565b906000526020600020018760060186815481106120f9576120f9613c47565b9060005260206000200187604051602001612118959493929190613d62565b60408051601f198184030181529181528151602092830120835290820192909252016000205460ff161561218e5760405162461bcd60e51b815260206004820152601760248201527f70726f706f73616c20616c7265616479207175657565640000000000000000006044820152606401610c72565b61233a8360030182815481106121a6576121a6613c47565b6000918252602090912001546004850180546001600160a01b0390921691849081106121d4576121d4613c47565b90600052602060002001548560050184815481106121f4576121f4613c47565b90600052602060002001805461220990613bcc565b80601f016020809104026020016040519081016040528092919081815260200182805461223590613bcc565b80156122825780601f1061225757610100808354040283529160200191612282565b820191906000526020600020905b81548152906001019060200180831161226557829003601f168201915b505050505086600601858154811061229c5761229c613c47565b9060005260206000200180546122b190613bcc565b80601f01602080910402602001604051908101604052809291908181526020018280546122dd90613bcc565b801561232a5780601f106122ff5761010080835404028352916020019161232a565b820191906000526020600020905b81548152906001019060200180831161230d57829003601f168201915b50505050508688600e0154612fac565b508061234581613c5d565b915050612067565b506002820181905560405181815283907f9a2e42fd6722813d69113e7d0079d3d940171428df7373df9c7f7617cfda28929060200160405180910390a2505050565b3330146123ae5760405162461bcd60e51b8152600401610c7290613b95565b600280549082905560408051828152602081018490527fc2adf06da6765dba7faaccde4c0ce3f91c35dd3390e7f0b6bc2844202c9fa9529101610eba565b6000600154600014156124365760405162461bcd60e51b8152602060048201526012602482015271436861726c6965206e6f742061637469766560701b6044820152606401610c72565b6005546008546001600160a01b031663782d6fe133612456600143613c07565b6040516001600160e01b031960e085901b1681526001600160a01b039092166004830152602482015260440160206040518083038186803b15801561249a57600080fd5b505afa1580156124ae573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124d29190613c1e565b6001600160601b03161015806124ec57506124ec3361069c565b6125385760405162461bcd60e51b815260206004820152601e60248201527f766f7465732062656c6f772070726f706f73616c207468726573686f6c6400006044820152606401610c72565b8551875114801561254a575084518751145b8015612557575083518751145b6125a35760405162461bcd60e51b815260206004820152601a60248201527f696e666f726d6174696f6e206172697479206d69736d617463680000000000006044820152606401610c72565b86516125e85760405162461bcd60e51b81526020600482015260146024820152736d7573742070726f7669646520616374696f6e7360601b6044820152606401610c72565b600a8751111561262d5760405162461bcd60e51b815260206004820152601060248201526f746f6f206d616e7920616374696f6e7360801b6044820152606401610c72565b336000908152600a6020526040902054801561271657600061264e82611516565b9050600181600781111561266457612664613671565b14156126b25760405162461bcd60e51b815260206004820152601e60248201527f6f6e65206c6976652070726f706f73616c207065722070726f706f73657200006044820152606401610c72565b60008160078111156126c6576126c6613671565b14156127145760405162461bcd60e51b815260206004820152601e60248201527f6f6e65206c6976652070726f706f73616c207065722070726f706f73657200006044820152606401610c72565b505b6007805490600061272683613c5d565b9190505550600060405180610220016040528060075481526020016127483390565b6001600160a01b03168152602001600081526020018a8152602001898152602001888152602001878152602001600354436127839190613af3565b8152602001600454600354436127999190613af3565b6127a39190613af3565b815260200160008152602001600081526020016000815260200160001515815260200160001515815260200185151581526020016001548152602001600c5481525090508380156127fa57506127f83361069c565b155b1561282c574360e08201819052600f5461281391613af3565b6101008201526002546101e08201526010546102008201525b6128353361069c565b15612876576013546101e08201526014546128509043613af3565b60e08201526004546014546128659043613af3565b61286f9190613af3565b6101008201525b805160009081526009602090815260409182902083518155818401516001820180546001600160a01b0319166001600160a01b03909216919091179055918301516002830155606083015180518493926128d792600385019291019061309d565b50608082015180516128f3916004840191602090910190613102565b5060a0820151805161290f91600584019160209091019061313d565b5060c0820151805161292b916006840191602090910190613196565b5060e08281015160078301556101008084015160088401556101208401516009840155610140840151600a80850191909155610160850151600b850155610180850151600c850180546101a08801516101c089015161ffff1990921693151561ff0019169390931792151585029290921762ff0000191662010000921515929092029190911790556101e0850151600d85015561020090940151600e9093019290925583516020808601516001600160a01b0316600090815294905260409384902055830151835191840151925190923392917f7d84a6263ae0d98d3329bd7b46bb4e8d6f98cd35a7adb45c274c8b7fd5ebd5e091612a33918f918f918f918f918f90613d9b565b60405180910390a45198975050505050505050565b333014612a675760405162461bcd60e51b8152600401610c7290613b95565b600c80549082905560408051828152602081018490527fed0229422af39d4d7d33f7a27d31d6f5cb20ec628293da58dd6e8a528ed466be9101610eba565b6005612ab082611516565b6007811115612ac157612ac1613671565b14612b0e5760405162461bcd60e51b815260206004820152601c60248201527f63616e206f6e6c792062652065786563276420696620717565756564000000006044820152606401610c72565b6000818152600960205260408120600c8101805461ff001916610100179055905b6003820154811015612c6057306001600160a01b0316630825f38f836004018381548110612b5f57612b5f613c47565b9060005260206000200154846003018481548110612b7f57612b7f613c47565b6000918252602090912001546004860180546001600160a01b039092169186908110612bad57612bad613c47565b9060005260206000200154866005018681548110612bcd57612bcd613c47565b90600052602060002001876006018781548110612bec57612bec613c47565b9060005260206000200188600201546040518763ffffffff1660e01b8152600401612c1b959493929190613d62565b6000604051808303818588803b158015612c3457600080fd5b505af1158015612c48573d6000803e3d6000fd5b50505050508080612c5890613c5d565b915050612b2f565b5060405182907f712ae1383f79ac853f8d882153778e0260ef8f03b504e2866e0593e04d2b291f90600090a25050565b60006001612c9d84611516565b6007811115612cae57612cae613671565b14612cee5760405162461bcd60e51b815260206004820152601060248201526f1d9bdd1a5b99c81a5cc818db1bdcd95960821b6044820152606401610c72565b60028260ff161115612d365760405162461bcd60e51b8152602060048201526011602482015270696e76616c696420766f7465207479706560781b6044820152606401610c72565b6000838152600960209081526040808320601183528184206001600160a01b0389168552909252909120805460ff1615612da85760405162461bcd60e51b81526020600482015260136024820152721d9bdd195c88185b1c9958591e481d9bdd1959606a1b6044820152606401610c72565b600854600783015460405163782d6fe160e01b81526000926001600160a01b03169163782d6fe191612df2918b916004016001600160a01b03929092168252602082015260400190565b60206040518083038186803b158015612e0a57600080fd5b505afa158015612e1e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612e429190613c1e565b905060ff8516612e6f57806001600160601b031683600a0154612e659190613af3565b600a840155612ec9565b8460ff1660011415612e9e57806001600160601b03168360090154612e949190613af3565b6009840155612ec9565b8460ff1660021415612ec957806001600160601b031683600b0154612ec39190613af3565b600b8401555b81546001600160601b03821662010000026dffffffffffffffffffffffff00001960ff88166101000261ffff199093169290921760011791909116179091559150509392505050565b60008585858585604051602001612f2d959493929190613a91565b60408051601f1981840301815282825280516020918201206000818152600b909252919020805460ff1916905591506001600160a01b0387169082907f2fffc091a501fd91bfbff27141450d3acb40fb8e6d8382b243ec7a812a3aaf8790612f9c908990899089908990613b58565b60405180910390a3505050505050565b6000612fb88242613af3565b831015612ffd5760405162461bcd60e51b815260206004820152601360248201527236bab9ba1039b0ba34b9b33c903232b630bc9760691b6044820152606401610c72565b60008787878787604051602001613018959493929190613a91565b60408051601f1981840301815282825280516020918201206000818152600b909252919020805460ff1916600117905591506001600160a01b0389169082907f76e2796dc3a81d57b0e8504b647febcbeeb5f4af818e164f11eef8131a6a763f9061308a908b908b908b908b90613b58565b60405180910390a3979650505050505050565b8280548282559060005260206000209081019282156130f2579160200282015b828111156130f257825182546001600160a01b0319166001600160a01b039091161782556020909201916001909101906130bd565b506130fe9291506131ef565b5090565b8280548282559060005260206000209081019282156130f2579160200282015b828111156130f2578251825591602001919060010190613122565b82805482825590600052602060002090810192821561318a579160200282015b8281111561318a578251805161317a918491602090910190613204565b509160200191906001019061315d565b506130fe929150613277565b8280548282559060005260206000209081019282156131e3579160200282015b828111156131e357825180516131d3918491602090910190613204565b50916020019190600101906131b6565b506130fe929150613294565b5b808211156130fe57600081556001016131f0565b82805461321090613bcc565b90600052602060002090601f01602090048101928261323257600085556130f2565b82601f1061324b57805160ff19168380011785556130f2565b828001600101855582156130f257918201828111156130f2578251825591602001919060010190613122565b808211156130fe57600061328b82826132b1565b50600101613277565b808211156130fe5760006132a882826132b1565b50600101613294565b5080546132bd90613bcc565b6000825580601f106132cd575050565b601f0160209004906000526020600020908101906132eb91906131ef565b50565b60006020828403121561330057600080fd5b5035919050565b60005b8381101561332257818101518382015260200161330a565b83811115613331576000848401525b50505050565b6000815180845261334f816020860160208601613307565b601f01601f19169290920160200192915050565b6020815260006133766020830184613337565b9392505050565b80356001600160a01b038116811461339457600080fd5b919050565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff811182821017156133d8576133d8613399565b604052919050565b600082601f8301126133f157600080fd5b813567ffffffffffffffff81111561340b5761340b613399565b61341e601f8201601f19166020016133af565b81815284602083860101111561343357600080fd5b816020850160208301376000918101602001919091529392505050565b600080600080600060a0868803121561346857600080fd5b6134718661337d565b945060208601359350604086013567ffffffffffffffff8082111561349557600080fd5b6134a189838a016133e0565b945060608801359150808211156134b757600080fd5b506134c4888289016133e0565b95989497509295608001359392505050565b6000602082840312156134e857600080fd5b6133768261337d565b600081518084526020808501945080840160005b8381101561352a5781516001600160a01b031687529582019590820190600101613505565b509495945050505050565b600081518084526020808501945080840160005b8381101561352a57815187529582019590820190600101613549565b600081518084526020808501808196508360051b8101915082860160005b858110156135ad57828403895261359b848351613337565b98850198935090840190600101613583565b5091979650505050505050565b6080815260006135cd60808301876134f1565b82810360208401526135df8187613535565b905082810360408401526135f38186613565565b905082810360608401526136078185613565565b979650505050505050565b803560ff8116811461339457600080fd5b600080600080600060a0868803121561363b57600080fd5b8535945061364b60208701613612565b935061365960408701613612565b94979396509394606081013594506080013592915050565b634e487b7160e01b600052602160045260246000fd5b60208101600883106136a957634e487b7160e01b600052602160045260246000fd5b91905290565b600080604083850312156136c257600080fd5b6136cb8361337d565b946020939093013593505050565b600080604083850312156136ec57600080fd5b823591506136fc60208401613612565b90509250929050565b6000806040838503121561371857600080fd5b823591506136fc6020840161337d565b6000806000806060858703121561373e57600080fd5b8435935061374e60208601613612565b9250604085013567ffffffffffffffff8082111561376b57600080fd5b818701915087601f83011261377f57600080fd5b81358181111561378e57600080fd5b8860208285010111156137a057600080fd5b95989497505060200194505050565b600067ffffffffffffffff8211156137c9576137c9613399565b5060051b60200190565b600082601f8301126137e457600080fd5b813560206137f96137f4836137af565b6133af565b82815260059290921b8401810191818101908684111561381857600080fd5b8286015b8481101561383a5761382d8161337d565b835291830191830161381c565b509695505050505050565b600082601f83011261385657600080fd5b813560206138666137f4836137af565b82815260059290921b8401810191818101908684111561388557600080fd5b8286015b8481101561383a5780358352918301918301613889565b600082601f8301126138b157600080fd5b813560206138c16137f4836137af565b82815260059290921b840181019181810190868411156138e057600080fd5b8286015b8481101561383a57803567ffffffffffffffff8111156139045760008081fd5b6139128986838b01016133e0565b8452509183019183016138e4565b600082601f83011261393157600080fd5b813560206139416137f4836137af565b82815260059290921b8401810191818101908684111561396057600080fd5b8286015b8481101561383a57803567ffffffffffffffff8111156139845760008081fd5b6139928986838b01016133e0565b845250918301918301613964565b8035801515811461339457600080fd5b60008060008060008060c087890312156139c957600080fd5b863567ffffffffffffffff808211156139e157600080fd5b6139ed8a838b016137d3565b97506020890135915080821115613a0357600080fd5b613a0f8a838b01613845565b96506040890135915080821115613a2557600080fd5b613a318a838b016138a0565b95506060890135915080821115613a4757600080fd5b613a538a838b01613920565b94506080890135915080821115613a6957600080fd5b50613a7689828a016133e0565b925050613a8560a088016139a0565b90509295509295509295565b60018060a01b038616815284602082015260a060408201526000613ab860a0830186613337565b8281036060840152613aca8186613337565b9150508260808301529695505050505050565b634e487b7160e01b600052601160045260246000fd5b60008219821115613b0657613b06613add565b500190565b6001600160e01b0319831681528151600090613b2e816004850160208701613307565b919091016004019392505050565b60008251613b4e818460208701613307565b9190910192915050565b848152608060208201526000613b716080830186613337565b8281036040840152613b838186613337565b91505082606083015295945050505050565b60208082526017908201527f6d75737420636f6d652066726f6d2074686520676f762e000000000000000000604082015260600190565b600181811c90821680613be057607f821691505b60208210811415613c0157634e487b7160e01b600052602260045260246000fd5b50919050565b600082821015613c1957613c19613add565b500390565b600060208284031215613c3057600080fd5b81516001600160601b038116811461337657600080fd5b634e487b7160e01b600052603260045260246000fd5b6000600019821415613c7157613c71613add565b5060010190565b60ff851681526001600160601b038416602082015260606040820152816060820152818360808301376000818301608090810191909152601f909201601f191601019392505050565b8054600090600181811c9080831680613cdb57607f831692505b6020808410821415613cfd57634e487b7160e01b600052602260045260246000fd5b838852818015613d145760018114613d2857613d56565b60ff19861689830152604089019650613d56565b876000528160002060005b86811015613d4e5781548b8201850152908501908301613d33565b8a0183019750505b50505050505092915050565b60018060a01b038616815284602082015260a060408201526000613d8960a0830186613cc1565b8281036060840152613aca8186613cc1565b60c081526000613dae60c08301896134f1565b8281036020840152613dc08189613535565b90508281036040840152613dd48188613565565b90508281036060840152613de88187613565565b905084608084015282810360a0840152613e028185613337565b999850505050505050505056fe"), + ("0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec", "PoolExercise", "6101c06040523480156200001257600080fd5b50604051620030713803806200307183398101604081905262000035916200016a565b6001600160a01b038681166101005285811660805284811660a05283811660c052821660e052600f81900b61012052858585858585620000846000808062000101602090811b6200011917901c565b6101408181525050620000a660016000806200010160201b620001191760201c565b6101608181525050620000c860026000806200010160201b620001191760201c565b6101808181525050620000ea60036000806200010160201b620001191760201c565b6101a05250620002319a5050505050505050505050565b600081600f0b6080846001600160401b0316901b60f88660078111156200012c576200012c620001f4565b6200013992911b6200020a565b6200014591906200020a565b949350505050565b80516001600160a01b03811681146200016557600080fd5b919050565b60008060008060008060c087890312156200018457600080fd5b6200018f876200014d565b95506200019f602088016200014d565b9450620001af604088016200014d565b9350620001bf606088016200014d565b9250620001cf608088016200014d565b915060a087015180600f0b8114620001e657600080fd5b809150509295509295509295565b634e487b7160e01b600052602160045260246000fd5b600082198211156200022c57634e487b7160e01b600052601160045260246000fd5b500190565b60805160a05160c05160e05161010051610120516101405161016051610180516101a051612d6f6200030260003960008181610e1c015261137b015260008181610e420152818161135101526113f4015260008181611327015281816114b801526122c90152600081816112fe015281816113cb0152818161148f015281816114e101526122ef0152600081816103a8015281816107ed0152610cda0152600050506000818161176601526117b201526000610485015260008181611964015261212c015260005050612d6f6000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063477130981461003b578063b50e7ee314610050575b600080fd5b61004e610049366004612986565b610063565b005b61004e61005e3660046129c7565b610109565b336001600160a01b038416146100f9576001600160a01b03831660009081527f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68c6020908152604080832033845290915290205460ff166100f95760405162461bcd60e51b815260206004820152600c60248201526b1b9bdd08185c1c1c9bdd995960a21b60448201526064015b60405180910390fd5b61010483838361015f565b505050565b6101156000838361015f565b5050565b600081600f0b60808467ffffffffffffffff16901b60f8866007811115610142576101426129e9565b61014d92911b612a15565b6101579190612a15565b949350505050565b608082901c8260006001600160a01b0386161560f883901c600481600781111561018b5761018b6129e9565b14806101a8575060068160078111156101a6576101a66129e9565b145b6101e35760405162461bcd60e51b815260206004820152600c60248201526b696e76616c6964207479706560a01b60448201526064016100f0565b8115806101f95750428567ffffffffffffffff16105b6102335760405162461bcd60e51b815260206004820152600b60248201526a1b9bdd08195e1c1a5c995960aa1b60448201526064016100f0565b6004816007811115610247576102476129e9565b149250506000610262600080516020612d1a83398151915290565b9050600061026f826104c0565b9050428667ffffffffffffffff16101561029a576102978267ffffffffffffffff8816610526565b90505b82806102be5750836102b45784600f0b81600f0b126102be565b84600f0b81600f0b135b6102f45760405162461bcd60e51b81526020600482015260076024820152666e6f742049544d60c81b60448201526064016100f0565b6000841561033a5785600f0b82600f0b1315610335576103328861032984610320600f82900b8b61061e565b600f0b90610659565b600f0b906106b1565b90505b610367565b85600f0b82600f0b12156103675761036461035d89610329600f8a900b8661061e565b8490610719565b90505b6000841561038c5761037b89838c89610754565b6103859082612a15565b9050610454565b6103978b8b8b6108cb565b600082156103ff576103d58c6103d07f0000000000000000000000000000000000000000000000000000000000000000600f0b866106b1565b610a5d565b90506103e18183612a15565b91506103ff8c6103f089610a8c565b6103fa8487612a2d565b610ae1565b604080518c8152602081018c9052908101849052606081018290526001600160a01b038d16907f31939b125e073bbdbf69ac6eb0cb59489894a9bea509d658589af5917b53cca19060800160405180910390a2505b610474898361046e6104678a6000610bb1565b8c8c610119565b89610be4565b61047e9082612a15565b90506104b37f00000000000000000000000000000000000000000000000000000000000000006104ad88610e13565b83610e67565b5050505050505050505050565b60004282600c015414156104de576104d88242610e82565b92915050565b6104e782610eb0565b90506104f38242610e82565b600f0b61050557610505824283610fd3565b42600c830155610516826001611051565b610521826000611051565b919050565b600080610535610e1084612a5a565b600881901c6000818152601287016020526040812054929350909160ff84169190821b821c90610568620e100042612a5a565b90505b811580156105795750808411155b156105a65760128801600061058d86612a7c565b955085815260200190815260200160002054915061056b565b600060805b80156105d25783811c156105ca576105c38183612a15565b93811c9391505b60011c6105ab565b5060118901600060018360086105e88a84612a15565b6105f392911b612a2d565b6105fd9190612a2d565b8152602081019190915260400160002054600f0b9998505050505050505050565b6000600f82810b9084900b0360016001607f1b03198112801590610649575060016001607f1b038113155b61065257600080fd5b9392505050565b600081600f0b6000141561066c57600080fd5b600082600f0b604085600f0b901b8161068757610687612a44565b05905060016001607f1b03198112801590610649575060016001607f1b0381131561065257600080fd5b6000816106c0575060006104d8565b600083600f0b12156106d157600080fd5b600f83900b6001600160801b038316810260401c90608084901c026001600160c01b0381111561070057600080fd5b60401b811981111561071157600080fd5b019392505050565b600080610737838560030160149054906101000a900460ff166110e1565b9050610157818560030160159054906101000a900460ff166110f7565b60008281527fb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424eb602052604081205b85156108c25760006107a9600161079884611112565b6107a29190612a2d565b839061111c565b905060006107b78287611128565b9050878111156107c45750865b600080881561084657896107d8848b612a97565b6107e29190612a5a565b9150610815846103d07f0000000000000000000000000000000000000000000000000000000000000000600f0b856106b1565b90506108218187612a15565b955061082d828a612a2d565b98506108468461083c89610a8c565b6103fa8486612a2d565b610850838b612a2d565b99506001600160a01b0384167f31939b125e073bbdbf69ac6eb0cb59489894a9bea509d658589af5917b53cca189856108898587612a2d565b604080519384526020840192909252908201526060810184905260800160405180910390a26108b98489856108cb565b50505050610782565b50949350505050565b6001600160a01b03831661092d5760405162461bcd60e51b815260206004820152602360248201527f455243313135353a206275726e2066726f6d20746865207a65726f206164647260448201526265737360e81b60648201526084016100f0565b61095b3384600061093d866111db565b610946866111db565b60405180602001604052806000815250611226565b60008281527f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68b602090815260408083206001600160a01b038716845291829052909120548211156109fc5760405162461bcd60e51b815260206004820152602560248201527f455243313135353a206275726e20616d6f756e7420657863656564732062616c604482015264616e63657360d81b60648201526084016100f0565b6001600160a01b03841660008181526020838152604080832080548790039055805187815291820186905291929133917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62910160405180910390a450505050565b600080610a6984611762565b9050612710610a788285612a97565b610a829190612a5a565b6101579084612a2d565b600081610ab157600080516020612d1a833981519152546001600160a01b03166104d8565b50507fbbd6af8edd89d04327b00c29df7f272b9b1ae01bf6d9c54a784f935706df52ec546001600160a01b031690565b80610aeb57505050565b60405163a9059cbb60e01b81526001600160a01b0384811660048301526024820183905283169063a9059cbb90604401602060405180830381600087803b158015610b3557600080fd5b505af1158015610b49573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b6d9190612ab6565b6101045760405162461bcd60e51b8152602060048201526015602482015274115490cc8c081d1c985b9cd9995c8819985a5b1959605a1b60448201526064016100f0565b60008215610bcf5781610bc5576005610bc8565b60045b90506104d8565b81610bdb576007610652565b60069392505050565b60008281527fb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424eb60205260408120835b8615610e09576000610c3a6001610c2985611112565b610c339190612a2d565b849061111c565b90506000610c488288611128565b905088811115610c555750875b600089610c62838b612a97565b610c6c9190612a5a565b9050610c78818a612a2d565b9850610c84828b612a2d565b9950600087610cc35781610cb4610c9f600f88900b866106b1565b600080516020612d1a83398151915290610719565b610cbe9190612a2d565b610ccd565b610ccd8284612a2d565b90506000610d02856103d07f0000000000000000000000000000000000000000000000000000000000000000600f0b856106b1565b9050610d0e8189612a15565b975082610d2a600080516020612d1a833981519152878c61182c565b15610d5457610d4386610d3d8486612a2d565b8c611869565b610d4d8282612a15565b9050610d7d565b610d7086610d618c610e13565b610d6b8587612a2d565b610e67565b610d7a8382612a15565b90505b610d97600080516020612d1a833981519152878c8461192c565b610da2868c876108cb565b6001600160a01b0386167f69a2ef6bf9e7ff92cbf1b71963ba1751b1abe8f99e3b3aae2ab99e416df614938c610dd88587612a2d565b60408051928352602083019190915281018890526060810185905260800160405180910390a2505050505050610c13565b5050949350505050565b600081610e40577f00000000000000000000000000000000000000000000000000000000000000006104d8565b7f000000000000000000000000000000000000000000000000000000000000000092915050565b61010483838360405180602001604052806000815250611a6c565b60006011830181610e95610e1085612a5a565b8152602081019190915260400160002054600f0b9392505050565b6000808260030160009054906101000a90046001600160a01b03166001600160a01b03166350d25bcd6040518163ffffffff1660e01b815260040160206040518083038186803b158015610f0357600080fd5b505afa158015610f17573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f3b9190612ad8565b905060008360020160009054906101000a90046001600160a01b03166001600160a01b03166350d25bcd6040518163ffffffff1660e01b815260040160206040518083038186803b158015610f8f57600080fd5b505afa158015610fa3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fc79190612ad8565b90506101578282611b93565b6000610fe1610e1084612a5a565b6000818152601186016020526040902080546001600160801b0319166001600160801b038516179055905061101a60ff80831690612a2d565b6001901b846012016000600884901c815260200190815260200160002060008282546110469190612a15565b909155505050505050565b80151560009081526013830160205260409020805415806110725750805442105b1561107c57505050565b60006110888484611c2e565b90506110c384826110bd6110b286600101546110ad898b611c9890919063ffffffff16565b6110e1565b600f86900b90611cc6565b86611cf9565b50501515600090815260139091016020526040812081815560010155565b6000610652836110f284600a612bd5565b611d76565b600061065261110783600a612bd5565b600f85900b906106b1565b60006104d8825490565b60006106528383611dad565b60006001600160a01b0383166111945760405162461bcd60e51b815260206004820152602b60248201527f455243313135353a2062616c616e636520717565727920666f7220746865207a60448201526a65726f206164647265737360a81b60648201526084016100f0565b7f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68b6000928352602090815260408084206001600160a01b0395909516845293905250205490565b6040805160018082528183019092526060916000919060208083019080368337019050509050828160008151811061121557611215612be4565b602090810291909101015292915050565b611234868686868686611e33565b600080516020612d1a83398151915260005b845181101561175857600085828151811061126357611263612be4565b60200260200101519050600085838151811061128157611281612be4565b60200260200101519050806000141561129b575050611746565b6001600160a01b0389166112b8576112b66015850183612011565b505b6001600160a01b0388161580156112e857506000828152600080516020612cfa8339815191526020526040902054155b156112fc576112fa601585018361201d565b505b7f000000000000000000000000000000000000000000000000000000000000000082148061134957507f000000000000000000000000000000000000000000000000000000000000000082145b8061137357507f000000000000000000000000000000000000000000000000000000000000000082145b8061139d57507f000000000000000000000000000000000000000000000000000000000000000082145b1561148d576001600160a01b038916158015906113c257506001600160a01b03881615155b1561148d5760007f000000000000000000000000000000000000000000000000000000000000000083148061141657507f000000000000000000000000000000000000000000000000000000000000000083145b6001600160a01b038b166000908152600d870160209081526040808320841515845290915290205490915042906114509062015180612a15565b1061148b5760405162461bcd60e51b815260206004820152600b60248201526a1b1a5c481b1bd8dac80c5960aa1b60448201526064016100f0565b505b7f00000000000000000000000000000000000000000000000000000000000000008214806114da57507f000000000000000000000000000000000000000000000000000000000000000082145b15611682577f00000000000000000000000000000000000000000000000000000000000000008214600061150e8683612029565b90506001600160a01b038b161561163857600061152b8c86611128565b9050818111801561154557506115418285612a15565b8111155b156115dd576001600160a01b038c166000908152601488016020908152604080832086151580855260138c01845282852054855290835281842090845290915290205484906115949083612a2d565b10156115d25760405162461bcd60e51b815260206004820152600d60248201526c496e7375662062616c616e636560981b60448201526064016100f0565b6115dd878d85612043565b6001600160a01b038b161561163657611611878d858c8a8151811061160457611604612be4565b602002602001015161192c565b611636878c858c8a8151811061162957611629612be4565b60200260200101516120f4565b505b6001600160a01b038a161561167f5760006116538b86611128565b905081811115801561166d57508161166b8583612a15565b115b1561167d5761167d878c85612218565b505b50505b60f882901c826001600160a01b038b16158015906116a857506001600160a01b038a1615155b80156116e0575060058260078111156116c3576116c36129e9565b14806116e0575060078260078111156116de576116de6129e9565b145b1561174157600060058360078111156116fb576116fb6129e9565b1490506000816117225761171d611716600f85900b876106b1565b8990610719565b611724565b845b9050611732888e848461192c565b61173e888d84846120f4565b50505b505050505b8061175081612a7c565b915050611246565b5050505050505050565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031615610521576040516303793c8d60e11b81526001600160a01b0383811660048301527f000000000000000000000000000000000000000000000000000000000000000016906306f2791a9060240160206040518083038186803b1580156117f457600080fd5b505afa158015611808573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104d89190612ad8565b6001600160a01b0382166000908152600e840160209081526040808320841515845290915281205480158061186057504281115b95945050505050565b600080516020612d1a83398151915261188b84611885846122c0565b85610e67565b60006101048061189b8142612a5a565b6118a59190612a97565b6118af9190612a15565b6001600160a01b03861660009081526014840160209081526040808320848452825280832087151584529091528120805492935086929091906118f3908490612a15565b90915550508215156000908152601383016020526040812060018101805491928792611920908490612a15565b90915550505550505050565b6001600160a01b03808416600090815260178601602090815260408083208615158452825280832054601889019092529091205490917f00000000000000000000000000000000000000000000000000000000000000001663edaf7d5b863087866119978982612a2d565b6040516001600160e01b031960e088901b1681526001600160a01b03958616600482015294909316602485015290151560448401526064830152608482015260a4810184905260c401600060405180830381600087803b1580156119fa57600080fd5b505af1158015611a0e573d6000803e3d6000fd5b505050508282611a1e9190612a2d565b6001600160a01b038616600090815260178801602090815260408083208815158452909152902055611a508382612a2d565b9315156000908152601890960160205250506040909320555050565b6001600160a01b038416611acc5760405162461bcd60e51b815260206004820152602160248201527f455243313135353a206d696e7420746f20746865207a65726f206164647265736044820152607360f81b60648201526084016100f0565b611aeb33600086611adc876111db565b611ae5876111db565b86611226565b60008381527f1799cf914cb0cb442ca7c7ac709ee40d0cb89e87351dc08d517fbda27d50c68b602090815260408083206001600160a01b0388168452918290528220805491928592611b3e908490612a15565b909155505060408051858152602081018590526001600160a01b0387169160009133917fc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62910160405180910390a45050505050565b600081611b9f57600080fd5b600080841215611bb457836000039350600190505b6000831215611bc65760009290920391155b6000611bd28585612314565b90508115611c00576001607f1b816001600160801b03161115611bf457600080fd5b60000391506104d89050565b60016001607f1b03816001600160801b03161115611c1d57600080fd5b91506104d89050565b505092915050565b600080611c4b83611c40576001611c43565b60005b600080610119565b831515600090815260138601602052604090206001015490915061015790600080516020612cfa83398151915260008481526020919091526040902054611c929190612a2d565b6110ad86865b600081611cb3576003830154600160a81b900460ff16610652565b505060030154600160a01b900460ff1690565b6000600f83810b9083900b0160016001607f1b03198112801590610649575060016001607f1b0381131561065257600080fd5b6000611d058583612476565b90506000611d16868387878761248f565b9050611d23868285612595565b60408051600f83810b825287810b602083015286900b818301529051841515917f4e23621c6f591f14bf9505cb8326b45af9dc6c5569fd608de2a7a2ddd6146b2e919081900360600190a2505050505050565b600081611d8257600080fd5b6000611d8e8484612314565b905060016001607f1b036001600160801b038216111561065257600080fd5b81546000908210611e0b5760405162461bcd60e51b815260206004820152602260248201527f456e756d657261626c655365743a20696e646578206f7574206f6620626f756e604482015261647360f01b60648201526084016100f0565b826000018281548110611e2057611e20612be4565b9060005260206000200154905092915050565b836001600160a01b0316856001600160a01b031614612009576001600160a01b0385811660009081527fb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424ec602052604080822092871682528120600080516020612cfa833981519152927fb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424eb929091905b87518110156104b3576000878281518110611edf57611edf612be4565b602002602001015190506000811115611ff6576000898381518110611f0657611f06612be4565b6020026020010151905060006001600160a01b03168c6001600160a01b03161415611f545760008181526020889052604081208054849290611f49908490612a15565b90915550611f8a9050565b81611f5f8d83611128565b1415611f8a576000818152602087905260409020611f7d908d6125ec565b50611f88858261201d565b505b6001600160a01b038b16611fc15760008181526020889052604081208054849290611fb6908490612a2d565b90915550611ff49050565b611fcb8b82611128565b611ff4576000818152602087905260409020611fe7908c612601565b50611ff28482612011565b505b505b508061200181612a7c565b915050611ec2565b505050505050565b60006106528383612612565b60006106528383612661565b60008161203a578260040154610652565b50506005015490565b6001600160a01b03821661205657600080fd5b8015156000908152600f8401602090815260408083206010870190925290912061208184838361274c565b61208c575050505050565b6001600160a01b0393841660008181526020838152604080832080549683528184208054978a16808652838620805499909b166001600160a01b0319998a168117909b5599855295909252822080548616909717909655528054821690558254169091555050565b6001600160a01b03808416600090815260178601602090815260408083208615158452825280832054601889019092529091205490917f00000000000000000000000000000000000000000000000000000000000000001663edaf7d5b8630878661215f8982612a15565b6040516001600160e01b031960e088901b1681526001600160a01b03958616600482015294909316602485015290151560448401526064830152608482015260a4810184905260c401600060405180830381600087803b1580156121c257600080fd5b505af11580156121d6573d6000803e3d6000fd5b5050505082826121e69190612a15565b6001600160a01b038616600090815260178801602090815260408083208815158452909152902055611a508382612a15565b6001600160a01b03821661222b57600080fd5b8015156000908152600f8401602090815260408083206010870190925290912061225684838361274c565b15612262575050505050565b60008080526020828152604080832080546001600160a01b0390811680865296845282852080546001600160a01b03199081169a909216998a1790558885529490925282208054841690941790935580528154169092179091555050565b6000816122ed577f00000000000000000000000000000000000000000000000000000000000000006104d8565b7f000000000000000000000000000000000000000000000000000000000000000092915050565b60008161232057600080fd5b60006001600160c01b03841161234b5782604085901b8161234357612343612a44565b049050612462565b60c084811c6401000000008110612364576020918201911c5b620100008110612376576010918201911c5b6101008110612387576008918201911c5b60108110612397576004918201911c5b600481106123a7576002918201911c5b600281106123b6576001820191505b60bf820360018603901c6001018260ff0387901b816123d7576123d7612a44565b0492506001600160801b038311156123ee57600080fd5b608085901c83026001600160801b038616840260c088901c604089901b8281101561241a576001820391505b608084901b92900382811015612431576001820391505b829003608084901c821461244757612447612bfa565b88818161245657612456612a44565b04870196505050505050505b6001600160801b0381111561065257600080fd5b60006124828383612798565b90506106528382846127bf565b600080826124a4576019870154600f0b6124b4565b6019870154600160801b9004600f0b5b905080600f0b600014156124cc57506008860154600f0b5b60405163e101a89b60e01b8152600f87810b600483015286810b602483015285810b604483015282900b6064820152730f6e8ef18fb5bb61d545fee60f779d8aed60408f9063e101a89b9060840160206040518083038186803b15801561253257600080fd5b505af4158015612546573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061256a9190612c10565b915067b33333333333333382600f0b121561258b5767b33333333333333391505b5095945050505050565b80156125c5576009830180546001600160801b0384166001600160801b031990911617905542600b840155505050565b6008830180546001600160801b03808516600160801b02911617905542600a840155505050565b6000610652836001600160a01b038416612661565b6000610652836001600160a01b0384165b6000818152600183016020526040812054612659575081546001818101845560008481526020808220909301849055845484825282860190935260409020919091556104d8565b5060006104d8565b60008181526001830160205260408120548015612742576000612685600183612a2d565b8554909150600090869061269b90600190612a2d565b815481106126ab576126ab612be4565b90600052602060002001549050808660000183815481106126ce576126ce612be4565b6000918252602090912001556126e5826001612a15565b6000828152600188016020526040902055855486908061270757612707612c33565b6001900381819060005260206000200160009055905585600101600086815260200190815260200160002060009055600193505050506104d8565b60009150506104d8565b6001600160a01b0383811660009081526020849052604081205490911615158061015757506000808052602083905260409020546001600160a01b039081169085161490509392505050565b6000816127b3576008830154600160801b9004600f0b610652565b505060090154600f0b90565b600080826127d15784600a01546127d7565b84600b01545b6127e19042612a2d565b905061a8c0811115612800576127f961a8c082612a2d565b9050612809565b83915050610652565b600061281782613840611d76565b9050600061282a85611c40576001611c43565b851515600090815260188901602090815260408083205460138c01835281842060010154858552600080516020612cfa83398151915290935290832054939450926128889161287891612a2d565b6128829084612a2d565b83611d76565b6040805161012081018252600f87810b82528b810b602083015283900b8183015267b333333333333333606082015267e666666666666666608082018190526801000000000000000060a0830181905260c083015260e082015268056fc2a2c515da32ea6101008201529051634916d70d60e01b8152919250730f6e8ef18fb5bb61d545fee60f779d8aed60408f91634916d70d9161292991600401612c49565b60206040518083038186803b15801561294157600080fd5b505af4158015612955573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129799190612c10565b9998505050505050505050565b60008060006060848603121561299b57600080fd5b83356001600160a01b03811681146129b257600080fd5b95602085013595506040909401359392505050565b600080604083850312156129da57600080fd5b50508035926020909101359150565b634e487b7160e01b600052602160045260246000fd5b634e487b7160e01b600052601160045260246000fd5b60008219821115612a2857612a286129ff565b500190565b600082821015612a3f57612a3f6129ff565b500390565b634e487b7160e01b600052601260045260246000fd5b600082612a7757634e487b7160e01b600052601260045260246000fd5b500490565b6000600019821415612a9057612a906129ff565b5060010190565b6000816000190483118215151615612ab157612ab16129ff565b500290565b600060208284031215612ac857600080fd5b8151801515811461065257600080fd5b600060208284031215612aea57600080fd5b5051919050565b600181815b80851115612b2c578160001904821115612b1257612b126129ff565b80851615612b1f57918102915b93841c9390800290612af6565b509250929050565b600082612b43575060016104d8565b81612b50575060006104d8565b8160018114612b665760028114612b7057612b8c565b60019150506104d8565b60ff841115612b8157612b816129ff565b50506001821b6104d8565b5060208310610133831016604e8410600b8410161715612baf575081810a6104d8565b612bb98383612af1565b8060001904821115612bcd57612bcd6129ff565b029392505050565b600061065260ff841683612b34565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052600160045260246000fd5b600060208284031215612c2257600080fd5b815180600f0b811461065257600080fd5b634e487b7160e01b600052603160045260246000fd5b6000610120820190508251600f0b82526020830151600f0b60208301526040830151612c7a6040840182600f0b9052565b506060830151612c8f6060840182600f0b9052565b506080830151612ca46080840182600f0b9052565b5060a0830151612cb960a0840182600f0b9052565b5060c0830151612cce60c0840182600f0b9052565b5060e0830151612ce360e0840182600f0b9052565b5061010080840151611c2682850182600f0b905256feb31c2c74f86ca3ce94d901f5f5bbe66f7161eec2f7b5aa0b75a86371436424eabbd6af8edd89d04327b00c29df7f272b9b1ae01bf6d9c54a784f935706df52eb"), + ("0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05", "MainchainGatewayV2", "608060405234801561001057600080fd5b506000805460ff1916905561582e806200002b6000396000f3fe60806040526004361061032d5760003560e01c80639157921c116101a5578063b2975794116100ec578063d547741f11610095578063dafae4081161006f578063dafae4081461096e578063dff525e11461098e578063e400327c146109ae578063e75235b8146109ce5761033c565b8063d547741f14610901578063d55ed10314610921578063d64af2a61461094e5761033c565b8063cdb67444116100c6578063cdb674441461089c578063cdf64a76146108b4578063d19773d2146108d45761033c565b8063b29757941461082f578063b9c362091461085c578063ca15c8731461087c5761033c565b8063a3912ec81161014e578063affed0e011610128578063affed0e0146107cc578063b1a2567e146107e2578063b1d08a03146108025761033c565b8063a3912ec81461033a578063ab7965661461077f578063ac78dfe8146107ac5761033c565b8063994390891161017f57806399439089146107155780639dcc4da314610735578063a217fddf1461076a5761033c565b80639157921c1461068f57806391d14854146106af57806393c5678f146106f55761033c565b806336568abe116102745780635c975abb1161021d5780637de5dedd116101f75780637de5dedd146106115780638456cb59146106265780638f34e3471461063b5780639010d07c1461066f5761033c565b80635c975abb146105ac5780636932be98146105c45780636c1ce670146105f15761033c565b80634d0d66731161024e5780634d0d66731461052f5780634d493f4e1461054f57806359122f6b1461057f5761033c565b806336568abe146104e75780633f4ba83a146105075780634b14557e1461051c5761033c565b80631d4a7210116102d65780632f2ff15d116102b05780632f2ff15d1461049b578063302d12db146104bb5780633644e515146104d25761033c565b80631d4a721014610428578063248a9ca3146104555780632dfdf0b5146104855761033c565b8063180ff1e911610307578063180ff1e9146103d55780631a8e55b0146103e85780631b6e7594146104085761033c565b806301ffc9a71461034457806317ce2dd41461037957806317fcb39b1461039d5761033c565b3661033c5761033a6109e6565b005b61033a6109e6565b34801561035057600080fd5b5061036461035f366004614843565b610a69565b60405190151581526020015b60405180910390f35b34801561038557600080fd5b5061038f60755481565b604051908152602001610370565b3480156103a957600080fd5b506074546103bd906001600160a01b031681565b6040516001600160a01b039091168152602001610370565b61033a6103e33660046148f4565b610aad565b3480156103f457600080fd5b5061033a6104033660046149e6565b610dbd565b34801561041457600080fd5b5061033a610423366004614a52565b610e8f565b34801561043457600080fd5b5061038f610443366004614aec565b603e6020526000908152604090205481565b34801561046157600080fd5b5061038f610470366004614b09565b60009081526072602052604090206001015490565b34801561049157600080fd5b5061038f60765481565b3480156104a757600080fd5b5061033a6104b6366004614b22565b610f64565b3480156104c757600080fd5b5061038f620f424081565b3480156104de57600080fd5b5060775461038f565b3480156104f357600080fd5b5061033a610502366004614b22565b610f8f565b34801561051357600080fd5b5061033a61101b565b61033a61052a366004614b52565b611083565b34801561053b57600080fd5b5061036461054a366004614b7d565b6110e1565b34801561055b57600080fd5b5061036461056a366004614b09565b607a6020526000908152604090205460ff1681565b34801561058b57600080fd5b5061038f61059a366004614aec565b603a6020526000908152604090205481565b3480156105b857600080fd5b5060005460ff16610364565b3480156105d057600080fd5b5061038f6105df366004614b09565b60796020526000908152604090205481565b3480156105fd57600080fd5b5061036461060c366004614c06565b61118c565b34801561061d57600080fd5b5061038f61119f565b34801561063257600080fd5b5061033a611234565b34801561064757600080fd5b5061038f7f5e5712e902fff5e704bc4d506ad976718319e019e9d2a872528a01a85db433e481565b34801561067b57600080fd5b506103bd61068a366004614c32565b61129c565b34801561069b57600080fd5b5061033a6106aa366004614c54565b6112b4565b3480156106bb57600080fd5b506103646106ca366004614b22565b60009182526072602090815260408084206001600160a01b0393909316845291905290205460ff1690565b34801561070157600080fd5b5061033a6107103660046149e6565b6115ca565b34801561072157600080fd5b506003546103bd906001600160a01b031681565b34801561074157600080fd5b50610755610750366004614c32565b611696565b60408051928352602083019190915201610370565b34801561077657600080fd5b5061038f600081565b34801561078b57600080fd5b5061038f61079a366004614aec565b603c6020526000908152604090205481565b3480156107b857600080fd5b506103646107c7366004614b09565b61172f565b3480156107d857600080fd5b5061038f60045481565b3480156107ee57600080fd5b5061033a6107fd3660046149e6565b6117ce565b34801561080e57600080fd5b5061038f61081d366004614aec565b60396020526000908152604090205481565b34801561083b57600080fd5b5061084f61084a366004614aec565b61189a565b6040516103709190614ca5565b34801561086857600080fd5b50610755610877366004614c32565b611992565b34801561088857600080fd5b5061038f610897366004614b09565b611a17565b3480156108a857600080fd5b50603754603854610755565b3480156108c057600080fd5b5061033a6108cf366004614aec565b611a2e565b3480156108e057600080fd5b5061038f6108ef366004614aec565b603b6020526000908152604090205481565b34801561090d57600080fd5b5061033a61091c366004614b22565b611a97565b34801561092d57600080fd5b5061038f61093c366004614aec565b603d6020526000908152604090205481565b34801561095a57600080fd5b5061033a610969366004614aec565b611abd565b34801561097a57600080fd5b50610364610989366004614b09565b611b26565b34801561099a57600080fd5b5061033a6109a9366004614cd2565b611bbd565b3480156109ba57600080fd5b5061033a6109c93660046149e6565b611cc7565b3480156109da57600080fd5b50600154600254610755565b60005460ff1615610a315760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b60448201526064015b60405180910390fd5b6074546001600160a01b03163314610a6757610a4b614802565b338152604080820151349101528051610a65908290611d93565b505b565b60006001600160e01b031982167f5a05180f000000000000000000000000000000000000000000000000000000001480610aa75750610aa78261210a565b92915050565b607154610100900460ff16610ac85760715460ff1615610acc565b303b155b610b3e5760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201527f647920696e697469616c697a65640000000000000000000000000000000000006064820152608401610a28565b607154610100900460ff16158015610b60576071805461ffff19166101011790555b610b6b60008d612171565b6075899055610b798b61217b565b610b828a6121dd565b610c29604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201527f159f52c1e3a2b6a6aad3950adf713516211484e0516dad685ea662a094b7c43b918101919091527fad7c5bef027816a800da1736444fb58a807ef4c9603b7848673f7e3a68eb14a560608201524660808201523060a082015260c00160408051601f198184030181529190528051602090910120607755565b610c338887612238565b5050610c3f87876122f8565b5050610c496123d3565b6000610c558680614da6565b90501115610d1657610c7e610c6a8680614da6565b610c776020890189614da6565b8787612467565b610ca4610c8b8680614da6565b8660005b602002810190610c9f9190614da6565b612666565b610cca610cb18680614da6565b8660015b602002810190610cc59190614da6565b612779565b610cf0610cd78680614da6565b8660025b602002810190610ceb9190614da6565b61288c565b610d16610cfd8680614da6565b8660035b602002810190610d119190614da6565b612a30565b60005b610d266040870187614da6565b9050811015610d9c57610d8a7f5e5712e902fff5e704bc4d506ad976718319e019e9d2a872528a01a85db433e4610d606040890189614da6565b84818110610d7057610d70614d90565b9050602002016020810190610d859190614aec565b612b43565b80610d9481614e06565b915050610d19565b508015610daf576071805461ff00191690555b505050505050505050505050565b6000805160206157b9833981519152546001600160a01b03163314610e1d5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b82610e7d5760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b610e8984848484612779565b50505050565b6000805160206157b9833981519152546001600160a01b03163314610eef5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b84610f4e5760405162461bcd60e51b815260206004820152602960248201527f4d61696e636861696e4761746577617956323a20717565727920666f7220656d60448201526870747920617272617960b81b6064820152608401610a28565b610f5c868686868686612467565b505050505050565b600082815260726020526040902060010154610f808133612b65565b610f8a8383612b43565b505050565b6001600160a01b038116331461100d5760405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201527f20726f6c657320666f722073656c6600000000000000000000000000000000006064820152608401610a28565b6110178282612be5565b5050565b6000805160206157b9833981519152546001600160a01b0316331461107b5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b610a67612c07565b60005460ff16156110c95760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606401610a28565b610a656110db36839003830183614ec0565b33611d93565b6000805460ff16156111285760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606401610a28565b611184848484808060200260200160405190810160405280939291908181526020016000905b8282101561117a5761116b60608302860136819003810190614f13565b8152602001906001019061114e565b5050505050612ca3565b949350505050565b600061119883836133bc565b9392505050565b600061122f600360009054906101000a90046001600160a01b03166001600160a01b031663926323d56040518163ffffffff1660e01b815260040160206040518083038186803b1580156111f257600080fd5b505afa158015611206573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061122a9190614f89565b613480565b905090565b6000805160206157b9833981519152546001600160a01b031633146112945760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b610a676134b6565b60008281526073602052604081206111989083613531565b7f5e5712e902fff5e704bc4d506ad976718319e019e9d2a872528a01a85db433e46112df8133612b65565b60006112f86112f336859003850185614ff0565b61353d565b905061130c6112f336859003850185614ff0565b8335600090815260796020526040902054146113765760405162461bcd60e51b815260206004820152602360248201527f4d61696e636861696e4761746577617956323a20696e76616c696420726563656044820152621a5c1d60ea1b6064820152608401610a28565b82356000908152607a602052604090205460ff166113fc5760405162461bcd60e51b815260206004820152603160248201527f4d61696e636861696e4761746577617956323a20717565727920666f7220617060448201527f70726f766564207769746864726177616c0000000000000000000000000000006064820152608401610a28565b82356000908152607a602052604090819020805460ff19169055517fd639511b37b3b002cca6cfe6bca0d833945a5af5a045578a0627fc43b79b26309061144690839086906150c4565b60405180910390a160006114606080850160608601614aec565b9050600061147661012086016101008701615151565b600181111561148757611487614c71565b141561154f5760006114a2368690038601610100870161516e565b6001600160a01b0383166000908152603b60205260409020549091506114ce90610140870135906135c6565b604082015260006114e8368790038701610100880161516e565b60408301519091506114ff9061014088013561518a565b604082015260745461151f908390339086906001600160a01b03166135e0565b6115486115326060880160408901614aec565b60745483919086906001600160a01b03166135e0565b505061158b565b61158b6115626060860160408701614aec565b60745483906001600160a01b03166115833689900389016101008a0161516e565b9291906135e0565b7f21e88e956aa3e086f6388e899965cef814688f99ad8bb29b08d396571016372d82856040516115bc9291906150c4565b60405180910390a150505050565b6000805160206157b9833981519152546001600160a01b0316331461162a5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b8261168a5760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b610e8984848484612666565b6000806116b86000805160206157b9833981519152546001600160a01b031690565b6001600160a01b0316336001600160a01b0316146117115760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b61171b84846122f8565b90925090506117286123d3565b9250929050565b6003546040805163926323d560e01b815290516000926001600160a01b03169163926323d5916004808301926020929190829003018186803b15801561177457600080fd5b505afa158015611788573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117ac9190614f89565b6037546117b991906151a1565b6038546117c690846151a1565b101592915050565b6000805160206157b9833981519152546001600160a01b0316331461182e5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b8261188e5760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b610e898484848461288c565b60408051808201909152600080825260208201526001600160a01b0382166000908152607860205260409081902081518083019092528054829060ff1660018111156118e8576118e8614c71565b60018111156118f9576118f9614c71565b815290546001600160a01b036101009091048116602092830152908201519192501661198d5760405162461bcd60e51b815260206004820152602560248201527f4d61696e636861696e4761746577617956323a20756e737570706f727465642060448201527f746f6b656e0000000000000000000000000000000000000000000000000000006064820152608401610a28565b919050565b6000806119b46000805160206157b9833981519152546001600160a01b031690565b6001600160a01b0316336001600160a01b031614611a0d5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b61171b8484612238565b6000818152607360205260408120610aa790613a13565b6000805160206157b9833981519152546001600160a01b03163314611a8e5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b610a65816121dd565b600082815260726020526040902060010154611ab38133612b65565b610f8a8383612be5565b6000805160206157b9833981519152546001600160a01b03163314611b1d5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b610a658161217b565b6003546040805163926323d560e01b815290516000926001600160a01b03169163926323d5916004808301926020929190829003018186803b158015611b6b57600080fd5b505afa158015611b7f573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611ba39190614f89565b600154611bb091906151a1565b6002546117c690846151a1565b6000805160206157b9833981519152546001600160a01b03163314611c1d5760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b85611c7c5760405162461bcd60e51b815260206004820152602960248201527f4d61696e636861696e4761746577617956323a20717565727920666f7220656d60448201526870747920617272617960b81b6064820152608401610a28565b611c8a878787878787612467565b611c978787836000610c8f565b611ca48787836001610cb5565b611cb18787836002610cdb565b611cbe8787836003610d01565b50505050505050565b6000805160206157b9833981519152546001600160a01b03163314611d275760405162461bcd60e51b815260206004820152602260248201526000805160206157d983398151915260448201526132b960f11b6064820152608401610a28565b82611d875760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b610e8984848484612a30565b604080518082018252600080825260208201526074549184015190916001600160a01b031690611dc290613a1d565b60208401516001600160a01b0316611ee1573484604001516040015114611e375760405162461bcd60e51b815260206004820152602360248201527f4d61696e636861696e4761746577617956323a20696e76616c69642072657175604482015262195cdd60ea1b6064820152608401610a28565b611e408161189a565b6040850151519092506001811115611e5a57611e5a614c71565b82516001811115611e6d57611e6d614c71565b14611ecd5760405162461bcd60e51b815260206004820152602a60248201527f4d61696e636861696e4761746577617956323a20696e76616c696420746f6b656044820152691b881cdd185b99185c9960b21b6064820152608401610a28565b6001600160a01b0381166020850152612087565b3415611f3b5760405162461bcd60e51b815260206004820152602360248201527f4d61696e636861696e4761746577617956323a20696e76616c69642072657175604482015262195cdd60ea1b6064820152608401610a28565b611f48846020015161189a565b6040850151519092506001811115611f6257611f62614c71565b82516001811115611f7557611f75614c71565b14611fd55760405162461bcd60e51b815260206004820152602a60248201527f4d61696e636861696e4761746577617956323a20696e76616c696420746f6b656044820152691b881cdd185b99185c9960b21b6064820152608401610a28565b60208401516040850151611fec9185903090613ac7565b83602001516001600160a01b0316816001600160a01b031614156120875760408481015181015190517f2e1a7d4d00000000000000000000000000000000000000000000000000000000815260048101919091526001600160a01b03821690632e1a7d4d90602401600060405180830381600087803b15801561206e57600080fd5b505af1158015612082573d6000803e3d6000fd5b505050505b607680546000918261209883614e06565b91905055905060006120bf858386602001516075548a613ce190949392919063ffffffff16565b90507fd7b25068d9dc8d00765254cfb7f5070f98d263c8d68931d937c7362fa738048b6120eb8261353d565b826040516120fa9291906151c0565b60405180910390a1505050505050565b60006001600160e01b031982167f7965db0b000000000000000000000000000000000000000000000000000000001480610aa757507f01ffc9a7000000000000000000000000000000000000000000000000000000006001600160e01b0319831614610aa7565b6110178282612b43565b6074805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040519081527f9d2334c23be647e994f27a72c5eee42a43d5bdcfe15bb88e939103c2b114cbaf906020015b60405180910390a150565b6003805473ffffffffffffffffffffffffffffffffffffffff19166001600160a01b0383169081179091556040519081527fef40dc07567635f84f5edbd2f8dbc16b40d9d282dd8e7e6f4ff58236b6836169906020016121d2565b6000808284111561228b5760405162461bcd60e51b815260206004820152601c60248201527f4761746577617956323a20696e76616c6964207468726573686f6c64000000006044820152606401610a28565b505060018054600280549285905583905560048054919291849186919060006122b383614e06565b9091555060408051868152602081018690527f976f8a9c5bdf8248dec172376d6e2b80a8e3df2f0328e381c6db8e1cf138c0f891015b60405180910390a49250929050565b600080828411156123715760405162461bcd60e51b815260206004820152602760248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c696420746860448201527f726573686f6c64000000000000000000000000000000000000000000000000006064820152608401610a28565b5050603780546038805492859055839055600480549192918491869190600061239983614e06565b9091555060408051868152602081018690527f31312c97b89cc751b832d98fd459b967a2c3eef3b49757d1cf5ebaa12bb6eee191016122e9565b6002546037546123e391906151a1565b6038546001546123f391906151a1565b1115610a675760405162461bcd60e51b815260206004820152602860248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c696420746860448201527f726573686f6c64730000000000000000000000000000000000000000000000006064820152608401610a28565b848314801561247557508481145b6124e75760405162461bcd60e51b815260206004820152602860248201527f4d61696e636861696e4761746577617956323a20696e76616c6964206172726160448201527f79206c656e6774680000000000000000000000000000000000000000000000006064820152608401610a28565b60005b8581101561262c5784848281811061250457612504614d90565b90506020020160208101906125199190614aec565b6078600089898581811061252f5761252f614d90565b90506020020160208101906125449190614aec565b6001600160a01b039081168252602082019290925260400160002080547fffffffffffffffffffffff0000000000000000000000000000000000000000ff1661010093909216929092021790558282828181106125a3576125a3614d90565b90506020020160208101906125b89190615151565b607860008989858181106125ce576125ce614d90565b90506020020160208101906125e39190614aec565b6001600160a01b031681526020810191909152604001600020805460ff19166001838181111561261557612615614c71565b02179055508061262481614e06565b9150506124ea565b507fa4f03cc9c0e0aeb5b71b4ec800702753f65748c2cf3064695ba8e8b46be704448686868686866040516120fa969594939291906152c1565b8281146126c85760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b60005b83811015612743578282828181106126e5576126e5614d90565b905060200201356039600087878581811061270257612702614d90565b90506020020160208101906127179190614aec565b6001600160a01b031681526020810191909152604001600020558061273b81614e06565b9150506126cb565b507f80bc635c452ae67f12f9b6f12ad4daa6dbbc04eeb9ebb87d354ce10c0e210dc0848484846040516115bc9493929190615339565b8281146127db5760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b60005b83811015612856578282828181106127f8576127f8614d90565b90506020020135603a600087878581811061281557612815614d90565b905060200201602081019061282a9190614aec565b6001600160a01b031681526020810191909152604001600020558061284e81614e06565b9150506127de565b507f64557254143204d91ba2d95acb9fda1e5fea55f77efd028685765bc1e94dd4b5848484846040516115bc9493929190615339565b8281146128ee5760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b60005b838110156129fa57620f424083838381811061290f5761290f614d90565b90506020020135111561298a5760405162461bcd60e51b815260206004820152602860248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c696420706560448201527f7263656e746167650000000000000000000000000000000000000000000000006064820152608401610a28565b82828281811061299c5761299c614d90565b90506020020135603b60008787858181106129b9576129b9614d90565b90506020020160208101906129ce9190614aec565b6001600160a01b03168152602081019190915260400160002055806129f281614e06565b9150506128f1565b507fb05f5de88ae0294ebb6f67c5af2fcbbd593cc6bdfe543e2869794a4c8ce3ea50848484846040516115bc9493929190615339565b828114612a925760405162461bcd60e51b815260206004820152602a60248201527f5769746864726177616c4c696d69746174696f6e3a20696e76616c69642061726044820152690e4c2f240d8cadccee8d60b31b6064820152608401610a28565b60005b83811015612b0d57828282818110612aaf57612aaf614d90565b90506020020135603c6000878785818110612acc57612acc614d90565b9050602002016020810190612ae19190614aec565b6001600160a01b0316815260208101919091526040016000205580612b0581614e06565b915050612a95565b507fb5d2963614d72181b4df1f993d45b83edf42fa19710f0204217ba1b3e183bb73848484846040516115bc9493929190615339565b612b4d8282613db6565b6000828152607360205260409020610f8a9082613e58565b60008281526072602090815260408083206001600160a01b038516845290915290205460ff1661101757612ba3816001600160a01b03166014613e6d565b612bae836020613e6d565b604051602001612bbf9291906153d0565b60408051601f198184030181529082905262461bcd60e51b8252610a2891600401615451565b612bef828261404e565b6000828152607360205260409020610f8a90826140d1565b60005460ff16612c595760405162461bcd60e51b815260206004820152601460248201527f5061757361626c653a206e6f74207061757365640000000000000000000000006044820152606401610a28565b6000805460ff191690557f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa335b6040516001600160a01b03909116815260200160405180910390a1565b6000823561014084013582612cbe6080870160608801614aec565b9050612cdb612cd6368890038801610100890161516e565b613a1d565b6001612ced6040880160208901615151565b6001811115612cfe57612cfe614c71565b14612d715760405162461bcd60e51b815260206004820152602860248201527f4d61696e636861696e4761746577617956323a20696e76616c6964207265636560448201527f697074206b696e640000000000000000000000000000000000000000000000006064820152608401610a28565b60808601354614612de95760405162461bcd60e51b8152602060048201526024808201527f4d61696e636861696e4761746577617956323a20696e76616c6964206368616960448201527f6e206964000000000000000000000000000000000000000000000000000000006064820152608401610a28565b6000612dfe61084a6080890160608a01614aec565b9050612e1261012088016101008901615151565b6001811115612e2357612e23614c71565b81516001811115612e3657612e36614c71565b148015612e675750612e4e60e0880160c08901614aec565b6001600160a01b031681602001516001600160a01b0316145b612ebf5760405162461bcd60e51b815260206004820152602360248201527f4d61696e636861696e4761746577617956323a20696e76616c696420726563656044820152621a5c1d60ea1b6064820152608401610a28565b60008481526079602052604090205415612f415760405162461bcd60e51b815260206004820152603260248201527f4d61696e636861696e4761746577617956323a20717565727920666f7220707260448201527f6f636573736564207769746864726177616c00000000000000000000000000006064820152608401610a28565b6001612f5561012089016101008a01615151565b6001811115612f6657612f66614c71565b1480612f795750612f7782846133bc565b155b612feb5760405162461bcd60e51b815260206004820152603260248201527f4d61696e636861696e4761746577617956323a2072656163686564206461696c60448201527f79207769746864726177616c206c696d697400000000000000000000000000006064820152608401610a28565b6000612fff6112f3368a90038a018a614ff0565b9050600061300f607754836140e6565b6003549091506001600160a01b0316600061303d6130356101208d016101008e01615151565b878985614142565b60408051606081018252600080825260208201819052918101829052919b50919250819081906000805b8f5181101561323c578f818151811061308257613082614d90565b6020908102919091018101518051818301516040808401518151600081529586018083528f905260ff9093169085015260608401526080830152935060019060a0016020604051602081039080840390855afa1580156130e6573d6000803e3d6000fd5b505050602060405103519450846001600160a01b0316846001600160a01b0316106131795760405162461bcd60e51b815260206004820152602160248201527f4d61696e636861696e4761746577617956323a20696e76616c6964206f72646560448201527f72000000000000000000000000000000000000000000000000000000000000006064820152608401610a28565b6040517f953865650000000000000000000000000000000000000000000000000000000081526001600160a01b03808716600483015286955089169063953865659060240160206040518083038186803b1580156131d657600080fd5b505afa1580156131ea573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061320e9190614f89565b6132189083615484565b915086821061322a576001955061323c565b8061323481614e06565b915050613067565b50846132b05760405162461bcd60e51b815260206004820152603660248201527f4d61696e636861696e4761746577617956323a20717565727920666f7220696e60448201527f73756666696369656e7420766f746520776569676874000000000000000000006064820152608401610a28565b50505060008a81526079602052604090208690555050881561332c576000888152607a602052604090819020805460ff19166001179055517f89e52969465b1f1866fc5d46fd62de953962e9cb33552443cd999eba05bd20dc906133179086908e906150c4565b60405180910390a15050505050505050610aa7565b6133368688614233565b61337561334960608d0160408e01614aec565b87607460009054906101000a90046001600160a01b03168e61010001803603810190611583919061516e565b7f21e88e956aa3e086f6388e899965cef814688f99ad8bb29b08d396571016372d848c6040516133a69291906150c4565b60405180910390a1505050505050505092915050565b6001600160a01b0382166000908152603a602052604081205482106133e357506000610aa7565b60006133f2620151804261549c565b6001600160a01b0385166000908152603e60205260409020549091508111156134385750506001600160a01b0382166000908152603c6020526040902054811015610aa7565b6001600160a01b0384166000908152603d602052604090205461345c908490615484565b6001600160a01b0385166000908152603c602052604090205411159150610aa79050565b600060025460016002548460015461349891906151a1565b6134a29190615484565b6134ac919061518a565b610aa7919061549c565b60005460ff16156134fc5760405162461bcd60e51b815260206004820152601060248201526f14185d5cd8589b194e881c185d5cd95960821b6044820152606401610a28565b6000805460ff191660011790557f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258612c863390565b600061119883836142c3565b60007fb9d1fe7c9deeec5dc90a2f47ff1684239519f2545b2228d3d91fb27df3189eea60001b8260000151836020015161357a85604001516142ed565b61358786606001516142ed565b6135948760800151614350565b6040516020016135a9969594939291906154be565b604051602081830303815290604052805190602001209050919050565b6000620f42406135d683856151a1565b611198919061549c565b6000816001600160a01b0316836001600160a01b031614156136905760408086015190516001600160a01b0386169180156108fc02916000818181858888f1935050505061368b57816001600160a01b031663d0e30db086604001516040518263ffffffff1660e01b81526004016000604051808303818588803b15801561366757600080fd5b505af115801561367b573d6000803e3d6000fd5b505050505061368b858585614393565b613a0c565b6000855160018111156136a5576136a5614c71565b1415613866576040517f70a082310000000000000000000000000000000000000000000000000000000081523060048201526000906001600160a01b038516906370a082319060240160206040518083038186803b15801561370657600080fd5b505afa15801561371a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061373e9190614f89565b9050856040015181101561385557836001600160a01b03166340c10f193083896040015161376c919061518a565b6040516001600160a01b03909216602483015260448201526064016040516020818303038152906040529060e01b6020820180516001600160e01b0383818316178352505050506040516137c091906154f8565b6000604051808303816000865af19150503d80600081146137fd576040519150601f19603f3d011682016040523d82523d6000602084013e613802565b606091505b505080925050816138555760405162461bcd60e51b815260206004820152601b60248201527f546f6b656e3a204552433230206d696e74696e67206661696c656400000000006044820152606401610a28565b613860868686614393565b50613a0c565b60018551600181111561387b5761387b614c71565b141561399e5761389083858760200151614437565b61368b57602085810151604080516001600160a01b038881166024830152604480830194909452825180830390940184526064909101825292820180516001600160e01b03167f40c10f1900000000000000000000000000000000000000000000000000000000179052519185169161390991906154f8565b6000604051808303816000865af19150503d8060008114613946576040519150601f19603f3d011682016040523d82523d6000602084013e61394b565b606091505b5050809150508061368b5760405162461bcd60e51b815260206004820152601c60248201527f546f6b656e3a20455243373231206d696e74696e67206661696c6564000000006044820152606401610a28565b60405162461bcd60e51b815260206004820152602160248201527f546f6b656e3a20756e737570706f7274656420746f6b656e207374616e64617260448201527f64000000000000000000000000000000000000000000000000000000000000006064820152608401610a28565b5050505050565b6000610aa7825490565b600081516001811115613a3257613a32614c71565b148015613a43575060008160400151115b8015613a5157506020810151155b80613a7b5750600181516001811115613a6c57613a6c614c71565b148015613a7b57506040810151155b610a655760405162461bcd60e51b815260206004820152601360248201527f546f6b656e3a20696e76616c696420696e666f000000000000000000000000006044820152606401610a28565b600060608186516001811115613adf57613adf614c71565b1415613bbd5760408681015181516001600160a01b038881166024830152878116604483015260648083019390935283518083039093018352608490910183526020820180516001600160e01b03166323b872dd60e01b179052915191851691613b4991906154f8565b6000604051808303816000865af19150503d8060008114613b86576040519150601f19603f3d011682016040523d82523d6000602084013e613b8b565b606091505b509092509050818015613bb6575080511580613bb6575080806020019051810190613bb69190615514565b9150613c84565b600186516001811115613bd257613bd2614c71565b141561399e57602086810151604080516001600160a01b0389811660248301528881166044830152606480830194909452825180830390940184526084909101825292820180516001600160e01b03166323b872dd60e01b1790525191851691613c3c91906154f8565b6000604051808303816000865af19150503d8060008114613c79576040519150601f19603f3d011682016040523d82523d6000602084013e613c7e565b606091505b50909250505b81610f5c57613c92866144e2565b613ca6866001600160a01b03166014613e6d565b613cba866001600160a01b03166014613e6d565b613cce866001600160a01b03166014613e6d565b604051602001612bbf9493929190615536565b613d516040805160a08101825260008082526020808301829052835160608082018652838252818301849052818601849052848601919091528451808201865283815280830184905280860184905281850152845190810185528281529081018290529283015290608082015290565b83815260006020820181905250604080820180516001600160a01b039788169052602080890151825190891690820152905146908301528751606084018051918916909152805195909716940193909352935182015292909201516080820152919050565b60008281526072602090815260408083206001600160a01b038516845290915290205460ff166110175760008281526072602090815260408083206001600160a01b03851684529091529020805460ff19166001179055613e143390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b6000611198836001600160a01b03841661454f565b60606000613e7c8360026151a1565b613e87906002615484565b67ffffffffffffffff811115613e9f57613e9f614e21565b6040519080825280601f01601f191660200182016040528015613ec9576020820181803683370190505b5090507f300000000000000000000000000000000000000000000000000000000000000081600081518110613f0057613f00614d90565b60200101906001600160f81b031916908160001a9053507f780000000000000000000000000000000000000000000000000000000000000081600181518110613f4b57613f4b614d90565b60200101906001600160f81b031916908160001a9053506000613f6f8460026151a1565b613f7a906001615484565b90505b6001811115613fff577f303132333435363738396162636465660000000000000000000000000000000085600f1660108110613fbb57613fbb614d90565b1a60f81b828281518110613fd157613fd1614d90565b60200101906001600160f81b031916908160001a90535060049490941c93613ff881615606565b9050613f7d565b5083156111985760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610a28565b60008281526072602090815260408083206001600160a01b038516845290915290205460ff16156110175760008281526072602090815260408083206001600160a01b0385168085529252808320805460ff1916905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b6000611198836001600160a01b03841661459e565b604080517f19010000000000000000000000000000000000000000000000000000000000006020808301919091526022820185905260428083018590528351808403909101815260629092019092528051910120600090611198565b6000806000836001600160a01b031663926323d56040518163ffffffff1660e01b815260040160206040518083038186803b15801561418057600080fd5b505afa158015614194573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906141b89190614f89565b90506141c381613480565b925060008760018111156141d9576141d9614c71565b1415614229576001600160a01b038616600090815260396020526040902054851061420a5761420781614691565b92505b6001600160a01b0386166000908152603a602052604090205485101591505b5094509492505050565b6000614242620151804261549c565b6001600160a01b0384166000908152603e6020526040902054909150811115614291576001600160a01b03929092166000908152603e6020908152604080832094909455603d90529190912055565b6001600160a01b0383166000908152603d6020526040812080548492906142b9908490615484565b9091555050505050565b60008260000182815481106142da576142da614d90565b9060005260206000200154905092915050565b805160208083015160408085015190516000946135a9947f353bdd8d69b9e3185b3972e08b03845c0c14a21a390215302776a7a34b0e87649491939192019384526001600160a01b03928316602085015291166040830152606082015260800190565b805160208083015160408085015190516000946135a9947f1e2b74b2a792d5c0f0b6e59b037fa9d43d84fbb759337f0112fcc15ca414fc8d94919391920161561d565b600080845160018111156143a9576143a9614c71565b14156143c5576143be828486604001516146a9565b90506143ef565b6001845160018111156143da576143da614c71565b141561399e576143be82848660200151614437565b80610e89576143fd846144e2565b614411846001600160a01b03166014613e6d565b614425846001600160a01b03166014613e6d565b604051602001612bbf93929190615648565b604080513060248201526001600160a01b038481166044830152606480830185905283518084039091018152608490920183526020820180516001600160e01b03166323b872dd60e01b1790529151600092861691614495916154f8565b6000604051808303816000865af19150503d80600081146144d2576040519150601f19603f3d011682016040523d82523d6000602084013e6144d7565b606091505b509095945050505050565b606061450d826000015160018111156144fd576144fd614c71565b6001600160a01b03166001613e6d565b61451a8360200151614795565b6145278460400151614795565b604051602001614539939291906156d9565b6040516020818303038152906040529050919050565b600081815260018301602052604081205461459657508154600181810184556000848152602080822090930184905584548482528286019093526040902091909155610aa7565b506000610aa7565b600081815260018301602052604081205480156146875760006145c260018361518a565b85549091506000906145d69060019061518a565b905081811461463b5760008660000182815481106145f6576145f6614d90565b906000526020600020015490508087600001848154811061461957614619614d90565b6000918252602080832090910192909255918252600188019052604090208390555b855486908061464c5761464c6157a2565b600190038181906000526020600020016000905590558560010160008681526020019081526020016000206000905560019350505050610aa7565b6000915050610aa7565b600060385460016038548460375461349891906151a1565b604080516001600160a01b038481166024830152604480830185905283518084039091018152606490920183526020820180516001600160e01b03167fa9059cbb0000000000000000000000000000000000000000000000000000000017905291516000926060929087169161471f91906154f8565b6000604051808303816000865af19150503d806000811461475c576040519150601f19603f3d011682016040523d82523d6000602084013e614761565b606091505b50909250905081801561478c57508051158061478c57508080602001905181019061478c9190615514565b95945050505050565b6060816147d557505060408051808201909152600481527f3078303000000000000000000000000000000000000000000000000000000000602082015290565b8160005b81156147f857806147e981614e06565b915050600882901c91506147d9565b6111848482613e6d565b604080516060810182526000808252602082015290810161483e6040805160608101909152806000815260200160008152602001600081525090565b905290565b60006020828403121561485557600080fd5b81356001600160e01b03198116811461119857600080fd5b6001600160a01b0381168114610a6557600080fd5b803561198d8161486d565b8060608101831015610aa757600080fd5b8060808101831015610aa757600080fd5b60008083601f8401126148c157600080fd5b50813567ffffffffffffffff8111156148d957600080fd5b6020830191508360208260051b850101111561172857600080fd5b60008060008060008060008060008060006101408c8e03121561491657600080fd5b61491f8c614882565b9a5061492d60208d01614882565b995061493b60408d01614882565b985060608c0135975060808c0135965060a08c0135955060c08c0135945067ffffffffffffffff8060e08e0135111561497357600080fd5b6149838e60e08f01358f0161488d565b9450806101008e0135111561499757600080fd5b6149a88e6101008f01358f0161489e565b9350806101208e013511156149bc57600080fd5b506149ce8d6101208e01358e016148af565b81935080925050509295989b509295989b9093969950565b600080600080604085870312156149fc57600080fd5b843567ffffffffffffffff80821115614a1457600080fd5b614a20888389016148af565b90965094506020870135915080821115614a3957600080fd5b50614a46878288016148af565b95989497509550505050565b60008060008060008060608789031215614a6b57600080fd5b863567ffffffffffffffff80821115614a8357600080fd5b614a8f8a838b016148af565b90985096506020890135915080821115614aa857600080fd5b614ab48a838b016148af565b90965094506040890135915080821115614acd57600080fd5b50614ada89828a016148af565b979a9699509497509295939492505050565b600060208284031215614afe57600080fd5b81356111988161486d565b600060208284031215614b1b57600080fd5b5035919050565b60008060408385031215614b3557600080fd5b823591506020830135614b478161486d565b809150509250929050565b600060a08284031215614b6457600080fd5b50919050565b60006101608284031215614b6457600080fd5b60008060006101808486031215614b9357600080fd5b614b9d8585614b6a565b925061016084013567ffffffffffffffff80821115614bbb57600080fd5b818601915086601f830112614bcf57600080fd5b813581811115614bde57600080fd5b876020606083028501011115614bf357600080fd5b6020830194508093505050509250925092565b60008060408385031215614c1957600080fd5b8235614c248161486d565b946020939093013593505050565b60008060408385031215614c4557600080fd5b50508035926020909101359150565b60006101608284031215614c6757600080fd5b6111988383614b6a565b634e487b7160e01b600052602160045260246000fd5b60028110610a6557634e487b7160e01b600052602160045260246000fd5b81516040820190614cb581614c87565b808352506001600160a01b03602084015116602083015292915050565b60008060008060008060006080888a031215614ced57600080fd5b873567ffffffffffffffff80821115614d0557600080fd5b614d118b838c016148af565b909950975060208a0135915080821115614d2a57600080fd5b614d368b838c016148af565b909750955060408a0135915080821115614d4f57600080fd5b614d5b8b838c016148af565b909550935060608a0135915080821115614d7457600080fd5b50614d818a828b0161489e565b91505092959891949750929550565b634e487b7160e01b600052603260045260246000fd5b6000808335601e19843603018112614dbd57600080fd5b83018035915067ffffffffffffffff821115614dd857600080fd5b6020019150600581901b360382131561172857600080fd5b634e487b7160e01b600052601160045260246000fd5b6000600019821415614e1a57614e1a614df0565b5060010190565b634e487b7160e01b600052604160045260246000fd5b6040516060810167ffffffffffffffff81118282101715614e6857634e487b7160e01b600052604160045260246000fd5b60405290565b60028110610a6557600080fd5b600060608284031215614e8d57600080fd5b614e95614e37565b90508135614ea281614e6e565b80825250602082013560208201526040820135604082015292915050565b600060a08284031215614ed257600080fd5b614eda614e37565b8235614ee58161486d565b81526020830135614ef58161486d565b6020820152614f078460408501614e7b565b60408201529392505050565b600060608284031215614f2557600080fd5b6040516060810181811067ffffffffffffffff82111715614f5657634e487b7160e01b600052604160045260246000fd5b604052823560ff81168114614f6a57600080fd5b8152602083810135908201526040928301359281019290925250919050565b600060208284031215614f9b57600080fd5b5051919050565b600060608284031215614fb457600080fd5b614fbc614e37565b90508135614fc98161486d565b81526020820135614fd98161486d565b806020830152506040820135604082015292915050565b6000610160828403121561500357600080fd5b60405160a0810181811067ffffffffffffffff8211171561503457634e487b7160e01b600052604160045260246000fd5b60405282358152602083013561504981614e6e565b602082015261505b8460408501614fa2565b604082015261506d8460a08501614fa2565b6060820152615080846101008501614e7b565b60808201529392505050565b80356150978161486d565b6001600160a01b0390811683526020820135906150b38261486d565b166020830152604090810135910152565b6000610180820190508382528235602083015260208301356150e581614e6e565b6150ee81614c87565b80604084015250615105606083016040850161508c565b61511560c0830160a0850161508c565b61012061010084013561512781614e6e565b61513081614c87565b81840152830135610140808401919091529092013561016090910152919050565b60006020828403121561516357600080fd5b813561119881614e6e565b60006060828403121561518057600080fd5b6111988383614e7b565b60008282101561519c5761519c614df0565b500390565b60008160001904831182151516156151bb576151bb614df0565b500290565b6000610180820190508382528251602083015260208301516151e181614c87565b6040838101919091528381015180516001600160a01b03908116606086015260208201511660808501529081015160a084015250606083015180516001600160a01b0390811660c085015260208201511660e08401526040810151610100840152506080830151805161525381614c87565b6101208401526020810151610140840152604001516101609092019190915292915050565b8183526000602080850194508260005b858110156152b657813561529b8161486d565b6001600160a01b031687529582019590820190600101615288565b509495945050505050565b6060815260006152d560608301888a615278565b6020838203818501526152e982888a615278565b8481036040860152858152869250810160005b8681101561532a57833561530f81614e6e565b61531881614c87565b825292820192908201906001016152fc565b509a9950505050505050505050565b60408152600061534d604083018688615278565b82810360208401528381527f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84111561538557600080fd5b8360051b80866020840137600091016020019081529695505050505050565b60005b838110156153bf5781810151838201526020016153a7565b83811115610e895750506000910152565b7f416363657373436f6e74726f6c3a206163636f756e74200000000000000000008152600083516154088160178501602088016153a4565b7f206973206d697373696e6720726f6c652000000000000000000000000000000060179184019182015283516154458160288401602088016153a4565b01602801949350505050565b60208152600082518060208401526154708160408501602087016153a4565b601f01601f19169190910160400192915050565b6000821982111561549757615497614df0565b500190565b6000826154b957634e487b7160e01b600052601260045260246000fd5b500490565b8681526020810186905260c081016154d586614c87565b8560408301528460608301528360808301528260a0830152979650505050505050565b6000825161550a8184602087016153a4565b9190910192915050565b60006020828403121561552657600080fd5b8151801515811461119857600080fd5b7f546f6b656e3a20636f756c64206e6f74207472616e7366657220000000000000815260008551602061556f82601a8601838b016153a4565b7f2066726f6d200000000000000000000000000000000000000000000000000000601a9285019283015286516155aa81838501848b016153a4565b630103a37960e51b92018181019290925285516155cd81602485018985016153a4565b660103a37b5b2b7160cd1b6024939091019283015284516155f481602b85018489016153a4565b91909101602b01979650505050505050565b60008161561557615615614df0565b506000190190565b8481526080810161562d85614c87565b84602083015283604083015282606083015295945050505050565b7f546f6b656e3a20636f756c64206e6f74207472616e736665722000000000000081526000845161568081601a8501602089016153a4565b630103a37960e51b601a9184019182015284516156a481601e8401602089016153a4565b660103a37b5b2b7160cd1b601e929091019182015283516156cc8160258401602088016153a4565b0160250195945050505050565b7f546f6b656e496e666f280000000000000000000000000000000000000000000081526000845161571181600a8501602089016153a4565b80830190507f2c0000000000000000000000000000000000000000000000000000000000000080600a830152855161575081600b850160208a016153a4565b600b920191820152835161576b81600c8401602088016153a4565b7f2900000000000000000000000000000000000000000000000000000000000000600c9290910191820152600d0195945050505050565b634e487b7160e01b600052603160045260246000fdfeb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610348617350726f787941646d696e3a20756e617574686f72697a65642073656e64") + ]; +} diff --git a/crates/forge/bin/cmd/config.rs b/crates/forge/bin/cmd/config.rs index 731a545e4..fc325e39d 100644 --- a/crates/forge/bin/cmd/config.rs +++ b/crates/forge/bin/cmd/config.rs @@ -11,22 +11,22 @@ foundry_config::impl_figment_convert!(ConfigArgs, opts, evm_opts); #[derive(Clone, Debug, Parser)] pub struct ConfigArgs { /// Print only a basic set of the currently set config values. - #[clap(long)] + #[arg(long)] basic: bool, /// Print currently set config values as JSON. - #[clap(long)] + #[arg(long)] json: bool, /// Attempt to fix any configuration warnings. - #[clap(long)] + #[arg(long)] fix: bool, // support nested build arguments - #[clap(flatten)] + #[command(flatten)] opts: BuildArgs, - #[clap(flatten)] + #[command(flatten)] evm_opts: EvmArgs, } @@ -39,7 +39,10 @@ impl ConfigArgs { return Ok(()) } - let config = self.try_load_config_unsanitized_emit_warnings()?; + let config = self + .try_load_config_unsanitized_emit_warnings()? + // we explicitly normalize the version, so mimic the behavior when invoking solc + .normalized_evm_version(); let s = if self.basic { let config = config.into_basic(); diff --git a/crates/forge/bin/cmd/coverage.rs b/crates/forge/bin/cmd/coverage.rs index c9d022bb8..cfea25043 100644 --- a/crates/forge/bin/cmd/coverage.rs +++ b/crates/forge/bin/cmd/coverage.rs @@ -1,40 +1,37 @@ -use super::{install, test::FilterArgs}; +use super::{install, test::TestArgs}; use alloy_primitives::{Address, Bytes, U256}; use clap::{Parser, ValueEnum, ValueHint}; use eyre::{Context, Result}; use forge::{ coverage::{ - analysis::SourceAnalyzer, anchors::find_anchors, BytecodeReporter, ContractId, - CoverageReport, CoverageReporter, DebugReporter, ItemAnchor, LcovReporter, SummaryReporter, + analysis::{SourceAnalysis, SourceAnalyzer, SourceFile, SourceFiles}, + anchors::find_anchors, + BytecodeReporter, ContractId, CoverageReport, CoverageReporter, DebugReporter, ItemAnchor, + LcovReporter, SummaryReporter, }, - inspectors::CheatsConfig, opts::EvmOpts, - result::SuiteResult, - revm::primitives::SpecId, utils::IcPcMap, MultiContractRunnerBuilder, TestOptions, }; use foundry_cli::{ - opts::CoreBuildArgs, p_println, utils::{LoadConfig, STATIC_FUZZ_SEED}, }; -use foundry_common::{compile::ProjectCompiler, evm::EvmArgs, fs}; +use foundry_common::{compile::ProjectCompiler, fs}; use foundry_compilers::{ - artifacts::{contract::CompactContractBytecode, Ast, CompactBytecode, CompactDeployedBytecode}, - sourcemap::SourceMap, - Artifact, Project, ProjectCompileOutput, + artifacts::{sourcemap::SourceMap, CompactBytecode, CompactDeployedBytecode}, + Artifact, ArtifactId, Project, ProjectCompileOutput, }; use foundry_config::{Config, SolcReq}; +use foundry_zksync_compiler::DualCompiledContracts; +use rayon::prelude::*; +use rustc_hash::FxHashMap; use semver::Version; -use std::{collections::HashMap, path::PathBuf, sync::mpsc::channel}; +use std::{collections::HashMap, path::PathBuf, sync::Arc}; use yansi::Paint; -/// A map, keyed by contract ID, to a tuple of the deployment source map and the runtime source map. -type SourceMaps = HashMap; - // Loads project's figment and merges the build cli arguments into it -foundry_config::impl_figment_convert!(CoverageArgs, opts, evm_opts); +foundry_config::impl_figment_convert!(CoverageArgs, test); /// CLI arguments for `forge coverage`. #[derive(Clone, Debug, Parser)] @@ -42,20 +39,20 @@ pub struct CoverageArgs { /// The report type to use for coverage. /// /// This flag can be used multiple times. - #[clap(long, value_enum, default_value = "summary")] + #[arg(long, value_enum, default_value = "summary")] report: Vec, /// Enable viaIR with minimum optimization /// /// This can fix most of the "stack too deep" errors while resulting a /// relatively accurate source map. - #[clap(long)] + #[arg(long)] ir_minimum: bool, /// The path to output the report. /// /// If not specified, the report will be stored in the root of the project. - #[clap( + #[arg( long, short, value_hint = ValueHint::FilePath, @@ -63,14 +60,12 @@ pub struct CoverageArgs { )] report_file: Option, - #[clap(flatten)] - filter: FilterArgs, - - #[clap(flatten)] - evm_opts: EvmArgs, + /// Whether to include libraries in the coverage report. + #[arg(long)] + include_libs: bool, - #[clap(flatten)] - opts: CoreBuildArgs, + #[command(flatten)] + test: TestArgs, } impl CoverageArgs { @@ -78,7 +73,7 @@ impl CoverageArgs { let (mut config, evm_opts) = self.load_config_and_evm_opts_emit_warnings()?; // install missing dependencies - if install::install_missing_dependencies(&mut config, self.build_args().silent) && + if install::install_missing_dependencies(&mut config, self.test.build_args().silent) && config.auto_detect_remappings { // need to re-configure here to also catch additional remappings @@ -92,17 +87,17 @@ impl CoverageArgs { config.ast = true; let (project, output) = self.build(&config)?; - p_println!(!self.opts.silent => "Analysing contracts..."); - let report = self.prepare(&config, output.clone())?; + p_println!(!self.test.build_args().silent => "Analysing contracts..."); + let report = self.prepare(&project, &output)?; - p_println!(!self.opts.silent => "Running tests..."); - self.collect(project, output, report, config, evm_opts).await + p_println!(!self.test.build_args().silent => "Running tests..."); + self.collect(project, output, report, Arc::new(config), evm_opts).await } /// Builds the project. fn build(&self, config: &Config) -> Result<(Project, ProjectCompileOutput)> { // Set up the project - let mut project = config.ephemeral_no_artifacts_project()?; + let mut project = config.create_project(false, false)?; if self.ir_minimum { // TODO: How to detect solc version if the user does not specify a solc version in // config case1: specify local installed solc ? @@ -116,26 +111,25 @@ impl CoverageArgs { } // print warning message - let msg = Paint::yellow(concat!( + let msg = concat!( "Warning! \"--ir-minimum\" flag enables viaIR with minimum optimization, \ which can result in inaccurate source mappings.\n", "Only use this flag as a workaround if you are experiencing \"stack too deep\" errors.\n", "Note that \"viaIR\" is only available in Solidity 0.8.13 and above.\n", "See more: https://github.com/foundry-rs/foundry/issues/3357", - )); - p_println!(!self.opts.silent => "{msg}"); + ).yellow(); + p_println!(!self.test.build_args().silent => "{msg}"); // Enable viaIR with minimum optimization // https://github.com/ethereum/solidity/issues/12533#issuecomment-1013073350 // And also in new releases of solidity: // https://github.com/ethereum/solidity/issues/13972#issuecomment-1628632202 - project.solc_config.settings = - project.solc_config.settings.with_via_ir_minimum_optimization() + project.settings.solc = project.settings.solc.with_via_ir_minimum_optimization() } else { - project.solc_config.settings.optimizer.disable(); - project.solc_config.settings.optimizer.runs = None; - project.solc_config.settings.optimizer.details = None; - project.solc_config.settings.via_ir = None; + project.settings.solc.optimizer.disable(); + project.settings.solc.optimizer.runs = None; + project.settings.solc.optimizer.details = None; + project.settings.solc.via_ir = None; } let output = ProjectCompiler::default() @@ -147,137 +141,80 @@ impl CoverageArgs { /// Builds the coverage report. #[instrument(name = "prepare", skip_all)] - fn prepare(&self, config: &Config, output: ProjectCompileOutput) -> Result { - let project_paths = config.project_paths(); - - // Extract artifacts - let (artifacts, sources) = output.into_artifacts_with_sources(); + fn prepare(&self, project: &Project, output: &ProjectCompileOutput) -> Result { let mut report = CoverageReport::default(); - // Collect ASTs and sources - let mut versioned_asts: HashMap> = HashMap::new(); - let mut versioned_sources: HashMap> = HashMap::new(); - for (path, mut source_file, version) in sources.into_sources_with_version() { + // Collect source files. + let project_paths = &project.paths; + let mut versioned_sources = HashMap::>::new(); + for (path, source_file, version) in output.output().sources.sources_with_version() { report.add_source(version.clone(), source_file.id as usize, path.clone()); // Filter out dependencies - if project_paths.has_library_ancestor(std::path::Path::new(&path)) { - continue + if !self.include_libs && project_paths.has_library_ancestor(path) { + continue; } - if let Some(ast) = source_file.ast.take() { - versioned_asts - .entry(version.clone()) - .or_default() - .insert(source_file.id as usize, ast); - - let file = project_paths.root.join(&path); + if let Some(ast) = &source_file.ast { + let file = project_paths.root.join(path); trace!(root=?project_paths.root, ?file, "reading source file"); - versioned_sources.entry(version.clone()).or_default().insert( - source_file.id as usize, - fs::read_to_string(&file) + let source = SourceFile { + ast, + source: fs::read_to_string(&file) .wrap_err("Could not read source code for analysis")?, - ); + }; + versioned_sources + .entry(version.clone()) + .or_default() + .sources + .insert(source_file.id as usize, source); } } // Get source maps and bytecodes - let (source_maps, bytecodes): (SourceMaps, HashMap) = artifacts - .into_iter() - .map(|(id, artifact)| (id, CompactContractBytecode::from(artifact))) + let artifacts: Vec = output + .artifact_ids() + .par_bridge() .filter_map(|(id, artifact)| { - let contract_id = ContractId { - version: id.version.clone(), - source_id: *report - .get_source_id(id.version, id.source.to_string_lossy().to_string())?, - contract_name: id.name, - }; - let source_maps = ( - contract_id.clone(), - ( - artifact.get_source_map()?.ok()?, - artifact - .get_deployed_bytecode() - .as_ref()? - .bytecode - .as_ref()? - .source_map()? - .ok()?, - ), - ); - let bytecodes = ( - contract_id, - ( - artifact - .get_bytecode() - .and_then(|bytecode| dummy_link_bytecode(bytecode.into_owned()))?, - artifact.get_deployed_bytecode().and_then(|bytecode| { - dummy_link_deployed_bytecode(bytecode.into_owned()) - })?, - ), - ); - - Some((source_maps, bytecodes)) - }) - .unzip(); - - // Build IC -> PC mappings - // - // The source maps are indexed by *instruction counters*, which are the indexes of - // instructions in the bytecode *minus any push bytes*. - // - // Since our coverage inspector collects hit data using program counters, the anchors also - // need to be based on program counters. - // TODO: Index by contract ID - let ic_pc_maps: HashMap = bytecodes - .iter() - .map(|(id, bytecodes)| { - // TODO: Creation bytecode as well - ( - id.clone(), - ( - IcPcMap::new(SpecId::LATEST, bytecodes.0.as_ref()), - IcPcMap::new(SpecId::LATEST, bytecodes.1.as_ref()), - ), - ) + let source_id = report.get_source_id(id.version.clone(), id.source.clone())?; + ArtifactData::new(&id, source_id, artifact) }) .collect(); // Add coverage items - for (version, asts) in versioned_asts.into_iter() { - let source_analysis = SourceAnalyzer::new( - version.clone(), - asts, - versioned_sources.remove(&version).ok_or_else(|| { - eyre::eyre!( - "File tree is missing source code, cannot perform coverage analysis" - ) - })?, - )? - .analyze()?; - let anchors: HashMap> = source_analysis - .contract_items - .iter() - .filter_map(|(contract_id, item_ids)| { - // TODO: Creation source map/bytecode as well - Some(( - contract_id.clone(), - find_anchors( - &bytecodes.get(contract_id)?.1, - &source_maps.get(contract_id)?.1, - &ic_pc_maps.get(contract_id)?.1, - item_ids, - &source_analysis.items, - ), - )) + for (version, sources) in &versioned_sources { + let source_analysis = SourceAnalyzer::new(sources).analyze()?; + + // Build helper mapping used by `find_anchors` + let mut items_by_source_id = FxHashMap::<_, Vec<_>>::with_capacity_and_hasher( + source_analysis.items.len(), + Default::default(), + ); + + for (item_id, item) in source_analysis.items.iter().enumerate() { + items_by_source_id.entry(item.loc.source_id).or_default().push(item_id); + } + + let anchors = artifacts + .par_iter() + .filter(|artifact| artifact.contract_id.version == *version) + .map(|artifact| { + let creation_code_anchors = + artifact.creation.find_anchors(&source_analysis, &items_by_source_id); + let deployed_code_anchors = + artifact.deployed.find_anchors(&source_analysis, &items_by_source_id); + (artifact.contract_id.clone(), (creation_code_anchors, deployed_code_anchors)) }) - .collect(); - report.add_items(version, source_analysis.items); + .collect::>(); + report.add_anchors(anchors); + report.add_items(version.clone(), source_analysis.items); } - report.add_source_maps(source_maps); + report.add_source_maps(artifacts.into_iter().map(|artifact| { + (artifact.contract_id, (artifact.creation.source_map, artifact.deployed.source_map)) + })); Ok(report) } @@ -288,78 +225,68 @@ impl CoverageArgs { project: Project, output: ProjectCompileOutput, mut report: CoverageReport, - config: Config, + config: Arc, evm_opts: EvmOpts, ) -> Result<()> { let root = project.paths.root; + let verbosity = evm_opts.verbosity; // Build the contract runner let env = evm_opts.evm_env().await?; - let mut runner = MultiContractRunnerBuilder::default() + let runner = MultiContractRunnerBuilder::new(config.clone()) .initial_balance(evm_opts.initial_balance) .evm_spec(config.evm_spec_id()) .sender(evm_opts.sender) .with_fork(evm_opts.get_fork(&config, env.clone())) - .with_cheats_config(CheatsConfig::new( - &config, - evm_opts.clone(), - None, - Default::default(), - false, - )) .with_test_options(TestOptions { - fuzz: config.fuzz, - invariant: config.invariant, + fuzz: config.fuzz.clone(), + invariant: config.invariant.clone(), ..Default::default() }) .set_coverage(true) - .build(&root, output, None, env, evm_opts)?; + .build(&root, output, None, env, evm_opts, DualCompiledContracts::default())?; - // Run tests let known_contracts = runner.known_contracts.clone(); - let filter = self.filter; - let (tx, rx) = channel::<(String, SuiteResult)>(); - let handle = tokio::task::spawn(async move { - runner.test(&filter, tx, runner.test_options.clone()).await - }); + + let outcome = self + .test + .run_tests(runner, config.clone(), verbosity, &self.test.filter(&config)) + .await?; // Add hit data to the coverage report - let data = rx - .into_iter() - .flat_map(|(_, suite)| suite.test_results.into_values()) - .filter_map(|mut result| result.coverage.take()) - .flat_map(|hit_maps| { - hit_maps.0.into_values().filter_map(|map| { - Some((known_contracts.find_by_code(map.bytecode.as_ref())?.0, map)) - }) - }); - for (artifact_id, hits) in data { - // TODO: Note down failing tests - if let Some(source_id) = report.get_source_id( - artifact_id.version.clone(), - artifact_id.source.to_string_lossy().to_string(), - ) { - let source_id = *source_id; - // TODO: Distinguish between creation/runtime in a smart way + let data = outcome.results.iter().flat_map(|(_, suite)| { + let mut hits = Vec::new(); + for result in suite.test_results.values() { + let Some(hit_maps) = result.coverage.as_ref() else { continue }; + for map in hit_maps.0.values() { + if let Some((id, _)) = known_contracts.find_by_deployed_code(&map.bytecode) { + hits.push((id, map, true)); + } else if let Some((id, _)) = + known_contracts.find_by_creation_code(&map.bytecode) + { + hits.push((id, map, false)); + } + } + } + hits + }); + + for (artifact_id, map, is_deployed_code) in data { + if let Some(source_id) = + report.get_source_id(artifact_id.version.clone(), artifact_id.source.clone()) + { report.add_hit_map( &ContractId { version: artifact_id.version.clone(), source_id, - contract_name: artifact_id.name.clone(), + contract_name: artifact_id.name.as_str().into(), }, - &hits, + map, + is_deployed_code, )?; } } - // Reattach the thread - if let Err(e) = handle.await { - match e.try_into_panic() { - Ok(payload) => std::panic::resume_unwind(payload), - Err(e) => return Err(e.into()), - } - } - // Output final report for report_kind in self.report { match report_kind { @@ -384,11 +311,6 @@ impl CoverageArgs { } Ok(()) } - - /// Returns the flattened [`CoreBuildArgs`] - pub fn build_args(&self) -> &CoreBuildArgs { - &self.opts - } } // TODO: HTML @@ -421,3 +343,67 @@ fn dummy_link_bytecode(mut obj: CompactBytecode) -> Option { fn dummy_link_deployed_bytecode(obj: CompactDeployedBytecode) -> Option { obj.bytecode.and_then(dummy_link_bytecode) } + +pub struct ArtifactData { + pub contract_id: ContractId, + pub creation: BytecodeData, + pub deployed: BytecodeData, +} + +impl ArtifactData { + pub fn new(id: &ArtifactId, source_id: usize, artifact: &impl Artifact) -> Option { + Some(Self { + contract_id: ContractId { + version: id.version.clone(), + source_id, + contract_name: id.name.as_str().into(), + }, + creation: BytecodeData::new( + artifact.get_source_map()?.ok()?, + artifact + .get_bytecode() + .and_then(|bytecode| dummy_link_bytecode(bytecode.into_owned()))?, + ), + deployed: BytecodeData::new( + artifact.get_source_map_deployed()?.ok()?, + artifact + .get_deployed_bytecode() + .and_then(|bytecode| dummy_link_deployed_bytecode(bytecode.into_owned()))?, + ), + }) + } +} + +pub struct BytecodeData { + source_map: SourceMap, + bytecode: Bytes, + /// The instruction counter to program counter mapping. + /// + /// The source maps are indexed by *instruction counters*, which are the indexes of + /// instructions in the bytecode *minus any push bytes*. + /// + /// Since our coverage inspector collects hit data using program counters, the anchors + /// also need to be based on program counters. + ic_pc_map: IcPcMap, +} + +impl BytecodeData { + fn new(source_map: SourceMap, bytecode: Bytes) -> Self { + let ic_pc_map = IcPcMap::new(&bytecode); + Self { source_map, bytecode, ic_pc_map } + } + + pub fn find_anchors( + &self, + source_analysis: &SourceAnalysis, + items_by_source_id: &FxHashMap>, + ) -> Vec { + find_anchors( + &self.bytecode, + &self.source_map, + &self.ic_pc_map, + &source_analysis.items, + items_by_source_id, + ) + } +} diff --git a/crates/forge/bin/cmd/create.rs b/crates/forge/bin/cmd/create.rs index fce233a7c..17bd72243 100644 --- a/crates/forge/bin/cmd/create.rs +++ b/crates/forge/bin/cmd/create.rs @@ -1,41 +1,30 @@ -use super::{retry::RetryArgs, verify}; - -use alloy_dyn_abi::{DynSolValue, JsonAbiExt, ResolveSolType}; +use alloy_chains::Chain; +use alloy_dyn_abi::{DynSolValue, JsonAbiExt, Specifier}; use alloy_json_abi::{Constructor, JsonAbi}; -use alloy_primitives::{Address, Bytes}; - +use alloy_network::{AnyNetwork, EthereumWallet, TransactionBuilder}; +use alloy_primitives::{hex, Address, Bytes}; +use alloy_provider::{Provider, ProviderBuilder}; +use alloy_rpc_types::{AnyTransactionReceipt, TransactionRequest}; +use alloy_serde::WithOtherFields; +use alloy_signer::Signer; +use alloy_transport::{Transport, TransportError}; use clap::{Parser, ValueHint}; -use ethers_contract::ContractError; -use ethers_core::{ - abi::InvalidOutputType, - types::{ - transaction::eip2718::TypedTransaction, BlockNumber, Eip1559TransactionRequest, - NameOrAddress, TransactionReceipt, TransactionRequest, - }, -}; -use ethers_middleware::SignerMiddleware; -use ethers_providers::Middleware; use eyre::{Context, Result}; +use forge_verify::RetryArgs; use foundry_cli::{ opts::{CoreBuildArgs, EthereumOpts, EtherscanOpts, TransactionOpts}, - utils::{self, read_constructor_args_file, remove_contract, LoadConfig}, + utils::{self, read_constructor_args_file, remove_contract, remove_zk_contract, LoadConfig}, }; use foundry_common::{ - compile::ProjectCompiler, + compile::{self, ProjectCompiler}, fmt::parse_tokens, - provider::ethers::estimate_eip1559_fees, - types::{ToAlloy, ToEthers}, }; use foundry_compilers::{ - artifacts::BytecodeObject, info::ContractInfo, utils::canonicalized, + artifacts::BytecodeObject, info::ContractInfo, utils::canonicalize, zksync::artifact_output::zk::ZkContractArtifact, }; -use foundry_config::Chain; use foundry_wallets::WalletSigner; -use foundry_zksync_compiler::libraries as zklibs; -use itertools::Either; -use zksync_types::H256; - +use foundry_zksync_core::convert::ConvertH160; use serde_json::json; use std::{ borrow::Borrow, @@ -45,15 +34,16 @@ use std::{ str::FromStr, sync::Arc, }; +use zksync_types::H256; /// CLI arguments for `forge create`. #[derive(Clone, Debug, Parser)] pub struct CreateArgs { /// The contract identifier in the form `:`. - contract: Option, + contract: ContractInfo, /// The constructor arguments. - #[clap( + #[arg( long, num_args(1..), conflicts_with = "constructor_args_path", @@ -62,7 +52,7 @@ pub struct CreateArgs { constructor_args: Vec, /// The path to a file containing the constructor arguments. - #[clap( + #[arg( long, value_hint = ValueHint::FilePath, value_name = "PATH", @@ -70,290 +60,260 @@ pub struct CreateArgs { )] constructor_args_path: Option, - /// Deploy the missing dependency libraries from last build. - #[clap( - long, - help = "Deploy the missing dependency libraries from last build.", - default_value_t = false, - conflicts_with = "contract" - )] - deploy_missing_libraries: bool, - /// Print the deployment information as JSON. - #[clap(long, help_heading = "Display options")] + #[arg(long, help_heading = "Display options")] json: bool, /// Verify contract after creation. - #[clap(long)] + #[arg(long)] verify: bool, /// Send via `eth_sendTransaction` using the `--from` argument or `$ETH_FROM` as sender - #[clap(long, requires = "from")] + #[arg(long, requires = "from")] unlocked: bool, /// Prints the standard json compiler input if `--verify` is provided. /// /// The standard json compiler input can be used to manually submit contract verification in /// the browser. - #[clap(long, requires = "verify")] + #[arg(long, requires = "verify")] show_standard_json_input: bool, - #[clap(flatten)] + #[command(flatten)] opts: CoreBuildArgs, - #[clap(flatten)] + #[command(flatten)] tx: TransactionOpts, - #[clap(flatten)] + #[command(flatten)] eth: EthereumOpts, - #[clap(flatten)] - pub verifier: verify::VerifierArgs, + #[command(flatten)] + pub verifier: forge_verify::VerifierArgs, - #[clap(flatten)] + #[command(flatten)] retry: RetryArgs, } +/// Data used to deploy a contract on zksync +pub struct ZkSyncData { + bytecode: Vec, + bytecode_hash: H256, + factory_deps: Vec>, +} + impl CreateArgs { /// Executes the command to create a contract - pub async fn run(self) -> Result<()> { - let mut config = self.eth.try_load_config_emit_warnings()?; - let project_root = config.project_paths().root; + pub async fn run(mut self) -> Result<()> { + // Find Project & Compile + let project = self.opts.project()?; let zksync = self.opts.compiler.zk.enabled(); + if zksync { + let target_path = if let Some(ref mut path) = self.contract.path { + canonicalize(project.root().join(path))? + } else { + project.find_contract_path(&self.contract.name)? + }; - // Resolve missing libraries - let libs_batches = if zksync && self.deploy_missing_libraries { - let missing_libraries = zklibs::get_detected_missing_libraries(&project_root)?; - - let mut all_deployed_libraries = Vec::with_capacity(config.libraries.len()); - for library in &config.libraries { - let split_lib = library.split(':').collect::>(); - let lib_path = split_lib[0]; - let lib_name = split_lib[1]; - all_deployed_libraries.push(ContractInfo { - name: lib_name.to_string(), - path: Some(lib_path.to_string()), - }); - } - info!("Resolving missing libraries"); - - zklibs::resolve_libraries(missing_libraries, &all_deployed_libraries)? - } else { - vec![] - }; - - let deploying_libraries = !libs_batches.is_empty(); - let contracts_to_deploy = if !deploying_libraries { - vec![vec![self - .contract - .clone() - .ok_or_else(|| eyre::eyre!("Contract to deploy must be passed"))?]] - } else { - libs_batches - }; - - for contracts_batch in contracts_to_deploy { - // Find Project & Compile - let project = self.opts.project()?; - let files = contracts_batch.iter().map(|lib| { - canonicalized( - project.root().join(lib.path.clone().expect("libraries must specify path")), - ) - }); - - let compiler = - ProjectCompiler::new().quiet_if(self.json || self.opts.silent).files(files.clone()); + let config = self.opts.try_load_config_emit_warnings()?; + let zk_project = foundry_zksync_compiler::create_project(&config, config.cache, false)?; + let zk_compiler = ProjectCompiler::new() + .quiet(self.json || self.opts.silent) + .files([target_path.clone()]); + let mut zk_output = + zk_compiler.zksync_compile(&zk_project, config.zksync.avoid_contracts())?; + + let artifact = remove_zk_contract(&mut zk_output, &target_path, &self.contract.name)?; + + let ZkContractArtifact { bytecode, hash, factory_dependencies, metadata, .. } = + artifact; + + // Get abi from solc_metadata + // TODO: This can probably be optimized by defining the proper + // deserializers on compilers but metadata is given as a stringified json + // and JsonAbi is complaining about not supporting serde_json::from_reader + // so there is some serde handling needed + let metadata = metadata.unwrap(); + let solc_metadata_value = metadata + .get("solc_metadata") + .and_then(serde_json::Value::as_str) + .expect("`solc_metadata` field not found in artifact"); + let solc_metadata_json: serde_json::Value = + serde_json::from_str(solc_metadata_value).unwrap(); + let abi_json = &solc_metadata_json["output"]["abi"]; + let abi_string = abi_json.to_string(); + let abi: JsonAbi = JsonAbi::from_json_str(&abi_string)?; + + let bin = bytecode.expect("Bytecode not found"); + let bytecode_hash = H256::from_str(&hash.expect("Contract hash not found"))?; + let bytecode = bin.object.clone().into_bytes().unwrap().to_vec(); + + // Add arguments to constructor + let config = self.eth.try_load_config_emit_warnings()?; + let provider = utils::get_provider(&config)?; + let params = match abi.constructor { + Some(ref v) => { + let constructor_args = + if let Some(ref constructor_args_path) = self.constructor_args_path { + read_constructor_args_file(constructor_args_path.to_path_buf())? + } else { + self.constructor_args.clone() + }; + self.parse_constructor_args(v, &constructor_args)? + } + None => vec![], + }; - let mut output = if !zksync { - Either::Left(compiler.compile(&project)?) + // respect chain, if set explicitly via cmd args + let chain_id = if let Some(chain_id) = self.chain_id() { + chain_id } else { - Either::Right(compiler.zksync_compile(&project)?) + provider.get_chain_id().await? }; - for mut contract in contracts_batch { - if let Some(ref mut path) = contract.path { - // paths are absolute in the project's output - *path = canonicalized(project.root().join(&path)).to_string_lossy().to_string(); + let factory_deps: Vec> = { + let factory_dependencies_map = + factory_dependencies.expect("factory deps not found"); + let mut visited_paths = HashSet::new(); + let mut visited_bytecodes = HashSet::new(); + let mut queue = VecDeque::new(); + + for dep in factory_dependencies_map.values() { + queue.push_back(dep.clone()); } - let process_evm_output = |output| { - let (abi, bin, _) = remove_contract(output, &contract)?; - let bytecode_object = match bin.object { - BytecodeObject::Bytecode(_) => bin.object, - _ => { - let link_refs = bin - .link_references - .iter() - .flat_map(|(path, names)| { - names.keys().map(move |name| format!("\t{name}: {path}")) - }) - .collect::>() - .join("\n"); - eyre::bail!("Dynamic linking not supported in `create` command - deploy the following library contracts first, then provide the address to link at compile time\n{}", link_refs) + while let Some(dep_info) = queue.pop_front() { + if visited_paths.insert(dep_info.clone()) { + let mut split = dep_info.split(':'); + let contract_path = split + .next() + .expect("Failed to extract contract path for factory dependency"); + let contract_name = split + .next() + .expect("Failed to extract contract name for factory dependency"); + let mut abs_path_buf = PathBuf::new(); + abs_path_buf.push(project.root()); + abs_path_buf.push(contract_path); + let abs_path_str = abs_path_buf.to_string_lossy(); + let fdep_art = + zk_output.find(abs_path_str, contract_name).unwrap_or_else(|| { + panic!( + "Could not find contract {contract_name} at path {contract_path} for compilation output", + ) + }); + let fdep_fdeps_map = + fdep_art.factory_dependencies.clone().expect("factory deps not found"); + for dep in fdep_fdeps_map.values() { + queue.push_back(dep.clone()) } - }; - Ok((abi, bytecode_object, None)) - }; - - let process_zk_output = |zk_output: &mut foundry_compilers::zksync::compile::output::ProjectCompileOutput| { - let artifact = - zk_output.remove_contract(&contract).expect("Artifact not found"); - let ZkContractArtifact { - bytecode, hash, factory_dependencies, metadata, .. - } = artifact; - - // Get abi from solc_metadata - // TODO: This can probably be optimized by defining the proper - // deserializers on compilers but metadata is given as a stringified - // json and JsonAbi is complaining about not - // supporting serde_json::from_reader - // so there is some serde handling neded - let metadata = metadata.unwrap(); - let solc_metadata_value = metadata - .get("solc_metadata") - .and_then(serde_json::Value::as_str) - .expect("`solc_metadata` field not found in artifact"); - let solc_metadata_json: serde_json::Value = - serde_json::from_str(solc_metadata_value).unwrap(); - let abi_json = &solc_metadata_json["output"]["abi"]; - let abi_string = abi_json.to_string(); - let abi: JsonAbi = JsonAbi::from_json_str(&abi_string)?; - - let bin = bytecode.expect("Bytecode not found"); - let bytecode_hash = H256::from_str(&hash.expect("Contract hash not found"))?; - let bytecode = bin.object.clone().into_bytes().unwrap().to_vec(); - - let factory_dependencies_map = - factory_dependencies.expect("factory deps not found"); - let mut visited_paths = HashSet::new(); - let mut visited_bytecodes = HashSet::new(); - let mut queue = VecDeque::new(); - - for dep in factory_dependencies_map.values() { - queue.push_back(dep.clone()); - } - while let Some(dep_info) = queue.pop_front() { - if visited_paths.insert(dep_info.clone()) { - let mut split = dep_info.split(':'); - let contract_path = split - .next() - .expect("Failed to extract contract path for factory dependency"); - let contract_name = split - .next() - .expect("Failed to extract contract name for factory dependency"); - let mut abs_path_buf = PathBuf::new(); - abs_path_buf.push(project.root()); - abs_path_buf.push(contract_path); - let abs_path_str = abs_path_buf.to_string_lossy(); - let fdep_art = - zk_output.find(abs_path_str, contract_name).unwrap_or_else(|| { - panic!( - "Could not find contract {} at path {} for compilation output", - contract_name, contract_path) - }); - let fdep_fdeps_map = fdep_art - .factory_dependencies - .clone() - .expect("factory deps not found"); - for dep in fdep_fdeps_map.values() { - queue.push_back(dep.clone()) - } - - let fdep_bytecode = fdep_art - .bytecode - .clone() - .expect("Bytecode not found for factory dependency") - .object - .clone() - .into_bytes() - .unwrap() - .to_vec(); - visited_bytecodes.insert(fdep_bytecode); - } + let fdep_bytecode = fdep_art + .bytecode + .clone() + .expect("Bytecode not found for factory dependency") + .object + .clone() + .into_bytes() + .unwrap() + .to_vec(); + visited_bytecodes.insert(fdep_bytecode); } + } + visited_bytecodes.insert(bytecode.clone()); + visited_bytecodes.into_iter().collect() + }; + let zk_data = ZkSyncData { bytecode, bytecode_hash, factory_deps }; - visited_bytecodes.insert(bytecode.clone()); - let factory_deps: Vec> = visited_bytecodes.into_iter().collect(); - let zk_data = ZkSyncData { bytecode, bytecode_hash, factory_deps }; - Ok((abi, bin.object, Some(zk_data))) - }; - - let (abi, bytecode_object, zk_data) = - output.as_mut().either(process_evm_output, process_zk_output)?; - - // Add arguments to constructor - let provider = utils::get_provider(&config)?; - let params = match abi.constructor { - Some(ref v) => { - let constructor_args = - if let Some(ref constructor_args_path) = self.constructor_args_path { - read_constructor_args_file(constructor_args_path.to_path_buf())? - } else { - self.constructor_args.clone() - }; - self.parse_constructor_args(v, &constructor_args)? - } - None => vec![], - }; + let result = if self.unlocked { + // Deploy with unlocked account + let sender = self.eth.wallet.from.expect("required"); + self.deploy_zk(abi, bin.object, params, provider, chain_id, sender, zk_data, None) + .await + } else { + // Deploy with signer + let signer = self.eth.wallet.signer().await?; + let zk_signer = self.eth.wallet.signer().await?; + let deployer = signer.address(); + let provider = ProviderBuilder::<_, _, AnyNetwork>::default() + .wallet(EthereumWallet::new(signer)) + .on_provider(provider); + self.deploy_zk( + abi, + bin.object, + params, + provider, + chain_id, + deployer, + zk_data, + Some(zk_signer), + ) + .await + }; - // respect chain, if set explicitly via cmd args - let chain_id = if let Some(chain_id) = self.chain_id() { - chain_id - } else { - provider.get_chainid().await?.as_u64() - }; - let address = if self.unlocked { - // Deploy with unlocked account - let sender = self.eth.wallet.from.expect("required"); - let provider = provider.with_sender(sender.to_ethers()); - self.deploy( - &contract, - abi, - bytecode_object, - params, - provider, - chain_id, - zk_data, - None, - ) - .await? - } else { - // Deploy with signer - let signer = self.eth.wallet.signer().await?; - let zk_signer = self.eth.wallet.signer().await?; - let provider = - SignerMiddleware::new_with_provider_chain(provider, signer).await?; - self.deploy( - &contract, - abi, - bytecode_object, - params, - provider, - chain_id, - zk_data, - Some(zk_signer), - ) - .await? - }; - - if deploying_libraries { - config.libraries.push(format!( - "{}:{}:{:#02x}", - contract.path.expect("library must have path"), - contract.name, - address - )); - config.update_libraries()?; - } - } + return result; } - if deploying_libraries { - zklibs::cleanup_detected_missing_libraries(&project_root)?; - } + let target_path = if let Some(ref mut path) = self.contract.path { + canonicalize(project.root().join(path))? + } else { + project.find_contract_path(&self.contract.name)? + }; - Ok(()) + let mut output = + compile::compile_target(&target_path, &project, self.json || self.opts.silent)?; + + let (abi, bin, _) = remove_contract(&mut output, &target_path, &self.contract.name)?; + + let bin = match bin.object { + BytecodeObject::Bytecode(_) => bin.object, + _ => { + let link_refs = bin + .link_references + .iter() + .flat_map(|(path, names)| { + names.keys().map(move |name| format!("\t{name}: {path}")) + }) + .collect::>() + .join("\n"); + eyre::bail!("Dynamic linking not supported in `create` command - deploy the following library contracts first, then provide the address to link at compile time\n{}", link_refs) + } + }; + + // Add arguments to constructor + let config = self.eth.try_load_config_emit_warnings()?; + let provider = utils::get_provider(&config)?; + let params = match abi.constructor { + Some(ref v) => { + let constructor_args = + if let Some(ref constructor_args_path) = self.constructor_args_path { + read_constructor_args_file(constructor_args_path.to_path_buf())? + } else { + self.constructor_args.clone() + }; + self.parse_constructor_args(v, &constructor_args)? + } + None => vec![], + }; + + // respect chain, if set explicitly via cmd args + let chain_id = if let Some(chain_id) = self.chain_id() { + chain_id + } else { + provider.get_chain_id().await? + }; + if self.unlocked { + // Deploy with unlocked account + let sender = self.eth.wallet.from.expect("required"); + self.deploy(abi, bin, params, provider, chain_id, sender).await + } else { + // Deploy with signer + let signer = self.eth.wallet.signer().await?; + let deployer = signer.address(); + let provider = ProviderBuilder::<_, _, AnyNetwork>::default() + .wallet(EthereumWallet::new(signer)) + .on_provider(provider); + self.deploy(abi, bin, params, provider, chain_id, deployer).await + } } /// Returns the provided chain id, if any. @@ -369,31 +329,35 @@ impl CreateArgs { /// verification. async fn verify_preflight_check( &self, - contract: &ContractInfo, constructor_args: Option, chain: u64, ) -> Result<()> { // NOTE: this does not represent the same `VerifyArgs` that would be sent after deployment, // since we don't know the address yet. - let mut verify = verify::VerifyArgs { + let mut verify = forge_verify::VerifyArgs { address: Default::default(), - contract: contract.clone(), + contract: Some(self.contract.clone()), compiler_version: None, constructor_args, constructor_args_path: None, num_of_optimizations: None, - etherscan: EtherscanOpts { key: self.eth.etherscan.key(), chain: Some(chain.into()) }, + etherscan: EtherscanOpts { + key: self.eth.etherscan.key.clone(), + chain: Some(chain.into()), + }, + rpc: Default::default(), flatten: false, force: false, skip_is_verified_check: true, watch: true, retry: self.retry, - libraries: vec![], + libraries: self.opts.libraries.clone(), root: None, verifier: self.verifier.clone(), via_ir: self.opts.via_ir, evm_version: self.opts.compiler.evm_version, show_standard_json_input: self.show_standard_json_input, + guess_constructor_args: false, zksync: self.opts.compiler.zk.enabled(), }; @@ -403,136 +367,233 @@ impl CreateArgs { verify.etherscan.key = config.get_etherscan_config_with_chain(Some(chain.into()))?.map(|c| c.key); - verify.verification_provider()?.preflight_check(verify).await?; + let context = verify.resolve_context().await?; + + verify.verification_provider()?.preflight_check(verify, context).await?; Ok(()) } /// Deploys the contract - #[allow(clippy::too_many_arguments)] - async fn deploy( - &self, - contract: &ContractInfo, + async fn deploy, T: Transport + Clone>( + self, abi: JsonAbi, bin: BytecodeObject, args: Vec, - provider: M, + provider: P, chain: u64, - maybe_zk_data: Option, - signer: Option, - ) -> Result
{ - let deployer_address = - provider.default_sender().expect("no sender address set for provider"); - let bin = bin - .into_bytes() - .unwrap_or_else(|| panic!("no bytecode found in bin object for {}", contract.name)); + deployer_address: Address, + ) -> Result<()> { + let bin = bin.into_bytes().unwrap_or_else(|| { + panic!("no bytecode found in bin object for {}", self.contract.name) + }); let provider = Arc::new(provider); let factory = ContractFactory::new(abi.clone(), bin.clone(), provider.clone()); let is_args_empty = args.is_empty(); - let deployer = if let Some(zk_data) = &maybe_zk_data { - factory.deploy_tokens_zk(args.clone(), zk_data).context("failed to deploy contract") - .map(|deployer| deployer.set_zk_factory_deps(zk_data.factory_deps.clone())) - } else { - factory.deploy_tokens(args.clone()).context("failed to deploy contract") - }.map_err(|e| { - if is_args_empty { - e.wrap_err("no arguments provided for contract constructor; consider --constructor-args or --constructor-args-path") - } else { - e - } - })?; - + let mut deployer = + factory.deploy_tokens(args.clone()).context("failed to deploy contract").map_err(|e| { + if is_args_empty { + e.wrap_err("no arguments provided for contract constructor; consider --constructor-args or --constructor-args-path") + } else { + e + } + })?; let is_legacy = self.tx.legacy || Chain::from(chain).is_legacy(); - let mut deployer = if is_legacy { deployer.legacy() } else { deployer }; + deployer.tx.set_from(deployer_address); + deployer.tx.set_chain_id(chain); + // `to` field must be set explicitly, cannot be None. + if deployer.tx.to.is_none() { + deployer.tx.set_create(); + } + deployer.tx.set_nonce(if let Some(nonce) = self.tx.nonce { + Ok(nonce.to()) + } else { + provider.get_transaction_count(deployer_address).await + }?); // set tx value if specified if let Some(value) = self.tx.value { - deployer.tx.set_value(value.to_ethers()); + deployer.tx.set_value(value); } - match maybe_zk_data { - None => provider.fill_transaction(&mut deployer.tx, None).await?, - Some(zk_data) => { - let chain_id = provider.get_chainid().await?.as_u64(); - deployer.tx.set_chain_id(chain_id); - - let gas_price = provider.get_gas_price().await?; - deployer.tx.set_gas_price(gas_price); - - deployer.tx.set_from(deployer_address); - - let nonce = provider.get_transaction_count(deployer_address, None).await?; - deployer.tx.set_nonce(nonce); - - let constructor_args = match abi.constructor() { - None => Default::default(), - Some(constructor) => constructor.abi_encode_input(&args).unwrap_or_default(), - }; - let data = foundry_zksync_core::encode_create_params( - &forge::revm::primitives::CreateScheme::Create, - zk_data.bytecode_hash, - constructor_args, - ); - let data = Bytes::from(data); - deployer.tx.set_data(data.to_ethers()); - - deployer - .tx - .set_to(NameOrAddress::from(foundry_zksync_core::CONTRACT_DEPLOYER_ADDRESS)); - - let estimated_gas = foundry_zksync_core::estimate_gas( - &deployer.tx, - zk_data.factory_deps.clone(), - &provider, - ) - .await?; - deployer.tx.set_gas(estimated_gas.limit.to_ethers()); - deployer.tx.set_gas_price(estimated_gas.price.to_ethers()); - } - } + deployer.tx.set_gas_limit(if let Some(gas_limit) = self.tx.gas_limit { + Ok(gas_limit.to()) + } else { + provider.estimate_gas(&deployer.tx).await + }?); - // the max - let mut priority_fee = self.tx.priority_gas_price; + if is_legacy { + let gas_price = if let Some(gas_price) = self.tx.gas_price { + gas_price.to() + } else { + provider.get_gas_price().await? + }; + deployer.tx.set_gas_price(gas_price); + } else { + let estimate = provider.estimate_eip1559_fees(None).await.wrap_err("Failed to estimate EIP1559 fees. This chain might not support EIP1559, try adding --legacy to your command.")?; + let priority_fee = if let Some(priority_fee) = self.tx.priority_gas_price { + priority_fee.to() + } else { + estimate.max_priority_fee_per_gas + }; + let max_fee = if let Some(max_fee) = self.tx.gas_price { + max_fee.to() + } else { + estimate.max_fee_per_gas + }; - // set gas price if specified - if let Some(gas_price) = self.tx.gas_price { - deployer.tx.set_gas_price(gas_price.to_ethers()); - } else if !is_legacy { - // estimate EIP1559 fees - let (max_fee, max_priority_fee) = estimate_eip1559_fees(&provider, Some(chain)) - .await - .wrap_err("Failed to estimate EIP1559 fees. This chain might not support EIP1559, try adding --legacy to your command.")?; - deployer.tx.set_gas_price(max_fee); - if priority_fee.is_none() { - priority_fee = Some(max_priority_fee.to_alloy()); + deployer.tx.set_max_fee_per_gas(max_fee); + deployer.tx.set_max_priority_fee_per_gas(priority_fee); + } + + // Before we actually deploy the contract we try check if the verify settings are valid + let mut constructor_args = None; + if self.verify { + if !args.is_empty() { + let encoded_args = abi + .constructor() + .ok_or_else(|| eyre::eyre!("could not find constructor"))? + .abi_encode_input(&args)?; + constructor_args = Some(hex::encode_prefixed(encoded_args)); } + + self.verify_preflight_check(constructor_args.clone(), chain).await?; } - // set gas limit if specified - if let Some(gas_limit) = self.tx.gas_limit { - deployer.tx.set_gas(gas_limit.to_ethers()); + // Deploy the actual contract + let (deployed_contract, receipt) = deployer.send_with_receipt().await?; + + let address = deployed_contract; + if self.json { + let output = json!({ + "deployer": deployer_address.to_string(), + "deployedTo": address.to_string(), + "transactionHash": receipt.transaction_hash + }); + println!("{output}"); + } else { + println!("Deployer: {deployer_address}"); + println!("Deployed to: {address}"); + println!("Transaction hash: {:?}", receipt.transaction_hash); + }; + + if !self.verify { + return Ok(()); } - // set nonce if specified - if let Some(nonce) = self.tx.nonce { - deployer.tx.set_nonce(nonce.to_ethers()); + println!("Starting contract verification..."); + + let num_of_optimizations = + if self.opts.compiler.optimize { self.opts.compiler.optimizer_runs } else { None }; + let verify = forge_verify::VerifyArgs { + address, + contract: Some(self.contract), + compiler_version: None, + constructor_args, + constructor_args_path: None, + num_of_optimizations, + etherscan: EtherscanOpts { key: self.eth.etherscan.key(), chain: Some(chain.into()) }, + rpc: Default::default(), + flatten: false, + force: false, + skip_is_verified_check: false, + watch: true, + retry: self.retry, + libraries: self.opts.libraries.clone(), + root: None, + verifier: self.verifier, + via_ir: self.opts.via_ir, + evm_version: self.opts.compiler.evm_version, + show_standard_json_input: self.show_standard_json_input, + guess_constructor_args: false, + zksync: self.opts.compiler.zk.enabled(), + }; + println!("Waiting for {} to detect contract deployment...", verify.verifier.verifier); + verify.run().await + } + + // Deploys the zk contract + #[allow(clippy::too_many_arguments)] + async fn deploy_zk, T: Transport + Clone>( + self, + abi: JsonAbi, + bin: BytecodeObject, + args: Vec, + provider: P, + chain: u64, + deployer_address: Address, + zk_data: ZkSyncData, + zk_signer: Option, + ) -> Result<()> { + let bin = bin.into_bytes().unwrap_or_else(|| { + panic!("no bytecode found in bin object for {}", self.contract.name) + }); + let provider = Arc::new(provider); + let factory = ContractFactory::new(abi.clone(), bin.clone(), provider.clone()); + + let is_args_empty = args.is_empty(); + let mut deployer = + factory.deploy_tokens_zk(args.clone(), &zk_data).context("failed to deploy contract") + .map(|deployer| deployer.set_zk_factory_deps(zk_data.factory_deps.clone())).map_err(|e| { + if is_args_empty { + e.wrap_err("no arguments provided for contract constructor; consider --constructor-args or --constructor-args-path") + } else { + e + } + })?; + + deployer.tx.set_from(deployer_address); + deployer.tx.set_chain_id(chain); + // `to` field must be set explicitly, cannot be None. + if deployer.tx.to.is_none() { + deployer.tx.set_create(); } + deployer.tx.set_nonce(if let Some(nonce) = self.tx.nonce { + Ok(nonce.to()) + } else { + provider.get_transaction_count(deployer_address).await + }?); - // set priority fee if specified - if let Some(priority_fee) = priority_fee { - if is_legacy { - eyre::bail!("there is no priority fee for legacy txs"); - } - deployer.tx = match deployer.tx { - TypedTransaction::Eip1559(eip1559_tx_request) => TypedTransaction::Eip1559( - eip1559_tx_request.max_priority_fee_per_gas(priority_fee.to_ethers()), - ), - _ => deployer.tx, - }; + // set tx value if specified + if let Some(value) = self.tx.value { + deployer.tx.set_value(value); } - // Before we actually deploy the contract we try check if the verify settings are valid + let gas_price = if let Some(gas_price) = self.tx.gas_price { + gas_price.to() + } else { + provider.get_gas_price().await? + }; + deployer.tx.set_gas_price(gas_price); + + let estimated_gas = foundry_zksync_core::estimate_gas( + &deployer.tx, + zk_data.factory_deps.clone(), + &provider, + ) + .await?; + + deployer.tx.set_gas_limit(if let Some(gas_limit) = self.tx.gas_limit { + gas_limit.to::() + } else { + estimated_gas.limit + }); + + let zk_constructor_args = match abi.constructor() { + None => Default::default(), + Some(constructor) => constructor.abi_encode_input(&args).unwrap_or_default(), + }; + let data = foundry_zksync_core::encode_create_params( + &forge::revm::primitives::CreateScheme::Create, + zk_data.bytecode_hash, + zk_constructor_args, + ); + let data = Bytes::from(data); + deployer.tx.set_input(data); + + deployer.tx.set_to(foundry_zksync_core::CONTRACT_DEPLOYER_ADDRESS.to_address()); + let mut constructor_args = None; if self.verify { if !args.is_empty() { @@ -540,60 +601,62 @@ impl CreateArgs { .constructor() .ok_or_else(|| eyre::eyre!("could not find constructor"))? .abi_encode_input(&args)?; - constructor_args = Some(hex::encode_prefixed(encoded_args)); + constructor_args = Some(hex::encode(encoded_args)); } - self.verify_preflight_check(contract, constructor_args.clone(), chain).await?; + self.verify_preflight_check(constructor_args.clone(), chain).await?; } // Deploy the actual contract - let (deployed_contract, receipt) = deployer.send_with_receipt(signer).await?; + let (deployed_contract, receipt) = deployer.send_with_receipt_zk(zk_signer).await?; let address = deployed_contract; if self.json { let output = json!({ - "deployer": deployer_address.to_alloy().to_string(), + "deployer": deployer_address.to_string(), "deployedTo": address.to_string(), "transactionHash": receipt.transaction_hash }); println!("{output}"); } else { - println!("Deployer: {}", deployer_address.to_alloy()); + println!("Deployer: {deployer_address}"); println!("Deployed to: {address}"); println!("Transaction hash: {:?}", receipt.transaction_hash); }; if !self.verify { - return Ok(address); + return Ok(()); } println!("Starting contract verification..."); let num_of_optimizations = if self.opts.compiler.optimize { self.opts.compiler.optimizer_runs } else { None }; - let verify = verify::VerifyArgs { + let verify = forge_verify::VerifyArgs { address, - contract: contract.clone(), + contract: Some(self.contract), compiler_version: None, constructor_args, constructor_args_path: None, num_of_optimizations, etherscan: EtherscanOpts { key: self.eth.etherscan.key(), chain: Some(chain.into()) }, + rpc: Default::default(), flatten: false, force: false, skip_is_verified_check: false, watch: true, retry: self.retry, - libraries: vec![], + libraries: self.opts.libraries.clone(), root: None, - verifier: self.verifier.clone(), + verifier: self.verifier, via_ir: self.opts.via_ir, evm_version: self.opts.compiler.evm_version, show_standard_json_input: self.show_standard_json_input, - zksync: self.opts.compiler.zk.enabled(), + guess_constructor_args: false, + zksync: true, }; println!("Waiting for {} to detect contract deployment...", verify.verifier.verifier); - verify.run().await.map(|_| address) + verify.run().await } /// Parses the given constructor arguments into a vector of `DynSolValue`s, by matching them @@ -627,23 +690,16 @@ impl CreateArgs { } let params = params.iter().map(|(ty, arg)| (ty, arg.as_str())); - parse_tokens(params) + parse_tokens(params).map_err(Into::into) } } -/// Data used to deploy a contract on zksync -pub struct ZkSyncData { - bytecode: Vec, - bytecode_hash: H256, - factory_deps: Vec>, -} - /// `ContractFactory` is a [`DeploymentTxFactory`] object with an /// [`Arc`] middleware. This type alias exists to preserve backwards /// compatibility with less-abstract Contracts. /// /// For full usage docs, see [`DeploymentTxFactory`]. -pub type ContractFactory = DeploymentTxFactory, M>; +pub type ContractFactory = DeploymentTxFactory, P, T>; /// Helper which manages the deployment transaction of a smart contract. It /// wraps a deployment transaction, and retrieves the contract address output @@ -652,26 +708,26 @@ pub type ContractFactory = DeploymentTxFactory, M>; /// Currently, we recommend using the [`ContractDeployer`] type alias. #[derive(Debug)] #[must_use = "ContractDeploymentTx does nothing unless you `send` it"] -pub struct ContractDeploymentTx { +pub struct ContractDeploymentTx { /// the actual deployer, exposed for overriding the defaults - pub deployer: Deployer, + pub deployer: Deployer, /// marker for the `Contract` type to create afterwards /// /// this type will be used to construct it via `From::from(Contract)` _contract: PhantomData, } -impl Clone for ContractDeploymentTx +impl Clone for ContractDeploymentTx where B: Clone, { fn clone(&self) -> Self { - ContractDeploymentTx { deployer: self.deployer.clone(), _contract: self._contract } + Self { deployer: self.deployer.clone(), _contract: self._contract } } } -impl From> for ContractDeploymentTx { - fn from(deployer: Deployer) -> Self { +impl From> for ContractDeploymentTx { + fn from(deployer: Deployer) -> Self { Self { deployer, _contract: PhantomData } } } @@ -679,99 +735,99 @@ impl From> for ContractDeploymentTx { /// Helper which manages the deployment transaction of a smart contract #[derive(Debug)] #[must_use = "Deployer does nothing unless you `send` it"] -pub struct Deployer { +pub struct Deployer { /// The deployer's transaction, exposed for overriding the defaults - pub tx: TypedTransaction, + pub tx: WithOtherFields, abi: JsonAbi, client: B, confs: usize, - block: BlockNumber, zk_factory_deps: Option>>, - _m: PhantomData, + _p: PhantomData

, + _t: PhantomData, } -impl Clone for Deployer +impl Clone for Deployer where B: Clone, { fn clone(&self) -> Self { - Deployer { + Self { tx: self.tx.clone(), abi: self.abi.clone(), client: self.client.clone(), confs: self.confs, - block: self.block, zk_factory_deps: self.zk_factory_deps.clone(), - _m: PhantomData, + _p: PhantomData, + _t: PhantomData, } } } -impl Deployer +impl Deployer where - B: Borrow + Clone, - M: Middleware, + B: Borrow

+ Clone, + P: Provider, + T: Transport + Clone, { + /// Set zksync's factory deps. pub fn set_zk_factory_deps(mut self, deps: Vec>) -> Self { self.zk_factory_deps = Some(deps); self } - /// Uses a Legacy transaction instead of an EIP-1559 one to do the deployment - pub fn legacy(mut self) -> Self { - self.tx = match self.tx { - TypedTransaction::Eip1559(inner) => { - let tx: TransactionRequest = inner.into(); - TypedTransaction::Legacy(tx) - } - other => other, - }; - self + /// Broadcasts the zk contract deployment transaction and after waiting for it to + /// be sufficiently confirmed (default: 1), it returns a tuple with + /// the [`Contract`](crate::Contract) struct at the deployed contract's address + /// and the corresponding [`AnyReceipt`]. + pub async fn send_with_receipt_zk( + self, + signer: Option, + ) -> Result<(Address, AnyTransactionReceipt), ContractDeploymentError> { + let factory_deps = self.zk_factory_deps.unwrap_or_default(); + let tx = foundry_zksync_core::new_eip712_transaction( + self.tx, + factory_deps, + self.client.borrow(), + signer.expect("No signer was found"), + ) + .await + .map_err(|_| ContractDeploymentError::ContractNotDeployed)?; + + let receipt = self + .client + .borrow() + .send_raw_transaction(&tx) + .await? + .with_required_confirmations(self.confs as u64) + .get_receipt() + .await?; + + let address = + receipt.contract_address.ok_or(ContractDeploymentError::ContractNotDeployed)?; + + Ok((address, receipt)) } /// Broadcasts the contract deployment transaction and after waiting for it to /// be sufficiently confirmed (default: 1), it returns a tuple with /// the [`Contract`](crate::Contract) struct at the deployed contract's address - /// and the corresponding [`TransactionReceipt`]. + /// and the corresponding [`AnyReceipt`]. pub async fn send_with_receipt( self, - signer: Option, - ) -> Result<(Address, TransactionReceipt), ContractError> { - let pending_tx = match self.zk_factory_deps { - None => self - .client - .borrow() - .send_transaction(self.tx, Some(self.block.into())) - .await - .map_err(ContractError::from_middleware_error)?, - Some(factory_deps) => { - let tx = foundry_zksync_core::new_eip712_transaction( - self.tx, - factory_deps, - self.client.borrow().provider(), - signer.expect("No signer was found"), - ) - .await - .map_err(|_| ContractError::DecodingError(ethers_core::abi::Error::InvalidData))?; - - self.client - .borrow() - .send_raw_transaction(tx.to_ethers()) - .await - .map_err(ContractError::from_middleware_error)? - } - }; - - // TODO: Should this be calculated "optimistically" by address/nonce? - let receipt = pending_tx - .confirmations(self.confs) - .await - .ok() - .flatten() - .ok_or(ContractError::ContractNotDeployed)?; - let address = receipt.contract_address.ok_or(ContractError::ContractNotDeployed)?; - - Ok((address.to_alloy(), receipt)) + ) -> Result<(Address, AnyTransactionReceipt), ContractDeploymentError> { + let receipt = self + .client + .borrow() + .send_transaction(self.tx) + .await? + .with_required_confirmations(self.confs as u64) + .get_receipt() + .await?; + + let address = + receipt.contract_address.ok_or(ContractDeploymentError::ContractNotDeployed)?; + + Ok((address, receipt)) } } @@ -812,55 +868,59 @@ where /// # Ok(()) /// # } #[derive(Debug)] -pub struct DeploymentTxFactory { +pub struct DeploymentTxFactory { client: B, abi: JsonAbi, bytecode: Bytes, - _m: PhantomData, + _p: PhantomData

, + _t: PhantomData, } -impl Clone for DeploymentTxFactory +impl Clone for DeploymentTxFactory where B: Clone, { fn clone(&self) -> Self { - DeploymentTxFactory { + Self { client: self.client.clone(), abi: self.abi.clone(), bytecode: self.bytecode.clone(), - _m: PhantomData, + _p: PhantomData, + _t: PhantomData, } } } -impl DeploymentTxFactory +impl DeploymentTxFactory where - B: Borrow + Clone, - M: Middleware, + B: Borrow

+ Clone, + P: Provider, + T: Transport + Clone, { /// Creates a factory for deployment of the Contract with bytecode, and the /// constructor defined in the abi. The client will be used to send any deployment /// transaction. pub fn new(abi: JsonAbi, bytecode: Bytes, client: B) -> Self { - Self { client, abi, bytecode, _m: PhantomData } + Self { client, abi, bytecode, _p: PhantomData, _t: PhantomData } } /// Create a deployment tx using the provided tokens as constructor /// arguments - pub fn deploy_tokens(self, params: Vec) -> Result, ContractError> + pub fn deploy_tokens( + self, + params: Vec, + ) -> Result, ContractDeploymentError> where B: Clone, { // Encode the constructor args & concatenate with the bytecode if necessary let data: Bytes = match (self.abi.constructor(), params.is_empty()) { - (None, false) => return Err(ContractError::ConstructorError), + (None, false) => return Err(ContractDeploymentError::ConstructorError), (None, true) => self.bytecode.clone(), (Some(constructor), _) => { let input: Bytes = constructor .abi_encode_input(¶ms) - .map_err(|f| { - ContractError::DetokenizationError(InvalidOutputType(f.to_string())) - })? + .map_err(ContractDeploymentError::DetokenizationError)? .into(); // Concatenate the bytecode and abi-encoded constructor call. self.bytecode.iter().copied().chain(input).collect() @@ -868,39 +928,31 @@ where }; // create the tx object. Since we're deploying a contract, `to` is `None` - // We default to EIP1559 transactions, but the sender can convert it back - // to a legacy one. - let tx = Eip1559TransactionRequest { - to: None, - data: Some(data.to_ethers()), - ..Default::default() - }; - - let tx = tx.into(); + let tx = WithOtherFields::new(TransactionRequest::default().input(data.into())); Ok(Deployer { client: self.client.clone(), abi: self.abi, tx, confs: 1, - block: BlockNumber::Latest, zk_factory_deps: None, - _m: PhantomData, + _p: PhantomData, + _t: PhantomData, }) } /// Create a deployment tx using the provided tokens as constructor - /// arguments for zk networks + /// arguments pub fn deploy_tokens_zk( self, params: Vec, zk_data: &ZkSyncData, - ) -> Result, ContractError> + ) -> Result, ContractDeploymentError> where B: Clone, { if self.abi.constructor().is_none() && !params.is_empty() { - return Err(ContractError::ConstructorError) + return Err(ContractDeploymentError::ConstructorError) } // Encode the constructor args & concatenate with the bytecode if necessary @@ -915,24 +967,38 @@ where ) .into(); - let tx = Eip1559TransactionRequest { - to: Some(NameOrAddress::from(foundry_zksync_core::CONTRACT_DEPLOYER_ADDRESS)), - data: Some(data.to_ethers()), - ..Default::default() - }; + // create the tx object. + let tx = WithOtherFields::new( + TransactionRequest::default() + .to(foundry_zksync_core::CONTRACT_DEPLOYER_ADDRESS.to_address()) + .input(data.into()), + ); Ok(Deployer { client: self.client.clone(), abi: self.abi, - tx: tx.into(), + tx, confs: 1, - block: BlockNumber::Latest, zk_factory_deps: Some(vec![zk_data.bytecode.clone()]), - _m: PhantomData, + _p: PhantomData, + _t: PhantomData, }) } } +#[derive(thiserror::Error, Debug)] +/// An Error which is thrown when interacting with a smart contract +pub enum ContractDeploymentError { + #[error("constructor is not defined in the ABI")] + ConstructorError, + #[error(transparent)] + DetokenizationError(#[from] alloy_dyn_abi::Error), + #[error("contract was not deployed")] + ContractNotDeployed, + #[error(transparent)] + RpcError(#[from] TransportError), +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/forge/bin/cmd/debug.rs b/crates/forge/bin/cmd/debug.rs index a69c2da3c..8fe1d2e32 100644 --- a/crates/forge/bin/cmd/debug.rs +++ b/crates/forge/bin/cmd/debug.rs @@ -1,5 +1,6 @@ -use super::{build::BuildArgs, retry::RETRY_VERIFY_ON_CREATE, script::ScriptArgs}; use clap::{Parser, ValueHint}; +use forge_script::ScriptArgs; +use forge_verify::retry::RETRY_VERIFY_ON_CREATE; use foundry_cli::opts::CoreBuildArgs; use foundry_common::evm::EvmArgs; use std::path::PathBuf; @@ -14,28 +15,28 @@ pub struct DebugArgs { /// /// If multiple contracts exist in the same file you must specify the target contract with /// --target-contract. - #[clap(value_hint = ValueHint::FilePath)] + #[arg(value_hint = ValueHint::FilePath)] pub path: PathBuf, /// Arguments to pass to the script function. pub args: Vec, /// The name of the contract you want to run. - #[clap(long, visible_alias = "tc", value_name = "CONTRACT_NAME")] + #[arg(long, visible_alias = "tc", value_name = "CONTRACT_NAME")] pub target_contract: Option, /// The signature of the function you want to call in the contract, or raw calldata. - #[clap(long, short, default_value = "run()", value_name = "SIGNATURE")] + #[arg(long, short, default_value = "run()", value_name = "SIGNATURE")] pub sig: String, /// Open the script in the debugger. - #[clap(long)] + #[arg(long)] pub debug: bool, - #[clap(flatten)] + #[command(flatten)] pub opts: CoreBuildArgs, - #[clap(flatten)] + #[command(flatten)] pub evm_opts: EvmArgs, } @@ -47,7 +48,7 @@ impl DebugArgs { target_contract: self.target_contract, sig: self.sig, gas_estimate_multiplier: 130, - opts: BuildArgs { args: self.opts, ..Default::default() }, + opts: self.opts, evm_opts: self.evm_opts, debug: true, retry: RETRY_VERIFY_ON_CREATE, diff --git a/crates/forge/bin/cmd/doc/mod.rs b/crates/forge/bin/cmd/doc/mod.rs index c3ef50aa2..e561dcc5c 100644 --- a/crates/forge/bin/cmd/doc/mod.rs +++ b/crates/forge/bin/cmd/doc/mod.rs @@ -4,6 +4,7 @@ use forge_doc::{ ContractInheritance, Deployments, DocBuilder, GitSource, InferInlineHyperlinks, Inheritdoc, }; use foundry_cli::opts::GH_REPO_PREFIX_REGEX; +use foundry_common::compile::ProjectCompiler; use foundry_config::{find_project_root_path, load_config_with_root}; use std::{path::PathBuf, process::Command}; @@ -16,13 +17,13 @@ pub struct DocArgs { /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] pub root: Option, /// The doc's output path. /// /// By default, it is the `docs/` in project root. - #[clap( + #[arg( long, short, value_hint = ValueHint::DirPath, @@ -31,32 +32,32 @@ pub struct DocArgs { out: Option, /// Build the `mdbook` from generated files. - #[clap(long, short)] + #[arg(long, short)] build: bool, /// Serve the documentation. - #[clap(long, short)] + #[arg(long, short)] serve: bool, /// Open the documentation in a browser after serving. - #[clap(long, requires = "serve")] + #[arg(long, requires = "serve")] open: bool, /// Hostname for serving documentation. - #[clap(long, requires = "serve")] + #[arg(long, requires = "serve")] hostname: Option, /// Port for serving documentation. - #[clap(long, short, requires = "serve")] + #[arg(long, short, requires = "serve")] port: Option, /// The relative path to the `hardhat-deploy` or `forge-deploy` artifact directory. Leave blank /// for default. - #[clap(long)] + #[arg(long)] deployments: Option>, /// Whether to create docs for external libraries. - #[clap(long, short)] + #[arg(long, short)] include_libraries: bool, } @@ -64,6 +65,9 @@ impl DocArgs { pub fn run(self) -> Result<()> { let root = self.root.clone().unwrap_or(find_project_root_path(None)?); let config = load_config_with_root(Some(root.clone())); + let project = config.project()?; + let compiler = ProjectCompiler::new().quiet(true); + let _output = compiler.compile(&project)?; let mut doc_config = config.doc.clone(); if let Some(out) = self.out { @@ -91,8 +95,8 @@ impl DocArgs { let mut builder = DocBuilder::new( root.clone(), - config.project_paths().sources, - config.project_paths().libraries, + project.paths.sources, + project.paths.libraries, self.include_libraries, ) .with_should_build(self.build) diff --git a/crates/forge/bin/cmd/doc/server.rs b/crates/forge/bin/cmd/doc/server.rs index 7c0927136..f5991ba43 100644 --- a/crates/forge/bin/cmd/doc/server.rs +++ b/crates/forge/bin/cmd/doc/server.rs @@ -1,6 +1,7 @@ use axum::{routing::get_service, Router}; use forge_doc::mdbook::{utils::fs::get_404_output_file, MDBook}; use std::{ + io, net::{SocketAddr, ToSocketAddrs}, path::PathBuf, }; @@ -82,18 +83,20 @@ impl Server { open(serving_url); } - let _ = thread_handle.join(); - - Ok(()) + match thread_handle.join() { + Ok(r) => r.map_err(Into::into), + Err(e) => std::panic::resume_unwind(e), + } } } #[tokio::main] -async fn serve(build_dir: PathBuf, address: SocketAddr, file_404: &str) { +async fn serve(build_dir: PathBuf, address: SocketAddr, file_404: &str) -> io::Result<()> { let file_404 = build_dir.join(file_404); let svc = ServeDir::new(build_dir).not_found_service(ServeFile::new(file_404)); let app = Router::new().nest_service("/", get_service(svc)); - hyper::Server::bind(&address).serve(app.into_make_service()).await.unwrap(); + let tcp_listener = tokio::net::TcpListener::bind(address).await?; + axum::serve(tcp_listener, app.into_make_service()).await } fn open>(path: P) { diff --git a/crates/forge/bin/cmd/flatten.rs b/crates/forge/bin/cmd/flatten.rs index b4c1edcd9..c4a011337 100644 --- a/crates/forge/bin/cmd/flatten.rs +++ b/crates/forge/bin/cmd/flatten.rs @@ -4,21 +4,25 @@ use foundry_cli::{ opts::{CoreBuildArgs, ProjectPathsArgs}, utils::LoadConfig, }; -use foundry_common::{compile::ProjectCompiler, fs}; -use foundry_compilers::{error::SolcError, flatten::Flattener}; +use foundry_common::{compile::with_compilation_reporter, fs}; +use foundry_compilers::{ + compilers::solc::SolcLanguage, + error::SolcError, + flatten::{Flattener, FlattenerError}, +}; use std::path::PathBuf; /// CLI arguments for `forge flatten`. #[derive(Clone, Debug, Parser)] pub struct FlattenArgs { /// The path to the contract to flatten. - #[clap(value_hint = ValueHint::FilePath, value_name = "PATH")] + #[arg(value_hint = ValueHint::FilePath, value_name = "PATH")] pub target_path: PathBuf, /// The path to output the flattened contract. /// /// If not specified, the flattened contract will be output to stdout. - #[clap( + #[arg( long, short, value_hint = ValueHint::FilePath, @@ -26,34 +30,34 @@ pub struct FlattenArgs { )] pub output: Option, - #[clap(flatten)] + #[command(flatten)] project_paths: ProjectPathsArgs, } impl FlattenArgs { pub fn run(self) -> Result<()> { - let FlattenArgs { target_path, output, project_paths } = self; + let Self { target_path, output, project_paths } = self; // flatten is a subset of `BuildArgs` so we can reuse that to get the config let build_args = CoreBuildArgs { project_paths, ..Default::default() }; - let mut config = build_args.try_load_config_emit_warnings()?; - // `Flattener` uses the typed AST for better flattening results. - config.ast = true; - let project = config.ephemeral_no_artifacts_project()?; + let config = build_args.try_load_config_emit_warnings()?; + let project = config.create_project(false, true)?; let target_path = dunce::canonicalize(target_path)?; - let compiler_output = ProjectCompiler::new().files([target_path.clone()]).compile(&project); - let flattened = match compiler_output { - Ok(compiler_output) => { - Flattener::new(&project, &compiler_output, &target_path).map(|f| f.flatten()) - } - Err(_) => { + let flattener = with_compilation_reporter(build_args.silent, || { + Flattener::new(project.clone(), &target_path) + }); + + let flattened = match flattener { + Ok(flattener) => Ok(flattener.flatten()), + Err(FlattenerError::Compilation(_)) => { // Fallback to the old flattening implementation if we couldn't compile the target // successfully. This would be the case if the target has invalid // syntax. (e.g. Solang) - project.paths.flatten(&target_path) + project.paths.clone().with_language::().flatten(&target_path) } + Err(FlattenerError::Other(err)) => Err(err), } .map_err(|err: SolcError| eyre::eyre!("Failed to flatten: {err}"))?; diff --git a/crates/forge/bin/cmd/fmt.rs b/crates/forge/bin/cmd/fmt.rs index a4787ed7d..bcfae7769 100644 --- a/crates/forge/bin/cmd/fmt.rs +++ b/crates/forge/bin/cmd/fmt.rs @@ -1,9 +1,10 @@ use clap::{Parser, ValueHint}; -use eyre::Result; -use forge_fmt::{format_to, parse, print_diagnostics_report}; +use eyre::{Context, Result}; +use forge_fmt::{format_to, parse}; use foundry_cli::utils::{FoundryPathExt, LoadConfig}; -use foundry_common::{fs, glob::expand_globs, term::cli_warn}; -use foundry_config::impl_figment_convert_basic; +use foundry_common::{fs, term::cli_warn}; +use foundry_compilers::{compilers::solc::SolcLanguage, solc::SOLC_EXTENSIONS}; +use foundry_config::{filter::expand_globs, impl_figment_convert_basic}; use rayon::prelude::*; use similar::{ChangeTag, TextDiff}; use std::{ @@ -12,44 +13,42 @@ use std::{ io::{Read, Write as _}, path::{Path, PathBuf}, }; -use yansi::Color; +use yansi::{Color, Paint, Style}; /// CLI arguments for `forge fmt`. #[derive(Clone, Debug, Parser)] pub struct FmtArgs { /// Path to the file, directory or '-' to read from stdin. - #[clap(value_hint = ValueHint::FilePath, value_name = "PATH", num_args(1..))] + #[arg(value_hint = ValueHint::FilePath, value_name = "PATH", num_args(1..))] paths: Vec, /// The project's root path. /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] root: Option, /// Run in 'check' mode. /// /// Exits with 0 if input is formatted correctly. /// Exits with 1 if formatting is required. - #[clap(long)] + #[arg(long)] check: bool, /// In 'check' and stdin modes, outputs raw formatted code instead of the diff. - #[clap(long, short)] + #[arg(long, short)] raw: bool, } impl_figment_convert_basic!(FmtArgs); -// === impl FmtArgs === - impl FmtArgs { pub fn run(self) -> Result<()> { let config = self.try_load_config_emit_warnings()?; // Expand ignore globs and canonicalize from the get go - let ignored = expand_globs(&config.__root.0, config.fmt.ignore.iter())? + let ignored = expand_globs(&config.root.0, config.fmt.ignore.iter())? .iter() .flat_map(foundry_common::fs::canonicalize_path) .collect::>(); @@ -59,7 +58,7 @@ impl FmtArgs { [] => { // Retrieve the project paths, and filter out the ignored ones. let project_paths: Vec = config - .project_paths() + .project_paths::() .input_files_iter() .filter(|p| !(ignored.contains(p) || ignored.contains(&cwd.join(p)))) .collect(); @@ -81,7 +80,10 @@ impl FmtArgs { } if path.is_dir() { - inputs.extend(foundry_compilers::utils::source_files_iter(path)); + inputs.extend(foundry_compilers::utils::source_files_iter( + path, + SOLC_EXTENSIONS, + )); } else if path.is_sol() { inputs.push(path.to_path_buf()); } else { @@ -95,14 +97,13 @@ impl FmtArgs { let format = |source: String, path: Option<&Path>| -> Result<_> { let name = match path { Some(path) => { - path.strip_prefix(&config.__root.0).unwrap_or(path).display().to_string() + path.strip_prefix(&config.root.0).unwrap_or(path).display().to_string() } None => "stdin".to_string(), }; - let parsed = parse(&source).map_err(|diagnostics| { - let _ = print_diagnostics_report(&source, path, diagnostics); - eyre::eyre!("Failed to parse Solidity code for {name}. Leaving source unchanged.") + let parsed = parse(&source).wrap_err_with(|| { + format!("Failed to parse Solidity code for {name}. Leaving source unchanged.") })?; if !parsed.invalid_inline_config_items.is_empty() { @@ -217,24 +218,24 @@ where } for op in group { for change in diff.iter_inline_changes(&op) { - let dimmed = Color::Default.style().dimmed(); + let dimmed = Style::new().dim(); let (sign, s) = match change.tag() { - ChangeTag::Delete => ("-", Color::Red.style()), - ChangeTag::Insert => ("+", Color::Green.style()), + ChangeTag::Delete => ("-", Color::Red.foreground()), + ChangeTag::Insert => ("+", Color::Green.foreground()), ChangeTag::Equal => (" ", dimmed), }; let _ = write!( diff_summary, "{}{} |{}", - dimmed.paint(Line(change.old_index())), - dimmed.paint(Line(change.new_index())), - s.bold().paint(sign), + Line(change.old_index()).paint(dimmed), + Line(change.new_index()).paint(dimmed), + sign.paint(s.bold()), ); for (emphasized, value) in change.iter_strings_lossy() { let s = if emphasized { s.underline().bg(Color::Black) } else { s }; - let _ = write!(diff_summary, "{}", s.paint(value)); + let _ = write!(diff_summary, "{}", value.paint(s)); } if change.missing_newline() { diff --git a/crates/forge/bin/cmd/geiger/error.rs b/crates/forge/bin/cmd/geiger/error.rs index 77c6374ea..010fb237c 100644 --- a/crates/forge/bin/cmd/geiger/error.rs +++ b/crates/forge/bin/cmd/geiger/error.rs @@ -1,12 +1,11 @@ +use forge_fmt::FormatterError; use foundry_common::errors::FsPathError; -use solang_parser::diagnostics::Diagnostic; -use std::path::PathBuf; /// Possible errors when scanning a solidity file #[derive(Debug, thiserror::Error)] pub enum ScanFileError { #[error(transparent)] Io(#[from] FsPathError), - #[error("Failed to parse {1:?}: {0:?}")] - ParseSol(Vec, PathBuf), + #[error(transparent)] + ParseSol(#[from] FormatterError), } diff --git a/crates/forge/bin/cmd/geiger/find.rs b/crates/forge/bin/cmd/geiger/find.rs index ed1d5e691..6629390ca 100644 --- a/crates/forge/bin/cmd/geiger/find.rs +++ b/crates/forge/bin/cmd/geiger/find.rs @@ -1,8 +1,8 @@ use super::{error::ScanFileError, visitor::CheatcodeVisitor}; use eyre::Result; -use forge_fmt::{offset_to_line_column, parse, Visitable}; +use forge_fmt::{offset_to_line_column, parse2, FormatterError, Visitable}; use foundry_common::fs; -use solang_parser::{diagnostics::Diagnostic, pt::Loc}; +use solang_parser::pt::Loc; use std::{ fmt, path::{Path, PathBuf}, @@ -12,14 +12,16 @@ use yansi::Paint; /// Scan a single file for `unsafe` cheatcode usage. pub fn find_cheatcodes_in_file(path: &Path) -> Result { let contents = fs::read_to_string(path)?; - let cheatcodes = find_cheatcodes_in_string(&contents) - .map_err(|diagnostic| ScanFileError::ParseSol(diagnostic, path.to_path_buf()))?; + let cheatcodes = find_cheatcodes_in_string(&contents, Some(path))?; Ok(SolFileMetrics { contents, cheatcodes, file: path.to_path_buf() }) } /// Scan a string for unsafe cheatcodes. -pub fn find_cheatcodes_in_string(src: &str) -> Result> { - let mut parsed = parse(src)?; +pub fn find_cheatcodes_in_string( + src: &str, + path: Option<&Path>, +) -> Result { + let mut parsed = parse2(src, path)?; let mut visitor = CheatcodeVisitor::default(); parsed.pt.visit(&mut visitor).unwrap(); Ok(visitor.cheatcodes) @@ -55,15 +57,15 @@ impl<'a, 'b> fmt::Display for SolFileMetricsPrinter<'a, 'b> { ($($name:literal => $field:ident),*) => {$( let $field = &metrics.cheatcodes.$field[..]; if !$field.is_empty() { - writeln!(f, " {} {}", Paint::red(metrics.cheatcodes.$field.len()), Paint::red($name))?; + writeln!(f, " {} {}", metrics.cheatcodes.$field.len().red(), $name.red())?; for &loc in $field { let content = &metrics.contents[loc.range()]; let (line, col) = offset_to_line_column(&metrics.contents, loc.start()); let pos = format!(" --> {}:{}:{}", file.display(), line, col); - writeln!(f,"{}", Paint::red(pos))?; + writeln!(f,"{}", pos.red())?; for line in content.lines() { - writeln!(f, " {}", Paint::red(line))?; + writeln!(f, " {}", line.red())?; } } } @@ -71,12 +73,7 @@ impl<'a, 'b> fmt::Display for SolFileMetricsPrinter<'a, 'b> { } if !metrics.cheatcodes.is_empty() { - writeln!( - f, - "{} {}", - Paint::red(metrics.cheatcodes.len()), - Paint::red(file.display()) - )?; + writeln!(f, "{} {}", metrics.cheatcodes.len().red(), file.display().red())?; print_unsafe_fn!( "ffi" => ffi, "readFile" => read_file, @@ -145,7 +142,7 @@ mod tests { } "; - let count = find_cheatcodes_in_string(s).unwrap(); + let count = find_cheatcodes_in_string(s, None).unwrap(); assert_eq!(count.ffi.len(), 1); assert!(!count.is_empty()); } @@ -161,7 +158,7 @@ mod tests { } "; - let count = find_cheatcodes_in_string(s).unwrap(); + let count = find_cheatcodes_in_string(s, None).unwrap(); assert_eq!(count.ffi.len(), 1); assert!(!count.is_empty()); } diff --git a/crates/forge/bin/cmd/geiger/mod.rs b/crates/forge/bin/cmd/geiger/mod.rs index 79d8e25b2..da0397cc5 100644 --- a/crates/forge/bin/cmd/geiger/mod.rs +++ b/crates/forge/bin/cmd/geiger/mod.rs @@ -1,7 +1,7 @@ use clap::{Parser, ValueHint}; use eyre::{Result, WrapErr}; use foundry_cli::utils::LoadConfig; -use foundry_compilers::Graph; +use foundry_compilers::{resolver::parse::SolData, Graph}; use foundry_config::{impl_figment_convert_basic, Config}; use itertools::Itertools; use rayon::prelude::*; @@ -19,7 +19,7 @@ mod visitor; #[derive(Clone, Debug, Parser)] pub struct GeigerArgs { /// Paths to files or directories to detect. - #[clap( + #[arg( conflicts_with = "root", value_hint = ValueHint::FilePath, value_name = "PATH", @@ -31,17 +31,17 @@ pub struct GeigerArgs { /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] root: Option, /// Run in "check" mode. /// /// The exit code of the program will be the number of unsafe cheatcodes found. - #[clap(long)] + #[arg(long)] pub check: bool, /// Globs to ignore. - #[clap( + #[arg( long, value_hint = ValueHint::FilePath, value_name = "PATH", @@ -50,7 +50,7 @@ pub struct GeigerArgs { ignore: Vec, /// Print a report of all files, even if no unsafe functions are found. - #[clap(long)] + #[arg(long)] full: bool, } @@ -62,7 +62,11 @@ impl GeigerArgs { let mut sources: Vec = { if self.paths.is_empty() { - Graph::resolve(&config.project_paths())?.files().keys().cloned().collect() + Graph::::resolve(&config.project_paths())? + .files() + .keys() + .cloned() + .collect() } else { self.paths .iter() @@ -91,10 +95,10 @@ impl GeigerArgs { let sources = self.sources(&config).wrap_err("Failed to resolve files")?; if config.ffi { - eprintln!("{}\n", Paint::red("ffi enabled")); + eprintln!("{}\n", "ffi enabled".red()); } - let root = config.__root.0; + let root = config.root.0; let sum = sources .par_iter() diff --git a/crates/forge/bin/cmd/generate/mod.rs b/crates/forge/bin/cmd/generate/mod.rs index 9e25d6532..2c3a51282 100644 --- a/crates/forge/bin/cmd/generate/mod.rs +++ b/crates/forge/bin/cmd/generate/mod.rs @@ -7,7 +7,7 @@ use yansi::Paint; /// CLI arguments for `forge generate`. #[derive(Debug, Parser)] pub struct GenerateArgs { - #[clap(subcommand)] + #[command(subcommand)] pub sub: GenerateSubcommands, } @@ -20,7 +20,7 @@ pub enum GenerateSubcommands { #[derive(Debug, Parser)] pub struct GenerateTestArgs { /// Contract name for test generation. - #[clap(long, short, value_name = "CONTRACT_NAME")] + #[arg(long, short, value_name = "CONTRACT_NAME")] pub contract_name: String, } @@ -39,12 +39,12 @@ impl GenerateTestArgs { fs::create_dir_all("test")?; // Define the test file path - let test_file_path = Path::new("test").join(format!("{}.t.sol", contract_name)); + let test_file_path = Path::new("test").join(format!("{contract_name}.t.sol")); // Write the test content to the test file. fs::write(&test_file_path, test_content)?; - println!("{} test file: {}", Paint::green("Generated"), test_file_path.to_str().unwrap()); + println!("{} test file: {}", "Generated".green(), test_file_path.to_str().unwrap()); Ok(()) } } diff --git a/crates/forge/bin/cmd/init.rs b/crates/forge/bin/cmd/init.rs index 9dc1eea1b..f19bc1de2 100644 --- a/crates/forge/bin/cmd/init.rs +++ b/crates/forge/bin/cmd/init.rs @@ -3,47 +3,47 @@ use clap::{Parser, ValueHint}; use eyre::Result; use foundry_cli::{p_println, utils::Git}; use foundry_common::fs; -use foundry_compilers::remappings::Remapping; +use foundry_compilers::artifacts::remappings::Remapping; use foundry_config::Config; use std::path::{Path, PathBuf}; use yansi::Paint; /// CLI arguments for `forge init`. -#[derive(Clone, Debug, Parser)] +#[derive(Clone, Debug, Default, Parser)] pub struct InitArgs { /// The root directory of the new project. - #[clap(value_hint = ValueHint::DirPath, default_value = ".", value_name = "PATH")] - root: PathBuf, + #[arg(value_hint = ValueHint::DirPath, default_value = ".", value_name = "PATH")] + pub root: PathBuf, /// The template to start from. - #[clap(long, short)] - template: Option, + #[arg(long, short)] + pub template: Option, /// Branch argument that can only be used with template option. /// If not specified, the default branch is used. - #[clap(long, short, requires = "template")] - branch: Option, + #[arg(long, short, requires = "template")] + pub branch: Option, /// Do not install dependencies from the network. - #[clap(long, conflicts_with = "template", visible_alias = "no-deps")] - offline: bool, + #[arg(long, conflicts_with = "template", visible_alias = "no-deps")] + pub offline: bool, /// Create the project even if the specified root directory is not empty. - #[clap(long, conflicts_with = "template")] - force: bool, + #[arg(long, conflicts_with = "template")] + pub force: bool, /// Create a .vscode/settings.json file with Solidity settings, and generate a remappings.txt /// file. - #[clap(long, conflicts_with = "template")] - vscode: bool, + #[arg(long, conflicts_with = "template")] + pub vscode: bool, - #[clap(flatten)] - opts: DependencyInstallOpts, + #[command(flatten)] + pub opts: DependencyInstallOpts, } impl InitArgs { pub fn run(self) -> Result<()> { - let InitArgs { root, template, branch, opts, offline, force, vscode } = self; + let Self { root, template, branch, opts, offline, force, vscode } = self; let DependencyInstallOpts { shallow, no_git, no_commit, quiet } = opts; // create the root dir if it does not exist @@ -159,16 +159,11 @@ impl InitArgs { } } - p_println!(!quiet => " {} forge project", Paint::green("Initialized")); + p_println!(!quiet => " {} forge project", "Initialized".green()); Ok(()) } } -/// Returns the commit hash of the project if it exists -pub fn get_commit_hash(root: &Path) -> Option { - Git::new(root).commit_hash(true, "HEAD").ok() -} - /// Initialises `root` as a git repository, if it isn't one already. /// /// Creates `.gitignore` and `.github/workflows/test.yml`, if they don't exist already. diff --git a/crates/forge/bin/cmd/inspect.rs b/crates/forge/bin/cmd/inspect.rs index e85fc37b0..1c782f8bd 100644 --- a/crates/forge/bin/cmd/inspect.rs +++ b/crates/forge/bin/cmd/inspect.rs @@ -23,21 +23,21 @@ pub struct InspectArgs { pub contract: ContractInfo, /// The contract artifact field to inspect. - #[clap(value_enum)] + #[arg(value_enum)] pub field: ContractArtifactField, /// Pretty print the selected field, if supported. - #[clap(long)] + #[arg(long)] pub pretty: bool, /// All build arguments are supported - #[clap(flatten)] + #[command(flatten)] build: CoreBuildArgs, } impl InspectArgs { pub fn run(self) -> Result<()> { - let InspectArgs { mut contract, field, build, pretty } = self; + let Self { mut contract, field, build, pretty } = self; trace!(target: "forge", ?field, ?contract, "running forge inspect"); diff --git a/crates/forge/bin/cmd/install.rs b/crates/forge/bin/cmd/install.rs index b7690d458..57096f859 100644 --- a/crates/forge/bin/cmd/install.rs +++ b/crates/forge/bin/cmd/install.rs @@ -22,7 +22,7 @@ static DEPENDENCY_VERSION_TAG_REGEX: Lazy = /// CLI arguments for `forge install`. #[derive(Clone, Debug, Parser)] -#[clap(override_usage = "forge install [OPTIONS] [DEPENDENCIES]... +#[command(override_usage = "forge install [OPTIONS] [DEPENDENCIES]... forge install [OPTIONS] /@... forge install [OPTIONS] =/@... forge install [OPTIONS] ...")] @@ -46,10 +46,10 @@ pub struct InstallArgs { /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] pub root: Option, - #[clap(flatten)] + #[command(flatten)] opts: DependencyInstallOpts, } @@ -67,19 +67,19 @@ pub struct DependencyInstallOpts { /// Perform shallow clones instead of deep ones. /// /// Improves performance and reduces disk usage, but prevents switching branches or tags. - #[clap(long)] + #[arg(long)] pub shallow: bool, /// Install without adding the dependency as a submodule. - #[clap(long)] + #[arg(long)] pub no_git: bool, /// Do not create a commit. - #[clap(long)] + #[arg(long)] pub no_commit: bool, /// Do not print any messages. - #[clap(short, long)] + #[arg(short, long)] pub quiet: bool, } @@ -94,7 +94,7 @@ impl DependencyInstallOpts { /// /// Returns true if any dependency was installed. pub fn install_missing_dependencies(mut self, config: &mut Config) -> bool { - let DependencyInstallOpts { quiet, .. } = self; + let Self { quiet, .. } = self; let lib = config.install_lib_dir(); if self.git(config).has_missing_dependencies(Some(lib)).unwrap_or(false) { // The extra newline is needed, otherwise the compiler output will overwrite the message @@ -103,9 +103,7 @@ impl DependencyInstallOpts { if self.install(config, Vec::new()).is_err() && !quiet { eprintln!( "{}", - Paint::yellow( - "Your project has missing dependencies that could not be installed." - ) + "Your project has missing dependencies that could not be installed.".yellow() ) } true @@ -116,7 +114,7 @@ impl DependencyInstallOpts { /// Installs all dependencies pub fn install(self, config: &mut Config, dependencies: Vec) -> Result<()> { - let DependencyInstallOpts { no_git, no_commit, quiet, .. } = self; + let Self { no_git, no_commit, quiet, .. } = self; let git = self.git(config); @@ -193,7 +191,7 @@ impl DependencyInstallOpts { } if !quiet { - let mut msg = format!(" {} {}", Paint::green("Installed"), dep.name); + let mut msg = format!(" {} {}", "Installed".green(), dep.name); if let Some(tag) = dep.tag.or(installed_tag) { msg.push(' '); msg.push_str(tag.as_str()); diff --git a/crates/forge/bin/cmd/mod.rs b/crates/forge/bin/cmd/mod.rs index 97d188773..d3e2f8b6d 100644 --- a/crates/forge/bin/cmd/mod.rs +++ b/crates/forge/bin/cmd/mod.rs @@ -23,9 +23,9 @@ //! // A new clap subcommand that accepts both `EvmArgs` and `BuildArgs` //! #[derive(Clone, Debug, Parser)] //! pub struct MyArgs { -//! #[clap(flatten)] +//! #[command(flatten)] //! evm_opts: EvmArgs, -//! #[clap(flatten)] +//! #[command(flatten)] //! opts: BuildArgs, //! } //! @@ -42,6 +42,7 @@ pub mod bind; pub mod build; pub mod cache; +pub mod clone; pub mod config; pub mod coverage; pub mod create; @@ -56,12 +57,10 @@ pub mod inspect; pub mod install; pub mod remappings; pub mod remove; -pub mod retry; -pub mod script; pub mod selectors; pub mod snapshot; +pub mod soldeer; pub mod test; pub mod tree; pub mod update; -pub mod verify; pub mod watch; diff --git a/crates/forge/bin/cmd/remappings.rs b/crates/forge/bin/cmd/remappings.rs index 2a0379af2..b33f3442c 100644 --- a/crates/forge/bin/cmd/remappings.rs +++ b/crates/forge/bin/cmd/remappings.rs @@ -2,8 +2,7 @@ use clap::{Parser, ValueHint}; use eyre::Result; use foundry_cli::utils::LoadConfig; use foundry_config::impl_figment_convert_basic; -use foundry_evm::hashbrown::HashMap; -use std::path::PathBuf; +use std::{collections::BTreeMap, path::PathBuf}; /// CLI arguments for `forge remappings`. #[derive(Clone, Debug, Parser)] @@ -12,10 +11,10 @@ pub struct RemappingArgs { /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] root: Option, /// Pretty-print the remappings, grouping each of them by context. - #[clap(long)] + #[arg(long)] pretty: bool, } impl_figment_convert_basic!(RemappingArgs); @@ -25,7 +24,7 @@ impl RemappingArgs { let config = self.try_load_config_emit_warnings()?; if self.pretty { - let mut groups = HashMap::<_, Vec<_>>::with_capacity(config.remappings.len()); + let mut groups = BTreeMap::<_, Vec<_>>::new(); for remapping in config.remappings { groups.entry(remapping.context.clone()).or_default().push(remapping); } @@ -36,14 +35,14 @@ impl RemappingArgs { println!("Global:"); } - for mut remapping in remappings.into_iter() { + for mut remapping in remappings { remapping.context = None; // avoid writing context twice println!("- {remapping}"); } println!(); } } else { - for remapping in config.remappings.into_iter() { + for remapping in config.remappings { println!("{remapping}"); } } diff --git a/crates/forge/bin/cmd/remove.rs b/crates/forge/bin/cmd/remove.rs index 0b77515c8..a4d62b9f7 100644 --- a/crates/forge/bin/cmd/remove.rs +++ b/crates/forge/bin/cmd/remove.rs @@ -11,17 +11,18 @@ use std::path::PathBuf; #[derive(Clone, Debug, Parser)] pub struct RemoveArgs { /// The dependencies you want to remove. + #[arg(required = true)] dependencies: Vec, /// The project's root path. /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] root: Option, /// Override the up-to-date check. - #[clap(short, long)] + #[arg(short, long)] force: bool, } impl_figment_convert_basic!(RemoveArgs); diff --git a/crates/forge/bin/cmd/script/artifacts.rs b/crates/forge/bin/cmd/script/artifacts.rs deleted file mode 100644 index 0a2bd77dd..000000000 --- a/crates/forge/bin/cmd/script/artifacts.rs +++ /dev/null @@ -1,9 +0,0 @@ -use alloy_json_abi::JsonAbi; - -/// Bundles info of an artifact -pub struct ArtifactInfo<'a> { - pub contract_name: String, - pub contract_id: String, - pub abi: &'a JsonAbi, - pub code: &'a Vec, -} diff --git a/crates/forge/bin/cmd/script/multi.rs b/crates/forge/bin/cmd/script/multi.rs deleted file mode 100644 index 874dd24ba..000000000 --- a/crates/forge/bin/cmd/script/multi.rs +++ /dev/null @@ -1,240 +0,0 @@ -use super::{ - receipts, - sequence::{sig_to_file_name, ScriptSequence, SensitiveScriptSequence, DRY_RUN_DIR}, - verify::VerifyBundle, - ScriptArgs, -}; -use alloy_primitives::Address; -use eyre::{ContextCompat, Report, Result, WrapErr}; -use foundry_cli::utils::now; -use foundry_common::{fs, provider::ethers::get_http_provider}; -use foundry_compilers::{artifacts::Libraries, ArtifactId}; -use foundry_config::Config; -use foundry_wallets::WalletSigner; -use futures::future::join_all; -use serde::{Deserialize, Serialize}; -use std::{ - collections::HashMap, - io::{BufWriter, Write}, - path::{Path, PathBuf}, - sync::Arc, -}; - -/// Holds the sequences of multiple chain deployments. -#[derive(Clone, Default, Serialize, Deserialize)] -pub struct MultiChainSequence { - pub deployments: Vec, - #[serde(skip)] - pub path: PathBuf, - #[serde(skip)] - pub sensitive_path: PathBuf, - pub timestamp: u64, -} - -/// Sensitive values from script sequences. -#[derive(Clone, Default, Serialize, Deserialize)] -pub struct SensitiveMultiChainSequence { - pub deployments: Vec, -} - -fn to_sensitive(sequence: &mut MultiChainSequence) -> SensitiveMultiChainSequence { - SensitiveMultiChainSequence { - deployments: sequence.deployments.iter_mut().map(|sequence| sequence.into()).collect(), - } -} - -impl Drop for MultiChainSequence { - fn drop(&mut self) { - self.deployments.iter_mut().for_each(|sequence| sequence.sort_receipts()); - self.save().expect("could not save multi deployment sequence"); - } -} - -impl MultiChainSequence { - pub fn new( - deployments: Vec, - sig: &str, - target: &ArtifactId, - config: &Config, - broadcasted: bool, - ) -> Result { - let (path, sensitive_path) = MultiChainSequence::get_paths( - &config.broadcast, - &config.cache_path, - sig, - target, - broadcasted, - )?; - - Ok(MultiChainSequence { deployments, path, sensitive_path, timestamp: now().as_secs() }) - } - - /// Gets paths in the formats - /// ./broadcast/multi/contract_filename[-timestamp]/sig.json and - /// ./cache/multi/contract_filename[-timestamp]/sig.json - pub fn get_paths( - broadcast: &Path, - cache: &Path, - sig: &str, - target: &ArtifactId, - broadcasted: bool, - ) -> Result<(PathBuf, PathBuf)> { - let mut broadcast = broadcast.to_path_buf(); - let mut cache = cache.to_path_buf(); - let mut common = PathBuf::new(); - - common.push("multi"); - - if !broadcasted { - common.push(DRY_RUN_DIR); - } - - let target_fname = target - .source - .file_name() - .wrap_err_with(|| format!("No filename for {:?}", target.source))? - .to_string_lossy(); - - common.push(format!("{target_fname}-latest")); - - broadcast.push(common.clone()); - cache.push(common); - - fs::create_dir_all(&broadcast)?; - fs::create_dir_all(&cache)?; - - let filename = format!("{}.json", sig_to_file_name(sig)); - - broadcast.push(filename.clone()); - cache.push(filename); - - Ok((broadcast, cache)) - } - - /// Loads the sequences for the multi chain deployment. - pub fn load(config: &Config, sig: &str, target: &ArtifactId) -> Result { - let (path, sensitive_path) = MultiChainSequence::get_paths( - &config.broadcast, - &config.cache_path, - sig, - target, - true, - )?; - let mut sequence: MultiChainSequence = foundry_compilers::utils::read_json_file(&path) - .wrap_err("Multi-chain deployment not found.")?; - let sensitive_sequence: SensitiveMultiChainSequence = - foundry_compilers::utils::read_json_file(&sensitive_path) - .wrap_err("Multi-chain deployment sensitive details not found.")?; - - sequence.deployments.iter_mut().enumerate().for_each(|(i, sequence)| { - sequence.fill_sensitive(&sensitive_sequence.deployments[i]); - }); - - sequence.path = path; - sequence.sensitive_path = sensitive_path; - - Ok(sequence) - } - - /// Saves the transactions as file if it's a standalone deployment. - pub fn save(&mut self) -> Result<()> { - self.timestamp = now().as_secs(); - - let sensitive_sequence: SensitiveMultiChainSequence = to_sensitive(self); - - // broadcast writes - //../Contract-latest/run.json - let mut writer = BufWriter::new(fs::create_file(&self.path)?); - serde_json::to_writer_pretty(&mut writer, &self)?; - writer.flush()?; - - //../Contract-[timestamp]/run.json - let path = self.path.to_string_lossy(); - let file = PathBuf::from(&path.replace("-latest", &format!("-{}", self.timestamp))); - fs::create_dir_all(file.parent().unwrap())?; - fs::copy(&self.path, &file)?; - - // cache writes - //../Contract-latest/run.json - let mut writer = BufWriter::new(fs::create_file(&self.sensitive_path)?); - serde_json::to_writer_pretty(&mut writer, &sensitive_sequence)?; - writer.flush()?; - - //../Contract-[timestamp]/run.json - let path = self.sensitive_path.to_string_lossy(); - let file = PathBuf::from(&path.replace("-latest", &format!("-{}", self.timestamp))); - fs::create_dir_all(file.parent().unwrap())?; - fs::copy(&self.sensitive_path, &file)?; - - println!("\nTransactions saved to: {}\n", self.path.display()); - println!("Sensitive details saved to: {}\n", self.sensitive_path.display()); - - Ok(()) - } -} - -impl ScriptArgs { - /// Given a [`MultiChainSequence`] with multiple sequences of different chains, it executes them - /// all in parallel. Supports `--resume` and `--verify`. - pub async fn multi_chain_deployment( - &self, - mut deployments: MultiChainSequence, - libraries: Libraries, - config: &Config, - verify: VerifyBundle, - signers: &HashMap, - ) -> Result<()> { - if !libraries.is_empty() { - eyre::bail!("Libraries are currently not supported on multi deployment setups."); - } - - if self.verify { - for sequence in &deployments.deployments { - sequence.verify_preflight_check(config, &verify)?; - } - } - - if self.resume { - trace!(target: "script", "resuming multi chain deployment"); - - let futs = deployments - .deployments - .iter_mut() - .map(|sequence| async move { - let rpc_url = sequence.rpc_url().unwrap(); - let provider = Arc::new(get_http_provider(rpc_url)); - receipts::wait_for_pending(provider, sequence).await - }) - .collect::>(); - - let errors = - join_all(futs).await.into_iter().filter(|res| res.is_err()).collect::>(); - - if !errors.is_empty() { - return Err(eyre::eyre!("{errors:?}")); - } - } - - trace!(target: "script", "broadcasting multi chain deployments"); - - let mut results: Vec> = Vec::new(); - - for sequence in deployments.deployments.iter_mut() { - let rpc_url = sequence.rpc_url().unwrap().to_string(); - let result = match self.send_transactions(sequence, &rpc_url, signers).await { - Ok(_) if self.verify => sequence.verify_contracts(config, verify.clone()).await, - Ok(_) => Ok(()), - Err(err) => Err(err), - }; - results.push(result); - } - - let errors = results.into_iter().filter(|res| res.is_err()).collect::>(); - - if !errors.is_empty() { - return Err(eyre::eyre!("{errors:?}")); - } - - Ok(()) - } -} diff --git a/crates/forge/bin/cmd/script/receipts.rs b/crates/forge/bin/cmd/script/receipts.rs deleted file mode 100644 index 29848aad3..000000000 --- a/crates/forge/bin/cmd/script/receipts.rs +++ /dev/null @@ -1,155 +0,0 @@ -use super::sequence::ScriptSequence; -use alloy_primitives::TxHash; -use ethers_core::types::TransactionReceipt; -use ethers_providers::{Middleware, PendingTransaction}; -use eyre::Result; -use foundry_cli::{init_progress, update_progress, utils::print_receipt}; -use foundry_common::{ - provider::ethers::RetryProvider, - types::{ToAlloy, ToEthers}, -}; -use futures::StreamExt; -use std::sync::Arc; - -/// Convenience enum for internal signalling of transaction status -enum TxStatus { - Dropped, - Success(TransactionReceipt), - Revert(TransactionReceipt), -} - -impl From for TxStatus { - fn from(receipt: TransactionReceipt) -> Self { - let status = receipt.status.expect("receipt is from an ancient, pre-EIP658 block"); - if status.is_zero() { - TxStatus::Revert(receipt) - } else { - TxStatus::Success(receipt) - } - } -} - -/// Gets the receipts of previously pending transactions, or removes them from -/// the deploy sequence's pending vector -pub async fn wait_for_pending( - provider: Arc, - deployment_sequence: &mut ScriptSequence, -) -> Result<()> { - if deployment_sequence.pending.is_empty() { - return Ok(()); - } - println!("##\nChecking previously pending transactions."); - clear_pendings(provider, deployment_sequence, None).await -} - -/// Traverses a set of pendings and either finds receipts, or clears them from -/// the deployment sequence. -/// -/// If no `tx_hashes` are provided, then `deployment_sequence.pending` will be -/// used. For each `tx_hash`, we check if it has confirmed. If it has -/// confirmed, we push the receipt (if successful) or push an error (if -/// revert). If the transaction has not confirmed, but can be found in the -/// node's mempool, we wait for its receipt to be available. If the transaction -/// has not confirmed, and cannot be found in the mempool, we remove it from -/// the `deploy_sequence.pending` vector so that it will be rebroadcast in -/// later steps. -pub async fn clear_pendings( - provider: Arc, - deployment_sequence: &mut ScriptSequence, - tx_hashes: Option>, -) -> Result<()> { - let to_query = tx_hashes.unwrap_or_else(|| deployment_sequence.pending.clone()); - - let count = deployment_sequence.pending.len(); - - trace!("Checking status of {count} pending transactions"); - - let futs = to_query.iter().copied().map(|tx| check_tx_status(&provider, tx)); - let mut tasks = futures::stream::iter(futs).buffer_unordered(10); - - let mut errors: Vec = vec![]; - let mut receipts = Vec::::with_capacity(count); - - // set up progress bar - let mut pos = 0; - let pb = init_progress!(deployment_sequence.pending, "receipts"); - pb.set_position(pos); - - while let Some((tx_hash, result)) = tasks.next().await { - match result { - Err(err) => { - errors.push(format!("Failure on receiving a receipt for {tx_hash:?}:\n{err}")) - } - Ok(TxStatus::Dropped) => { - // We want to remove it from pending so it will be re-broadcast. - deployment_sequence.remove_pending(tx_hash); - errors.push(format!("Transaction dropped from the mempool: {tx_hash:?}")); - } - Ok(TxStatus::Success(receipt)) => { - trace!(tx_hash=?tx_hash, "received tx receipt"); - deployment_sequence.remove_pending(receipt.transaction_hash.to_alloy()); - receipts.push(receipt); - } - Ok(TxStatus::Revert(receipt)) => { - // consider: - // if this is not removed from pending, then the script becomes - // un-resumable. Is this desirable on reverts? - warn!(tx_hash=?tx_hash, "Transaction Failure"); - deployment_sequence.remove_pending(receipt.transaction_hash.to_alloy()); - errors.push(format!("Transaction Failure: {:?}", receipt.transaction_hash)); - } - } - // update the progress bar - update_progress!(pb, pos); - pos += 1; - } - - // sort receipts by blocks asc and index - receipts.sort_unstable(); - - // print all receipts - for receipt in receipts { - print_receipt(deployment_sequence.chain.into(), &receipt); - deployment_sequence.add_receipt(receipt); - } - - // print any errors - if !errors.is_empty() { - let mut error_msg = errors.join("\n"); - if !deployment_sequence.pending.is_empty() { - error_msg += "\n\n Add `--resume` to your command to try and continue broadcasting - the transactions." - } - eyre::bail!(error_msg); - } - - Ok(()) -} - -/// Checks the status of a txhash by first polling for a receipt, then for -/// mempool inclusion. Returns the tx hash, and a status -async fn check_tx_status( - provider: &RetryProvider, - hash: TxHash, -) -> (TxHash, Result) { - // We use the inner future so that we can use ? operator in the future, but - // still neatly return the tuple - let result = async move { - // First check if there's a receipt - let receipt_opt = provider.get_transaction_receipt(hash.to_ethers()).await?; - if let Some(receipt) = receipt_opt { - return Ok(receipt.into()); - } - - // If the tx is present in the mempool, run the pending tx future, and - // assume the next drop is really really real - let pending_res = PendingTransaction::new(hash.to_ethers(), provider).await?; - match pending_res { - Some(receipt) => Ok(receipt.into()), - None => Ok(TxStatus::Dropped), - } - } - .await; - - (hash, result) -} diff --git a/crates/forge/bin/cmd/script/transaction.rs b/crates/forge/bin/cmd/script/transaction.rs deleted file mode 100644 index 0cd050a2f..000000000 --- a/crates/forge/bin/cmd/script/transaction.rs +++ /dev/null @@ -1,513 +0,0 @@ -use super::{artifacts::ArtifactInfo, ScriptResult}; -use alloy_dyn_abi::JsonAbiExt; -use alloy_primitives::{Address, Bytes, B256}; -use alloy_rpc_types::request::TransactionRequest; -use ethers_core::types::{ - transaction::eip2718::TypedTransaction, NameOrAddress, - TransactionRequest as EthersTransactionRequest, -}; -use eyre::{ContextCompat, Result, WrapErr}; -use foundry_common::{ - fmt::format_token_raw, - provider::ethers::RpcUrl, - types::{ToAlloy, ToEthers}, - SELECTOR_LEN, -}; -use foundry_evm::{constants::DEFAULT_CREATE2_DEPLOYER, traces::CallTraceDecoder}; -use itertools::Itertools; -use revm_inspectors::tracing::types::CallKind; -use serde::{Deserialize, Serialize}; -use std::collections::BTreeMap; - -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct AdditionalContract { - #[serde(rename = "transactionType")] - pub opcode: CallKind, - #[serde(serialize_with = "wrapper::serialize_addr")] - pub address: Address, - pub init_code: Bytes, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct ZkTransaction { - pub factory_deps: Vec>, -} - -#[derive(Clone, Debug, Default, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TransactionWithMetadata { - pub hash: Option, - #[serde(rename = "transactionType")] - pub opcode: CallKind, - #[serde(default = "default_string")] - pub contract_name: Option, - #[serde(default = "default_address", serialize_with = "wrapper::serialize_opt_addr")] - pub contract_address: Option

, - #[serde(default = "default_string")] - pub function: Option, - #[serde(default = "default_vec_of_strings")] - pub arguments: Option>, - #[serde(skip)] - pub rpc: Option, - pub transaction: TypedTransaction, - pub additional_contracts: Vec, - pub is_fixed_gas_limit: bool, - #[serde(skip_serializing_if = "Option::is_none")] - pub zk: Option, -} - -fn default_string() -> Option { - Some("".to_string()) -} - -fn default_address() -> Option
{ - Some(Address::ZERO) -} - -fn default_vec_of_strings() -> Option> { - Some(vec![]) -} - -impl TransactionWithMetadata { - pub fn from_tx_request(transaction: TransactionRequest) -> Self { - Self { - transaction: TypedTransaction::Legacy(EthersTransactionRequest { - from: transaction.from.map(ToEthers::to_ethers), - to: transaction.to.map(ToEthers::to_ethers).map(Into::into), - value: transaction.value.map(ToEthers::to_ethers), - data: transaction.input.into_input().map(ToEthers::to_ethers), - nonce: transaction.nonce.map(|n| n.to::().into()), - gas: transaction.gas.map(ToEthers::to_ethers), - ..Default::default() - }), - ..Default::default() - } - } - - pub fn from_zk_tx_request(transaction: TransactionRequest, zk: Option) -> Self { - Self { - transaction: TypedTransaction::Legacy(EthersTransactionRequest { - from: transaction.from.map(ToEthers::to_ethers), - to: transaction.to.map(ToEthers::to_ethers).map(Into::into), - value: transaction.value.map(ToEthers::to_ethers), - data: transaction.input.into_input().map(ToEthers::to_ethers), - nonce: transaction.nonce.map(|n| n.to::().into()), - gas: transaction.gas.map(ToEthers::to_ethers), - ..Default::default() - }), - zk, - ..Default::default() - } - } - - pub fn new( - transaction: TransactionRequest, - rpc: Option, - result: &ScriptResult, - local_contracts: &BTreeMap, - decoder: &CallTraceDecoder, - additional_contracts: Vec, - is_fixed_gas_limit: bool, - ) -> Result { - Self::new_with_zk( - transaction, - rpc, - result, - local_contracts, - decoder, - additional_contracts, - is_fixed_gas_limit, - None, - ) - } - - #[allow(clippy::too_many_arguments)] - pub fn new_with_zk( - transaction: TransactionRequest, - rpc: Option, - result: &ScriptResult, - local_contracts: &BTreeMap, - decoder: &CallTraceDecoder, - additional_contracts: Vec, - is_fixed_gas_limit: bool, - zk: Option, - ) -> Result { - let mut metadata = Self::from_tx_request(transaction); - metadata.rpc = rpc; - metadata.is_fixed_gas_limit = is_fixed_gas_limit; - metadata.zk = zk; - - // Specify if any contract was directly created with this transaction - if let Some(NameOrAddress::Address(to)) = metadata.transaction.to().cloned() { - if to.to_alloy() == DEFAULT_CREATE2_DEPLOYER { - metadata.set_create( - true, - Address::from_slice(&result.returned), - local_contracts, - )?; - } else { - metadata - .set_call(to.to_alloy(), local_contracts, decoder) - .wrap_err("Could not decode transaction type.")?; - } - } else if metadata.transaction.to().is_none() { - metadata.set_create( - false, - result.address.wrap_err("There should be a contract address from CREATE.")?, - local_contracts, - )?; - } - - // Add the additional contracts created in this transaction, so we can verify them later. - if let Some(tx_address) = metadata.contract_address { - metadata.additional_contracts = additional_contracts - .into_iter() - .filter_map(|contract| { - // Filter out the transaction contract repeated init_code. - if contract.address != tx_address { - Some(contract) - } else { - None - } - }) - .collect(); - } - - Ok(metadata) - } - - /// Populate the transaction as CREATE tx - /// - /// If this is a CREATE2 transaction this attempt to decode the arguments from the CREATE2 - /// deployer's function - fn set_create( - &mut self, - is_create2: bool, - address: Address, - contracts: &BTreeMap, - ) -> Result<()> { - if is_create2 { - self.opcode = CallKind::Create2; - } else { - self.opcode = CallKind::Create; - } - - let info = contracts.get(&address); - self.contract_name = info.map(|info| info.contract_name.clone()); - self.contract_address = Some(address); - - let Some(data) = self.transaction.data() else { return Ok(()) }; - let Some(info) = info else { return Ok(()) }; - - // `create2` transactions are prefixed by a 32 byte salt. - let creation_code = if is_create2 { - if data.len() < 32 { - return Ok(()) - } - &data[32..] - } else { - data - }; - - // The constructor args start after bytecode. - let contains_constructor_args = creation_code.len() > info.code.len(); - if !contains_constructor_args { - return Ok(()); - } - let constructor_args = &creation_code[info.code.len()..]; - - let Some(constructor) = info.abi.constructor() else { return Ok(()) }; - let values = constructor.abi_decode_input(constructor_args, false).map_err(|e| { - error!( - contract=?self.contract_name, - signature=%format!("constructor({})", constructor.inputs.iter().map(|p| &p.ty).format(",")), - is_create2, - constructor_args=%hex::encode(constructor_args), - "Failed to decode constructor arguments", - ); - debug!(full_data=%hex::encode(data), bytecode=%hex::encode(creation_code)); - e - })?; - self.arguments = Some(values.iter().map(format_token_raw).collect()); - - Ok(()) - } - - /// Populate the transaction as CALL tx - fn set_call( - &mut self, - target: Address, - local_contracts: &BTreeMap, - decoder: &CallTraceDecoder, - ) -> Result<()> { - self.opcode = CallKind::Call; - - let Some(data) = self.transaction.data() else { return Ok(()) }; - if data.len() < SELECTOR_LEN { - return Ok(()); - } - let (selector, data) = data.split_at(SELECTOR_LEN); - - let function = if let Some(info) = local_contracts.get(&target) { - // This CALL is made to a local contract. - self.contract_name = Some(info.contract_name.clone()); - info.abi.functions().find(|function| function.selector() == selector) - } else { - // This CALL is made to an external contract; try to decode it from the given decoder. - decoder.functions.get(selector).and_then(|v| v.first()) - }; - if let Some(function) = function { - if self.contract_address.is_none() { - self.contract_name = decoder.contracts.get(&target).cloned(); - } - - self.function = Some(function.signature()); - - let values = function.abi_decode_input(data, false).map_err(|e| { - error!( - contract=?self.contract_name, - signature=?function, - data=hex::encode(data), - "Failed to decode function arguments", - ); - e - })?; - self.arguments = Some(values.iter().map(format_token_raw).collect()); - } - - self.contract_address = Some(target); - - Ok(()) - } - - pub fn set_tx(&mut self, tx: TypedTransaction) { - self.transaction = tx; - } - - pub fn change_type(&mut self, is_legacy: bool) { - self.transaction = if is_legacy { - TypedTransaction::Legacy(self.transaction.clone().into()) - } else { - TypedTransaction::Eip1559(self.transaction.clone().into()) - }; - } - - pub fn typed_tx(&self) -> &TypedTransaction { - &self.transaction - } - - pub fn typed_tx_mut(&mut self) -> &mut TypedTransaction { - &mut self.transaction - } - - pub fn is_create2(&self) -> bool { - self.opcode == CallKind::Create2 - } -} - -// wrapper for modifying ethers-rs type serialization -pub mod wrapper { - pub use super::*; - use ethers_core::{ - types::{Bloom, Bytes, Log, TransactionReceipt, H256, U256, U64}, - utils::to_checksum, - }; - - pub fn serialize_addr(addr: &Address, serializer: S) -> Result - where - S: serde::Serializer, - { - serializer.serialize_str(&to_checksum(&addr.to_ethers(), None)) - } - - pub fn serialize_opt_addr(opt: &Option
, serializer: S) -> Result - where - S: serde::Serializer, - { - match opt { - Some(addr) => serialize_addr(addr, serializer), - None => serializer.serialize_none(), - } - } - - pub fn serialize_vec_with_wrapped( - vec: &[T], - serializer: S, - ) -> Result - where - S: serde::Serializer, - T: Clone, - WrappedType: serde::Serialize + From, - { - serializer.collect_seq(vec.iter().cloned().map(WrappedType::from)) - } - - // copied from https://github.com/gakonst/ethers-rs - #[derive(Serialize, Deserialize)] - struct WrappedLog { - /// The contract address that emitted the log. - #[serde(serialize_with = "serialize_addr")] - pub address: Address, - - /// Array of 0 to 4 32 Bytes of indexed log arguments. - /// - /// (In solidity: The first topic is the hash of the signature of the event - /// (e.g. `Deposit(address,bytes32,uint256)`), except you declared the event - /// with the anonymous specifier.) - pub topics: Vec, - - /// Data - pub data: Bytes, - - /// Block Hash - #[serde(rename = "blockHash")] - #[serde(skip_serializing_if = "Option::is_none")] - pub block_hash: Option, - - /// Block Number - #[serde(rename = "blockNumber")] - #[serde(skip_serializing_if = "Option::is_none")] - pub block_number: Option, - - /// Transaction Hash - #[serde(rename = "transactionHash")] - #[serde(skip_serializing_if = "Option::is_none")] - pub transaction_hash: Option, - - /// Transaction Index - #[serde(rename = "transactionIndex")] - #[serde(skip_serializing_if = "Option::is_none")] - pub transaction_index: Option, - - /// Integer of the log index position in the block. None if it's a pending log. - #[serde(rename = "logIndex")] - #[serde(skip_serializing_if = "Option::is_none")] - pub log_index: Option, - - /// Integer of the transactions index position log was created from. - /// None when it's a pending log. - #[serde(rename = "transactionLogIndex")] - #[serde(skip_serializing_if = "Option::is_none")] - pub transaction_log_index: Option, - - /// Log Type - #[serde(rename = "logType")] - #[serde(skip_serializing_if = "Option::is_none")] - pub log_type: Option, - - /// True when the log was removed, due to a chain reorganization. - /// false if it's a valid log. - #[serde(skip_serializing_if = "Option::is_none")] - pub removed: Option, - } - impl From for WrappedLog { - fn from(log: Log) -> Self { - Self { - address: log.address.to_alloy(), - topics: log.topics, - data: log.data, - block_hash: log.block_hash, - block_number: log.block_number, - transaction_hash: log.transaction_hash, - transaction_index: log.transaction_index, - log_index: log.log_index, - transaction_log_index: log.transaction_log_index, - log_type: log.log_type, - removed: log.removed, - } - } - } - - fn serialize_logs( - logs: &[Log], - serializer: S, - ) -> Result { - serialize_vec_with_wrapped::(logs, serializer) - } - - // "Receipt" of an executed transaction: details of its execution. - // copied from https://github.com/gakonst/ethers-rs - #[derive(Clone, Default, Serialize, Deserialize)] - pub struct WrappedTransactionReceipt { - /// Transaction hash. - #[serde(rename = "transactionHash")] - pub transaction_hash: H256, - /// Index within the block. - #[serde(rename = "transactionIndex")] - pub transaction_index: U64, - /// Hash of the block this transaction was included within. - #[serde(rename = "blockHash")] - pub block_hash: Option, - /// Number of the block this transaction was included within. - #[serde(rename = "blockNumber")] - pub block_number: Option, - /// The address of the sender. - #[serde(serialize_with = "serialize_addr")] - pub from: Address, - // The address of the receiver. `None` when its a contract creation transaction. - #[serde(serialize_with = "serialize_opt_addr")] - pub to: Option
, - /// Cumulative gas used within the block after this was executed. - #[serde(rename = "cumulativeGasUsed")] - pub cumulative_gas_used: U256, - /// Gas used by this transaction alone. - /// - /// Gas used is `None` if the the client is running in light client mode. - #[serde(rename = "gasUsed")] - pub gas_used: Option, - /// Contract address created, or `None` if not a deployment. - #[serde(rename = "contractAddress", serialize_with = "serialize_opt_addr")] - pub contract_address: Option
, - /// Logs generated within this transaction. - #[serde(serialize_with = "serialize_logs")] - pub logs: Vec, - /// Status: either 1 (success) or 0 (failure). Only present after activation of [EIP-658](https://eips.ethereum.org/EIPS/eip-658) - pub status: Option, - /// State root. Only present before activation of [EIP-658](https://eips.ethereum.org/EIPS/eip-658) - #[serde(default, skip_serializing_if = "Option::is_none")] - pub root: Option, - /// Logs bloom - #[serde(rename = "logsBloom")] - pub logs_bloom: Bloom, - /// Transaction type, Some(1) for AccessList transaction, None for Legacy - #[serde(rename = "type", default, skip_serializing_if = "Option::is_none")] - pub transaction_type: Option, - /// The price paid post-execution by the transaction (i.e. base fee + priority fee). - /// Both fields in 1559-style transactions are *maximums* (max fee + max priority fee), the - /// amount that's actually paid by users can only be determined post-execution - #[serde(rename = "effectiveGasPrice", default, skip_serializing_if = "Option::is_none")] - pub effective_gas_price: Option, - } - impl From for WrappedTransactionReceipt { - fn from(receipt: TransactionReceipt) -> Self { - Self { - transaction_hash: receipt.transaction_hash, - transaction_index: receipt.transaction_index, - block_hash: receipt.block_hash, - block_number: receipt.block_number, - from: receipt.from.to_alloy(), - to: receipt.to.map(|addr| addr.to_alloy()), - cumulative_gas_used: receipt.cumulative_gas_used, - gas_used: receipt.gas_used, - contract_address: receipt.contract_address.map(|addr| addr.to_alloy()), - logs: receipt.logs, - status: receipt.status, - root: receipt.root, - logs_bloom: receipt.logs_bloom, - transaction_type: receipt.transaction_type, - effective_gas_price: receipt.effective_gas_price, - } - } - } - - pub fn serialize_receipts( - receipts: &[TransactionReceipt], - serializer: S, - ) -> Result { - serialize_vec_with_wrapped::( - receipts, serializer, - ) - } -} diff --git a/crates/forge/bin/cmd/selectors.rs b/crates/forge/bin/cmd/selectors.rs index 7318fa04f..81188f35b 100644 --- a/crates/forge/bin/cmd/selectors.rs +++ b/crates/forge/bin/cmd/selectors.rs @@ -6,7 +6,7 @@ use foundry_cli::{ utils::FoundryPathExt, }; use foundry_common::{ - compile::ProjectCompiler, + compile::{compile_target, ProjectCompiler}, selectors::{import_selectors, SelectorImportData}, }; use foundry_compilers::{artifacts::output_selection::ContractOutputSelection, info::ContractInfo}; @@ -16,7 +16,7 @@ use std::fs::canonicalize; #[derive(Clone, Debug, Parser)] pub enum SelectorsSubcommands { /// Check for selector collisions between contracts - #[clap(visible_alias = "co")] + #[command(visible_alias = "co")] Collision { /// The first of the two contracts for which to look selector collisions for, in the form /// `(:)?`. @@ -26,33 +26,33 @@ pub enum SelectorsSubcommands { /// `(:)?`. second_contract: ContractInfo, - #[clap(flatten)] + #[command(flatten)] build: Box, }, /// Upload selectors to registry - #[clap(visible_alias = "up")] + #[command(visible_alias = "up")] Upload { /// The name of the contract to upload selectors for. - #[clap(required_unless_present = "all")] + #[arg(required_unless_present = "all")] contract: Option, /// Upload selectors for all contracts in the project. - #[clap(long, required_unless_present = "contract")] + #[arg(long, required_unless_present = "contract")] all: bool, - #[clap(flatten)] + #[command(flatten)] project_paths: ProjectPathsArgs, }, /// List selectors from current workspace - #[clap(visible_alias = "ls")] + #[command(visible_alias = "ls")] List { /// The name of the contract to list selectors for. - #[clap(help = "The name of the contract to list selectors for.")] + #[arg(help = "The name of the contract to list selectors for.")] contract: Option, - #[clap(flatten)] + #[command(flatten)] project_paths: ProjectPathsArgs, }, } @@ -60,7 +60,7 @@ pub enum SelectorsSubcommands { impl SelectorsSubcommands { pub async fn run(self) -> Result<()> { match self { - SelectorsSubcommands::Upload { contract, all, project_paths } => { + Self::Upload { contract, all, project_paths } => { let build_args = CoreBuildArgs { project_paths: project_paths.clone(), compiler: CompilerArgs { @@ -71,13 +71,17 @@ impl SelectorsSubcommands { }; let project = build_args.project()?; - let output = ProjectCompiler::new().quiet(true).compile(&project)?; + let output = if let Some(name) = &contract { + let target_path = project.find_contract_path(name)?; + compile_target(&target_path, &project, false)? + } else { + ProjectCompiler::new().compile(&project)? + }; let artifacts = if all { output .into_artifacts_with_files() .filter(|(file, _, _)| { - let is_sources_path = file - .starts_with(&project.paths.sources.to_string_lossy().to_string()); + let is_sources_path = file.starts_with(&project.paths.sources); let is_test = file.is_sol_test(); is_sources_path && !is_test @@ -114,7 +118,7 @@ impl SelectorsSubcommands { } } } - SelectorsSubcommands::Collision { mut first_contract, mut second_contract, build } => { + Self::Collision { mut first_contract, mut second_contract, build } => { // Compile the project with the two contracts included let project = build.project()?; let mut compiler = ProjectCompiler::new().quiet(true); @@ -170,7 +174,7 @@ impl SelectorsSubcommands { println!("{table}"); } } - SelectorsSubcommands::List { contract, project_paths } => { + Self::List { contract, project_paths } => { println!("Listing selectors for contracts in the project..."); let build_args = CoreBuildArgs { project_paths: project_paths.clone(), @@ -195,7 +199,7 @@ impl SelectorsSubcommands { let suggestion = if let Some(suggestion) = foundry_cli::utils::did_you_mean(&contract, candidates).pop() { format!("\nDid you mean `{suggestion}`?") } else { - "".to_string() + String::new() }; eyre::eyre!( "Could not find artifact `{contract}` in the compiled artifacts{suggestion}", @@ -207,8 +211,7 @@ impl SelectorsSubcommands { outcome .into_artifacts_with_files() .filter(|(file, _, _)| { - let is_sources_path = file - .starts_with(&project.paths.sources.to_string_lossy().to_string()); + let is_sources_path = file.starts_with(&project.paths.sources); let is_test = file.is_sol_test(); is_sources_path && !is_test diff --git a/crates/forge/bin/cmd/snapshot.rs b/crates/forge/bin/cmd/snapshot.rs index f60ab01bb..6c102c9d6 100644 --- a/crates/forge/bin/cmd/snapshot.rs +++ b/crates/forge/bin/cmd/snapshot.rs @@ -14,7 +14,6 @@ use std::{ path::{Path, PathBuf}, str::FromStr, }; -use watchexec::config::{InitConfig, RuntimeConfig}; use yansi::Paint; /// A regex that matches a basic snapshot entry like @@ -29,7 +28,7 @@ pub struct SnapshotArgs { /// Output a diff against a pre-existing snapshot. /// /// By default, the comparison is done with .gas-snapshot. - #[clap( + #[arg( conflicts_with = "snap", long, value_hint = ValueHint::FilePath, @@ -42,7 +41,7 @@ pub struct SnapshotArgs { /// Outputs a diff if the snapshots do not match. /// /// By default, the comparison is done with .gas-snapshot. - #[clap( + #[arg( conflicts_with = "diff", long, value_hint = ValueHint::FilePath, @@ -52,11 +51,11 @@ pub struct SnapshotArgs { // Hidden because there is only one option /// How to format the output. - #[clap(long, hide(true))] + #[arg(long, hide(true))] format: Option, /// Output file for the snapshot. - #[clap( + #[arg( long, default_value = ".gas-snapshot", value_hint = ValueHint::FilePath, @@ -65,7 +64,7 @@ pub struct SnapshotArgs { snap: PathBuf, /// Tolerates gas deviations up to the specified percentage. - #[clap( + #[arg( long, value_parser = RangedU64ValueParser::::new().range(0..100), value_name = "SNAPSHOT_THRESHOLD" @@ -73,11 +72,11 @@ pub struct SnapshotArgs { tolerance: Option, /// All test arguments are supported - #[clap(flatten)] + #[command(flatten)] pub(crate) test: test::TestArgs, /// Additional configs for test results - #[clap(flatten)] + #[command(flatten)] config: SnapshotConfig, } @@ -89,7 +88,7 @@ impl SnapshotArgs { /// Returns the [`watchexec::InitConfig`] and [`watchexec::RuntimeConfig`] necessary to /// bootstrap a new [`watchexe::Watchexec`] loop. - pub(crate) fn watchexec_config(&self) -> Result<(InitConfig, RuntimeConfig)> { + pub(crate) fn watchexec_config(&self) -> Result { self.test.watchexec_config() } @@ -131,7 +130,7 @@ impl FromStr for Format { fn from_str(s: &str) -> Result { match s { - "t" | "table" => Ok(Format::Table), + "t" | "table" => Ok(Self::Table), _ => Err(format!("Unrecognized format `{s}`")), } } @@ -141,19 +140,19 @@ impl FromStr for Format { #[derive(Clone, Debug, Default, Parser)] struct SnapshotConfig { /// Sort results by gas used (ascending). - #[clap(long)] + #[arg(long)] asc: bool, /// Sort results by gas used (descending). - #[clap(conflicts_with = "asc", long)] + #[arg(conflicts_with = "asc", long)] desc: bool, /// Only include tests that used more gas that the given amount. - #[clap(long, value_name = "MIN_GAS")] + #[arg(long, value_name = "MIN_GAS")] min: Option, /// Only include tests that used less gas that the given amount. - #[clap(long, value_name = "MAX_GAS")] + #[arg(long, value_name = "MAX_GAS")] max: Option, } @@ -211,17 +210,17 @@ impl FromStr for SnapshotEntry { cap.name("file").and_then(|file| { cap.name("sig").and_then(|sig| { if let Some(gas) = cap.name("gas") { - Some(SnapshotEntry { + Some(Self { contract_name: file.as_str().to_string(), signature: sig.as_str().to_string(), - gas_used: TestKindReport::Standard { + gas_used: TestKindReport::Unit { gas: gas.as_str().parse().unwrap(), }, }) } else if let Some(runs) = cap.name("runs") { cap.name("avg") .and_then(|avg| cap.name("med").map(|med| (runs, avg, med))) - .map(|(runs, avg, med)| SnapshotEntry { + .map(|(runs, avg, med)| Self { contract_name: file.as_str().to_string(), signature: sig.as_str().to_string(), gas_used: TestKindReport::Fuzz { @@ -237,7 +236,7 @@ impl FromStr for SnapshotEntry { cap.name("reverts").map(|med| (runs, avg, med)) }) }) - .map(|(runs, calls, reverts)| SnapshotEntry { + .map(|(runs, calls, reverts)| Self { contract_name: file.as_str().to_string(), signature: sig.as_str().to_string(), gas_used: TestKindReport::Invariant { @@ -398,21 +397,21 @@ fn diff(tests: Vec, snaps: Vec) -> Result<()> { fn fmt_pct_change(change: f64) -> String { let change_pct = change * 100.0; match change.partial_cmp(&0.0).unwrap_or(Ordering::Equal) { - Ordering::Less => Paint::green(format!("{change_pct:.3}%")).to_string(), + Ordering::Less => format!("{change_pct:.3}%").green().to_string(), Ordering::Equal => { format!("{change_pct:.3}%") } - Ordering::Greater => Paint::red(format!("{change_pct:.3}%")).to_string(), + Ordering::Greater => format!("{change_pct:.3}%").red().to_string(), } } fn fmt_change(change: i128) -> String { match change.cmp(&0) { - Ordering::Less => Paint::green(format!("{change}")).to_string(), + Ordering::Less => format!("{change}").green().to_string(), Ordering::Equal => { format!("{change}") } - Ordering::Greater => Paint::red(format!("{change}")).to_string(), + Ordering::Greater => format!("{change}").red().to_string(), } } @@ -455,7 +454,7 @@ mod tests { SnapshotEntry { contract_name: "Test".to_string(), signature: "deposit()".to_string(), - gas_used: TestKindReport::Standard { gas: 7222 } + gas_used: TestKindReport::Unit { gas: 7222 } } ); } diff --git a/crates/forge/bin/cmd/soldeer.rs b/crates/forge/bin/cmd/soldeer.rs new file mode 100644 index 000000000..52e1240a0 --- /dev/null +++ b/crates/forge/bin/cmd/soldeer.rs @@ -0,0 +1,37 @@ +use clap::Parser; +use eyre::Result; + +use soldeer::commands::Subcommands; + +// CLI arguments for `forge soldeer`. +#[derive(Clone, Debug, Parser)] +#[clap(override_usage = "forge soldeer install [DEPENDENCY]~[VERSION] + forge soldeer push [DEPENDENCY]~[VERSION] + forge soldeer login + forge soldeer update + forge soldeer version")] +pub struct SoldeerArgs { + /// Command must be one of the following install/push/login/update/version. + #[command(subcommand)] + command: Subcommands, +} + +impl SoldeerArgs { + pub fn run(self) -> Result<()> { + match soldeer::run(self.command) { + Ok(_) => Ok(()), + Err(err) => Err(eyre::eyre!("Failed to run soldeer {}", err.message)), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use soldeer::commands::VersionDryRun; + + #[test] + fn test_soldeer_version() { + assert!(soldeer::run(Subcommands::VersionDryRun(VersionDryRun {})).is_ok()); + } +} diff --git a/crates/forge/bin/cmd/test/filter.rs b/crates/forge/bin/cmd/test/filter.rs index 81497ca14..7ececa7d4 100644 --- a/crates/forge/bin/cmd/test/filter.rs +++ b/crates/forge/bin/cmd/test/filter.rs @@ -1,40 +1,38 @@ use clap::Parser; use forge::TestFilter; -use foundry_cli::utils::FoundryPathExt; -use foundry_common::glob::GlobMatcher; use foundry_compilers::{FileFilter, ProjectPathsConfig}; -use foundry_config::Config; +use foundry_config::{filter::GlobMatcher, Config}; use std::{fmt, path::Path}; /// The filter to use during testing. /// /// See also `FileFilter`. #[derive(Clone, Parser)] -#[clap(next_help_heading = "Test filtering")] +#[command(next_help_heading = "Test filtering")] pub struct FilterArgs { /// Only run test functions matching the specified regex pattern. - #[clap(long = "match-test", visible_alias = "mt", value_name = "REGEX")] + #[arg(long = "match-test", visible_alias = "mt", value_name = "REGEX")] pub test_pattern: Option, /// Only run test functions that do not match the specified regex pattern. - #[clap(long = "no-match-test", visible_alias = "nmt", value_name = "REGEX")] + #[arg(long = "no-match-test", visible_alias = "nmt", value_name = "REGEX")] pub test_pattern_inverse: Option, /// Only run tests in contracts matching the specified regex pattern. - #[clap(long = "match-contract", visible_alias = "mc", value_name = "REGEX")] + #[arg(long = "match-contract", visible_alias = "mc", value_name = "REGEX")] pub contract_pattern: Option, /// Only run tests in contracts that do not match the specified regex pattern. - #[clap(long = "no-match-contract", visible_alias = "nmc", value_name = "REGEX")] + #[arg(long = "no-match-contract", visible_alias = "nmc", value_name = "REGEX")] pub contract_pattern_inverse: Option, /// Only run tests in source files matching the specified glob pattern. - #[clap(long = "match-path", visible_alias = "mp", value_name = "GLOB")] + #[arg(long = "match-path", visible_alias = "mp", value_name = "GLOB")] pub path_pattern: Option, /// Only run tests in source files that do not match the specified glob pattern. - #[clap( - name = "no-match-path", + #[arg( + id = "no-match-path", long = "no-match-path", visible_alias = "nmp", value_name = "GLOB" @@ -93,16 +91,9 @@ impl fmt::Debug for FilterArgs { impl FileFilter for FilterArgs { /// Returns true if the file regex pattern match the `file` /// - /// If no file regex is set this returns true if the file ends with `.t.sol`, see - /// [`FoundryPathExt::is_sol_test()`]. + /// If no file regex is set this returns true by default fn is_match(&self, file: &Path) -> bool { - if let Some(glob) = &self.path_pattern { - return glob.is_match(file) - } - if let Some(glob) = &self.path_pattern_inverse { - return !glob.is_match(file) - } - file.is_sol_test() + self.matches_path(file) } } @@ -172,8 +163,6 @@ pub struct ProjectPathsAwareFilter { paths: ProjectPathsConfig, } -// === impl ProjectPathsAwareFilter === - impl ProjectPathsAwareFilter { /// Returns true if the filter is empty. pub fn is_empty(&self) -> bool { @@ -194,9 +183,9 @@ impl ProjectPathsAwareFilter { impl FileFilter for ProjectPathsAwareFilter { /// Returns true if the file regex pattern match the `file` /// - /// If no file regex is set this returns true if the file ends with `.t.sol`, see - /// [FoundryPathExr::is_sol_test()] - fn is_match(&self, file: &Path) -> bool { + /// If no file regex is set this returns true by default + fn is_match(&self, mut file: &Path) -> bool { + file = file.strip_prefix(&self.paths.root).unwrap_or(file); self.args_filter.is_match(file) } } @@ -210,8 +199,9 @@ impl TestFilter for ProjectPathsAwareFilter { self.args_filter.matches_contract(contract_name) } - fn matches_path(&self, path: &Path) -> bool { + fn matches_path(&self, mut path: &Path) -> bool { // we don't want to test files that belong to a library + path = path.strip_prefix(&self.paths.root).unwrap_or(path); self.args_filter.matches_path(path) && !self.paths.has_library_ancestor(path) } } diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index e91139ac4..d39e95e41 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -5,13 +5,10 @@ use eyre::Result; use forge::{ decode::decode_console_logs, gas_report::GasReport, - inspectors::CheatsConfig, + multi_runner::matches_contract, result::{SuiteResult, TestOutcome, TestStatus}, - traces::{ - identifier::{EtherscanIdentifier, LocalTraceIdentifier, SignaturesIdentifier}, - CallTraceDecoderBuilder, TraceKind, - }, - MultiContractRunner, MultiContractRunnerBuilder, TestOptions, TestOptionsBuilder, + traces::{identifier::SignaturesIdentifier, CallTraceDecoderBuilder, TraceKind}, + MultiContractRunner, MultiContractRunnerBuilder, TestFilter, TestOptions, TestOptionsBuilder, }; use foundry_cli::{ opts::CoreBuildArgs, @@ -22,6 +19,12 @@ use foundry_common::{ evm::EvmArgs, shell, }; +use foundry_compilers::{ + artifacts::output_selection::OutputSelection, + compilers::{multi::MultiCompilerLanguage, CompilerSettings, Language}, + solc::SolcLanguage, + utils::source_files_iter, +}; use foundry_config::{ figment, figment::{ @@ -31,10 +34,15 @@ use foundry_config::{ get_available_profiles, Config, }; use foundry_debugger::Debugger; +use foundry_evm::traces::identifier::TraceIdentifiers; use foundry_zksync_compiler::DualCompiledContracts; use regex::Regex; -use std::{sync::mpsc::channel, time::Instant}; -use watchexec::config::{InitConfig, RuntimeConfig}; +use std::{ + collections::{BTreeMap, BTreeSet}, + path::PathBuf, + sync::{mpsc::channel, Arc}, + time::Instant, +}; use yansi::Paint; mod filter; @@ -49,7 +57,7 @@ foundry_config::merge_impl_figment_convert!(TestArgs, opts, evm_opts); /// CLI arguments for `forge test`. #[derive(Clone, Debug, Parser)] -#[clap(next_help_heading = "Test options")] +#[command(next_help_heading = "Test options")] pub struct TestArgs { /// Run a test in the debugger. /// @@ -66,59 +74,72 @@ pub struct TestArgs { /// If the fuzz test does not fail, it will open the debugger on the last fuzz case. /// /// For more fine-grained control of which fuzz case is run, see forge run. - #[clap(long, value_name = "TEST_FUNCTION")] + #[arg(long, value_name = "TEST_FUNCTION")] debug: Option, /// Print a gas report. - #[clap(long, env = "FORGE_GAS_REPORT")] + #[arg(long, env = "FORGE_GAS_REPORT")] gas_report: bool, /// Exit with code 0 even if a test fails. - #[clap(long, env = "FORGE_ALLOW_FAILURE")] + #[arg(long, env = "FORGE_ALLOW_FAILURE")] allow_failure: bool, /// Output test results in JSON format. - #[clap(long, short, help_heading = "Display options")] + #[arg(long, short, help_heading = "Display options")] json: bool, /// Stop running tests after the first failure. - #[clap(long)] + #[arg(long)] pub fail_fast: bool, /// The Etherscan (or equivalent) API key. - #[clap(long, env = "ETHERSCAN_API_KEY", value_name = "KEY")] + #[arg(long, env = "ETHERSCAN_API_KEY", value_name = "KEY")] etherscan_api_key: Option, /// List tests instead of running them. - #[clap(long, short, help_heading = "Display options")] + #[arg(long, short, help_heading = "Display options")] list: bool, /// Set seed used to generate randomness during your fuzz runs. - #[clap(long)] + #[arg(long)] pub fuzz_seed: Option, - #[clap(long, env = "FOUNDRY_FUZZ_RUNS", value_name = "RUNS")] + #[arg(long, env = "FOUNDRY_FUZZ_RUNS", value_name = "RUNS")] pub fuzz_runs: Option, - #[clap(flatten)] + /// File to rerun fuzz failures from. + #[arg(long)] + pub fuzz_input_file: Option, + + /// Max concurrent threads to use. + /// Default value is the number of available CPUs. + #[arg(long)] + pub max_threads: Option, + + #[command(flatten)] filter: FilterArgs, - #[clap(flatten)] + #[command(flatten)] evm_opts: EvmArgs, - #[clap(flatten)] + #[command(flatten)] opts: CoreBuildArgs, - #[clap(flatten)] + #[command(flatten)] pub watch: WatchArgs, /// Print test summary table. - #[clap(long, help_heading = "Display options")] + #[arg(long, help_heading = "Display options")] pub summary: bool, /// Print detailed test summary table. - #[clap(long, help_heading = "Display options", requires = "summary")] + #[arg(long, help_heading = "Display options", requires = "summary")] pub detailed: bool, + + /// Show test execution progress. + #[arg(long)] + pub show_progress: bool, } impl TestArgs { @@ -133,6 +154,74 @@ impl TestArgs { self.execute_tests().await } + /// Returns sources which include any tests to be executed. + /// If no filters are provided, sources are filtered by existence of test/invariant methods in + /// them, If filters are provided, sources are additionaly filtered by them. + pub fn get_sources_to_compile( + &self, + config: &Config, + filter: &ProjectPathsAwareFilter, + ) -> Result> { + let mut project = config.create_project(true, true)?; + project.settings.update_output_selection(|selection| { + *selection = OutputSelection::common_output_selection(["abi".to_string()]); + }); + + let output = project.compile()?; + + if output.has_compiler_errors() { + println!("{output}"); + eyre::bail!("Compilation failed"); + } + + // ABIs of all sources + let abis = output + .into_artifacts() + .filter_map(|(id, artifact)| artifact.abi.map(|abi| (id, abi))) + .collect::>(); + + // Filter sources by their abis and contract names. + let mut test_sources = abis + .iter() + .filter(|(id, abi)| matches_contract(id, abi, filter)) + .map(|(id, _)| id.source.clone()) + .collect::>(); + + if test_sources.is_empty() { + if filter.is_empty() { + println!( + "No tests found in project! \ + Forge looks for functions that starts with `test`." + ); + } else { + println!("No tests match the provided pattern:"); + print!("{filter}"); + + // Try to suggest a test when there's no match + if let Some(test_pattern) = &filter.args().test_pattern { + let test_name = test_pattern.as_str(); + let candidates = abis + .into_iter() + .filter(|(id, _)| { + filter.matches_path(&id.source) && filter.matches_contract(&id.name) + }) + .flat_map(|(_, abi)| abi.functions.into_keys()) + .collect::>(); + if let Some(suggestion) = utils::did_you_mean(test_name, candidates).pop() { + println!("\nDid you mean `{suggestion}`?"); + } + } + } + + eyre::bail!("No tests to run"); + } + + // Always recompile all sources to ensure that `getCode` cheatcode can use any artifact. + test_sources.extend(source_files_iter(project.paths.sources, L::FILE_EXTENSIONS)); + + Ok(test_sources) + } + /// Executes all the tests in the project. /// /// This will trigger the build process first. On success all test contracts that match the @@ -140,12 +229,23 @@ impl TestArgs { /// /// Returns the test results for all matching tests. pub async fn execute_tests(self) -> Result { + // Set number of max threads to execute tests. + // If not specified then the number of threads determined by rayon will be used. + if let Some(test_threads) = self.max_threads { + trace!(target: "forge::test", "execute tests with {} max threads", test_threads); + rayon::ThreadPoolBuilder::new().num_threads(test_threads as usize).build_global()?; + } + // Merge all configs let (mut config, mut evm_opts) = self.load_config_and_evm_opts_emit_warnings()?; // Explicitly enable isolation for gas reports for more correct gas accounting if self.gas_report { evm_opts.isolate = true; + } else { + // Do not collect gas report traces if gas report is not enabled. + config.fuzz.gas_report_samples = 0; + config.invariant.gas_report_samples = 0; } // Set up the project. @@ -163,18 +263,30 @@ impl TestArgs { let mut filter = self.filter(&config); trace!(target: "forge::test", ?filter, "using filter"); - let mut compiler = ProjectCompiler::new().quiet_if(self.json || self.opts.silent); - if config.sparse_mode { - compiler = compiler.filter(Box::new(filter.clone())); - } + let sources_to_compile = + self.get_sources_to_compile::(&config, &filter)?; + + let compiler = ProjectCompiler::new() + .quiet_if(self.json || self.opts.silent) + .files(sources_to_compile); + let output = compiler.compile(&project)?; let (zk_output, dual_compiled_contracts) = if config.zksync.should_compile() { - let zk_compiler = ProjectCompiler::new().quiet_if(self.json || self.opts.silent); - let zk_output = zk_compiler.zksync_compile(&project)?; + let zk_project = foundry_zksync_compiler::create_project(&config, config.cache, false)?; + + let sources_to_compile = + self.get_sources_to_compile::(&config, &filter)?; + let zk_compiler = ProjectCompiler::new() + .quiet_if(self.json || self.opts.silent) + .files(sources_to_compile); + + let zk_output = + zk_compiler.zksync_compile(&zk_project, config.zksync.avoid_contracts())?; let dual_compiled_contracts = - Some(DualCompiledContracts::new(&output, &zk_output, &project.paths)); - (Some(zk_output), dual_compiled_contracts) + DualCompiledContracts::new(&output, &zk_output, &project.paths); + + (Some(zk_output), Some(dual_compiled_contracts)) } else { (None, None) }; @@ -185,8 +297,8 @@ impl TestArgs { let profiles = get_available_profiles(toml)?; let test_options: TestOptions = TestOptionsBuilder::default() - .fuzz(config.fuzz) - .invariant(config.invariant) + .fuzz(config.fuzz.clone()) + .invariant(config.invariant.clone()) .profiles(profiles) .build(&output, project_root)?; @@ -204,22 +316,24 @@ impl TestArgs { // Clone the output only if we actually need it later for the debugger. let output_clone = should_debug.then(|| output.clone()); - let runner = MultiContractRunnerBuilder::default() + let config = Arc::new(config); + + let runner = MultiContractRunnerBuilder::new(config.clone()) .set_debug(should_debug) .initial_balance(evm_opts.initial_balance) .evm_spec(config.evm_spec_id()) .sender(evm_opts.sender) .with_fork(evm_opts.get_fork(&config, env.clone())) - .with_cheats_config(CheatsConfig::new( - &config, - evm_opts.clone(), - None, - dual_compiled_contracts.unwrap_or_default(), - config.zksync.run_in_zk_mode(), - )) .with_test_options(test_options.clone()) .enable_isolation(evm_opts.isolate) - .build(project_root, output, zk_output, env, evm_opts)?; + .build( + project_root, + output, + zk_output, + env, + evm_opts, + dual_compiled_contracts.unwrap_or_default(), + )?; if let Some(debug_test_pattern) = &self.debug { let test_pattern = &mut filter.args_mut().test_pattern; @@ -232,25 +346,32 @@ impl TestArgs { *test_pattern = Some(debug_test_pattern.clone()); } - let outcome = self.run_tests(runner, config, verbosity, &filter, test_options).await?; + let libraries = runner.libraries.clone(); + let outcome = self.run_tests(runner, config, verbosity, &filter).await?; if should_debug { - // There is only one test. - let Some(test) = outcome.into_tests_cloned().next() else { + // Get first non-empty suite result. We will have only one such entry + let Some((_, test_result)) = outcome + .results + .iter() + .find(|(_, r)| !r.test_results.is_empty()) + .map(|(_, r)| (r, r.test_results.values().next().unwrap())) + else { return Err(eyre::eyre!("no tests were executed")); }; let sources = ContractSources::from_project_output( output_clone.as_ref().unwrap(), project.root(), + Some(&libraries), )?; // Run the debugger. let mut builder = Debugger::builder() - .debug_arenas(test.result.debug.as_slice()) + .debug_arenas(test_result.debug.as_slice()) .sources(sources) - .breakpoints(test.result.breakpoints); - if let Some(decoder) = &outcome.decoder { + .breakpoints(test_result.breakpoints.clone()); + if let Some(decoder) = &outcome.last_run_decoder { builder = builder.decoder(decoder); } let mut debugger = builder.build(); @@ -264,10 +385,9 @@ impl TestArgs { pub async fn run_tests( &self, mut runner: MultiContractRunner, - config: Config, + config: Arc, verbosity: u8, filter: &ProjectPathsAwareFilter, - test_options: TestOptions, ) -> eyre::Result { if self.list { return list(runner, filter, self.json); @@ -275,28 +395,7 @@ impl TestArgs { trace!(target: "forge::test", "running all tests"); - let num_filtered = runner.matching_test_function_count(filter); - if num_filtered == 0 { - println!(); - if filter.is_empty() { - println!( - "No tests found in project! \ - Forge looks for functions that starts with `test`." - ); - } else { - println!("No tests match the provided pattern:"); - print!("{filter}"); - - // Try to suggest a test when there's no match - if let Some(test_pattern) = &filter.args().test_pattern { - let test_name = test_pattern.as_str(); - let candidates = runner.get_tests(filter); - if let Some(suggestion) = utils::did_you_mean(test_name, candidates).pop() { - println!("\nDid you mean `{suggestion}`?"); - } - } - } - } + let num_filtered = runner.matching_test_functions(filter).count(); if self.debug.is_some() && num_filtered != 1 { eyre::bail!( "{num_filtered} tests matched your criteria, but exactly 1 test must match in order to run the debugger.\n\n\ @@ -306,31 +405,35 @@ impl TestArgs { } if self.json { - let results = runner.test_collect(filter, test_options).await; + let results = runner.test_collect(filter); println!("{}", serde_json::to_string(&results)?); return Ok(TestOutcome::new(results, self.allow_failure)); } - // Set up trace identifiers. + let remote_chain_id = runner.evm_opts.get_remote_chain_id().await; let known_contracts = runner.known_contracts.clone(); - let mut local_identifier = LocalTraceIdentifier::new(&known_contracts); - let remote_chain_id = runner.evm_opts.get_remote_chain_id(); - let mut etherscan_identifier = EtherscanIdentifier::new(&config, remote_chain_id)?; // Run tests. let (tx, rx) = channel::<(String, SuiteResult)>(); let timer = Instant::now(); - let handle = tokio::task::spawn({ + let show_progress = self.show_progress; + let handle = tokio::task::spawn_blocking({ let filter = filter.clone(); - async move { runner.test(&filter, tx, test_options).await } + move || runner.test(&filter, tx, show_progress) }); - let mut gas_report = - self.gas_report.then(|| GasReport::new(config.gas_reports, config.gas_reports_ignore)); + // Set up trace identifiers. + let mut identifier = TraceIdentifiers::new().with_local(&known_contracts); + + // Avoid using etherscan for gas report as we decode more traces and this will be + // expensive. + if !self.gas_report { + identifier = identifier.with_etherscan(&config, remote_chain_id)?; + } // Build the trace decoder. let mut builder = CallTraceDecoderBuilder::new() - .with_local_identifier_abis(&local_identifier) + .with_known_contracts(&known_contracts) .with_verbosity(verbosity); // Signatures are of no value for gas reports. if !self.gas_report { @@ -341,8 +444,9 @@ impl TestArgs { } let mut decoder = builder.build(); - // We identify addresses if we're going to print *any* trace or gas report. - let identify_addresses = verbosity >= 3 || self.gas_report || self.debug.is_some(); + let mut gas_report = self + .gas_report + .then(|| GasReport::new(config.gas_reports.clone(), config.gas_reports_ignore.clone())); let mut outcome = TestOutcome::empty(self.allow_failure); @@ -350,10 +454,16 @@ impl TestArgs { for (contract_name, suite_result) in rx { let tests = &suite_result.test_results; + // Clear the addresses and labels from previous test. + decoder.clear_addresses(); + + // We identify addresses if we're going to print *any* trace or gas report. + let identify_addresses = verbosity >= 3 || self.gas_report || self.debug.is_some(); + // Print suite header. println!(); for warning in suite_result.warnings.iter() { - eprintln!("{} {warning}", Paint::yellow("Warning:").bold()); + eprintln!("{} {warning}", "Warning:".yellow().bold()); } if !tests.is_empty() { let len = tests.len(); @@ -382,10 +492,6 @@ impl TestArgs { // processing the remaining tests and print the suite summary. any_test_failed |= result.status == TestStatus::Failure; - if result.traces.is_empty() { - continue; - } - // Clear the addresses and labels from previous runs. decoder.clear_addresses(); decoder @@ -396,8 +502,7 @@ impl TestArgs { let mut decoded_traces = Vec::with_capacity(result.traces.len()); for (kind, arena) in &result.traces { if identify_addresses { - decoder.identify(arena, &mut local_identifier); - decoder.identify(arena, &mut etherscan_identifier); + decoder.identify(arena, &mut identifier); } // verbosity: @@ -428,7 +533,26 @@ impl TestArgs { } if let Some(gas_report) = &mut gas_report { - gas_report.analyze(&result.traces, &decoder).await; + gas_report + .analyze(result.traces.iter().map(|(_, arena)| arena), &decoder) + .await; + + for trace in result.gas_report_traces.iter() { + decoder.clear_addresses(); + + // Re-execute setup and deployment traces to collect identities created in + // setUp and constructor. + for (kind, arena) in &result.traces { + if !matches!(kind, TraceKind::Execution) { + decoder.identify(arena, &mut identifier); + } + } + + for arena in trace { + decoder.identify(arena, &mut identifier); + gas_report.analyze([arena], &decoder).await; + } + } } } @@ -443,14 +567,15 @@ impl TestArgs { break; } } + outcome.last_run_decoder = Some(decoder); let duration = timer.elapsed(); trace!(target: "forge::test", len=outcome.results.len(), %any_test_failed, "done with results"); - outcome.decoder = Some(decoder); - if let Some(gas_report) = gas_report { - shell::println(gas_report.finalize())?; + let finalized = gas_report.finalize(); + shell::println(&finalized)?; + outcome.gas_report = Some(finalized); } if !outcome.results.is_empty() { @@ -486,10 +611,10 @@ impl TestArgs { /// Returns the [`watchexec::InitConfig`] and [`watchexec::RuntimeConfig`] necessary to /// bootstrap a new [`watchexe::Watchexec`] loop. - pub(crate) fn watchexec_config(&self) -> Result<(InitConfig, RuntimeConfig)> { + pub(crate) fn watchexec_config(&self) -> Result { self.watch.watchexec_config(|| { let config = Config::from(self); - vec![config.src, config.test] + [config.src, config.test] }) } } @@ -509,6 +634,9 @@ impl Provider for TestArgs { if let Some(fuzz_runs) = self.fuzz_runs { fuzz_dict.insert("runs".to_string(), fuzz_runs.into()); } + if let Some(fuzz_input_file) = self.fuzz_input_file.clone() { + fuzz_dict.insert("failure_persist_file".to_string(), fuzz_input_file.into()); + } dict.insert("fuzz".to_string(), fuzz_dict.into()); if let Some(etherscan_api_key) = @@ -546,7 +674,8 @@ fn list( #[cfg(test)] mod tests { use super::*; - use foundry_config::Chain; + use foundry_config::{Chain, InvariantConfig}; + use foundry_test_utils::forgetest_async; #[test] fn watch_parse() { @@ -580,4 +709,69 @@ mod tests { test("--chain-id=1", Chain::mainnet()); test("--chain-id=42", Chain::from_id(42)); } + + forgetest_async!(gas_report_fuzz_invariant, |prj, _cmd| { + // speed up test by running with depth of 15 + let config = Config { + invariant: { InvariantConfig { depth: 15, ..Default::default() } }, + ..Default::default() + }; + prj.write_config(config); + + prj.insert_ds_test(); + prj.add_source( + "Contracts.sol", + r#" +//SPDX-license-identifier: MIT + +import "./test.sol"; + +contract Foo { + function foo() public {} +} + +contract Bar { + function bar() public {} +} + + +contract FooBarTest is DSTest { + Foo public targetContract; + + function setUp() public { + targetContract = new Foo(); + } + + function invariant_dummy() public { + assertTrue(true); + } + + function testFuzz_bar(uint256 _val) public { + (new Bar()).bar(); + } +} + "#, + ) + .unwrap(); + + let args = TestArgs::parse_from([ + "foundry-cli", + "--gas-report", + "--root", + &prj.root().to_string_lossy(), + "--silent", + ]); + + let outcome = args.run().await.unwrap(); + let gas_report = outcome.gas_report.unwrap(); + + assert_eq!(gas_report.contracts.len(), 3); + let call_cnts = gas_report + .contracts + .values() + .flat_map(|c| c.functions.values().flat_map(|f| f.values().map(|v| v.calls.len()))) + .collect::>(); + // assert that all functions were called at least 100 times + assert!(call_cnts.iter().all(|c| *c > 100)); + }); } diff --git a/crates/forge/bin/cmd/tree.rs b/crates/forge/bin/cmd/tree.rs index 689d6e444..088975d87 100644 --- a/crates/forge/bin/cmd/tree.rs +++ b/crates/forge/bin/cmd/tree.rs @@ -2,7 +2,7 @@ use clap::Parser; use eyre::Result; use foundry_cli::{opts::ProjectPathsArgs, utils::LoadConfig}; use foundry_compilers::{ - resolver::{Charset, TreeOptions}, + resolver::{parse::SolData, Charset, TreeOptions}, Graph, }; @@ -10,16 +10,16 @@ use foundry_compilers::{ #[derive(Clone, Debug, Parser)] pub struct TreeArgs { /// Do not de-duplicate (repeats all shared dependencies) - #[clap(long)] + #[arg(long)] no_dedupe: bool, /// Character set to use in output. /// /// [possible values: utf8, ascii] - #[clap(long, default_value = "utf8")] + #[arg(long, default_value = "utf8")] charset: Charset, - #[clap(flatten)] + #[command(flatten)] opts: ProjectPathsArgs, } @@ -28,7 +28,7 @@ foundry_config::impl_figment_convert!(TreeArgs, opts); impl TreeArgs { pub fn run(self) -> Result<()> { let config = self.try_load_config_emit_warnings()?; - let graph = Graph::resolve(&config.project_paths())?; + let graph = Graph::::resolve(&config.project_paths())?; let opts = TreeOptions { charset: self.charset, no_dedupe: self.no_dedupe }; graph.print_with_options(opts); diff --git a/crates/forge/bin/cmd/update.rs b/crates/forge/bin/cmd/update.rs index 37e5baccb..5ddc5460a 100644 --- a/crates/forge/bin/cmd/update.rs +++ b/crates/forge/bin/cmd/update.rs @@ -17,15 +17,15 @@ pub struct UpdateArgs { /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] root: Option, /// Override the up-to-date check. - #[clap(short, long)] + #[arg(short, long)] force: bool, /// Recursively update submodules. - #[clap(short, long)] + #[arg(short, long)] recursive: bool, } impl_figment_convert_basic!(UpdateArgs); @@ -44,7 +44,7 @@ impl UpdateArgs { git.submodule_update(self.force, true, false, false, paths)?; // initialize submodules of each submodule recursively (otherwise direct submodule // dependencies will revert to last commit) - git.submodule_foreach(false, "git submodule update --init --progress --recursive ") + git.submodule_foreach(false, "git submodule update --init --progress --recursive") } } } @@ -52,7 +52,7 @@ impl UpdateArgs { /// Returns `(root, paths)` where `root` is the root of the Git repository and `paths` are the /// relative paths of the dependencies. pub fn dependencies_paths(deps: &[Dependency], config: &Config) -> Result<(PathBuf, Vec)> { - let git_root = Git::root_of(&config.__root.0)?; + let git_root = Git::root_of(&config.root.0)?; let libs = config.install_lib_dir(); let mut paths = Vec::with_capacity(deps.len()); diff --git a/crates/forge/bin/cmd/verify/etherscan/standard_json.rs b/crates/forge/bin/cmd/verify/etherscan/standard_json.rs deleted file mode 100644 index b42380ec6..000000000 --- a/crates/forge/bin/cmd/verify/etherscan/standard_json.rs +++ /dev/null @@ -1,80 +0,0 @@ -use super::{EtherscanSourceProvider, VerifyArgs}; -use eyre::{Context, Result}; -use foundry_block_explorers::verify::CodeFormat; -use foundry_compilers::{artifacts::StandardJsonCompilerInput, Project}; -use semver::Version; - -use std::path::Path; - -#[derive(Debug)] -pub struct EtherscanStandardJsonSource; -impl EtherscanSourceProvider for EtherscanStandardJsonSource { - fn source( - &self, - args: &VerifyArgs, - project: &Project, - target: &Path, - version: &Version, - ) -> Result<(String, String, CodeFormat)> { - let mut input: StandardJsonCompilerInput = project - .standard_json_input(target) - .wrap_err("Failed to get standard json input")? - .normalize_evm_version(version); - - input.settings.libraries.libs = input - .settings - .libraries - .libs - .into_iter() - .map(|(f, libs)| (f.strip_prefix(project.root()).unwrap_or(&f).to_path_buf(), libs)) - .collect(); - - // remove all incompatible settings - input.settings.sanitize(version); - - let source = - serde_json::to_string(&input).wrap_err("Failed to parse standard json input")?; - - trace!(target: "forge::verify", standard_json=?source, "determined standard json input"); - - let name = format!( - "{}:{}", - target.strip_prefix(project.root()).unwrap_or(target).display(), - args.contract.name.clone() - ); - Ok((source, name, CodeFormat::StandardJsonInput)) - } - - fn zk_source( - &self, - args: &VerifyArgs, - project: &Project, - target: &Path, - version: &Version, - ) -> Result<(String, String, CodeFormat)> { - let mut input = project - .zksync_standard_json_input(target) - .wrap_err("Failed to get standard json input")? - .normalize_evm_version(version); - - input.settings.libraries.libs = input - .settings - .libraries - .libs - .into_iter() - .map(|(f, libs)| (f.strip_prefix(project.root()).unwrap_or(&f).to_path_buf(), libs)) - .collect(); - - let source = - serde_json::to_string(&input).wrap_err("Failed to parse standard json input")?; - - trace!(target: "forge::verify", standard_json=?source, "determined zksync standard json input"); - - let name = format!( - "{}:{}", - target.strip_prefix(project.root()).unwrap_or(target).display(), - args.contract.name.clone() - ); - Ok((source, name, CodeFormat::StandardJsonInput)) - } -} diff --git a/crates/forge/bin/cmd/verify/provider.rs b/crates/forge/bin/cmd/verify/provider.rs deleted file mode 100644 index d5721018d..000000000 --- a/crates/forge/bin/cmd/verify/provider.rs +++ /dev/null @@ -1,84 +0,0 @@ -use super::{ - etherscan::EtherscanVerificationProvider, sourcify::SourcifyVerificationProvider, VerifyArgs, - VerifyCheckArgs, -}; -use async_trait::async_trait; -use eyre::Result; -use std::{fmt, str::FromStr}; - -/// An abstraction for various verification providers such as etherscan, sourcify, blockscout -#[async_trait] -pub trait VerificationProvider { - /// This should ensure the verify request can be prepared successfully. - /// - /// Caution: Implementers must ensure that this _never_ sends the actual verify request - /// `[VerificationProvider::verify]`, instead this is supposed to evaluate whether the given - /// [`VerifyArgs`] are valid to begin with. This should prevent situations where there's a - /// contract deployment that's executed before the verify request and the subsequent verify task - /// fails due to misconfiguration. - async fn preflight_check(&mut self, args: VerifyArgs) -> Result<()>; - - /// Sends the actual verify request for the targeted contract. - async fn verify(&mut self, args: VerifyArgs) -> Result<()>; - - /// Checks whether the contract is verified. - async fn check(&self, args: VerifyCheckArgs) -> Result<()>; -} - -impl FromStr for VerificationProviderType { - type Err = String; - - fn from_str(s: &str) -> Result { - match s { - "e" | "etherscan" => Ok(VerificationProviderType::Etherscan), - "s" | "sourcify" => Ok(VerificationProviderType::Sourcify), - "b" | "blockscout" => Ok(VerificationProviderType::Blockscout), - _ => Err(format!("Unknown provider: {s}")), - } - } -} - -impl fmt::Display for VerificationProviderType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - VerificationProviderType::Etherscan => { - write!(f, "etherscan")?; - } - VerificationProviderType::Sourcify => { - write!(f, "sourcify")?; - } - VerificationProviderType::Blockscout => { - write!(f, "blockscout")?; - } - }; - Ok(()) - } -} - -#[derive(Clone, Debug, Default, PartialEq, Eq, clap::ValueEnum)] -pub enum VerificationProviderType { - #[default] - Etherscan, - Sourcify, - Blockscout, -} - -impl VerificationProviderType { - /// Returns the corresponding `VerificationProvider` for the key - pub fn client(&self, key: &Option) -> Result> { - match self { - VerificationProviderType::Etherscan => { - if key.as_ref().map_or(true, |key| key.is_empty()) { - eyre::bail!("ETHERSCAN_API_KEY must be set") - } - Ok(Box::::default()) - } - VerificationProviderType::Sourcify => { - Ok(Box::::default()) - } - VerificationProviderType::Blockscout => { - Ok(Box::::default()) - } - } - } -} diff --git a/crates/forge/bin/cmd/watch.rs b/crates/forge/bin/cmd/watch.rs index f2ca664be..8ace624f5 100644 --- a/crates/forge/bin/cmd/watch.rs +++ b/crates/forge/bin/cmd/watch.rs @@ -3,40 +3,47 @@ use clap::Parser; use eyre::Result; use foundry_cli::utils::{self, FoundryPathExt}; use foundry_config::Config; -use std::{collections::HashSet, convert::Infallible, path::PathBuf, sync::Arc}; +use parking_lot::Mutex; +use std::{ + collections::HashSet, + path::PathBuf, + sync::{ + atomic::{AtomicU8, Ordering}, + Arc, + }, + time::Duration, +}; +use tokio::process::Command as TokioCommand; use watchexec::{ - action::{Action, Outcome, PreSpawn}, - command::Command, - config::{InitConfig, RuntimeConfig}, - event::{Event, Priority, ProcessEnd}, - handler::SyncFnHandler, + action::ActionHandler, + command::{Command, Program}, + job::{CommandState, Job}, paths::summarise_events_to_env, - signal::source::MainSignal, Watchexec, }; +use watchexec_events::{Event, Priority, ProcessEnd}; +use watchexec_signals::Signal; +use yansi::{Color, Paint}; + +type SpawnHook = Arc; #[derive(Clone, Debug, Default, Parser)] -#[clap(next_help_heading = "Watch options")] +#[command(next_help_heading = "Watch options")] pub struct WatchArgs { /// Watch the given files or directories for changes. /// /// If no paths are provided, the source and test directories of the project are watched. - #[clap( - long, - short, - num_args(0..), - value_name = "PATH", - )] + #[arg(long, short, num_args(0..), value_name = "PATH")] pub watch: Option>, /// Do not restart the command while it's still running. - #[clap(long)] + #[arg(long)] pub no_restart: bool, /// Explicitly re-run all tests when a change is made. /// /// By default, only the tests of the last modified test file are executed. - #[clap(long)] + #[arg(long)] pub run_all: bool, /// File update debounce delay. @@ -52,212 +59,269 @@ pub struct WatchArgs { /// /// When using --poll mode, you'll want a larger duration, or risk /// overloading disk I/O. - #[clap(long, value_name = "DELAY")] + #[arg(long, value_name = "DELAY")] pub watch_delay: Option, } impl WatchArgs { - /// Returns new [InitConfig] and [RuntimeConfig] based on the [WatchArgs] + /// Creates a new [`watchexec::Config`]. /// /// If paths were provided as arguments the these will be used as the watcher's pathset, - /// otherwise the path the closure returns will be used - pub fn watchexec_config( + /// otherwise the path the closure returns will be used. + pub fn watchexec_config, P: Into>( &self, - f: impl FnOnce() -> Vec, - ) -> Result<(InitConfig, RuntimeConfig)> { - let init = init()?; - let mut runtime = runtime(self)?; + default_paths: impl FnOnce() -> PS, + ) -> Result { + self.watchexec_config_generic(default_paths, None) + } - // contains all the arguments `--watch p1, p2, p3` - let has_paths = self.watch.as_ref().map(|paths| !paths.is_empty()).unwrap_or_default(); + /// Creates a new [`watchexec::Config`] with a custom command spawn hook. + /// + /// If paths were provided as arguments the these will be used as the watcher's pathset, + /// otherwise the path the closure returns will be used. + pub fn watchexec_config_with_override, P: Into>( + &self, + default_paths: impl FnOnce() -> PS, + spawn_hook: impl Fn(&[Event], &mut TokioCommand) + Send + Sync + 'static, + ) -> Result { + self.watchexec_config_generic(default_paths, Some(Arc::new(spawn_hook))) + } - if !has_paths { - // use alternative pathset, but only those that exists - runtime.pathset(f().into_iter().filter(|p| p.exists())); + fn watchexec_config_generic, P: Into>( + &self, + default_paths: impl FnOnce() -> PS, + spawn_hook: Option, + ) -> Result { + let mut paths = self.watch.as_deref().unwrap_or_default(); + let storage: Vec<_>; + if paths.is_empty() { + storage = default_paths().into_iter().map(Into::into).filter(|p| p.exists()).collect(); + paths = &storage; } - Ok((init, runtime)) + self.watchexec_config_inner(paths, spawn_hook) } -} -/// Executes a [`Watchexec`] that listens for changes in the project's src dir and reruns `forge -/// build` -pub async fn watch_build(args: BuildArgs) -> Result<()> { - let (init, mut runtime) = args.watchexec_config()?; - let cmd = cmd_args(args.watch.watch.as_ref().map(|paths| paths.len()).unwrap_or_default()); + fn watchexec_config_inner( + &self, + paths: &[PathBuf], + spawn_hook: Option, + ) -> Result { + let config = watchexec::Config::default(); - trace!("watch build cmd={:?}", cmd); - runtime.command(watch_command(cmd.clone())); + config.on_error(|err| eprintln!("[[{err:?}]]")); - let wx = Watchexec::new(init, runtime.clone())?; - on_action(args.watch, runtime, Arc::clone(&wx), cmd, (), |_| {}); + if let Some(delay) = &self.watch_delay { + config.throttle(utils::parse_delay(delay)?); + } - // start executing the command immediately - wx.send_event(Event::default(), Priority::default()).await?; - wx.main().await??; + config.pathset(paths.iter().map(|p| p.as_path())); + + let n_path_args = self.watch.as_deref().unwrap_or_default().len(); + let base_command = Arc::new(watch_command(cmd_args(n_path_args))); + + let id = watchexec::Id::default(); + let quit_again = Arc::new(AtomicU8::new(0)); + let stop_timeout = Duration::from_secs(5); + let no_restart = self.no_restart; + let stop_signal = Signal::Terminate; + config.on_action(move |mut action| { + let base_command = base_command.clone(); + let job = action.get_or_create_job(id, move || base_command.clone()); + + let events = action.events.clone(); + let spawn_hook = spawn_hook.clone(); + job.set_spawn_hook(move |command, _| { + // https://github.com/watchexec/watchexec/blob/72f069a8477c679e45f845219276b0bfe22fed79/crates/cli/src/emits.rs#L9 + let env = summarise_events_to_env(events.iter()); + for (k, v) in env { + command.command_mut().env(format!("WATCHEXEC_{k}_PATH"), v); + } - Ok(()) -} + if let Some(spawn_hook) = &spawn_hook { + spawn_hook(&events, command.command_mut()); + } + }); + + let clear_screen = || { + let _ = clearscreen::clear(); + }; + + let quit = |mut action: ActionHandler| { + match quit_again.fetch_add(1, Ordering::Relaxed) { + 0 => { + eprintln!( + "[Waiting {stop_timeout:?} for processes to exit before stopping... \ + Ctrl-C again to exit faster]" + ); + action.quit_gracefully(stop_signal, stop_timeout); + } + 1 => action.quit_gracefully(Signal::ForceStop, Duration::ZERO), + _ => action.quit(), + } -/// Executes a [`Watchexec`] that listens for changes in the project's src dir and reruns `forge -/// snapshot` -pub async fn watch_snapshot(args: SnapshotArgs) -> Result<()> { - let (init, mut runtime) = args.watchexec_config()?; - let cmd = cmd_args(args.test.watch.watch.as_ref().map(|paths| paths.len()).unwrap_or_default()); + action + }; - trace!("watch snapshot cmd={:?}", cmd); - runtime.command(watch_command(cmd.clone())); - let wx = Watchexec::new(init, runtime.clone())?; + let signals = action.signals().collect::>(); - on_action(args.test.watch.clone(), runtime, Arc::clone(&wx), cmd, (), |_| {}); + if signals.contains(&Signal::Terminate) || signals.contains(&Signal::Interrupt) { + return quit(action); + } - // start executing the command immediately - wx.send_event(Event::default(), Priority::default()).await?; - wx.main().await??; + // Only filesystem events below here (or empty synthetic events). + if action.paths().next().is_none() && !action.events.iter().any(|e| e.is_empty()) { + debug!("no filesystem or synthetic events, skip without doing more"); + return action; + } - Ok(()) -} + job.run({ + let job = job.clone(); + move |context| { + if context.current.is_running() && no_restart { + return; + } + job.restart_with_signal(stop_signal, stop_timeout); + job.run({ + let job = job.clone(); + move |context| { + clear_screen(); + setup_process(job, &context.command) + } + }); + } + }); -/// Executes a [`Watchexec`] that listens for changes in the project's src dir and reruns `forge -/// test` -pub async fn watch_test(args: TestArgs) -> Result<()> { - let (init, mut runtime) = args.watchexec_config()?; - let cmd = cmd_args(args.watch.watch.as_ref().map(|paths| paths.len()).unwrap_or_default()); - trace!("watch test cmd={:?}", cmd); - runtime.command(watch_command(cmd.clone())); - let wx = Watchexec::new(init, runtime.clone())?; + action + }); - let config: Config = args.build_args().into(); + Ok(config) + } +} - let filter = args.filter(&config); +fn setup_process(job: Job, _command: &Command) { + tokio::spawn(async move { + job.to_wait().await; + job.run(move |context| end_of_process(context.current)); + }); +} - // marker to check whether to override the command - let no_reconfigure = filter.args().test_pattern.is_some() || - filter.args().path_pattern.is_some() || - filter.args().contract_pattern.is_some() || - args.watch.run_all; +fn end_of_process(state: &CommandState) { + let CommandState::Finished { status, started, finished } = state else { + return; + }; - let state = WatchTestState { - project_root: config.__root.0, - no_reconfigure, - last_test_files: Default::default(), + let duration = *finished - *started; + let timings = true; + let timing = if timings { format!(", lasted {duration:?}") } else { String::new() }; + let (msg, fg) = match status { + ProcessEnd::ExitError(code) => (format!("Command exited with {code}{timing}"), Color::Red), + ProcessEnd::ExitSignal(sig) => { + (format!("Command killed by {sig:?}{timing}"), Color::Magenta) + } + ProcessEnd::ExitStop(sig) => (format!("Command stopped by {sig:?}{timing}"), Color::Blue), + ProcessEnd::Continued => (format!("Command continued{timing}"), Color::Cyan), + ProcessEnd::Exception(ex) => { + (format!("Command ended by exception {ex:#x}{timing}"), Color::Yellow) + } + ProcessEnd::Success => (format!("Command was successful{timing}"), Color::Green), }; - on_action(args.watch.clone(), runtime, Arc::clone(&wx), cmd, state, on_test); - // start executing the command immediately - wx.send_event(Event::default(), Priority::default()).await?; - wx.main().await??; + let quiet = false; + if !quiet { + eprintln!("{}", format!("[{msg}]").paint(fg.foreground())); + } +} +/// Runs the given [`watchexec::Config`]. +pub async fn run(config: watchexec::Config) -> Result<()> { + let wx = Watchexec::with_config(config)?; + wx.send_event(Event::default(), Priority::Urgent).await?; + wx.main().await??; Ok(()) } -#[derive(Clone, Debug)] -struct WatchTestState { - /// the root directory of the project - project_root: PathBuf, - /// marks whether we can reconfigure the watcher command with the `--match-path` arg - no_reconfigure: bool, - /// Tracks the last changed test files, if any so that if a non-test file was modified we run - /// this file instead *Note:* this is a vec, so we can also watch out for changes - /// introduced by `forge fmt` - last_test_files: HashSet, +/// Executes a [`Watchexec`] that listens for changes in the project's src dir and reruns `forge +/// build` +pub async fn watch_build(args: BuildArgs) -> Result<()> { + let config = args.watchexec_config()?; + run(config).await } -/// The `on_action` hook for `forge test --watch` -fn on_test(action: OnActionState) { - let OnActionState { args, runtime, action, wx, cmd, other } = action; - let WatchTestState { project_root, no_reconfigure, last_test_files } = other; +/// Executes a [`Watchexec`] that listens for changes in the project's src dir and reruns `forge +/// snapshot` +pub async fn watch_snapshot(args: SnapshotArgs) -> Result<()> { + let config = args.watchexec_config()?; + run(config).await +} - if no_reconfigure { - // nothing to reconfigure - return - } +/// Executes a [`Watchexec`] that listens for changes in the project's src dir and reruns `forge +/// test` +pub async fn watch_test(args: TestArgs) -> Result<()> { + let config: Config = args.build_args().into(); + let filter = args.filter(&config); + // Marker to check whether to override the command. + let _no_reconfigure = filter.args().test_pattern.is_some() || + filter.args().path_pattern.is_some() || + filter.args().contract_pattern.is_some() || + args.watch.run_all; - let mut cmd = cmd.clone(); - - let mut changed_sol_test_files: HashSet<_> = action - .events - .iter() - .flat_map(|e| e.paths()) - .filter(|(path, _)| path.is_sol_test()) - .filter_map(|(path, _)| path.to_str()) - .map(str::to_string) - .collect(); - - // replace `--match-path` | `-mp` argument - if let Some(pos) = cmd.iter().position(|arg| arg == "--match-path" || arg == "-mp") { - // --match-path requires 1 argument - cmd.drain(pos..=(pos + 1)); - } + let last_test_files = Mutex::new(HashSet::::new()); + let project_root = config.root.0.to_string_lossy().into_owned(); + let config = args.watch.watchexec_config_with_override( + || [&config.test, &config.src], + move |events, command| { + let mut changed_sol_test_files: HashSet<_> = events + .iter() + .flat_map(|e| e.paths()) + .filter(|(path, _)| path.is_sol_test()) + .filter_map(|(path, _)| path.to_str()) + .map(str::to_string) + .collect(); + + if changed_sol_test_files.len() > 1 { + // Run all tests if multiple files were changed at once, for example when running + // `forge fmt`. + return; + } - if changed_sol_test_files.len() > 1 || - (changed_sol_test_files.is_empty() && last_test_files.is_empty()) - { - // this could happen if multiple files were changed at once, for example `forge fmt` was - // run, or if no test files were changed and no previous test files were modified in which - // case we simply run all - let mut config = runtime.clone(); - config.command(watch_command(cmd.clone())); - // re-register the action - on_action( - args.clone(), - config, - wx, - cmd, - WatchTestState { - project_root, - no_reconfigure, - last_test_files: changed_sol_test_files, - }, - on_test, - ); - return - } + if changed_sol_test_files.is_empty() { + // Reuse the old test files if a non-test file was changed. + let last = last_test_files.lock(); + if last.is_empty() { + return; + } + changed_sol_test_files.clone_from(&last); + } - if changed_sol_test_files.is_empty() { - // reuse the old test files if a non-test file was changed - changed_sol_test_files = last_test_files; - } + // append `--match-path` glob + let mut file = changed_sol_test_files.iter().next().expect("test file present").clone(); - // append `--match-path` glob - let mut file = changed_sol_test_files.clone().into_iter().next().expect("test file present"); + // remove the project root dir from the detected file + if let Some(f) = file.strip_prefix(&project_root) { + file = f.trim_start_matches('/').to_string(); + } - // remove the project root dir from the detected file - if let Some(root) = project_root.as_os_str().to_str() { - if let Some(f) = file.strip_prefix(root) { - file = f.trim_start_matches('/').to_string(); - } - } + trace!(?file, "reconfigure test command"); + + command.arg("--match-path").arg(&file); + }, + )?; + run(config).await?; - let mut new_cmd = cmd.clone(); - new_cmd.push("--match-path".to_string()); - new_cmd.push(file); - trace!("reconfigure test command {:?}", new_cmd); - - // reconfigure the executor with a new runtime - let mut config = runtime.clone(); - config.command(watch_command(new_cmd)); - - // re-register the action - on_action( - args.clone(), - config, - wx, - cmd, - WatchTestState { project_root, no_reconfigure, last_test_files: changed_sol_test_files }, - on_test, - ); + Ok(()) } -/// Converts a list of arguments to a `watchexec::Command` +/// Converts a list of arguments to a `watchexec::Command`. /// -/// The first index in `args`, is expected to be the path to the executable, See `cmd_args` +/// The first index in `args` is the path to the executable. /// /// # Panics -/// if `args` is empty +/// +/// Panics if `args` is empty. fn watch_command(mut args: Vec) -> Command { debug_assert!(!args.is_empty()); let prog = args.remove(0); - Command::Exec { prog, args } + Command { program: Program::Exec { prog: prog.into(), args }, options: Default::default() } } /// Returns the env args without the `--watch` flag from the args for the Watchexec command @@ -299,148 +363,6 @@ fn clean_cmd_args(num: usize, mut cmd_args: Vec) -> Vec { cmd_args } -/// Returns the Initialisation configuration for [`Watchexec`]. -pub fn init() -> Result { - let mut config = InitConfig::default(); - config.on_error(SyncFnHandler::from(|data| { - trace!("[[{:?}]]", data); - Ok::<_, Infallible>(()) - })); - - Ok(config) -} - -/// Contains all necessary context to reconfigure a [`Watchexec`] on the fly -struct OnActionState<'a, T: Clone> { - args: &'a WatchArgs, - runtime: &'a RuntimeConfig, - action: &'a Action, - cmd: &'a Vec, - wx: Arc, - // additional context to inject - other: T, -} - -/// Registers the `on_action` hook on the `RuntimeConfig` currently in use in the `Watchexec` -/// -/// **Note** this is a bit weird since we're installing the hook on the config that's already used -/// in `Watchexec` but necessary if we want to have access to it in order to -/// [`Watchexec::reconfigure`] -fn on_action( - args: WatchArgs, - mut config: RuntimeConfig, - wx: Arc, - cmd: Vec, - other: T, - f: F, -) where - F: for<'a> Fn(OnActionState<'a, T>) + Send + 'static, - T: Clone + Send + 'static, -{ - let on_busy = if args.no_restart { "do-nothing" } else { "restart" }; - let runtime = config.clone(); - let w = Arc::clone(&wx); - config.on_action(move |action: Action| { - let fut = async { Ok::<(), Infallible>(()) }; - let signals: Vec = action.events.iter().flat_map(|e| e.signals()).collect(); - let has_paths = action.events.iter().flat_map(|e| e.paths()).next().is_some(); - - if signals.contains(&MainSignal::Terminate) || signals.contains(&MainSignal::Interrupt) { - action.outcome(Outcome::both(Outcome::Stop, Outcome::Exit)); - return fut - } - - if !has_paths { - if !signals.is_empty() { - let mut out = Outcome::DoNothing; - for sig in signals { - out = Outcome::both(out, Outcome::Signal(sig)); - } - - action.outcome(out); - return fut - } - - let completion = action.events.iter().flat_map(|e| e.completions()).next(); - if let Some(status) = completion { - match status { - Some(ProcessEnd::ExitError(code)) => { - trace!("Command exited with {code}") - } - Some(ProcessEnd::ExitSignal(sig)) => { - trace!("Command killed by {:?}", sig) - } - Some(ProcessEnd::ExitStop(sig)) => { - trace!("Command stopped by {:?}", sig) - } - Some(ProcessEnd::Continued) => trace!("Command continued"), - Some(ProcessEnd::Exception(ex)) => { - trace!("Command ended by exception {:#x}", ex) - } - Some(ProcessEnd::Success) => trace!("Command was successful"), - None => trace!("Command completed"), - }; - - action.outcome(Outcome::DoNothing); - return fut - } - } - - f(OnActionState { - args: &args, - runtime: &runtime, - action: &action, - wx: w.clone(), - cmd: &cmd, - other: other.clone(), - }); - - // mattsse: could be made into flag to never clear the shell - let clear = false; - let when_running = match (clear, on_busy) { - (_, "do-nothing") => Outcome::DoNothing, - (true, "restart") => { - Outcome::both(Outcome::Stop, Outcome::both(Outcome::Clear, Outcome::Start)) - } - (false, "restart") => Outcome::both(Outcome::Stop, Outcome::Start), - _ => Outcome::DoNothing, - }; - - let when_idle = - if clear { Outcome::both(Outcome::Clear, Outcome::Start) } else { Outcome::Start }; - - action.outcome(Outcome::if_running(when_running, when_idle)); - - fut - }); - - let _ = wx.reconfigure(config); -} - -/// Returns the Runtime configuration for [`Watchexec`]. -pub fn runtime(args: &WatchArgs) -> Result { - let mut config = RuntimeConfig::default(); - - config.pathset(args.watch.clone().unwrap_or_default()); - - if let Some(delay) = &args.watch_delay { - config.action_throttle(utils::parse_delay(delay)?); - } - - config.on_pre_spawn(move |prespawn: PreSpawn| async move { - let envs = summarise_events_to_env(prespawn.events.iter()); - if let Some(mut command) = prespawn.command().await { - for (k, v) in envs { - command.env(format!("CARGO_WATCH_{k}_PATH"), v); - } - } - - Ok::<(), Infallible>(()) - }); - - Ok(config) -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/forge/bin/main.rs b/crates/forge/bin/main.rs index 79378aa86..aff2ad530 100644 --- a/crates/forge/bin/main.rs +++ b/crates/forge/bin/main.rs @@ -5,13 +5,18 @@ use clap::{CommandFactory, Parser}; use clap_complete::generate; use eyre::Result; use foundry_cli::{handler, utils}; +use foundry_evm::inspectors::cheatcodes::{set_execution_context, ForgeContext}; mod cmd; -mod opts; - use cmd::{cache::CacheSubcommands, generate::GenerateSubcommands, watch}; + +mod opts; use opts::{Forge, ForgeSubcommand}; +#[cfg(all(feature = "jemalloc", unix))] +#[global_allocator] +static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; + fn main() -> Result<()> { handler::install(); utils::load_dotenv(); @@ -19,6 +24,8 @@ fn main() -> Result<()> { utils::enable_paint(); let opts = Forge::parse(); + init_execution_context(&opts.cmd); + match opts.cmd { ForgeSubcommand::Test(cmd) => { if cmd.is_watch() { @@ -31,7 +38,7 @@ fn main() -> Result<()> { ForgeSubcommand::Script(cmd) => { // install the shell before executing the command foundry_common::shell::set_shell(foundry_common::shell::Shell::from_args( - cmd.opts.args.silent, + cmd.opts.silent, cmd.json, ))?; utils::block_on(cmd.run_script()) @@ -42,12 +49,13 @@ fn main() -> Result<()> { if cmd.is_watch() { utils::block_on(watch::watch_build(cmd)) } else { - cmd.run() + cmd.run().map(drop) } } ForgeSubcommand::Debug(cmd) => utils::block_on(cmd.run()), ForgeSubcommand::VerifyContract(args) => utils::block_on(args.run()), ForgeSubcommand::VerifyCheck(args) => utils::block_on(args.run()), + ForgeSubcommand::Clone(cmd) => utils::block_on(cmd.run()), ForgeSubcommand::Cache(cmd) => match cmd.sub { CacheSubcommands::Clean(cmd) => cmd.run(), CacheSubcommands::Ls(cmd) => cmd.run(), @@ -73,7 +81,8 @@ fn main() -> Result<()> { } ForgeSubcommand::Clean { root } => { let config = utils::load_config_with_root(root); - config.project()?.cleanup()?; + let project = config.project()?; + config.cleanup(&project)?; Ok(()) } ForgeSubcommand::Snapshot(cmd) => { @@ -101,5 +110,29 @@ fn main() -> Result<()> { ForgeSubcommand::Generate(cmd) => match cmd.sub { GenerateSubcommands::Test(cmd) => cmd.run(), }, + ForgeSubcommand::VerifyBytecode(cmd) => utils::block_on(cmd.run()), + ForgeSubcommand::Soldeer(cmd) => cmd.run(), } } + +/// Set the program execution context based on `forge` subcommand used. +/// The execution context can be set only once per program, and it can be checked by using +/// cheatcodes. +fn init_execution_context(subcommand: &ForgeSubcommand) { + let context = match subcommand { + ForgeSubcommand::Test(_) => ForgeContext::Test, + ForgeSubcommand::Coverage(_) => ForgeContext::Coverage, + ForgeSubcommand::Snapshot(_) => ForgeContext::Snapshot, + ForgeSubcommand::Script(cmd) => { + if cmd.broadcast { + ForgeContext::ScriptBroadcast + } else if cmd.resume { + ForgeContext::ScriptResume + } else { + ForgeContext::ScriptDryRun + } + } + _ => ForgeContext::Unknown, + }; + set_execution_context(context); +} diff --git a/crates/forge/bin/opts.rs b/crates/forge/bin/opts.rs index e62ac1908..a449bd75f 100644 --- a/crates/forge/bin/opts.rs +++ b/crates/forge/bin/opts.rs @@ -1,25 +1,12 @@ use crate::cmd::{ - bind::BindArgs, - build::BuildArgs, - cache::CacheArgs, - config, coverage, - create::CreateArgs, - debug::DebugArgs, - doc::DocArgs, - flatten, - fmt::FmtArgs, - geiger, generate, - init::InitArgs, - inspect, - install::InstallArgs, - remappings::RemappingArgs, - remove::RemoveArgs, - script::ScriptArgs, - selectors::SelectorsSubcommands, - snapshot, test, tree, update, - verify::{VerifyArgs, VerifyCheckArgs}, + bind::BindArgs, build::BuildArgs, cache::CacheArgs, clone::CloneArgs, config, coverage, + create::CreateArgs, debug::DebugArgs, doc::DocArgs, flatten, fmt::FmtArgs, geiger, generate, + init::InitArgs, inspect, install::InstallArgs, remappings::RemappingArgs, remove::RemoveArgs, + selectors::SelectorsSubcommands, snapshot, soldeer, test, tree, update, }; use clap::{Parser, Subcommand, ValueHint}; +use forge_script::ScriptArgs; +use forge_verify::{bytecode::VerifyBytecodeArgs, VerifyArgs, VerifyCheckArgs}; use std::path::PathBuf; const VERSION_MESSAGE: &str = concat!( @@ -33,14 +20,14 @@ const VERSION_MESSAGE: &str = concat!( /// Build, test, fuzz, debug and deploy Solidity contracts. #[derive(Parser)] -#[clap( +#[command( name = "forge", version = VERSION_MESSAGE, after_help = "Find more information in the book: http://book.getfoundry.sh/reference/forge/forge.html", next_display_order = None, )] pub struct Forge { - #[clap(subcommand)] + #[command(subcommand)] pub cmd: ForgeSubcommand, } @@ -48,7 +35,7 @@ pub struct Forge { #[allow(clippy::large_enum_variant)] pub enum ForgeSubcommand { /// Run the project's tests. - #[clap(visible_alias = "t")] + #[command(visible_alias = "t")] Test(test::TestArgs), /// Run a smart contract as a script, building transactions that can be sent onchain. @@ -58,71 +45,74 @@ pub enum ForgeSubcommand { Coverage(coverage::CoverageArgs), /// Generate Rust bindings for smart contracts. - #[clap(alias = "bi")] + #[command(alias = "bi")] Bind(BindArgs), /// Build the project's smart contracts. - #[clap(visible_aliases = ["b", "compile"])] + #[command(visible_aliases = ["b", "compile"])] Build(BuildArgs), + /// Clone a contract from Etherscan. + Clone(CloneArgs), + /// Debugs a single smart contract as a script. - #[clap(visible_alias = "d")] + #[command(visible_alias = "d")] Debug(DebugArgs), /// Update one or multiple dependencies. /// /// If no arguments are provided, then all dependencies are updated. - #[clap(visible_alias = "u")] + #[command(visible_alias = "u")] Update(update::UpdateArgs), /// Install one or multiple dependencies. /// /// If no arguments are provided, then existing dependencies will be installed. - #[clap(visible_alias = "i")] + #[command(visible_alias = "i")] Install(InstallArgs), /// Remove one or multiple dependencies. - #[clap(visible_alias = "rm")] + #[command(visible_alias = "rm")] Remove(RemoveArgs), /// Get the automatically inferred remappings for the project. - #[clap(visible_alias = "re")] + #[command(visible_alias = "re")] Remappings(RemappingArgs), /// Verify smart contracts on Etherscan. - #[clap(visible_alias = "v")] + #[command(visible_alias = "v")] VerifyContract(VerifyArgs), /// Check verification status on Etherscan. - #[clap(visible_alias = "vc")] + #[command(visible_alias = "vc")] VerifyCheck(VerifyCheckArgs), /// Deploy a smart contract. - #[clap(visible_alias = "c")] + #[command(visible_alias = "c")] Create(CreateArgs), /// Create a new Forge project. Init(InitArgs), /// Generate shell completions script. - #[clap(visible_alias = "com")] + #[command(visible_alias = "com")] Completions { - #[clap(value_enum)] + #[arg(value_enum)] shell: clap_complete::Shell, }, /// Generate Fig autocompletion spec. - #[clap(visible_alias = "fig")] + #[command(visible_alias = "fig")] GenerateFigSpec, /// Remove the build artifacts and cache directories. - #[clap(visible_alias = "cl")] + #[command(visible_alias = "cl")] Clean { /// The project's root path. /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] root: Option, }, @@ -130,26 +120,26 @@ pub enum ForgeSubcommand { Cache(CacheArgs), /// Create a snapshot of each test's gas usage. - #[clap(visible_alias = "s")] + #[command(visible_alias = "s")] Snapshot(snapshot::SnapshotArgs), /// Display the current config. - #[clap(visible_alias = "co")] + #[command(visible_alias = "co")] Config(config::ConfigArgs), /// Flatten a source file and all of its imports into one file. - #[clap(visible_alias = "f")] + #[command(visible_alias = "f")] Flatten(flatten::FlattenArgs), /// Format Solidity source files. Fmt(FmtArgs), /// Get specialized information about a smart contract. - #[clap(visible_alias = "in")] + #[command(visible_alias = "in")] Inspect(inspect::InspectArgs), /// Display a tree visualization of the project's dependency graph. - #[clap(visible_alias = "tr")] + #[command(visible_alias = "tr")] Tree(tree::TreeArgs), /// Detects usage of unsafe cheat codes in a project and its dependencies. @@ -159,14 +149,21 @@ pub enum ForgeSubcommand { Doc(DocArgs), /// Function selector utilities - #[clap(visible_alias = "se")] + #[command(visible_alias = "se")] Selectors { - #[clap(subcommand)] + #[command(subcommand)] command: SelectorsSubcommands, }, /// Generate scaffold files. Generate(generate::GenerateArgs), + + /// Verify the deployed bytecode against its source. + #[clap(visible_alias = "vb")] + VerifyBytecode(VerifyBytecodeArgs), + + /// Soldeer dependency manager. + Soldeer(soldeer::SoldeerArgs), } #[cfg(test)] diff --git a/crates/forge/src/coverage.rs b/crates/forge/src/coverage.rs index 1c79c0e33..8b06f3074 100644 --- a/crates/forge/src/coverage.rs +++ b/crates/forge/src/coverage.rs @@ -7,7 +7,7 @@ pub use foundry_evm::coverage::*; use std::{ collections::{hash_map, HashMap}, io::Write, - path::PathBuf, + path::{Path, PathBuf}, }; /// A coverage reporter. @@ -49,7 +49,7 @@ impl CoverageReporter for SummaryReporter { fn report(mut self, report: &CoverageReport) -> eyre::Result<()> { for (path, summary) in report.summary_by_file() { self.total += &summary; - self.add_row(path, summary); + self.add_row(path.display(), summary); } self.add_row("Total", self.total.clone()); @@ -81,7 +81,7 @@ pub struct LcovReporter<'a> { } impl<'a> LcovReporter<'a> { - pub fn new(destination: &'a mut (dyn Write + 'a)) -> LcovReporter<'a> { + pub fn new(destination: &'a mut (dyn Write + 'a)) -> Self { Self { destination } } } @@ -95,7 +95,7 @@ impl<'a> CoverageReporter for LcovReporter<'a> { }); writeln!(self.destination, "TN:")?; - writeln!(self.destination, "SF:{file}")?; + writeln!(self.destination, "SF:{}", file.display())?; for item in items { let line = item.loc.line; @@ -150,7 +150,7 @@ pub struct DebugReporter; impl CoverageReporter for DebugReporter { fn report(self, report: &CoverageReport) -> eyre::Result<()> { for (path, items) in report.items_by_source() { - println!("Uncovered for {path}:"); + println!("Uncovered for {}:", path.display()); items.iter().for_each(|item| { if item.hits == 0 { println!("- {item}"); @@ -161,17 +161,27 @@ impl CoverageReporter for DebugReporter { for (contract_id, anchors) in &report.anchors { println!("Anchors for {contract_id}:"); - anchors.iter().for_each(|anchor| { - println!("- {anchor}"); - println!( - " - Refers to item: {}", - report - .items - .get(&contract_id.version) - .and_then(|items| items.get(anchor.item_id)) - .map_or("None".to_owned(), |item| item.to_string()) - ); - }); + anchors + .0 + .iter() + .map(|anchor| (false, anchor)) + .chain(anchors.1.iter().map(|anchor| (true, anchor))) + .for_each(|(is_deployed, anchor)| { + println!("- {anchor}"); + if is_deployed { + println!("- Creation code"); + } else { + println!("- Runtime code"); + } + println!( + " - Refers to item: {}", + report + .items + .get(&contract_id.version) + .and_then(|items| items.get(anchor.item_id)) + .map_or("None".to_owned(), |item| item.to_string()) + ); + }); println!(); } @@ -185,7 +195,7 @@ pub struct BytecodeReporter { } impl BytecodeReporter { - pub fn new(root: PathBuf, destdir: PathBuf) -> BytecodeReporter { + pub fn new(root: PathBuf, destdir: PathBuf) -> Self { Self { root, destdir } } } @@ -208,16 +218,16 @@ impl CoverageReporter for BytecodeReporter { let hits = hits .hits .get(&(code.offset as usize)) - .map(|h| format!("[{:03}]", h)) + .map(|h| format!("[{h:03}]")) .unwrap_or(" ".to_owned()); - let source_id = source_element.index; + let source_id = source_element.index(); let source_path = source_id.and_then(|i| { report.source_paths.get(&(contract_id.version.clone(), i as usize)) }); - let code = format!("{:?}", code); - let start = source_element.offset; - let end = source_element.offset + source_element.length; + let code = format!("{code:?}"); + let start = source_element.offset() as usize; + let end = (source_element.offset() + source_element.length()) as usize; if let Some(source_path) = source_path { let (sline, spos) = line_number_cache.get_position(source_path, start)?; @@ -225,20 +235,24 @@ impl CoverageReporter for BytecodeReporter { writeln!( formatted, "{} {:40} // {}: {}:{}-{}:{} ({}-{})", - hits, code, source_path, sline, spos, eline, epos, start, end + hits, + code, + source_path.display(), + sline, + spos, + eline, + epos, + start, + end )?; } else if let Some(source_id) = source_id { - writeln!( - formatted, - "{} {:40} // SRCID{}: ({}-{})", - hits, code, source_id, start, end - )?; + writeln!(formatted, "{hits} {code:40} // SRCID{source_id}: ({start}-{end})")?; } else { - writeln!(formatted, "{} {:40}", hits, code)?; + writeln!(formatted, "{hits} {code:40}")?; } } fs::write( - &self.destdir.join(contract_id.contract_name.clone()).with_extension("asm"), + self.destdir.join(&*contract_id.contract_name).with_extension("asm"), formatted, )?; } @@ -250,16 +264,16 @@ impl CoverageReporter for BytecodeReporter { /// Cache line number offsets for source files struct LineNumberCache { root: PathBuf, - line_offsets: HashMap>, + line_offsets: HashMap>, } impl LineNumberCache { pub fn new(root: PathBuf) -> Self { - LineNumberCache { root, line_offsets: HashMap::new() } + Self { root, line_offsets: HashMap::new() } } - pub fn get_position(&mut self, path: &str, offset: usize) -> eyre::Result<(usize, usize)> { - let line_offsets = match self.line_offsets.entry(path.to_owned()) { + pub fn get_position(&mut self, path: &Path, offset: usize) -> eyre::Result<(usize, usize)> { + let line_offsets = match self.line_offsets.entry(path.to_path_buf()) { hash_map::Entry::Occupied(o) => o.into_mut(), hash_map::Entry::Vacant(v) => { let text = fs::read_to_string(self.root.join(path))?; diff --git a/crates/forge/src/gas_report.rs b/crates/forge/src/gas_report.rs index f6c269d70..058af0587 100644 --- a/crates/forge/src/gas_report.rs +++ b/crates/forge/src/gas_report.rs @@ -2,17 +2,20 @@ use crate::{ constants::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS}, - hashbrown::HashSet, - traces::{CallTraceArena, CallTraceDecoder, CallTraceNode, DecodedCallData, TraceKind}, + traces::{CallTraceArena, CallTraceDecoder, CallTraceNode, DecodedCallData}, }; use comfy_table::{presets::ASCII_MARKDOWN, *}; use foundry_common::{calc, TestFunctionExt}; use foundry_evm::traces::CallKind; use serde::{Deserialize, Serialize}; -use std::{collections::BTreeMap, fmt::Display}; +use std::{ + collections::{BTreeMap, HashSet}, + fmt::Display, +}; +use yansi::Paint; /// Represents the gas report for a set of contracts. -#[derive(Debug, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct GasReport { /// Whether to report any contracts. report_any: bool, @@ -22,7 +25,7 @@ pub struct GasReport { ignore: HashSet, /// All contracts that were analyzed grouped by their identifier /// ``test/Counter.t.sol:CounterTest - contracts: BTreeMap, + pub contracts: BTreeMap, } impl GasReport { @@ -49,7 +52,7 @@ impl GasReport { // indicating the "double listing". eprintln!( "{}: {} is listed in both 'gas_reports' and 'gas_reports_ignore'.", - yansi::Paint::yellow("warning").bold(), + "warning".yellow().bold(), contract_name ); } @@ -61,10 +64,10 @@ impl GasReport { /// Analyzes the given traces and generates a gas report. pub async fn analyze( &mut self, - traces: &[(TraceKind, CallTraceArena)], + arenas: impl IntoIterator, decoder: &CallTraceDecoder, ) { - for node in traces.iter().flat_map(|(_, arena)| arena.nodes()) { + for node in arenas.into_iter().flat_map(|arena| arena.nodes()) { self.analyze_node(node, decoder).await; } } @@ -78,7 +81,7 @@ impl GasReport { // Only include top-level calls which accout for calldata and base (21.000) cost. // Only include Calls and Creates as only these calls are isolated in inspector. - if trace.depth != 1 && + if trace.depth > 1 && (trace.kind == CallKind::Call || trace.kind == CallKind::Create || trace.kind == CallKind::Create2) @@ -186,7 +189,7 @@ impl Display for GasReport { } } -#[derive(Debug, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct ContractInfo { pub gas: u64, pub size: usize, @@ -194,7 +197,7 @@ pub struct ContractInfo { pub functions: BTreeMap>, } -#[derive(Debug, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct GasInfo { pub calls: Vec, pub min: u64, diff --git a/crates/forge/src/lib.rs b/crates/forge/src/lib.rs index e79d1470d..c46800067 100644 --- a/crates/forge/src/lib.rs +++ b/crates/forge/src/lib.rs @@ -1,28 +1,30 @@ +#![doc = include_str!("../README.md")] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + #[macro_use] extern crate tracing; -use alloy_primitives::B256; use foundry_compilers::ProjectCompileOutput; use foundry_config::{ validate_profiles, Config, FuzzConfig, InlineConfig, InlineConfigError, InlineConfigParser, InvariantConfig, NatSpec, }; - -use proptest::test_runner::{RngAlgorithm, TestRng, TestRunner}; +use proptest::test_runner::{ + FailurePersistence, FileFailurePersistence, RngAlgorithm, TestRng, TestRunner, +}; use std::path::Path; pub mod coverage; pub mod gas_report; -pub mod link; - -mod multi_runner; -pub use multi_runner::{MultiContractRunner, MultiContractRunnerBuilder, ProjectCompileDualOutput}; +pub mod multi_runner; +pub use multi_runner::{MultiContractRunner, MultiContractRunnerBuilder}; mod runner; pub use runner::ContractRunner; +mod progress; pub mod result; // TODO: remove @@ -93,12 +95,19 @@ impl TestOptions { /// - `contract_id` is the id of the test contract, expressed as a relative path from the /// project root. /// - `test_fn` is the name of the test function declared inside the test contract. - pub fn fuzz_runner(&self, contract_id: S, test_fn: S) -> TestRunner - where - S: Into, - { - let fuzz = self.fuzz_config(contract_id, test_fn); - self.fuzzer_with_cases(fuzz.runs) + pub fn fuzz_runner(&self, contract_id: &str, test_fn: &str) -> TestRunner { + let fuzz_config = self.fuzz_config(contract_id, test_fn).clone(); + let failure_persist_path = fuzz_config + .failure_persist_dir + .unwrap() + .join(fuzz_config.failure_persist_file.unwrap()) + .into_os_string() + .into_string() + .unwrap(); + self.fuzzer_with_cases( + fuzz_config.runs, + Some(Box::new(FileFailurePersistence::Direct(failure_persist_path.leak()))), + ) } /// Returns an "invariant" test runner instance. Parameters are used to select tight scoped fuzz @@ -108,12 +117,9 @@ impl TestOptions { /// - `contract_id` is the id of the test contract, expressed as a relative path from the /// project root. /// - `test_fn` is the name of the test function declared inside the test contract. - pub fn invariant_runner(&self, contract_id: S, test_fn: S) -> TestRunner - where - S: Into, - { + pub fn invariant_runner(&self, contract_id: &str, test_fn: &str) -> TestRunner { let invariant = self.invariant_config(contract_id, test_fn); - self.fuzzer_with_cases(invariant.runs) + self.fuzzer_with_cases(invariant.runs, None) } /// Returns a "fuzz" configuration setup. Parameters are used to select tight scoped fuzz @@ -123,10 +129,7 @@ impl TestOptions { /// - `contract_id` is the id of the test contract, expressed as a relative path from the /// project root. /// - `test_fn` is the name of the test function declared inside the test contract. - pub fn fuzz_config(&self, contract_id: S, test_fn: S) -> &FuzzConfig - where - S: Into, - { + pub fn fuzz_config(&self, contract_id: &str, test_fn: &str) -> &FuzzConfig { self.inline_fuzz.get(contract_id, test_fn).unwrap_or(&self.fuzz) } @@ -137,29 +140,32 @@ impl TestOptions { /// - `contract_id` is the id of the test contract, expressed as a relative path from the /// project root. /// - `test_fn` is the name of the test function declared inside the test contract. - pub fn invariant_config(&self, contract_id: S, test_fn: S) -> &InvariantConfig - where - S: Into, - { + pub fn invariant_config(&self, contract_id: &str, test_fn: &str) -> &InvariantConfig { self.inline_invariant.get(contract_id, test_fn).unwrap_or(&self.invariant) } - pub fn fuzzer_with_cases(&self, cases: u32) -> TestRunner { - // TODO: Add Options to modify the persistence - let cfg = proptest::test_runner::Config { - failure_persistence: None, + pub fn fuzzer_with_cases( + &self, + cases: u32, + file_failure_persistence: Option>, + ) -> TestRunner { + let config = proptest::test_runner::Config { + failure_persistence: file_failure_persistence, cases, max_global_rejects: self.fuzz.max_test_rejects, + // Disable proptest shrink: for fuzz tests we provide single counterexample, + // for invariant tests we shrink outside proptest. + max_shrink_iters: 0, ..Default::default() }; - if let Some(ref fuzz_seed) = self.fuzz.seed { - trace!(target: "forge::test", "building deterministic fuzzer with seed {}", fuzz_seed); - let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &B256::from(*fuzz_seed).0); - TestRunner::new_with_rng(cfg, rng) + if let Some(seed) = &self.fuzz.seed { + trace!(target: "forge::test", %seed, "building deterministic fuzzer"); + let rng = TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>()); + TestRunner::new_with_rng(config, rng) } else { trace!(target: "forge::test", "building stochastic fuzzer"); - TestRunner::new(cfg) + TestRunner::new(config) } } } @@ -211,23 +217,3 @@ impl TestOptionsBuilder { TestOptions::new(output, root, profiles, base_fuzz, base_invariant) } } - -mod utils2 { - use alloy_primitives::Address; - use ethers_core::types::BlockId; - use ethers_providers::{Middleware, Provider}; - use eyre::Context; - use foundry_common::types::{ToAlloy, ToEthers}; - - pub async fn next_nonce( - caller: Address, - provider_url: &str, - block: Option, - ) -> eyre::Result { - let provider = Provider::try_from(provider_url) - .wrap_err_with(|| format!("bad fork_url provider: {provider_url}"))?; - let res = provider.get_transaction_count(caller.to_ethers(), block).await?.to_alloy(); - res.try_into().map_err(Into::into) - } -} -pub use utils2::*; diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index 1e0726ac3..b4fa3d680 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -1,39 +1,45 @@ //! Forge test runner for multiple contracts. use crate::{ - link::{LinkOutput, Linker}, - result::SuiteResult, - ContractRunner, TestFilter, TestOptions, + progress::TestsProgress, result::SuiteResult, runner::LIBRARY_DEPLOYER, ContractRunner, + TestFilter, TestOptions, }; use alloy_json_abi::{Function, JsonAbi}; use alloy_primitives::{Address, Bytes, U256}; use eyre::Result; use foundry_common::{get_contract_name, ContractsByArtifact, TestFunctionExt}; use foundry_compilers::{ - contracts::ArtifactContracts, - zksync::compile::output::ProjectCompileOutput as ZkProjectCompileOutput, Artifact, ArtifactId, - ArtifactOutput, ConfigurableArtifacts, ProjectCompileOutput, + artifacts::{CompactBytecode, CompactContractBytecode, CompactDeployedBytecode, Libraries}, + compilers::Compiler, + zksync::compile::output::ProjectCompileOutput as ZkProjectCompileOutput, + Artifact, ArtifactId, ProjectCompileOutput, }; +use foundry_config::Config; use foundry_evm::{ - backend::Backend, - decode::RevertDecoder, - executors::{Executor, ExecutorBuilder}, - fork::CreateFork, - inspectors::CheatsConfig, - opts::EvmOpts, - revm, + backend::Backend, decode::RevertDecoder, executors::ExecutorBuilder, fork::CreateFork, + inspectors::CheatsConfig, opts::EvmOpts, revm, }; +use foundry_linking::{LinkOutput, Linker}; +use foundry_zksync_compiler::DualCompiledContracts; use rayon::prelude::*; use revm::primitives::SpecId; use std::{ + borrow::Borrow, collections::BTreeMap, fmt::Debug, path::Path, sync::{mpsc, Arc}, + time::Instant, }; -pub type DeployableContracts = BTreeMap)>; +#[derive(Debug, Clone)] +pub struct TestContract { + pub abi: JsonAbi, + pub bytecode: Bytes, +} + +pub type DeployableContracts = BTreeMap; /// A multi contract runner receives a set of contracts deployed in an EVM instance and proceeds /// to run all test functions in these contracts. @@ -41,8 +47,6 @@ pub struct MultiContractRunner { /// Mapping of contract name to JsonAbi, creation bytecode and library bytecode which /// needs to be deployed & linked against pub contracts: DeployableContracts, - /// Compiled contracts by name that have an JsonAbi and runtime bytecode - pub known_contracts: ContractsByArtifact, /// The EVM instance used in the test runner pub evm_opts: EvmOpts, /// The configured evm @@ -53,12 +57,10 @@ pub struct MultiContractRunner { pub revert_decoder: RevertDecoder, /// The address which will be used as the `from` field in all EVM calls pub sender: Option
, - /// A map of contract names to absolute source file paths - pub source_paths: BTreeMap, /// The fork to use at launch pub fork: Option, - /// Additional cheatcode inspector related settings derived from the `Config` - pub cheats_config: Arc, + /// Project config. + pub config: Arc, /// Whether to collect coverage info pub coverage: bool, /// Whether to collect debug info @@ -67,61 +69,62 @@ pub struct MultiContractRunner { pub test_options: TestOptions, /// Whether to enable call isolation pub isolation: bool, + /// Known contracts linked with computed library addresses. + pub known_contracts: ContractsByArtifact, + /// Libraries to deploy. + pub libs_to_deploy: Vec, + /// Library addresses used to link contracts. + pub libraries: Libraries, + /// Dual compiled contracts + pub dual_compiled_contracts: DualCompiledContracts, + /// Use zk runner. pub use_zk: bool, } impl MultiContractRunner { - /// Returns the number of matching tests - pub fn matching_test_function_count(&self, filter: &dyn TestFilter) -> usize { - self.matching_test_functions(filter).count() + /// Returns an iterator over all contracts that match the filter. + pub fn matching_contracts<'a>( + &'a self, + filter: &'a dyn TestFilter, + ) -> impl Iterator { + self.contracts + .iter() + .filter(|&(id, TestContract { abi, .. })| matches_contract(id, abi, filter)) } - /// Returns all test functions matching the filter + /// Returns an iterator over all test functions that match the filter. pub fn matching_test_functions<'a>( &'a self, filter: &'a dyn TestFilter, ) -> impl Iterator { - self.contracts - .iter() - .filter(|(id, _)| filter.matches_path(&id.source) && filter.matches_contract(&id.name)) - .flat_map(|(_, (abi, _, _))| { - abi.functions().filter(|func| filter.matches_test(&func.signature())) - }) + self.matching_contracts(filter) + .flat_map(|(_, TestContract { abi, .. })| abi.functions()) + .filter(|func| is_matching_test(func, filter)) } - /// Get an iterator over all test contract functions that matches the filter path and contract - /// name - fn filtered_tests<'a>(&'a self, filter: &'a dyn TestFilter) -> impl Iterator { + /// Returns an iterator over all test functions in contracts that match the filter. + pub fn all_test_functions<'a>( + &'a self, + filter: &'a dyn TestFilter, + ) -> impl Iterator { self.contracts .iter() .filter(|(id, _)| filter.matches_path(&id.source) && filter.matches_contract(&id.name)) - .flat_map(|(_, (abi, _, _))| abi.functions()) - } - - /// Get all test names matching the filter - pub fn get_tests(&self, filter: &dyn TestFilter) -> Vec { - self.filtered_tests(filter) - .map(|func| func.name.clone()) - .filter(|name| name.is_test()) - .collect() + .flat_map(|(_, TestContract { abi, .. })| abi.functions()) + .filter(|func| func.is_test() || func.is_invariant_test()) } /// Returns all matching tests grouped by contract grouped by file (file -> (contract -> tests)) pub fn list(&self, filter: &dyn TestFilter) -> BTreeMap>> { - self.contracts - .iter() - .filter(|(id, _)| filter.matches_path(&id.source) && filter.matches_contract(&id.name)) - .filter(|(_, (abi, _, _))| abi.functions().any(|func| filter.matches_test(&func.name))) - .map(|(id, (abi, _, _))| { + self.matching_contracts(filter) + .map(|(id, TestContract { abi, .. })| { let source = id.source.as_path().display().to_string(); let name = id.name.clone(); let tests = abi .functions() - .filter(|func| func.name.is_test()) - .filter(|func| filter.matches_test(&func.signature())) + .filter(|func| is_matching_test(func, filter)) .map(|func| func.name.clone()) .collect::>(); - (source, name, tests) }) .fold(BTreeMap::new(), |mut acc, (source, name, tests)| { @@ -135,12 +138,8 @@ impl MultiContractRunner { /// The same as [`test`](Self::test), but returns the results instead of streaming them. /// /// Note that this method returns only when all tests have been executed. - pub async fn test_collect( - &mut self, - filter: &dyn TestFilter, - test_options: TestOptions, - ) -> BTreeMap { - self.test_iter(filter, test_options).await.collect() + pub fn test_collect(&mut self, filter: &dyn TestFilter) -> BTreeMap { + self.test_iter(filter).collect() } /// Executes _all_ tests that match the given `filter`. @@ -148,13 +147,12 @@ impl MultiContractRunner { /// The same as [`test`](Self::test), but returns the results instead of streaming them. /// /// Note that this method returns only when all tests have been executed. - pub async fn test_iter( + pub fn test_iter( &mut self, filter: &dyn TestFilter, - test_options: TestOptions, ) -> impl Iterator { let (tx, rx) = mpsc::channel(); - self.test(filter, tx, test_options).await; + self.test(filter, tx, false); rx.into_iter() } @@ -164,92 +162,134 @@ impl MultiContractRunner { /// before executing all contracts and their tests in _parallel_. /// /// Each Executor gets its own instance of the `Backend`. - pub async fn test( + pub fn test( &mut self, filter: &dyn TestFilter, - stream_result: mpsc::Sender<(String, SuiteResult)>, - test_options: TestOptions, + tx: mpsc::Sender<(String, SuiteResult)>, + show_progress: bool, ) { + let handle = tokio::runtime::Handle::current(); trace!("running all tests"); - // the db backend that serves all the data, each contract gets its own instance - let mut db = Backend::spawn(self.fork.take()).await; - db.is_zk = self.cheats_config.use_zk; + // The DB backend that serves all the data. + let mut db = Backend::spawn(self.fork.take()); + db.is_zk = self.use_zk; + + let find_timer = Instant::now(); + let contracts = self.matching_contracts(filter).collect::>(); + let find_time = find_timer.elapsed(); + debug!( + "Found {} test contracts out of {} in {:?}", + contracts.len(), + self.contracts.len(), + find_time, + ); - self.contracts - .par_iter() - .filter(|(id, _)| filter.matches_path(&id.source) && filter.matches_contract(&id.name)) - .filter(|(_, (abi, _, _))| abi.functions().any(|func| filter.matches_test(&func.name))) - .for_each_with(stream_result, |stream_result, (id, (abi, deploy_code, libs))| { - let executor = ExecutorBuilder::new() - .inspectors(|stack| { - stack - .cheatcodes(self.cheats_config.clone()) - .trace(self.evm_opts.verbosity >= 3 || self.debug) - .debug(self.debug) - .coverage(self.coverage) - .enable_isolation(self.isolation) - }) - .spec(self.evm_spec) - .gas_limit(self.evm_opts.gas_limit()) - .use_zk_vm(self.use_zk) - .build(self.env.clone(), db.clone()); - let identifier = id.identifier(); - trace!(contract=%identifier, "start executing all tests in contract"); - - let result = self.run_tests( - &identifier, - abi, - executor, - deploy_code.clone(), - libs, - filter, - test_options.clone(), - ); - trace!(contract=?identifier, "executed all tests in contract"); - - let _ = stream_result.send((identifier, result)); + if show_progress { + let tests_progress = TestsProgress::new(contracts.len(), rayon::current_num_threads()); + // Collect test suite results to stream at the end of test run. + let results: Vec<(String, SuiteResult)> = contracts + .par_iter() + .map(|&(id, contract)| { + let _guard = handle.enter(); + tests_progress.inner.lock().start_suite_progress(&id.identifier()); + + let result = self.run_test_suite( + id, + contract, + db.clone(), + filter, + &handle, + Some(&tests_progress), + ); + + tests_progress + .inner + .lock() + .end_suite_progress(&id.identifier(), result.summary()); + + (id.identifier(), result) + }) + .collect(); + + tests_progress.inner.lock().clear(); + + results.iter().for_each(|result| { + let _ = tx.send(result.to_owned()); + }); + } else { + contracts.par_iter().for_each(|&(id, contract)| { + let _guard = handle.enter(); + let result = self.run_test_suite(id, contract, db.clone(), filter, &handle, None); + let _ = tx.send((id.identifier(), result)); }) + } } - #[allow(clippy::too_many_arguments)] - fn run_tests( + fn run_test_suite( &self, - name: &str, - contract: &JsonAbi, - executor: Executor, - deploy_code: Bytes, - libs: &[Bytes], + artifact_id: &ArtifactId, + contract: &TestContract, + db: Backend, filter: &dyn TestFilter, - test_options: TestOptions, + handle: &tokio::runtime::Handle, + progress: Option<&TestsProgress>, ) -> SuiteResult { - let span = info_span!("run_tests"); - if !span.is_disabled() { - if enabled!(tracing::Level::TRACE) { - span.record("contract", name); - } else { - span.record("contract", get_contract_name(name)); - } + let identifier = artifact_id.identifier(); + let mut span_name = identifier.as_str(); + + let cheats_config = CheatsConfig::new( + &self.config, + self.evm_opts.clone(), + Some(self.known_contracts.clone()), + None, + Some(artifact_id.version.clone()), + self.dual_compiled_contracts.clone(), + self.use_zk, + ); + + let executor = ExecutorBuilder::new() + .inspectors(|stack| { + stack + .cheatcodes(Arc::new(cheats_config)) + .trace(self.evm_opts.verbosity >= 3 || self.debug) + .debug(self.debug) + .coverage(self.coverage) + .enable_isolation(self.isolation) + }) + .spec(self.evm_spec) + .gas_limit(self.evm_opts.gas_limit()) + .build(self.env.clone(), db); + + if !enabled!(tracing::Level::TRACE) { + span_name = get_contract_name(&identifier); } - let _guard = span.enter(); + let _guard = debug_span!("suite", name = span_name).entered(); + + debug!("start executing all tests in contract"); let runner = ContractRunner::new( - name, + &identifier, executor, contract, - deploy_code, + &self.libs_to_deploy, self.evm_opts.initial_balance, self.sender, &self.revert_decoder, - libs, self.debug, + progress, ); - runner.run_tests(filter, test_options, Some(&self.known_contracts)) + + let r = runner.run_tests(filter, &self.test_options, self.known_contracts.clone(), handle); + + debug!(duration=?r.duration, "executed all tests in contract"); + + r } } /// Builder used for instantiating the multi-contract runner -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] #[must_use = "builders do nothing unless you call `build` on them"] pub struct MultiContractRunnerBuilder { /// The address which will be used to deploy the initial contracts and send all @@ -261,8 +301,8 @@ pub struct MultiContractRunnerBuilder { pub evm_spec: Option, /// The fork to use at launch pub fork: Option, - /// Additional cheatcode inspector related settings derived from the `Config` - pub cheats_config: Option, + /// Project config. + pub config: Arc, /// Whether or not to collect coverage info pub coverage: bool, /// Whether or not to collect debug info @@ -273,22 +313,21 @@ pub struct MultiContractRunnerBuilder { pub test_options: Option, } -#[derive(Debug)] -pub struct ProjectCompileDualOutput { - pub zk_output: Option>, - pub solc_output: Option>, -} - -impl ProjectCompileDualOutput { - pub fn only_zk(zk_output: ProjectCompileOutput) -> Self { - ProjectCompileDualOutput { zk_output: Some(zk_output), solc_output: None } - } - pub fn only_evm(solc_output: ProjectCompileOutput) -> Self { - ProjectCompileDualOutput { zk_output: None, solc_output: Some(solc_output) } +impl MultiContractRunnerBuilder { + pub fn new(config: Arc) -> Self { + Self { + config, + sender: Default::default(), + initial_balance: Default::default(), + evm_spec: Default::default(), + fork: Default::default(), + coverage: Default::default(), + debug: Default::default(), + isolation: Default::default(), + test_options: Default::default(), + } } -} -impl MultiContractRunnerBuilder { pub fn sender(mut self, sender: Address) -> Self { self.sender = Some(sender); self @@ -309,11 +348,6 @@ impl MultiContractRunnerBuilder { self } - pub fn with_cheats_config(mut self, cheats_config: CheatsConfig) -> Self { - self.cheats_config = Some(cheats_config); - self - } - pub fn with_test_options(mut self, test_options: TestOptions) -> Self { self.test_options = Some(test_options); self @@ -336,73 +370,63 @@ impl MultiContractRunnerBuilder { /// Given an EVM, proceeds to return a runner which is able to execute all tests /// against that evm - pub fn build( + pub fn build( self, root: &Path, - output: ProjectCompileOutput, + output: ProjectCompileOutput, zk_output: Option, env: revm::primitives::Env, evm_opts: EvmOpts, + dual_compiled_contracts: DualCompiledContracts, ) -> Result { let use_zk = zk_output.is_some(); let mut known_contracts = ContractsByArtifact::default(); + let output = output.with_stripped_file_prefixes(root); + let linker = Linker::new(root, output.artifact_ids().collect()); - // This is just the contracts compiled, but we need to merge this with the read cached - // artifacts. - let contracts = output - .with_stripped_file_prefixes(root) - .into_artifacts() - .map(|(i, c)| (i, c.into_contract_bytecode())) - .collect::(); - - let source_paths = contracts + // Build revert decoder from ABIs of all artifacts. + let abis = linker + .contracts .iter() - .map(|(i, _)| (i.identifier(), root.join(&i.source).to_string_lossy().into())) - .collect::>(); + .filter_map(|(_, contract)| contract.abi.as_ref().map(|abi| abi.borrow())); + let revert_decoder = RevertDecoder::new().with_abis(abis); - let linker = Linker::new(root, contracts); + let LinkOutput { libraries, libs_to_deploy } = linker.link_with_nonce_or_address( + Default::default(), + LIBRARY_DEPLOYER, + 0, + linker.contracts.keys(), + )?; + + let linked_contracts = linker.get_linked_artifacts(&libraries)?; // Create a mapping of name => (abi, deployment code, Vec) let mut deployable_contracts = DeployableContracts::default(); - for (id, contract) in &linker.contracts.0 { - let Some(abi) = contract.abi.as_ref() else { - continue; - }; - - let LinkOutput { libs_to_deploy, libraries } = - linker.link_with_nonce_or_address(Default::default(), evm_opts.sender, 1, id)?; - - let linked_contract = linker.link(id, &libraries)?; - - // get bytes if deployable, else add to known contracts and continue. - // interfaces and abstract contracts should be known to enable fuzzing of their ABI - // but they should not be deployable and their source code should be skipped by the - // debugger and linker. - let Some(bytecode) = linked_contract - .get_bytecode_bytes() - .map(|b| b.into_owned()) - .filter(|b| !b.is_empty()) - else { - known_contracts.insert(id.clone(), (abi.clone(), vec![])); - continue; - }; - - // if it's a test, add it to deployable contracts + for (id, contract) in linked_contracts.iter() { + let Some(abi) = &contract.abi else { continue }; + + // if it's a test, link it and add to deployable contracts if abi.constructor.as_ref().map(|c| c.inputs.is_empty()).unwrap_or(true) && abi.functions().any(|func| func.name.is_test() || func.name.is_invariant_test()) { - deployable_contracts.insert(id.clone(), (abi.clone(), bytecode, libs_to_deploy)); - } - - if !use_zk { - if let Some(bytes) = linked_contract.get_deployed_bytecode_bytes() { - known_contracts.insert(id.clone(), (abi.clone(), bytes.to_vec())); - } + let Some(bytecode) = + contract.get_bytecode_bytes().map(|b| b.into_owned()).filter(|b| !b.is_empty()) + else { + continue; + }; + + deployable_contracts + .insert(id.clone(), TestContract { abi: abi.clone(), bytecode }); } } - if let Some(zk_output) = zk_output { + + if !use_zk { + known_contracts = ContractsByArtifact::new(linked_contracts); + } else if let Some(zk_output) = zk_output { let zk_contracts = zk_output.with_stripped_file_prefixes(root).into_artifacts(); + let mut zk_contracts_map = BTreeMap::new(); + for (id, contract) in zk_contracts { if let Some(metadata) = contract.metadata { if let Some(solc_metadata_value) = @@ -411,39 +435,62 @@ impl MultiContractRunnerBuilder { if let Ok(solc_metadata_json) = serde_json::from_str::(solc_metadata_value) { - let abi_json = &solc_metadata_json["output"]["abi"]; - let abi_string = abi_json.to_string(); - let abi: JsonAbi = JsonAbi::from_json_str(&abi_string)?; - let bytecode = contract - .bytecode - .as_ref() - .and_then(|b| b.object.as_bytes()) - .map_or_else(Vec::new, |b| b.to_vec()); - known_contracts.insert(id, (abi.clone(), bytecode)); + let abi: JsonAbi = JsonAbi::from_json_str( + &solc_metadata_json["output"]["abi"].to_string(), + )?; + let bytecode = contract.bytecode.as_ref(); + + if let Some(bytecode_object) = bytecode.map(|b| b.object.clone()) { + let compact_bytecode = CompactBytecode { + object: bytecode_object.clone(), + source_map: None, + link_references: BTreeMap::new(), + }; + let compact_contract = CompactContractBytecode { + abi: Some(abi), + bytecode: Some(compact_bytecode.clone()), + deployed_bytecode: Some(CompactDeployedBytecode { + bytecode: Some(compact_bytecode), + immutable_references: BTreeMap::new(), + }), + }; + zk_contracts_map.insert(id.clone(), compact_contract); + } } } } } + known_contracts = ContractsByArtifact::new(zk_contracts_map); } - let revert_decoder = - RevertDecoder::new().with_abis(known_contracts.values().map(|(abi, _)| abi)); Ok(MultiContractRunner { contracts: deployable_contracts, - known_contracts, evm_opts, env, evm_spec: self.evm_spec.unwrap_or(SpecId::MERGE), sender: self.sender, revert_decoder, - source_paths, fork: self.fork, - cheats_config: self.cheats_config.unwrap_or_default().into(), + config: self.config, coverage: self.coverage, debug: self.debug, test_options: self.test_options.unwrap_or_default(), isolation: self.isolation, + known_contracts, + libs_to_deploy, + libraries, + dual_compiled_contracts, use_zk, }) } } + +pub fn matches_contract(id: &ArtifactId, abi: &JsonAbi, filter: &dyn TestFilter) -> bool { + (filter.matches_path(&id.source) && filter.matches_contract(&id.name)) && + abi.functions().any(|func| is_matching_test(func, filter)) +} + +/// Returns `true` if the function is a test function that matches the given filter. +pub(crate) fn is_matching_test(func: &Function, filter: &dyn TestFilter) -> bool { + (func.is_test() || func.is_invariant_test()) && filter.matches_test(&func.signature()) +} diff --git a/crates/forge/src/progress.rs b/crates/forge/src/progress.rs new file mode 100644 index 000000000..8d047c413 --- /dev/null +++ b/crates/forge/src/progress.rs @@ -0,0 +1,116 @@ +use indicatif::{MultiProgress, ProgressBar}; +use parking_lot::Mutex; +use std::{collections::HashMap, sync::Arc, time::Duration}; + +/// State of [ProgressBar]s displayed for the given test run. +/// Shows progress of all test suites matching filter. +/// For each test within the test suite an individual progress bar is displayed. +/// When a test suite completes, their progress is removed from overall progress and result summary +/// is displayed. +#[derive(Debug)] +pub struct TestsProgressState { + /// Main [MultiProgress] instance showing progress for all test suites. + multi: MultiProgress, + /// Progress bar counting completed / remaining test suites. + overall_progress: ProgressBar, + /// Individual test suites progress. + suites_progress: HashMap, +} + +impl TestsProgressState { + // Creates overall tests progress state. + pub fn new(suites_len: usize, threads_no: usize) -> Self { + let multi = MultiProgress::new(); + let overall_progress = multi.add(ProgressBar::new(suites_len as u64)); + overall_progress.set_style( + indicatif::ProgressStyle::with_template("{bar:40.cyan/blue} {pos:>7}/{len:7} {msg}") + .unwrap() + .progress_chars("##-"), + ); + overall_progress.set_message(format!("completed (with {} threads)", threads_no as u64)); + Self { multi, overall_progress, suites_progress: HashMap::default() } + } + + /// Creates new test suite progress and add it to overall progress. + pub fn start_suite_progress(&mut self, suite_name: &String) { + let suite_progress = self.multi.add(ProgressBar::new_spinner()); + suite_progress.set_style( + indicatif::ProgressStyle::with_template("{spinner} {wide_msg:.bold.dim}") + .unwrap() + .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ "), + ); + suite_progress.set_message(format!("{suite_name} ")); + suite_progress.enable_steady_tick(Duration::from_millis(100)); + self.suites_progress.insert(suite_name.to_owned(), suite_progress); + } + + /// Prints suite result summary and removes it from overall progress. + pub fn end_suite_progress(&mut self, suite_name: &String, result_summary: String) { + if let Some(suite_progress) = self.suites_progress.remove(suite_name) { + self.multi.suspend(|| { + println!("{suite_name}\n ↪ {result_summary}"); + }); + suite_progress.finish_and_clear(); + // Increment test progress bar to reflect completed test suite. + self.overall_progress.inc(1); + } + } + + /// Creates progress entry for fuzz tests. + /// Set the prefix and total number of runs. Message is updated during execution with current + /// phase. Test progress is placed under test suite progress entry so all tests within suite + /// are grouped. + pub fn start_fuzz_progress( + &mut self, + suite_name: &str, + test_name: &String, + runs: u32, + ) -> Option { + if let Some(suite_progress) = self.suites_progress.get(suite_name) { + let fuzz_progress = + self.multi.insert_after(suite_progress, ProgressBar::new(runs as u64)); + fuzz_progress.set_style( + indicatif::ProgressStyle::with_template( + " ↪ {prefix:.bold.dim}: [{pos}/{len}]{msg} Runs", + ) + .unwrap() + .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ "), + ); + fuzz_progress.set_prefix(test_name.to_string()); + Some(fuzz_progress) + } else { + None + } + } + + /// Removes overall test progress. + pub fn clear(&mut self) { + self.multi.clear().unwrap(); + } +} + +/// Clonable wrapper around [TestsProgressState]. +#[derive(Debug, Clone)] +pub struct TestsProgress { + pub inner: Arc>, +} + +impl TestsProgress { + pub fn new(suites_len: usize, threads_no: usize) -> Self { + Self { inner: Arc::new(Mutex::new(TestsProgressState::new(suites_len, threads_no))) } + } +} + +/// Helper function for creating fuzz test progress bar. +pub fn start_fuzz_progress( + tests_progress: Option<&TestsProgress>, + suite_name: &str, + test_name: &String, + runs: u32, +) -> Option { + if let Some(progress) = tests_progress { + progress.inner.lock().start_fuzz_progress(suite_name, test_name, runs) + } else { + None + } +} diff --git a/crates/forge/src/result.rs b/crates/forge/src/result.rs index 1605f72fe..33db197b2 100644 --- a/crates/forge/src/result.rs +++ b/crates/forge/src/result.rs @@ -1,13 +1,14 @@ //! Test outcomes. -use alloy_primitives::{Address, Log}; +use crate::gas_report::GasReport; +use alloy_primitives::{Address, Bytes, Log}; use foundry_common::{evm::Breakpoints, get_contract_name, get_file_name, shell}; use foundry_evm::{ coverage::HitMaps, debug::DebugArena, executors::EvmError, - fuzz::{CounterExample, FuzzCase}, - traces::{CallTraceDecoder, TraceKind, Traces}, + fuzz::{CounterExample, FuzzCase, FuzzFixtures}, + traces::{CallTraceArena, CallTraceDecoder, TraceKind, Traces}, }; use serde::{Deserialize, Serialize}; use std::{ @@ -31,13 +32,15 @@ pub struct TestOutcome { /// This is `None` if traces and logs were not decoded. /// /// Note that `Address` fields only contain the last executed test case's data. - pub decoder: Option, + pub last_run_decoder: Option, + /// The gas report, if requested. + pub gas_report: Option, } impl TestOutcome { /// Creates a new test outcome with the given results. pub fn new(results: BTreeMap, allow_failure: bool) -> Self { - Self { results, allow_failure, decoder: None } + Self { results, allow_failure, last_run_decoder: None, gas_report: None } } /// Creates a new empty test outcome. @@ -133,9 +136,9 @@ impl TestOutcome { suites, wall_clock_time, self.total_time(), - Paint::green(total_passed), - Paint::red(total_failed), - Paint::yellow(total_skipped), + total_passed.green(), + total_failed.red(), + total_skipped.yellow(), total_tests ) } @@ -171,8 +174,8 @@ impl TestOutcome { let successes = outcome.passed(); shell::println(format!( "Encountered a total of {} failing tests, {} tests succeeded", - Paint::red(failures.to_string()), - Paint::green(successes.to_string()) + failures.to_string().red(), + successes.to_string().green() ))?; // TODO: Avoid process::exit @@ -184,6 +187,7 @@ impl TestOutcome { #[derive(Clone, Debug, Serialize)] pub struct SuiteResult { /// Wall clock time it took to execute all tests in this suite. + #[serde(with = "humantime_serde")] pub duration: Duration, /// Individual test results: `test fn signature -> TestResult`. pub test_results: BTreeMap, @@ -255,13 +259,13 @@ impl SuiteResult { /// Returns the summary of a single test suite. pub fn summary(&self) -> String { let failed = self.failed(); - let result = if failed == 0 { Paint::green("ok") } else { Paint::red("FAILED") }; + let result = if failed == 0 { "ok".green() } else { "FAILED".red() }; format!( "Suite result: {}. {} passed; {} failed; {} skipped; finished in {:.2?} ({:.2?} CPU time)", result, - Paint::green(self.passed()), - Paint::red(failed), - Paint::yellow(self.skipped()), + self.passed().green(), + failed.red(), + self.skipped().yellow(), self.duration, self.total_time(), ) @@ -358,6 +362,10 @@ pub struct TestResult { #[serde(skip)] pub traces: Traces, + /// Additional traces to use for gas report. + #[serde(skip)] + pub gas_report_traces: Vec>, + /// Raw coverage info #[serde(skip)] pub coverage: Option, @@ -377,8 +385,8 @@ pub struct TestResult { impl fmt::Display for TestResult { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.status { - TestStatus::Success => Paint::green("[PASS]").fmt(f), - TestStatus::Skipped => Paint::yellow("[SKIP]").fmt(f), + TestStatus::Success => "[PASS]".green().fmt(f), + TestStatus::Skipped => "[SKIP]".yellow().fmt(f), TestStatus::Failure => { let mut s = String::from("[FAIL. Reason: "); @@ -401,7 +409,7 @@ impl fmt::Display for TestResult { s.push(']'); } - Paint::red(s).fmt(f) + s.red().fmt(f) } } } @@ -426,21 +434,21 @@ impl TestResult { /// Data report by a test. #[derive(Clone, Debug, PartialEq, Eq)] pub enum TestKindReport { - Standard { gas: u64 }, + Unit { gas: u64 }, Fuzz { runs: usize, mean_gas: u64, median_gas: u64 }, Invariant { runs: usize, calls: usize, reverts: usize }, } impl fmt::Display for TestKindReport { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - TestKindReport::Standard { gas } => { + match *self { + Self::Unit { gas } => { write!(f, "(gas: {gas})") } - TestKindReport::Fuzz { runs, mean_gas, median_gas } => { + Self::Fuzz { runs, mean_gas, median_gas } => { write!(f, "(runs: {runs}, μ: {mean_gas}, ~: {median_gas})") } - TestKindReport::Invariant { runs, calls, reverts } => { + Self::Invariant { runs, calls, reverts } => { write!(f, "(runs: {runs}, calls: {calls}, reverts: {reverts})") } } @@ -450,12 +458,12 @@ impl fmt::Display for TestKindReport { impl TestKindReport { /// Returns the main gas value to compare against pub fn gas(&self) -> u64 { - match self { - TestKindReport::Standard { gas } => *gas, + match *self { + Self::Unit { gas } => gas, // We use the median for comparisons - TestKindReport::Fuzz { median_gas, .. } => *median_gas, + Self::Fuzz { median_gas, .. } => median_gas, // We return 0 since it's not applicable - TestKindReport::Invariant { .. } => 0, + Self::Invariant { .. } => 0, } } } @@ -463,11 +471,9 @@ impl TestKindReport { /// Various types of tests #[derive(Clone, Debug, Serialize, Deserialize)] pub enum TestKind { - /// A standard test that consists of calling the defined solidity function - /// - /// Holds the consumed gas - Standard(u64), - /// A solidity fuzz test, that stores all test cases + /// A unit test. + Unit { gas: u64 }, + /// A fuzz test. Fuzz { /// we keep this for the debugger first_case: FuzzCase, @@ -475,26 +481,26 @@ pub enum TestKind { mean_gas: u64, median_gas: u64, }, - /// A solidity invariant test, that stores all test cases + /// An invariant test. Invariant { runs: usize, calls: usize, reverts: usize }, } impl Default for TestKind { fn default() -> Self { - Self::Standard(0) + Self::Unit { gas: 0 } } } impl TestKind { /// The gas consumed by this test pub fn report(&self) -> TestKindReport { - match self { - TestKind::Standard(gas) => TestKindReport::Standard { gas: *gas }, - TestKind::Fuzz { runs, mean_gas, median_gas, .. } => { - TestKindReport::Fuzz { runs: *runs, mean_gas: *mean_gas, median_gas: *median_gas } + match *self { + Self::Unit { gas } => TestKindReport::Unit { gas }, + Self::Fuzz { first_case: _, runs, mean_gas, median_gas } => { + TestKindReport::Fuzz { runs, mean_gas, median_gas } } - TestKind::Invariant { runs, calls, reverts } => { - TestKindReport::Invariant { runs: *runs, calls: *calls, reverts: *reverts } + Self::Invariant { runs, calls, reverts } => { + TestKindReport::Invariant { runs, calls, reverts } } } } @@ -502,6 +508,8 @@ impl TestKind { #[derive(Clone, Debug, Default)] pub struct TestSetup { + /// Deployments generated during the setup + pub deployments: HashMap, /// The address at which the test contract was deployed pub address: Address, /// The logs emitted during setup @@ -514,6 +522,8 @@ pub struct TestSetup { pub reason: Option, /// Coverage info during setup pub coverage: Option, + /// Defined fuzz test fixtures + pub fuzz_fixtures: FuzzFixtures, } impl TestSetup { @@ -526,9 +536,9 @@ impl TestSetup { match error { EvmError::Execution(err) => { // force the tracekind to be setup so a trace is shown. - traces.extend(err.traces.map(|traces| (TraceKind::Setup, traces))); - logs.extend(err.logs); - labeled_addresses.extend(err.labels); + traces.extend(err.raw.traces.map(|traces| (TraceKind::Setup, traces))); + logs.extend(err.raw.logs); + labeled_addresses.extend(err.raw.labels); Self::failed_with(logs, traces, labeled_addresses, err.reason) } e => Self::failed_with( @@ -541,13 +551,24 @@ impl TestSetup { } pub fn success( + deployments: HashMap, address: Address, logs: Vec, traces: Traces, labeled_addresses: HashMap, coverage: Option, + fuzz_fixtures: FuzzFixtures, ) -> Self { - Self { address, logs, traces, labeled_addresses, reason: None, coverage } + Self { + deployments, + address, + logs, + traces, + labeled_addresses, + reason: None, + coverage, + fuzz_fixtures, + } } pub fn failed_with( @@ -557,12 +578,14 @@ impl TestSetup { reason: String, ) -> Self { Self { + deployments: HashMap::new(), address: Address::ZERO, logs, traces, labeled_addresses, reason: Some(reason), coverage: None, + fuzz_fixtures: FuzzFixtures::default(), } } diff --git a/crates/forge/src/runner.rs b/crates/forge/src/runner.rs index f4df3f89b..59b0943a6 100644 --- a/crates/forge/src/runner.rs +++ b/crates/forge/src/runner.rs @@ -1,11 +1,15 @@ //! The Forge test runner. use crate::{ + fuzz::{invariant::BasicTxDetails, BaseCounterExample}, + multi_runner::{is_matching_test, TestContract}, + progress::{start_fuzz_progress, TestsProgress}, result::{SuiteResult, TestKind, TestResult, TestSetup, TestStatus}, TestFilter, TestOptions, }; -use alloy_json_abi::{Function, JsonAbi}; -use alloy_primitives::{Address, Bytes, U256}; +use alloy_dyn_abi::DynSolValue; +use alloy_json_abi::Function; +use alloy_primitives::{address, Address, Bytes, U256}; use eyre::Result; use foundry_common::{ contracts::{ContractsByAddress, ContractsByArtifact}, @@ -18,31 +22,44 @@ use foundry_evm::{ decode::{decode_console_logs, RevertDecoder}, executors::{ fuzz::{CaseOutcome, CounterExampleOutcome, FuzzOutcome, FuzzedExecutor}, - invariant::{replay_run, InvariantExecutor, InvariantFuzzError, InvariantFuzzTestResult}, - CallResult, EvmError, ExecutionErr, Executor, + invariant::{ + check_sequence, replay_error, replay_run, InvariantExecutor, InvariantFuzzError, + InvariantFuzzTestResult, + }, + CallResult, EvmError, ExecutionErr, Executor, RawCallResult, + }, + fuzz::{ + fixture_name, + invariant::{CallDetails, InvariantContract}, + CounterExample, FuzzFixtures, }, - fuzz::{invariant::InvariantContract, CounterExample}, traces::{load_contracts, TraceKind}, }; -use proptest::test_runner::{TestError, TestRunner}; +use proptest::test_runner::TestRunner; use rayon::prelude::*; use std::{ + cmp::min, collections::{BTreeMap, HashMap}, time::Instant, }; +/// When running tests, we deploy all external libraries present in the project. To avoid additional +/// libraries affecting nonces of senders used in tests, we are using separate address to +/// predeploy libraries. +/// +/// `address(uint160(uint256(keccak256("foundry library deployer"))))` +pub const LIBRARY_DEPLOYER: Address = address!("1F95D37F27EA0dEA9C252FC09D5A6eaA97647353"); + /// A type that executes all tests of a contract #[derive(Clone, Debug)] pub struct ContractRunner<'a> { pub name: &'a str, + /// The data of the contract being ran. + pub contract: &'a TestContract, + /// The libraries that need to be deployed before the contract. + pub libs_to_deploy: &'a Vec, /// The executor used by the runner. pub executor: Executor, - /// Library contracts to be deployed before the test contract - pub predeploy_libs: &'a [Bytes], - /// The deployed contract's code - pub code: Bytes, - /// The test contract's ABI - pub contract: &'a JsonAbi, /// Revert decoder. Contains all known errors. pub revert_decoder: &'a RevertDecoder, /// The initial balance of the test contract @@ -51,6 +68,8 @@ pub struct ContractRunner<'a> { pub sender: Address, /// Should generate debug traces pub debug: bool, + /// Overall test run progress. + progress: Option<&'a TestsProgress>, } impl<'a> ContractRunner<'a> { @@ -58,24 +77,24 @@ impl<'a> ContractRunner<'a> { pub fn new( name: &'a str, executor: Executor, - contract: &'a JsonAbi, - code: Bytes, + contract: &'a TestContract, + libs_to_deploy: &'a Vec, initial_balance: U256, sender: Option
, revert_decoder: &'a RevertDecoder, - predeploy_libs: &'a [Bytes], debug: bool, + progress: Option<&'a TestsProgress>, ) -> Self { Self { name, executor, contract, - code, + libs_to_deploy, initial_balance, sender: sender.unwrap_or_default(), revert_decoder, - predeploy_libs, debug, + progress, } } } @@ -83,15 +102,15 @@ impl<'a> ContractRunner<'a> { impl<'a> ContractRunner<'a> { /// Deploys the test contract inside the runner from the sending account, and optionally runs /// the `setUp` function on the test contract. - pub fn setup(&mut self, setup: bool) -> TestSetup { - match self._setup(setup) { + pub fn setup(&mut self, call_setup: bool) -> TestSetup { + match self._setup(call_setup) { Ok(setup) => setup, Err(err) => TestSetup::failed(err.to_string()), } } - fn _setup(&mut self, setup: bool) -> Result { - info!(?setup, name = self.name, "Setting test contract"); + fn _setup(&mut self, call_setup: bool) -> Result { + trace!(call_setup, "setting up"); // We max out their balance so that they can deploy and make calls. self.executor.set_balance(self.sender, U256::MAX)?; @@ -101,18 +120,20 @@ impl<'a> ContractRunner<'a> { self.executor.set_nonce(self.sender, 1)?; // Deploy libraries + self.executor.set_balance(LIBRARY_DEPLOYER, U256::MAX)?; + let mut logs = Vec::new(); - let mut traces = Vec::with_capacity(self.predeploy_libs.len()); - for code in self.predeploy_libs.iter() { + let mut traces = Vec::with_capacity(self.libs_to_deploy.len()); + for code in self.libs_to_deploy.iter() { match self.executor.deploy( - self.sender, + LIBRARY_DEPLOYER, code.clone(), U256::ZERO, Some(self.revert_decoder), ) { Ok(d) => { - logs.extend(d.logs); - traces.extend(d.traces.map(|traces| (TraceKind::Deployment, traces))); + logs.extend(d.raw.logs); + traces.extend(d.raw.traces.map(|traces| (TraceKind::Deployment, traces))); } Err(e) => { return Ok(TestSetup::from_evm_error_with(e, logs, traces, Default::default())) @@ -126,16 +147,18 @@ impl<'a> ContractRunner<'a> { // construction self.executor.set_balance(address, self.initial_balance)?; + let mut zk_setup_deployments = HashMap::new(); + // Deploy the test contract match self.executor.deploy( self.sender, - self.code.clone(), + self.contract.bytecode.clone(), U256::ZERO, Some(self.revert_decoder), ) { Ok(d) => { - logs.extend(d.logs); - traces.extend(d.traces.map(|traces| (TraceKind::Deployment, traces))); + logs.extend(d.raw.logs); + traces.extend(d.raw.traces.map(|traces| (TraceKind::Deployment, traces))); d.address } Err(e) => { @@ -143,64 +166,132 @@ impl<'a> ContractRunner<'a> { } }; - // Reset `self.sender`s and `CALLER`s balance to the initial balance we want + // Reset `self.sender`s, `CALLER`s and `LIBRARY_DEPLOYER`'s balance to the initial balance. self.executor.set_balance(self.sender, self.initial_balance)?; self.executor.set_balance(CALLER, self.initial_balance)?; + self.executor.set_balance(LIBRARY_DEPLOYER, self.initial_balance)?; self.executor.deploy_create2_deployer()?; // Optionally call the `setUp` function - let setup = if setup { - trace!("setting up"); - let (setup_logs, setup_traces, labeled_addresses, reason, coverage) = - match self.executor.setup(None, address) { - Ok(CallResult { traces, labels, logs, coverage, .. }) => { - trace!(contract=%address, "successfully setUp test"); - (logs, traces, labels, None, coverage) - } - Err(EvmError::Execution(err)) => { - let ExecutionErr { traces, labels, logs, reason, .. } = *err; - error!(reason=%reason, contract=%address, "setUp failed {}", self.name); - (logs, traces, labels, Some(format!("setup failed: {reason}")), None) - } - Err(err) => { - error!(reason=%err, contract=%address, "setUp failed"); - ( - Vec::new(), - None, - HashMap::new(), - Some(format!("setup failed {}: {err}", self.name)), - None, - ) - } - }; + let result = if call_setup { + trace!("calling setUp"); + let res = self.executor.setup(None, address, Some(self.revert_decoder)); + let (setup_logs, setup_traces, labeled_addresses, reason, coverage) = match res { + Ok(RawCallResult { traces, labels, logs, coverage, deployments, .. }) => { + zk_setup_deployments.extend(deployments); + trace!(%address, "successfully called setUp"); + (logs, traces, labels, None, coverage) + } + Err(EvmError::Execution(err)) => { + let ExecutionErr { + raw: RawCallResult { traces, labels, logs, coverage, .. }, + reason, + } = *err; + (logs, traces, labels, Some(format!("setup failed: {reason}")), coverage) + } + Err(err) => { + (Vec::new(), None, HashMap::new(), Some(format!("setup failed: {err}")), None) + } + }; traces.extend(setup_traces.map(|traces| (TraceKind::Setup, traces))); logs.extend(setup_logs); - TestSetup { address, logs, traces, labeled_addresses, reason, coverage } + TestSetup { + deployments: zk_setup_deployments, + address, + logs, + traces, + labeled_addresses, + reason, + coverage, + fuzz_fixtures: self.fuzz_fixtures(address), + } } else { - TestSetup::success(address, logs, traces, Default::default(), None) + TestSetup::success( + zk_setup_deployments, + address, + logs, + traces, + Default::default(), + None, + self.fuzz_fixtures(address), + ) }; - Ok(setup) + Ok(result) + } + + /// Collect fixtures from test contract. + /// + /// Fixtures can be defined: + /// - as storage arrays in test contract, prefixed with `fixture` + /// - as functions prefixed with `fixture` and followed by parameter name to be fuzzed + /// + /// Storage array fixtures: + /// `uint256[] public fixture_amount = [1, 2, 3];` + /// define an array of uint256 values to be used for fuzzing `amount` named parameter in scope + /// of the current test. + /// + /// Function fixtures: + /// `function fixture_owner() public returns (address[] memory){}` + /// returns an array of addresses to be used for fuzzing `owner` named parameter in scope of the + /// current test. + fn fuzz_fixtures(&mut self, address: Address) -> FuzzFixtures { + let mut fixtures = HashMap::new(); + let fixture_functions = self.contract.abi.functions().filter(|func| func.is_fixture()); + for func in fixture_functions { + if func.inputs.is_empty() { + // Read fixtures declared as functions. + if let Ok(CallResult { raw: _, decoded_result }) = + self.executor.call(CALLER, address, func, &[], U256::ZERO, None) + { + fixtures.insert(fixture_name(func.name.clone()), decoded_result); + } + } else { + // For reading fixtures from storage arrays we collect values by calling the + // function with incremented indexes until there's an error. + let mut vals = Vec::new(); + let mut index = 0; + loop { + if let Ok(CallResult { raw: _, decoded_result }) = self.executor.call( + CALLER, + address, + func, + &[DynSolValue::Uint(U256::from(index), 256)], + U256::ZERO, + None, + ) { + vals.push(decoded_result); + } else { + // No result returned for this index, we reached the end of storage + // array or the function is not a valid fixture. + break; + } + index += 1; + } + fixtures.insert(fixture_name(func.name.clone()), DynSolValue::Array(vals)); + }; + } + FuzzFixtures::new(fixtures) } /// Runs all tests for a contract whose names match the provided regular expression pub fn run_tests( mut self, filter: &dyn TestFilter, - test_options: TestOptions, - known_contracts: Option<&ContractsByArtifact>, + test_options: &TestOptions, + known_contracts: ContractsByArtifact, + handle: &tokio::runtime::Handle, ) -> SuiteResult { info!("starting tests"); let start = Instant::now(); let mut warnings = Vec::new(); + // Check if `setUp` function with valid signature declared. let setup_fns: Vec<_> = - self.contract.functions().filter(|func| func.name.is_setup()).collect(); - - let needs_setup = setup_fns.len() == 1 && setup_fns[0].name == "setUp"; - + self.contract.abi.functions().filter(|func| func.name.is_setup()).collect(); + let call_setup = setup_fns.len() == 1 && setup_fns[0].name == "setUp"; // There is a single miss-cased `setUp` function, so we add a warning for &setup_fn in setup_fns.iter() { if setup_fn.name != "setUp" { @@ -210,7 +301,6 @@ impl<'a> ContractRunner<'a> { )); } } - // There are multiple setUp function, so we return a single test result for `setUp` if setup_fns.len() > 1 { return SuiteResult::new( @@ -221,14 +311,41 @@ impl<'a> ContractRunner<'a> { ) } - let has_invariants = self.contract.functions().any(|func| func.is_invariant_test()); + // Check if `afterInvariant` function with valid signature declared. + let after_invariant_fns: Vec<_> = + self.contract.abi.functions().filter(|func| func.name.is_after_invariant()).collect(); + if after_invariant_fns.len() > 1 { + // Return a single test result failure if multiple functions declared. + return SuiteResult::new( + start.elapsed(), + [( + "afterInvariant()".to_string(), + TestResult::fail("multiple afterInvariant functions".to_string()), + )] + .into(), + warnings, + ) + } + let call_after_invariant = after_invariant_fns.first().map_or(false, |after_invariant_fn| { + let match_sig = after_invariant_fn.name == "afterInvariant"; + if !match_sig { + warnings.push(format!( + "Found invalid afterInvariant function \"{}\" did you mean \"afterInvariant()\"?", + after_invariant_fn.signature() + )); + } + match_sig + }); // Invariant testing requires tracing to figure out what contracts were created. - let tmp_tracing = self.executor.inspector.tracer.is_none() && has_invariants && needs_setup; + let has_invariants = self.contract.abi.functions().any(|func| func.is_invariant_test()); + let tmp_tracing = self.executor.inspector.tracer.is_none() && has_invariants && call_setup; if tmp_tracing { self.executor.set_tracing(true); } - let setup = self.setup(needs_setup); + let setup_time = Instant::now(); + let setup = self.setup(call_setup); + debug!("finished setting up in {:?}", setup_time.elapsed()); if tmp_tracing { self.executor.set_tracing(false); } @@ -242,10 +359,8 @@ impl<'a> ContractRunner<'a> { TestResult { status: TestStatus::Failure, reason: setup.reason, - counterexample: None, decoded_logs: decode_console_logs(&setup.logs), logs: setup.logs, - kind: TestKind::Standard(0), traces: setup.traces, coverage: setup.coverage, labeled_addresses: setup.labeled_addresses, @@ -257,60 +372,89 @@ impl<'a> ContractRunner<'a> { ) } - let functions: Vec<_> = self.contract.functions().collect(); - let mut test_results = functions + // Filter out functions sequentially since it's very fast and there is no need to do it + // in parallel. + let find_timer = Instant::now(); + let functions = self + .contract + .abi + .functions() + .filter(|func| is_matching_test(func, filter)) + .collect::>(); + let find_time = find_timer.elapsed(); + debug!( + "Found {} test functions out of {} in {:?}", + functions.len(), + self.contract.abi.functions().count(), + find_time, + ); + + let identified_contracts = has_invariants.then(|| { + load_contracts( + setup.traces.iter().map(|(_, t)| t), + &known_contracts, + &setup.deployments, + ) + }); + let test_results = functions .par_iter() - .filter(|&&func| func.is_test() && filter.matches_test(&func.signature())) .map(|&func| { + let start = Instant::now(); + + let _guard = handle.enter(); + + let sig = func.signature(); + let span = debug_span!("test", name = tracing::field::Empty).entered(); + if !span.is_disabled() { + if enabled!(tracing::Level::TRACE) { + span.record("name", &sig); + } else { + span.record("name", &func.name); + } + } + + let setup = setup.clone(); let should_fail = func.is_test_fail(); - let res = if func.is_fuzz_test() { + let mut res = if func.is_invariant_test() { + let runner = test_options.invariant_runner(self.name, &func.name); + let invariant_config = test_options.invariant_config(self.name, &func.name); + + self.run_invariant_test( + runner, + setup, + invariant_config.clone(), + func, + call_after_invariant, + &known_contracts, + identified_contracts.as_ref().unwrap(), + ) + } else if func.is_fuzz_test() { + debug_assert!(func.is_test()); let runner = test_options.fuzz_runner(self.name, &func.name); let fuzz_config = test_options.fuzz_config(self.name, &func.name); info!(name = func.name, "run fuzz test"); - self.run_fuzz_test(func, should_fail, runner, setup.clone(), *fuzz_config) + self.run_fuzz_test(func, should_fail, runner, setup, fuzz_config.clone()) } else { + debug_assert!(func.is_test()); info!(name = func.name, "run test"); - self.run_test(func, should_fail, setup.clone()) + self.run_test(func, should_fail, setup) }; - (func.signature(), res) + + res.duration = start.elapsed(); + + (sig, res) }) .collect::>(); - if has_invariants { - let identified_contracts = load_contracts(setup.traces.clone(), known_contracts); - let results: Vec<_> = functions - .par_iter() - .filter(|&&func| func.is_invariant_test() && filter.matches_test(&func.signature())) - .map(|&func| { - let runner = test_options.invariant_runner(self.name, &func.name); - let invariant_config = test_options.invariant_config(self.name, &func.name); - let res = self.run_invariant_test( - runner, - setup.clone(), - *invariant_config, - func, - known_contracts, - &identified_contracts, - ); - (func.signature(), res) - }) - .collect(); - test_results.extend(results); - } - let duration = start.elapsed(); - if !test_results.is_empty() { - let successful = - test_results.iter().filter(|(_, tst)| tst.status == TestStatus::Success).count(); - info!( - duration = ?duration, - "done. {}/{} successful", - successful, - test_results.len() - ); - } - - SuiteResult::new(duration, test_results, warnings) + let suite_result = SuiteResult::new(duration, test_results, warnings); + info!( + duration=?suite_result.duration, + "done. {}/{} successful", + suite_result.passed(), + suite_result.test_results.len() + ); + suite_result } /// Runs a single test @@ -319,107 +463,66 @@ impl<'a> ContractRunner<'a> { /// /// State modifications are not committed to the evm database but discarded after the call, /// similar to `eth_call`. + #[instrument(level = "debug", name = "normal", skip_all)] pub fn run_test(&self, func: &Function, should_fail: bool, setup: TestSetup) -> TestResult { - let span = info_span!("test", %should_fail); - if !span.is_disabled() { - let sig = &func.signature()[..]; - if enabled!(tracing::Level::TRACE) { - span.record("sig", sig); - } else { - span.record("sig", sig.split('(').next().unwrap()); - } - } - let _guard = span.enter(); - let TestSetup { address, mut logs, mut traces, mut labeled_addresses, mut coverage, .. } = setup; // Run unit test - let mut executor = self.executor.clone(); - let start = Instant::now(); - let debug_arena; - let (reverted, reason, gas, stipend, coverage, state_changeset, breakpoints) = - match executor.execute_test::<_, _>( - self.sender, - address, - func.clone(), - vec![], - U256::ZERO, - Some(self.revert_decoder), - ) { - Ok(CallResult { - reverted, - gas_used: gas, - stipend, - logs: execution_logs, - traces: execution_trace, - coverage: execution_coverage, - labels: new_labels, - state_changeset, - debug, - breakpoints, - .. - }) => { - traces.extend(execution_trace.map(|traces| (TraceKind::Execution, traces))); - labeled_addresses.extend(new_labels); - logs.extend(execution_logs); - debug_arena = debug; - coverage = merge_coverages(coverage, execution_coverage); - - (reverted, None, gas, stipend, coverage, state_changeset, breakpoints) - } - Err(EvmError::Execution(err)) => { - traces.extend(err.traces.map(|traces| (TraceKind::Execution, traces))); - labeled_addresses.extend(err.labels); - logs.extend(err.logs); - debug_arena = err.debug; - ( - err.reverted, - Some(err.reason), - err.gas_used, - err.stipend, - None, - err.state_changeset, - HashMap::new(), - ) - } - Err(EvmError::SkipError) => { - return TestResult { - status: TestStatus::Skipped, - reason: None, - decoded_logs: decode_console_logs(&logs), - traces, - labeled_addresses, - kind: TestKind::Standard(0), - duration: start.elapsed(), - ..Default::default() - } + let (mut raw_call_result, reason) = match self.executor.call( + self.sender, + address, + func, + &[], + U256::ZERO, + Some(self.revert_decoder), + ) { + Ok(res) => (res.raw, None), + Err(EvmError::Execution(err)) => (err.raw, Some(err.reason)), + Err(EvmError::SkipError) => { + return TestResult { + status: TestStatus::Skipped, + reason: None, + decoded_logs: decode_console_logs(&logs), + traces, + labeled_addresses, + ..Default::default() } - Err(err) => { - return TestResult { - status: TestStatus::Failure, - reason: Some(err.to_string()), - decoded_logs: decode_console_logs(&logs), - traces, - labeled_addresses, - kind: TestKind::Standard(0), - duration: start.elapsed(), - ..Default::default() - } + } + Err(err) => { + return TestResult { + status: TestStatus::Failure, + reason: Some(err.to_string()), + decoded_logs: decode_console_logs(&logs), + traces, + labeled_addresses, + ..Default::default() } - }; + } + }; - let success = executor.is_success( - setup.address, - reverted, - state_changeset.expect("we should have a state changeset"), - should_fail, - ); + let success = + self.executor.is_raw_call_mut_success(setup.address, &mut raw_call_result, should_fail); - // Record test execution time - let duration = start.elapsed(); - debug!(?duration, gas, reverted, should_fail, success); + let RawCallResult { + gas_used: gas, + stipend, + logs: execution_logs, + traces: execution_trace, + coverage: execution_coverage, + labels: new_labels, + debug, + cheatcodes, + .. + } = raw_call_result; + + let breakpoints = cheatcodes.map(|c| c.breakpoints).unwrap_or_default(); + let debug_arena = debug; + traces.extend(execution_trace.map(|traces| (TraceKind::Execution, traces))); + labeled_addresses.extend(new_labels); + logs.extend(execution_logs); + coverage = merge_coverages(coverage, execution_coverage); TestResult { status: match success { @@ -430,38 +533,38 @@ impl<'a> ContractRunner<'a> { counterexample: None, decoded_logs: decode_console_logs(&logs), logs, - kind: TestKind::Standard(gas.overflowing_sub(stipend).0), + kind: TestKind::Unit { gas: gas.wrapping_sub(stipend) }, traces, coverage, labeled_addresses, debug: debug_arena, breakpoints, - duration, + duration: std::time::Duration::default(), + gas_report_traces: Vec::new(), } } - #[instrument(name = "invariant_test", skip_all)] + #[instrument(level = "debug", name = "invariant", skip_all)] + #[allow(clippy::too_many_arguments)] pub fn run_invariant_test( &self, runner: TestRunner, setup: TestSetup, invariant_config: InvariantConfig, func: &Function, - known_contracts: Option<&ContractsByArtifact>, + call_after_invariant: bool, + known_contracts: &ContractsByArtifact, identified_contracts: &ContractsByAddress, ) -> TestResult { - trace!(target: "forge::test::fuzz", "executing invariant test for {:?}", func.name); - let empty = ContractsByArtifact::default(); - let project_contracts = known_contracts.unwrap_or(&empty); - let TestSetup { address, logs, traces, labeled_addresses, coverage, .. } = setup; + let TestSetup { address, logs, traces, labeled_addresses, coverage, fuzz_fixtures, .. } = + setup; // First, run the test normally to see if it needs to be skipped. - let start = Instant::now(); - if let Err(EvmError::SkipError) = self.executor.clone().execute_test::<_, _>( + if let Err(EvmError::SkipError) = self.executor.call( self.sender, address, - func.clone(), - vec![], + func, + &[], U256::ZERO, Some(self.revert_decoder), ) { @@ -473,7 +576,6 @@ impl<'a> ContractRunner<'a> { labeled_addresses, kind: TestKind::Invariant { runs: 1, calls: 1, reverts: 1 }, coverage, - duration: start.elapsed(), ..Default::default() } }; @@ -481,61 +583,154 @@ impl<'a> ContractRunner<'a> { let mut evm = InvariantExecutor::new( self.executor.clone(), runner, - invariant_config, + invariant_config.clone(), identified_contracts, - project_contracts, + known_contracts, ); + let invariant_contract = InvariantContract { + address, + invariant_function: func, + call_after_invariant, + abi: &self.contract.abi, + }; - let invariant_contract = - InvariantContract { address, invariant_function: func, abi: self.contract }; + let mut logs = logs.clone(); + let mut traces = traces.clone(); + let mut coverage = coverage.clone(); + + let failure_dir = invariant_config.clone().failure_dir(self.name); + let failure_file = failure_dir.join(invariant_contract.invariant_function.clone().name); - let InvariantFuzzTestResult { error, cases, reverts, last_run_inputs } = match evm - .invariant_fuzz(invariant_contract.clone()) + // Try to replay recorded failure if any. + if let Ok(call_sequence) = + foundry_common::fs::read_json_file::>(failure_file.as_path()) { - Ok(x) => x, - Err(e) => { - return TestResult { - status: TestStatus::Failure, - reason: Some(format!("failed to set up invariant testing environment: {e}")), - decoded_logs: decode_console_logs(&logs), - traces, - labeled_addresses, - kind: TestKind::Invariant { runs: 0, calls: 0, reverts: 0 }, - duration: start.elapsed(), - ..Default::default() + // Create calls from failed sequence and check if invariant still broken. + let txes = call_sequence + .iter() + .map(|seq| BasicTxDetails { + sender: seq.sender.unwrap_or_default(), + call_details: CallDetails { + target: seq.addr.unwrap_or_default(), + calldata: seq.calldata.clone(), + }, + }) + .collect::>(); + if let Ok((success, replayed_entirely)) = check_sequence( + self.executor.clone(), + &txes, + (0..min(txes.len(), invariant_config.depth as usize)).collect(), + invariant_contract.address, + invariant_contract.invariant_function.selector().to_vec().into(), + invariant_config.fail_on_revert, + invariant_contract.call_after_invariant, + ) { + if !success { + // If sequence still fails then replay error to collect traces and + // exit without executing new runs. + let _ = replay_run( + &invariant_contract, + self.executor.clone(), + known_contracts, + identified_contracts.clone(), + &mut logs, + &mut traces, + &mut coverage, + &txes, + ); + return TestResult { + status: TestStatus::Failure, + reason: if replayed_entirely { + Some(format!( + "{} replay failure", + invariant_contract.invariant_function.name + )) + } else { + Some(format!( + "{} persisted failure revert", + invariant_contract.invariant_function.name + )) + }, + decoded_logs: decode_console_logs(&logs), + traces, + coverage, + counterexample: Some(CounterExample::Sequence(call_sequence)), + kind: TestKind::Invariant { runs: 1, calls: 1, reverts: 1 }, + ..Default::default() + } } } - }; + } + + let progress = + start_fuzz_progress(self.progress, self.name, &func.name, invariant_config.runs); + let InvariantFuzzTestResult { error, cases, reverts, last_run_inputs, gas_report_traces } = + match evm.invariant_fuzz(invariant_contract.clone(), &fuzz_fixtures, progress.as_ref()) + { + Ok(x) => x, + Err(e) => { + return TestResult { + status: TestStatus::Failure, + reason: Some(format!( + "failed to set up invariant testing environment: {e}" + )), + decoded_logs: decode_console_logs(&logs), + traces, + labeled_addresses, + kind: TestKind::Invariant { runs: 0, calls: 0, reverts: 0 }, + ..Default::default() + } + } + }; let mut counterexample = None; - let mut logs = logs.clone(); - let mut traces = traces.clone(); let success = error.is_none(); - let reason = error - .as_ref() - .and_then(|err| (!err.revert_reason.is_empty()).then(|| err.revert_reason.clone())); - let mut coverage = coverage.clone(); + let reason = error.as_ref().and_then(|err| err.revert_reason()); + match error { // If invariants were broken, replay the error to collect logs and traces - Some(error @ InvariantFuzzError { test_error: TestError::Fail(_, _), .. }) => { - match error.replay( - self.executor.clone(), - known_contracts, - identified_contracts.clone(), - &mut logs, - &mut traces, - ) { - Ok(c) => counterexample = c, - Err(err) => { - error!(%err, "Failed to replay invariant error"); - } - }; - } + Some(error) => match error { + InvariantFuzzError::BrokenInvariant(case_data) | + InvariantFuzzError::Revert(case_data) => { + // Replay error to create counterexample and to collect logs, traces and + // coverage. + match replay_error( + &case_data, + &invariant_contract, + self.executor.clone(), + known_contracts, + identified_contracts.clone(), + &mut logs, + &mut traces, + &mut coverage, + progress.as_ref(), + ) { + Ok(call_sequence) => { + if !call_sequence.is_empty() { + // Persist error in invariant failure dir. + if let Err(err) = foundry_common::fs::create_dir_all(failure_dir) { + error!(%err, "Failed to create invariant failure dir"); + } else if let Err(err) = foundry_common::fs::write_json_file( + failure_file.as_path(), + &call_sequence, + ) { + error!(%err, "Failed to record call sequence"); + } + counterexample = Some(CounterExample::Sequence(call_sequence)) + } + } + Err(err) => { + error!(%err, "Failed to replay invariant error"); + } + }; + } + InvariantFuzzError::MaxAssumeRejects(_) => {} + }, // If invariants ran successfully, replay the last run to collect logs and // traces. _ => { - replay_run( + if let Err(err) = replay_run( &invariant_contract, self.executor.clone(), known_contracts, @@ -543,9 +738,10 @@ impl<'a> ContractRunner<'a> { &mut logs, &mut traces, &mut coverage, - func.clone(), - last_run_inputs.clone(), - ); + &last_run_inputs, + ) { + error!(%err, "Failed to replay last invariant run"); + } } } @@ -566,12 +762,12 @@ impl<'a> ContractRunner<'a> { coverage, traces, labeled_addresses: labeled_addresses.clone(), - duration: start.elapsed(), + gas_report_traces, ..Default::default() // TODO collect debug traces on the last run or error } } - #[instrument(name = "fuzz_test", skip_all, fields(name = %func.signature(), %should_fail))] + #[instrument(level = "debug", name = "fuzz", skip_all)] pub fn run_fuzz_test( &self, func: &Function, @@ -580,27 +776,32 @@ impl<'a> ContractRunner<'a> { setup: TestSetup, fuzz_config: FuzzConfig, ) -> TestResult { - let span = info_span!("fuzz_test", %should_fail); - if !span.is_disabled() { - let sig = &func.signature()[..]; - if enabled!(tracing::Level::TRACE) { - span.record("test", sig); - } else { - span.record("test", sig.split('(').next().unwrap()); - } - } - let _guard = span.enter(); - let TestSetup { - address, mut logs, mut traces, mut labeled_addresses, mut coverage, .. + address, + mut logs, + mut traces, + mut labeled_addresses, + mut coverage, + fuzz_fixtures, + .. } = setup; // Run fuzz test - let start = Instant::now(); - let fuzzed_executor = - FuzzedExecutor::new(self.executor.clone(), runner.clone(), self.sender, fuzz_config); - let state = fuzzed_executor.build_fuzz_state(); - let result = fuzzed_executor.fuzz(func, address, should_fail, self.revert_decoder); + let progress = start_fuzz_progress(self.progress, self.name, &func.name, fuzz_config.runs); + let fuzzed_executor = FuzzedExecutor::new( + self.executor.clone(), + runner.clone(), + self.sender, + fuzz_config.clone(), + ); + let result = fuzzed_executor.fuzz( + func, + &fuzz_fixtures, + address, + should_fail, + self.revert_decoder, + progress.as_ref(), + ); let mut debug = Default::default(); let mut breakpoints = Default::default(); @@ -610,15 +811,12 @@ impl<'a> ContractRunner<'a> { if let Some("SKIPPED") = result.reason.as_deref() { return TestResult { status: TestStatus::Skipped, - reason: None, decoded_logs: decode_console_logs(&logs), traces, labeled_addresses, - kind: TestKind::Standard(0), debug, breakpoints, coverage, - duration: start.elapsed(), ..Default::default() } } @@ -638,13 +836,12 @@ impl<'a> ContractRunner<'a> { result.first_case.calldata.clone() }; // rerun the last relevant test with traces - let debug_result = FuzzedExecutor::new( - debug_executor, - runner, - self.sender, - fuzz_config, - ) - .single_fuzz(&state, address, should_fail, calldata); + let debug_result = + FuzzedExecutor::new(debug_executor, runner, self.sender, fuzz_config).single_fuzz( + address, + should_fail, + calldata, + ); (debug, breakpoints) = match debug_result { Ok(fuzz_outcome) => match fuzz_outcome { @@ -674,10 +871,6 @@ impl<'a> ContractRunner<'a> { traces.extend(result.traces.map(|traces| (TraceKind::Execution, traces))); coverage = merge_coverages(coverage, result.coverage); - // Record test execution time - let duration = start.elapsed(); - debug!(?duration, success = %result.success); - TestResult { status: match result.success { true => TestStatus::Success, @@ -693,7 +886,8 @@ impl<'a> ContractRunner<'a> { labeled_addresses, debug, breakpoints, - duration, + duration: std::time::Duration::default(), + gas_report_traces: result.gas_report_traces.into_iter().map(|t| vec![t]).collect(), } } } @@ -702,7 +896,7 @@ impl<'a> ContractRunner<'a> { fn merge_coverages(mut coverage: Option, other: Option) -> Option { let old_coverage = std::mem::take(&mut coverage); match (old_coverage, other) { - (Some(old_coverage), Some(other)) => Some(old_coverage.merge(other)), + (Some(old_coverage), Some(other)) => Some(old_coverage.merged(other)), (None, Some(other)) => Some(other), (Some(old_coverage), None) => Some(old_coverage), (None, None) => None, diff --git a/crates/forge/tests/cli/build.rs b/crates/forge/tests/cli/build.rs index 240b54614..93b313742 100644 --- a/crates/forge/tests/cli/build.rs +++ b/crates/forge/tests/cli/build.rs @@ -1,5 +1,8 @@ -use foundry_test_utils::{forgetest, util::OutputExt}; -use std::path::PathBuf; +use foundry_common::fs::read_json_file; +use foundry_config::Config; +use foundry_test_utils::forgetest; +use globset::Glob; +use std::{collections::BTreeMap, path::PathBuf}; // tests that json is printed when --json is passed forgetest!(compile_json, |prj, cmd| { @@ -19,29 +22,58 @@ contract Dummy { // set up command cmd.args(["compile", "--format-json"]); - // run command and assert - cmd.unchecked_output().stdout_matches_path( - PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/compile_json.stdout"), - ); + // Exclude build_infos from output as IDs depend on root dir and are not deterministic. + let mut output: BTreeMap = + serde_json::from_str(&cmd.stdout_lossy()).unwrap(); + output.remove("build_infos"); + + let expected: BTreeMap = read_json_file( + &PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/compile_json.stdout"), + ) + .unwrap(); + + similar_asserts::assert_eq!(output, expected); }); // tests build output is as expected forgetest_init!(exact_build_output, |prj, cmd| { cmd.args(["build", "--force"]); - let (stdout, _) = cmd.unchecked_output_lossy(); - // Expected output from build - let expected = r#"Compiling 24 files with 0.8.23 -Solc 0.8.23 finished in 2.36s -Compiler run successful! -"#; - - // skip all dynamic parts of the output (numbers) - let expected_words = - expected.split(|c: char| c == ' ').filter(|w| !w.chars().next().unwrap().is_numeric()); - let output_words = - stdout.split(|c: char| c == ' ').filter(|w| !w.chars().next().unwrap().is_numeric()); - - for (expected, output) in expected_words.zip(output_words) { - assert_eq!(expected, output, "expected: {}, output: {}", expected, output); - } + let stdout = cmd.stdout_lossy(); + assert!(stdout.contains("Compiling"), "\n{stdout}"); +}); + +// tests build output is as expected +forgetest_init!(build_sizes_no_forge_std, |prj, cmd| { + cmd.args(["build", "--sizes"]); + let stdout = cmd.stdout_lossy(); + assert!(!stdout.contains("console"), "\n{stdout}"); + assert!(!stdout.contains("std"), "\n{stdout}"); + assert!(stdout.contains("Counter"), "\n{stdout}"); +}); + +// tests that skip key in config can be used to skip non-compilable contract +forgetest_init!(test_can_skip_contract, |prj, cmd| { + prj.add_source( + "InvalidContract", + r" +contract InvalidContract { + some_invalid_syntax +} +", + ) + .unwrap(); + + prj.add_source( + "ValidContract", + r" +contract ValidContract {} +", + ) + .unwrap(); + + let config = + Config { skip: vec![Glob::new("src/InvalidContract.sol").unwrap()], ..Default::default() }; + prj.write_config(config); + + cmd.args(["build"]).assert_success(); }); diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index 188772657..65f5f6ae8 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -1,10 +1,13 @@ //! Contains various tests for checking forge's commands use crate::constants::*; -use foundry_compilers::{artifacts::Metadata, remappings::Remapping, ConfigurableContractArtifact}; -use foundry_config::{parse_with_profile, BasicConfig, Chain, Config, SolidityErrorCode}; +use foundry_compilers::artifacts::{remappings::Remapping, ConfigurableContractArtifact, Metadata}; +use foundry_config::{ + parse_with_profile, BasicConfig, Chain, Config, FuzzConfig, InvariantConfig, SolidityErrorCode, +}; use foundry_test_utils::{ foundry_compilers::PathStyle, + rpc::next_etherscan_api_key, util::{pretty_err, read_string, OutputExt, TestCommand}, }; use semver::Version; @@ -393,7 +396,7 @@ forgetest!(can_init_vscode, |prj, cmd| { let remappings = prj.root().join("remappings.txt"); assert!(remappings.is_file()); let content = std::fs::read_to_string(remappings).unwrap(); - assert_eq!(content, "ds-test/=lib/forge-std/lib/ds-test/src/\nforge-std/=lib/forge-std/src/",); + assert_eq!(content, "forge-std/=lib/forge-std/src/",); }); // checks that forge can init with template @@ -432,6 +435,83 @@ forgetest!(fail_init_nonexistent_template, |prj, cmd| { cmd.assert_non_empty_stderr(); }); +// checks that clone works +forgetest!(can_clone, |prj, cmd| { + prj.wipe(); + + let foundry_toml = prj.root().join(Config::FILE_NAME); + assert!(!foundry_toml.exists()); + + cmd.args([ + "clone", + "--etherscan-api-key", + next_etherscan_api_key().as_str(), + "0x044b75f554b886A065b9567891e45c79542d7357", + ]) + .arg(prj.root()); + cmd.assert_non_empty_stdout(); + + let s = read_string(&foundry_toml); + let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1; +}); + +// Checks that quiet mode does not print anything for clone +forgetest!(can_clone_quiet, |prj, cmd| { + prj.wipe(); + + cmd.args([ + "clone", + "--etherscan-api-key", + next_etherscan_api_key().as_str(), + "--quiet", + "0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec", + ]) + .arg(prj.root()); + cmd.assert_empty_stdout(); +}); + +// checks that clone works with --no-remappings-txt +forgetest!(can_clone_no_remappings_txt, |prj, cmd| { + prj.wipe(); + + let foundry_toml = prj.root().join(Config::FILE_NAME); + assert!(!foundry_toml.exists()); + + cmd.args([ + "clone", + "--etherscan-api-key", + next_etherscan_api_key().as_str(), + "--no-remappings-txt", + "0x33e690aEa97E4Ef25F0d140F1bf044d663091DAf", + ]) + .arg(prj.root()); + cmd.assert_non_empty_stdout(); + + let s = read_string(&foundry_toml); + let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1; +}); + +// checks that clone works with --keep-directory-structure +forgetest!(can_clone_keep_directory_structure, |prj, cmd| { + prj.wipe(); + + let foundry_toml = prj.root().join(Config::FILE_NAME); + assert!(!foundry_toml.exists()); + + cmd.args([ + "clone", + "--etherscan-api-key", + next_etherscan_api_key().as_str(), + "--keep-directory-structure", + "0x33e690aEa97E4Ef25F0d140F1bf044d663091DAf", + ]) + .arg(prj.root()); + cmd.assert_non_empty_stdout(); + + let s = read_string(&foundry_toml); + let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1; +}); + // checks that `clean` removes dapptools style paths forgetest!(can_clean, |prj, cmd| { prj.assert_create_dirs_exists(); @@ -466,6 +546,29 @@ forgetest_init!(can_clean_config, |prj, cmd| { assert!(!artifact.exists()); }); +// checks that `clean` removes fuzz and invariant cache dirs +forgetest_init!(can_clean_test_cache, |prj, cmd| { + let config = Config { + fuzz: FuzzConfig::new("cache/fuzz".into()), + invariant: InvariantConfig::new("cache/invariant".into()), + ..Default::default() + }; + prj.write_config(config); + // default test contract is written in custom out directory + let fuzz_cache_dir = prj.root().join("cache/fuzz"); + let _ = fs::create_dir(fuzz_cache_dir.clone()); + let invariant_cache_dir = prj.root().join("cache/invariant"); + let _ = fs::create_dir(invariant_cache_dir.clone()); + + assert!(fuzz_cache_dir.exists()); + assert!(invariant_cache_dir.exists()); + + cmd.forge_fuse().arg("clean"); + cmd.output(); + assert!(!fuzz_cache_dir.exists()); + assert!(!invariant_cache_dir.exists()); +}); + // checks that extra output works forgetest_init!(can_emit_extra_output, |prj, cmd| { cmd.args(["build", "--extra-output", "metadata"]); @@ -748,37 +851,6 @@ contract A { assert!(!out.contains("Compiler run successful with warnings:")); }); -// test against a local checkout, useful to debug with local ethers-rs patch -forgetest!( - #[ignore] - can_compile_local_spells, - |_prj, cmd| { - let current_dir = std::env::current_dir().unwrap(); - let root = current_dir - .join("../../foundry-integration-tests/testdata/spells-mainnet") - .to_string_lossy() - .to_string(); - println!("project root: \"{root}\""); - - let eth_rpc_url = foundry_common::rpc::next_http_archive_rpc_endpoint(); - let dss_exec_lib = "src/DssSpell.sol:DssExecLib:0xfD88CeE74f7D78697775aBDAE53f9Da1559728E4"; - - cmd.args([ - "test", - "--root", - root.as_str(), - "--fork-url", - eth_rpc_url.as_str(), - "--fork-block-number", - "14435000", - "--libraries", - dss_exec_lib, - "-vvvvv", - ]); - cmd.assert_non_empty_stdout(); - } -); - // test that a failing `forge build` does not impact followup builds forgetest!(can_build_after_failure, |prj, cmd| { prj.insert_ds_test(); @@ -1556,7 +1628,7 @@ forgetest_init!(can_install_missing_deps_build, |prj, cmd| { // checks that extra output works forgetest_init!(can_build_skip_contracts, |prj, cmd| { - prj.clear_cache(); + prj.clear(); // only builds the single template contract `src/*` cmd.args(["build", "--skip", "tests", "--skip", "scripts"]); @@ -1573,8 +1645,6 @@ forgetest_init!(can_build_skip_contracts, |prj, cmd| { }); forgetest_init!(can_build_skip_glob, |prj, cmd| { - prj.clear_cache(); - prj.add_test( "Foo", r" @@ -1585,10 +1655,79 @@ function test_run() external {} .unwrap(); // only builds the single template contract `src/*` even if `*.t.sol` or `.s.sol` is absent - cmd.args(["build", "--skip", "*/test/**", "--skip", "*/script/**"]); + prj.clear(); + cmd.args(["build", "--skip", "*/test/**", "--skip", "*/script/**", "--force"]); cmd.unchecked_output().stdout_matches_path( PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/can_build_skip_glob.stdout"), ); + + cmd.forge_fuse().args(["build", "--skip", "./test/**", "--skip", "./script/**", "--force"]); + cmd.unchecked_output().stdout_matches_path( + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/can_build_skip_glob.stdout"), + ); +}); + +forgetest_init!(can_build_specific_paths, |prj, cmd| { + prj.wipe(); + prj.add_source( + "Counter.sol", + r" +contract Counter { +function count() external {} +}", + ) + .unwrap(); + prj.add_test( + "Foo.sol", + r" +contract Foo { +function test_foo() external {} +}", + ) + .unwrap(); + prj.add_test( + "Bar.sol", + r" +contract Bar { +function test_bar() external {} +}", + ) + .unwrap(); + + // Build 2 files within test dir + prj.clear(); + cmd.args(["build", "test", "--force"]); + cmd.unchecked_output().stdout_matches_path( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/can_build_path_with_two_files.stdout"), + ); + + // Build one file within src dir + prj.clear(); + cmd.forge_fuse(); + cmd.args(["build", "src", "--force"]); + cmd.unchecked_output().stdout_matches_path( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/can_build_path_with_one_file.stdout"), + ); + + // Build 3 files from test and src dirs + prj.clear(); + cmd.forge_fuse(); + cmd.args(["build", "src", "test", "--force"]); + cmd.unchecked_output().stdout_matches_path( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/can_build_path_with_three_files.stdout"), + ); + + // Build single test file + prj.clear(); + cmd.forge_fuse(); + cmd.args(["build", "test/Bar.sol", "--force"]); + cmd.unchecked_output().stdout_matches_path( + PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("tests/fixtures/can_build_path_with_one_file.stdout"), + ); }); // checks that build --sizes includes all contracts even if unchanged diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index 7ddf44a3e..f5d47c624 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -2,7 +2,10 @@ use alloy_primitives::{Address, B256, U256}; use foundry_cli::utils as forge_utils; -use foundry_compilers::artifacts::{OptimizerDetails, RevertStrings, YulDetails}; +use foundry_compilers::{ + artifacts::{BytecodeHash, OptimizerDetails, RevertStrings, YulDetails}, + solc::Solc, +}; use foundry_config::{ cache::{CachedChains, CachedEndpoints, StorageCachingConfig}, fs_permissions::{FsAccessPermission, PathPermission}, @@ -10,11 +13,11 @@ use foundry_config::{ }; use foundry_evm::opts::EvmOpts; use foundry_test_utils::{ - foundry_compilers::{remappings::Remapping, EvmVersion}, + foundry_compilers::artifacts::{remappings::Remapping, EvmVersion}, util::{pretty_err, OutputExt, TestCommand, OTHER_SOLC_VERSION}, }; use path_slash::PathBufExt; -use pretty_assertions::assert_eq; +use similar_asserts::assert_eq; use std::{ fs, path::{Path, PathBuf}, @@ -26,7 +29,7 @@ forgetest!(can_extract_config_values, |prj, cmd| { // explicitly set all values let input = Config { profile: Config::DEFAULT_PROFILE, - __root: Default::default(), + root: Default::default(), src: "test-src".into(), test: "test-test".into(), script: "test-script".into(), @@ -65,11 +68,18 @@ forgetest!(can_extract_config_values, |prj, cmd| { runs: 1000, max_test_rejects: 100203, seed: Some(U256::from(1000)), + failure_persist_dir: Some("test-cache/fuzz".into()), + failure_persist_file: Some("failures".to_string()), + ..Default::default() + }, + invariant: InvariantConfig { + runs: 256, + failure_persist_dir: Some("test-cache/fuzz".into()), ..Default::default() }, - invariant: InvariantConfig { runs: 256, ..Default::default() }, ffi: true, always_use_create_2_factory: false, + prompt_timeout: 0, sender: "00a329c0648769A73afAc7F9381D08FB43dBEA72".parse().unwrap(), tx_origin: "00a329c0648769A73afAc7F9F81E08FB43dBEA72".parse().unwrap(), initial_balance: U256::from(0xffffffffffffffffffffffffu128), @@ -85,6 +95,7 @@ forgetest!(can_extract_config_values, |prj, cmd| { block_difficulty: 10, block_prevrandao: B256::random(), block_gas_limit: Some(100u64.into()), + disable_block_gas_limit: false, memory_limit: 1 << 27, eth_rpc_url: Some("localhost".to_string()), eth_rpc_jwt: None, @@ -120,15 +131,20 @@ forgetest!(can_extract_config_values, |prj, cmd| { doc: Default::default(), fs_permissions: Default::default(), labels: Default::default(), - cancun: true, + prague: true, isolate: true, - __non_exhaustive: (), - __warnings: vec![], + unchecked_cheatcode_artifacts: false, + create2_library_salt: Config::DEFAULT_CREATE2_LIBRARY_SALT, + vyper: Default::default(), + skip: vec![], + dependencies: Default::default(), + warnings: vec![], + _non_exhaustive: (), zksync: Default::default(), }; prj.write_config(input.clone()); let config = cmd.config(); - pretty_assertions::assert_eq!(input, config); + similar_asserts::assert_eq!(input, config); }); // tests config gets printed to std out @@ -150,14 +166,14 @@ forgetest_init!(can_override_config, |prj, cmd| { let profile = Config::load_with_root(prj.root()); // ensure that the auto-generated internal remapping for forge-std's ds-test exists - assert_eq!(profile.remappings.len(), 2); - assert_eq!("ds-test/=lib/forge-std/lib/ds-test/src/", profile.remappings[0].to_string()); + assert_eq!(profile.remappings.len(), 1); + assert_eq!("forge-std/=lib/forge-std/src/", profile.remappings[0].to_string()); // ensure remappings contain test - assert_eq!("ds-test/=lib/forge-std/lib/ds-test/src/", profile.remappings[0].to_string()); + assert_eq!("forge-std/=lib/forge-std/src/", profile.remappings[0].to_string()); // the loaded config has resolved, absolute paths assert_eq!( - "ds-test/=lib/forge-std/lib/ds-test/src/", + "forge-std/=lib/forge-std/src/", Remapping::from(profile.remappings[0].clone()).to_string() ); @@ -221,12 +237,12 @@ forgetest_init!(can_parse_remappings_correctly, |prj, cmd| { let profile = Config::load_with_root(prj.root()); // ensure that the auto-generated internal remapping for forge-std's ds-test exists - assert_eq!(profile.remappings.len(), 2); - let [r, _] = &profile.remappings[..] else { unreachable!() }; - assert_eq!("ds-test/=lib/forge-std/lib/ds-test/src/", r.to_string()); + assert_eq!(profile.remappings.len(), 1); + let r = &profile.remappings[0]; + assert_eq!("forge-std/=lib/forge-std/src/", r.to_string()); // the loaded config has resolved, absolute paths - assert_eq!("ds-test/=lib/forge-std/lib/ds-test/src/", Remapping::from(r.clone()).to_string()); + assert_eq!("forge-std/=lib/forge-std/src/", Remapping::from(r.clone()).to_string()); cmd.arg("config"); let expected = profile.to_string_pretty().unwrap(); @@ -301,8 +317,10 @@ forgetest_init!(can_get_evm_opts, |prj, _cmd| { // checks that we can set various config values forgetest_init!(can_set_config_values, |prj, _cmd| { - let config = prj.config_from_output(["--via-ir"]); + let config = prj.config_from_output(["--via-ir", "--no-metadata"]); assert!(config.via_ir); + assert_eq!(config.cbor_metadata, false); + assert_eq!(config.bytecode_hash, BytecodeHash::None); }); // tests that solc can be explicitly set @@ -347,12 +365,10 @@ contract Foo {} // fails to use solc that does not exist cmd.forge_fuse().args(["build", "--use", "this/solc/does/not/exist"]); - assert!(cmd.stderr_lossy().contains("this/solc/does/not/exist does not exist")); + assert!(cmd.stderr_lossy().contains("`solc` this/solc/does/not/exist does not exist")); // `OTHER_SOLC_VERSION` was installed in previous step, so we can use the path to this directly - let local_solc = foundry_compilers::Solc::find_svm_installed_version(OTHER_SOLC_VERSION) - .unwrap() - .expect("solc is installed"); + let local_solc = Solc::find_or_install(&OTHER_SOLC_VERSION.parse().unwrap()).unwrap(); cmd.forge_fuse().args(["build", "--force", "--use"]).arg(local_solc.solc).root_arg(); let stdout = cmd.stdout_lossy(); assert!(stdout.contains("Compiler run successful")); @@ -432,11 +448,10 @@ forgetest!(can_set_gas_price, |prj, cmd| { forgetest_init!(can_detect_lib_foundry_toml, |prj, cmd| { let config = cmd.config(); let remappings = config.remappings.iter().cloned().map(Remapping::from).collect::>(); - pretty_assertions::assert_eq!( + similar_asserts::assert_eq!( remappings, vec![ // global - "ds-test/=lib/forge-std/lib/ds-test/src/".parse().unwrap(), "forge-std/=lib/forge-std/src/".parse().unwrap(), ] ); @@ -451,11 +466,10 @@ forgetest_init!(can_detect_lib_foundry_toml, |prj, cmd| { let config = cmd.config(); let remappings = config.remappings.iter().cloned().map(Remapping::from).collect::>(); - pretty_assertions::assert_eq!( + similar_asserts::assert_eq!( remappings, vec![ // default - "ds-test/=lib/forge-std/lib/ds-test/src/".parse().unwrap(), "forge-std/=lib/forge-std/src/".parse().unwrap(), // remapping is local to the lib "nested-lib/=lib/nested-lib/src/".parse().unwrap(), @@ -475,13 +489,12 @@ forgetest_init!(can_detect_lib_foundry_toml, |prj, cmd| { let another_config = cmd.config(); let remappings = another_config.remappings.iter().cloned().map(Remapping::from).collect::>(); - pretty_assertions::assert_eq!( + similar_asserts::assert_eq!( remappings, vec![ // local to the lib "another-lib/=lib/nested-lib/lib/another-lib/src/".parse().unwrap(), // global - "ds-test/=lib/forge-std/lib/ds-test/src/".parse().unwrap(), "forge-std/=lib/forge-std/src/".parse().unwrap(), "nested-lib/=lib/nested-lib/src/".parse().unwrap(), // remappings local to the lib @@ -494,13 +507,12 @@ forgetest_init!(can_detect_lib_foundry_toml, |prj, cmd| { pretty_err(&toml_file, fs::write(&toml_file, config.to_string_pretty().unwrap())); let config = cmd.config(); let remappings = config.remappings.iter().cloned().map(Remapping::from).collect::>(); - pretty_assertions::assert_eq!( + similar_asserts::assert_eq!( remappings, vec![ // local to the lib "another-lib/=lib/nested-lib/lib/another-lib/custom-source-dir/".parse().unwrap(), // global - "ds-test/=lib/forge-std/lib/ds-test/src/".parse().unwrap(), "forge-std/=lib/forge-std/src/".parse().unwrap(), "nested-lib/=lib/nested-lib/src/".parse().unwrap(), // remappings local to the lib @@ -525,11 +537,10 @@ forgetest_init!(can_prioritise_closer_lib_remappings, |prj, cmd| { let config = cmd.config(); let remappings = config.get_all_remappings().collect::>(); - pretty_assertions::assert_eq!( + similar_asserts::assert_eq!( remappings, vec![ "dep1/=lib/dep1/src/".parse().unwrap(), - "ds-test/=lib/forge-std/lib/ds-test/src/".parse().unwrap(), "forge-std/=lib/forge-std/src/".parse().unwrap() ] ); @@ -674,3 +685,11 @@ forgetest_init!(can_resolve_symlink_fs_permissions, |prj, cmd| { let permission = fs_permissions.find_permission(&config_path.join("config.json")).unwrap(); assert_eq!(permission, FsAccessPermission::Read); }); + +// tests if evm version is normalized for config output +forgetest!(normalize_config_evm_version, |_prj, cmd| { + cmd.args(["config", "--use", "0.8.0", "--json"]); + let output = cmd.stdout_lossy(); + let config: Config = serde_json::from_str(&output).unwrap(); + assert_eq!(config.evm_version, EvmVersion::Istanbul); +}); diff --git a/crates/forge/tests/cli/context.rs b/crates/forge/tests/cli/context.rs new file mode 100644 index 000000000..34a7598a3 --- /dev/null +++ b/crates/forge/tests/cli/context.rs @@ -0,0 +1,81 @@ +//! Contains tests for checking forge execution context cheatcodes +const FORGE_TEST_CONTEXT_CONTRACT: &str = r#" +import "./test.sol"; +interface Vm { + enum ForgeContext { TestGroup, Test, Coverage, Snapshot, ScriptGroup, ScriptDryRun, ScriptBroadcast, ScriptResume, Unknown } + function isContext(ForgeContext context) external view returns (bool isContext); +} + +contract ForgeContextTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testForgeTestContext() external view { + require(vm.isContext(Vm.ForgeContext.TestGroup) && !vm.isContext(Vm.ForgeContext.ScriptGroup), "wrong context"); + require(vm.isContext(Vm.ForgeContext.Test), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.Coverage), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.Snapshot), "wrong context"); + } + function testForgeSnapshotContext() external view { + require(vm.isContext(Vm.ForgeContext.TestGroup) && !vm.isContext(Vm.ForgeContext.ScriptGroup), "wrong context"); + require(vm.isContext(Vm.ForgeContext.Snapshot), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.Test), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.Coverage), "wrong context"); + } + function testForgeCoverageContext() external view { + require(vm.isContext(Vm.ForgeContext.TestGroup) && !vm.isContext(Vm.ForgeContext.ScriptGroup), "wrong context"); + require(vm.isContext(Vm.ForgeContext.Coverage), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.Test), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.Snapshot), "wrong context"); + } + + function runDryRun() external view { + require(vm.isContext(Vm.ForgeContext.ScriptGroup) && !vm.isContext(Vm.ForgeContext.TestGroup), "wrong context"); + require(vm.isContext(Vm.ForgeContext.ScriptDryRun), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.ScriptBroadcast), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.ScriptResume), "wrong context"); + } + function runBroadcast() external view { + require(vm.isContext(Vm.ForgeContext.ScriptGroup) && !vm.isContext(Vm.ForgeContext.TestGroup), "wrong context"); + require(vm.isContext(Vm.ForgeContext.ScriptBroadcast), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.ScriptDryRun), "wrong context"); + require(!vm.isContext(Vm.ForgeContext.ScriptResume), "wrong context"); + } +} + "#; + +// tests that context properly set for `forge test` command +forgetest!(can_set_forge_test_standard_context, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source("ForgeContextTest.t.sol", FORGE_TEST_CONTEXT_CONTRACT).unwrap(); + cmd.args(["test", "--match-test", "testForgeTestContext"]).assert_success(); +}); + +// tests that context properly set for `forge snapshot` command +forgetest!(can_set_forge_test_snapshot_context, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source("ForgeContextTest.t.sol", FORGE_TEST_CONTEXT_CONTRACT).unwrap(); + cmd.args(["snapshot", "--match-test", "testForgeSnapshotContext"]).assert_success(); +}); + +// tests that context properly set for `forge coverage` command +forgetest!(can_set_forge_test_coverage_context, |prj, cmd| { + prj.insert_ds_test(); + prj.add_source("ForgeContextTest.t.sol", FORGE_TEST_CONTEXT_CONTRACT).unwrap(); + cmd.args(["coverage", "--match-test", "testForgeCoverageContext"]).assert_success(); +}); + +// tests that context properly set for `forge script` command +forgetest!(can_set_forge_script_dry_run_context, |prj, cmd| { + prj.insert_ds_test(); + let script = + prj.add_source("ForgeScriptContextTest.s.sol", FORGE_TEST_CONTEXT_CONTRACT).unwrap(); + cmd.arg("script").arg(script).args(["--sig", "runDryRun()"]).assert_success(); +}); + +// tests that context properly set for `forge script --broadcast` command +forgetest!(can_set_forge_script_broadcast_context, |prj, cmd| { + prj.insert_ds_test(); + let script = + prj.add_source("ForgeScriptContextTest.s.sol", FORGE_TEST_CONTEXT_CONTRACT).unwrap(); + cmd.arg("script").arg(script).args(["--broadcast", "--sig", "runBroadcast()"]).assert_success(); +}); diff --git a/crates/forge/tests/cli/coverage.rs b/crates/forge/tests/cli/coverage.rs index df44a1cf6..546a11038 100644 --- a/crates/forge/tests/cli/coverage.rs +++ b/crates/forge/tests/cli/coverage.rs @@ -70,11 +70,9 @@ contract AContractTest is DSTest { let lcov_data = std::fs::read_to_string(lcov_info).unwrap(); // AContract.init must be hit at least once let re = Regex::new(r"FNDA:(\d+),AContract\.init").unwrap(); - assert!(lcov_data.lines().any(|line| re.captures(line).map_or(false, |caps| caps - .get(1) - .unwrap() - .as_str() - .parse::() - .unwrap() > - 0))); + let valid_line = |line| { + re.captures(line) + .map_or(false, |caps| caps.get(1).unwrap().as_str().parse::().unwrap() > 0) + }; + assert!(lcov_data.lines().any(valid_line), "{lcov_data}"); }); diff --git a/crates/forge/tests/cli/create.rs b/crates/forge/tests/cli/create.rs index 20f0aa7cc..0df24f88b 100644 --- a/crates/forge/tests/cli/create.rs +++ b/crates/forge/tests/cli/create.rs @@ -6,7 +6,7 @@ use crate::{ }; use alloy_primitives::Address; use anvil::{spawn, NodeConfig}; -use foundry_compilers::{artifacts::BytecodeHash, remappings::Remapping}; +use foundry_compilers::artifacts::{remappings::Remapping, BytecodeHash}; use foundry_config::Config; use foundry_test_utils::{ forgetest, forgetest_async, @@ -135,7 +135,7 @@ forgetest_async!(can_create_template_contract, |prj, cmd| { let (_api, handle) = spawn(NodeConfig::test()).await; let rpc = handle.http_endpoint(); let wallet = handle.dev_wallets().next().unwrap(); - let pk = hex::encode(wallet.signer().to_bytes()); + let pk = hex::encode(wallet.credential().to_bytes()); // explicitly byte code hash for consistent checks let config = Config { bytecode_hash: BytecodeHash::None, ..Default::default() }; @@ -201,7 +201,7 @@ forgetest_async!(can_create_with_constructor_args, |prj, cmd| { let (_api, handle) = spawn(NodeConfig::test()).await; let rpc = handle.http_endpoint(); let wallet = handle.dev_wallets().next().unwrap(); - let pk = hex::encode(wallet.signer().to_bytes()); + let pk = hex::encode(wallet.credential().to_bytes()); // explicitly byte code hash for consistent checks let config = Config { bytecode_hash: BytecodeHash::None, ..Default::default() }; @@ -276,7 +276,7 @@ forgetest_async!(can_create_and_call, |prj, cmd| { let (_api, handle) = spawn(NodeConfig::test()).await; let rpc = handle.http_endpoint(); let wallet = handle.dev_wallets().next().unwrap(); - let pk = hex::encode(wallet.signer().to_bytes()); + let pk = hex::encode(wallet.credential().to_bytes()); // explicitly byte code hash for consistent checks let config = Config { bytecode_hash: BytecodeHash::None, ..Default::default() }; diff --git a/crates/forge/tests/cli/ext_integration.rs b/crates/forge/tests/cli/ext_integration.rs index 3483708f2..889000ad5 100644 --- a/crates/forge/tests/cli/ext_integration.rs +++ b/crates/forge/tests/cli/ext_integration.rs @@ -81,7 +81,7 @@ fn lil_web3() { #[test] #[cfg_attr(windows, ignore = "Windows cannot find installed programs")] fn snekmate() { - ExtTester::new("pcaversaccio", "snekmate", "ed49a0454393673cdf9a4250dd7051c28e6ac35f") + ExtTester::new("pcaversaccio", "snekmate", "1aa50098720d49e04b257a4aa5138b3d737a0667") .install_command(&["pnpm", "install", "--prefer-offline"]) // Try npm if pnpm fails / is not installed. .install_command(&["npm", "install", "--prefer-offline"]) diff --git a/crates/forge/tests/cli/main.rs b/crates/forge/tests/cli/main.rs index e9dab70f7..0ac67d81b 100644 --- a/crates/forge/tests/cli/main.rs +++ b/crates/forge/tests/cli/main.rs @@ -8,12 +8,14 @@ mod build; mod cache; mod cmd; mod config; +mod context; mod coverage; mod create; mod debug; mod doc; mod multi_script; mod script; +mod soldeer; mod svm; mod test_cmd; mod verify; diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index 4a83c8d82..8e71dcb9c 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -3,8 +3,7 @@ use crate::constants::TEMPLATE_CONTRACT; use alloy_primitives::{Address, Bytes}; use anvil::{spawn, NodeConfig}; -use foundry_common::rpc; -use foundry_test_utils::{util::OutputExt, ScriptOutcome, ScriptTester}; +use foundry_test_utils::{rpc, util::OutputExt, ScriptOutcome, ScriptTester}; use regex::Regex; use serde_json::Value; use std::{env, path::PathBuf, str::FromStr}; @@ -32,7 +31,7 @@ contract ContractScript is Script { ) .unwrap(); - let rpc = foundry_common::rpc::next_http_rpc_endpoint(); + let rpc = foundry_test_utils::rpc::next_http_rpc_endpoint(); cmd.arg("script").arg(script).args(["--fork-url", rpc.as_str(), "-vvvvv"]).assert_success(); } @@ -692,7 +691,7 @@ forgetest_async!(check_broadcast_log, |prj, cmd| { std::fs::read_to_string("broadcast/Broadcast.t.sol/31337/run-latest.json").unwrap(); let _run_log = re.replace_all(&run_log, ""); - // pretty_assertions::assert_eq!(fixtures_log, run_log); + // similar_asserts::assert_eq!(fixtures_log, run_log); // Uncomment to recreate the sensitive log // std::fs::copy( @@ -720,7 +719,7 @@ forgetest_async!(check_broadcast_log, |prj, cmd| { let fixtures_log = re.replace_all(&fixtures_log, "\n"); let run_log = re.replace_all(&run_log, "\n"); - pretty_assertions::assert_eq!(fixtures_log, run_log); + similar_asserts::assert_eq!(fixtures_log, run_log); }); forgetest_async!(test_default_sender_balance, |prj, cmd| { @@ -809,9 +808,8 @@ contract Script0 is Script { assert!(cmd.stdout_lossy().contains("SIMULATION COMPLETE")); - let run_latest = foundry_common::fs::json_files(prj.root().join("broadcast")) - .into_iter() - .find(|file| file.ends_with("run-latest.json")) + let run_latest = foundry_common::fs::json_files(&prj.root().join("broadcast")) + .find(|path| path.ends_with("run-latest.json")) .expect("No broadcast artifacts"); let content = foundry_common::fs::read_to_string(run_latest).unwrap(); @@ -894,8 +892,7 @@ contract Script0 is Script { assert!(cmd.stdout_lossy().contains("SIMULATION COMPLETE")); - let run_latest = foundry_common::fs::json_files(prj.root().join("broadcast")) - .into_iter() + let run_latest = foundry_common::fs::json_files(&prj.root().join("broadcast")) .find(|file| file.ends_with("run-latest.json")) .expect("No broadcast artifacts"); @@ -1080,6 +1077,28 @@ interface Interface {} assert!(cmd.stdout_lossy().contains("Script ran successfully.")); }); +forgetest_async!(assert_can_detect_unlinked_target_with_libraries, |prj, cmd| { + let script = prj + .add_script( + "ScriptWithExtLib.s.sol", + r#" +library Lib { + function f() public {} +} + +contract Script { + function run() external { + Lib.f(); + } +} + "#, + ) + .unwrap(); + + cmd.arg("script").arg(script); + assert!(cmd.stdout_lossy().contains("Script ran successfully.")); +}); + forgetest_async!(assert_can_resume_with_additional_contracts, |prj, cmd| { let (_api, handle) = spawn(NodeConfig::test()).await; let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); @@ -1136,6 +1155,303 @@ contract ScriptC {} tester.simulate(ScriptOutcome::OkNoEndpoint); }); +forgetest_async!(can_sign_with_script_wallet_single, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + + let mut tester = ScriptTester::new_broadcast_without_endpoint(cmd, prj.root()); + tester + .add_sig("ScriptSign", "run()") + .load_private_keys(&[0]) + .await + .simulate(ScriptOutcome::OkNoEndpoint); +}); + +forgetest_async!(can_sign_with_script_wallet_multiple, |prj, cmd| { + let mut tester = ScriptTester::new_broadcast_without_endpoint(cmd, prj.root()); + let acc = tester.accounts_pub[0].to_checksum(None); + tester + .add_sig("ScriptSign", "run(address)") + .arg(&acc) + .load_private_keys(&[0, 1, 2]) + .await + .simulate(ScriptOutcome::OkRun); +}); + +forgetest_async!(fails_with_function_name_and_overloads, |prj, cmd| { + let script = prj + .add_script( + "Sctipt.s.sol", + r#" +contract Script { + function run() external {} + + function run(address,uint256) external {} +} + "#, + ) + .unwrap(); + + cmd.arg("script").args([&script.to_string_lossy(), "--sig", "run"]); + assert!(cmd.stderr_lossy().contains("Multiple functions with the same name")); +}); + +forgetest_async!(can_decode_custom_errors, |prj, cmd| { + cmd.args(["init", "--force"]).arg(prj.root()); + cmd.assert_non_empty_stdout(); + cmd.forge_fuse(); + + let script = prj + .add_script( + "CustomErrorScript.s.sol", + r#" +import { Script } from "forge-std/Script.sol"; + +contract ContractWithCustomError { + error CustomError(); + + constructor() { + revert CustomError(); + } +} + +contract CustomErrorScript is Script { + ContractWithCustomError test; + + function run() public { + test = new ContractWithCustomError(); + } +} +"#, + ) + .unwrap(); + + cmd.arg("script").arg(script).args(["--tc", "CustomErrorScript"]); + assert!(cmd.stderr_lossy().contains("script failed: CustomError()")); +}); + +// https://github.com/foundry-rs/foundry/issues/7620 +forgetest_async!(can_run_zero_base_fee, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + prj.add_script( + "Foo", + r#" +import "forge-std/Script.sol"; + +contract SimpleScript is Script { + function run() external { + vm.startBroadcast(); + address(0).call(""); + } +} + "#, + ) + .unwrap(); + + let node_config = NodeConfig::test().with_base_fee(Some(0)); + let (_api, handle) = spawn(node_config).await; + let dev = handle.dev_accounts().next().unwrap(); + + // Firstly run script with non-zero gas prices to ensure that eth_feeHistory contains non-zero + // values. + cmd.args([ + "script", + "SimpleScript", + "--fork-url", + &handle.http_endpoint(), + "--sender", + format!("{dev:?}").as_str(), + "--broadcast", + "--unlocked", + "--with-gas-price", + "2000000", + "--priority-gas-price", + "100000", + ]); + + let output = cmd.stdout_lossy(); + assert!(output.contains("ONCHAIN EXECUTION COMPLETE & SUCCESSFUL")); + + // Ensure that we can correctly estimate gas when base fee is zero but priority fee is not. + cmd.forge_fuse().args([ + "script", + "SimpleScript", + "--fork-url", + &handle.http_endpoint(), + "--sender", + format!("{dev:?}").as_str(), + "--broadcast", + "--unlocked", + ]); + + let output = cmd.stdout_lossy(); + assert!(output.contains("ONCHAIN EXECUTION COMPLETE & SUCCESSFUL")); +}); + +// https://github.com/foundry-rs/foundry/pull/7742 +forgetest_async!(unlocked_no_sender, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + prj.add_script( + "Foo", + r#" +import "forge-std/Script.sol"; + +contract SimpleScript is Script { + function run() external { + vm.startBroadcast(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); + address(0).call(""); + } +} + "#, + ) + .unwrap(); + + let (_api, handle) = spawn(NodeConfig::test()).await; + + cmd.args([ + "script", + "SimpleScript", + "--fork-url", + &handle.http_endpoint(), + "--broadcast", + "--unlocked", + ]); + + let output = cmd.stdout_lossy(); + assert!(output.contains("ONCHAIN EXECUTION COMPLETE & SUCCESSFUL")); +}); + +// https://github.com/foundry-rs/foundry/issues/7833 +forgetest_async!(error_no_create2, |prj, cmd| { + let (_api, handle) = + spawn(NodeConfig::test().with_disable_default_create2_deployer(true)).await; + + foundry_test_utils::util::initialize(prj.root()); + prj.add_script( + "Foo", + r#" +import "forge-std/Script.sol"; + +contract SimpleContract {} + +contract SimpleScript is Script { + function run() external { + vm.startBroadcast(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266); + new SimpleContract{salt: bytes32(0)}(); + } +} + "#, + ) + .unwrap(); + + cmd.args([ + "script", + "SimpleScript", + "--fork-url", + &handle.http_endpoint(), + "--broadcast", + "--unlocked", + ]); + + let output = cmd.stderr_lossy(); + assert!(output.contains("missing CREATE2 deployer")); +}); + +forgetest_async!(can_switch_forks_in_setup, |prj, cmd| { + let (_api, handle) = + spawn(NodeConfig::test().with_disable_default_create2_deployer(true)).await; + + foundry_test_utils::util::initialize(prj.root()); + let url = handle.http_endpoint(); + + prj.add_script( + "Foo", + &r#" +import "forge-std/Script.sol"; + +contract SimpleScript is Script { + function setUp() external { + uint256 initialFork = vm.activeFork(); + vm.createSelectFork(""); + vm.selectFork(initialFork); + } + + function run() external { + assert(vm.getNonce(msg.sender) == 0); + } +} + "# + .replace("", &url), + ) + .unwrap(); + + cmd.args([ + "script", + "SimpleScript", + "--fork-url", + &url, + "--sender", + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + ]); + + cmd.stdout_lossy(); +}); + +// Asserts that running the same script twice only deploys library once. +forgetest_async!(can_deploy_library_create2, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); + + tester + .load_private_keys(&[0, 1]) + .await + .add_sig("BroadcastTest", "deploy()") + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast) + .assert_nonce_increment(&[(0, 2), (1, 1)]) + .await; + + tester.clear(); + + tester + .load_private_keys(&[0, 1]) + .await + .add_sig("BroadcastTest", "deploy()") + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast) + .assert_nonce_increment(&[(0, 1), (1, 1)]) + .await; +}); + +// Asserts that running the same script twice only deploys library once when using different +// senders. +forgetest_async!(can_deploy_library_create2_different_sender, |prj, cmd| { + let (_api, handle) = spawn(NodeConfig::test()).await; + + let mut tester = ScriptTester::new_broadcast(cmd, &handle.http_endpoint(), prj.root()); + + tester + .load_private_keys(&[0, 1]) + .await + .add_sig("BroadcastTest", "deploy()") + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast) + .assert_nonce_increment(&[(0, 2), (1, 1)]) + .await; + + tester.clear(); + + // Run different script from the same contract (which requires the same library). + tester + .load_private_keys(&[2]) + .await + .add_sig("BroadcastTest", "deployNoArgs()") + .simulate(ScriptOutcome::OkSimulation) + .broadcast(ScriptOutcome::OkBroadcast) + .assert_nonce_increment(&[(2, 2)]) + .await; +}); + // ignoring test as it requires a local era-test-node to be running on port 8011 forgetest_async!( #[ignore] @@ -1231,8 +1547,7 @@ contract DeployScript is Script { assert!(cmd.stdout_lossy().contains("ONCHAIN EXECUTION COMPLETE & SUCCESSFUL")); - let run_latest = foundry_common::fs::json_files(prj.root().join("broadcast")) - .into_iter() + let run_latest = foundry_common::fs::json_files(prj.root().join("broadcast").as_path()) .find(|file| file.ends_with("run-latest.json")) .expect("No broadcast artifacts"); diff --git a/crates/forge/tests/cli/soldeer.rs b/crates/forge/tests/cli/soldeer.rs new file mode 100644 index 000000000..6bbba534d --- /dev/null +++ b/crates/forge/tests/cli/soldeer.rs @@ -0,0 +1,176 @@ +//! Contains various tests related to `forge soldeer`. + +use std::{ + fs::{self, OpenOptions}, + path::Path, +}; + +use foundry_test_utils::forgesoldeer; +use std::io::Write; +forgesoldeer!(install_dependency, |prj, cmd| { + let command = "install"; + let dependency = "forge-std~1.8.1"; + + cmd.arg("soldeer").args([command, dependency]); + cmd.execute(); + + // Making sure the path was created to the dependency and that foundry.toml exists + // meaning that the dependencies were installed correctly + let path_dep_forge = + prj.root().join("dependencies").join("forge-std-1.8.1").join("foundry.toml"); + assert!(path_dep_forge.exists()); + + // Making sure the lock contents are the right ones + let path_lock_file = prj.root().join("soldeer.lock"); + let lock_contents = r#" +[[dependencies]] +name = "forge-std" +version = "1.8.1" +source = "https://soldeer-revisions.s3.amazonaws.com/forge-std/v1_8_1_23-03-2024_00:05:44_forge-std-v1.8.1.zip" +checksum = "0f7cd44f5670c31a9646d4031e70c66321cd3ed6ebac3c7278e4e57e4e5c5bd0" +"#; + + let actual_lock_contents = read_file_to_string(&path_lock_file); + assert_eq!(lock_contents, actual_lock_contents); + + // Making sure the foundry contents are the right ones + let foundry_file = prj.root().join("foundry.toml"); + let foundry_contents = r#"[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options + +[dependencies] +forge-std = { version = "1.8.1" } +"#; + + let actual_foundry_contents = read_file_to_string(&foundry_file); + assert_eq!(foundry_contents, actual_foundry_contents); +}); + +forgesoldeer!(update_dependencies, |prj, cmd| { + let command = "update"; + + // We need to write this into the foundry.toml to make the update install the dependency + let foundry_updates = r#" +[dependencies] +forge-std = { version = "1.8.1" } +"#; + let foundry_file = prj.root().join("foundry.toml"); + + let mut file = OpenOptions::new().append(true).open(&foundry_file).unwrap(); + + if let Err(e) = write!(file, "{foundry_updates}") { + eprintln!("Couldn't write to file: {e}"); + } + + cmd.arg("soldeer").arg(command); + cmd.execute(); + + // Making sure the path was created to the dependency and that foundry.toml exists + // meaning that the dependencies were installed correctly + let path_dep_forge = + prj.root().join("dependencies").join("forge-std-1.8.1").join("foundry.toml"); + assert!(path_dep_forge.exists()); + + // Making sure the lock contents are the right ones + let path_lock_file = prj.root().join("soldeer.lock"); + let lock_contents = r#" +[[dependencies]] +name = "forge-std" +version = "1.8.1" +source = "https://soldeer-revisions.s3.amazonaws.com/forge-std/v1_8_1_23-03-2024_00:05:44_forge-std-v1.8.1.zip" +checksum = "0f7cd44f5670c31a9646d4031e70c66321cd3ed6ebac3c7278e4e57e4e5c5bd0" +"#; + + let actual_lock_contents = read_file_to_string(&path_lock_file); + assert_eq!(lock_contents, actual_lock_contents); + + // Making sure the foundry contents are the right ones + let foundry_contents = r#"[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options + +[dependencies] +forge-std = { version = "1.8.1" } +"#; + + let actual_foundry_contents = read_file_to_string(&foundry_file); + assert_eq!(foundry_contents, actual_foundry_contents); +}); + +forgesoldeer!(update_dependencies_simple_version, |prj, cmd| { + let command = "update"; + + // We need to write this into the foundry.toml to make the update install the dependency, this + // is he simplified version of version specification + let foundry_updates = r#" +[dependencies] +forge-std = "1.8.1" +"#; + let foundry_file = prj.root().join("foundry.toml"); + + let mut file = OpenOptions::new().append(true).open(&foundry_file).unwrap(); + + if let Err(e) = write!(file, "{foundry_updates}") { + eprintln!("Couldn't write to file: {e}"); + } + + cmd.arg("soldeer").arg(command); + cmd.execute(); + + // Making sure the path was created to the dependency and that foundry.toml exists + // meaning that the dependencies were installed correctly + let path_dep_forge = + prj.root().join("dependencies").join("forge-std-1.8.1").join("foundry.toml"); + assert!(path_dep_forge.exists()); + + // Making sure the lock contents are the right ones + let path_lock_file = prj.root().join("soldeer.lock"); + let lock_contents = r#" +[[dependencies]] +name = "forge-std" +version = "1.8.1" +source = "https://soldeer-revisions.s3.amazonaws.com/forge-std/v1_8_1_23-03-2024_00:05:44_forge-std-v1.8.1.zip" +checksum = "0f7cd44f5670c31a9646d4031e70c66321cd3ed6ebac3c7278e4e57e4e5c5bd0" +"#; + + let actual_lock_contents = read_file_to_string(&path_lock_file); + assert_eq!(lock_contents, actual_lock_contents); + + // Making sure the foundry contents are the right ones + let foundry_contents = r#"[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options + +[dependencies] +forge-std = "1.8.1" +"#; + + let actual_foundry_contents = read_file_to_string(&foundry_file); + assert_eq!(foundry_contents, actual_foundry_contents); +}); + +forgesoldeer!(login, |prj, cmd| { + let command = "login"; + + cmd.arg("soldeer").arg(command); + let output = cmd.unchecked_output(); + + // On login, we can only check if the prompt is displayed in the stdout + let stdout = String::from_utf8(output.stdout).expect("Could not parse the output"); + assert!(stdout.contains("Please enter your email")); +}); + +fn read_file_to_string(path: &Path) -> String { + let contents: String = fs::read_to_string(path).unwrap_or_default(); + contents +} diff --git a/crates/forge/tests/cli/test_cmd.rs b/crates/forge/tests/cli/test_cmd.rs index 61cbab489..0e4f24138 100644 --- a/crates/forge/tests/cli/test_cmd.rs +++ b/crates/forge/tests/cli/test_cmd.rs @@ -1,7 +1,11 @@ -//! Contains various tests for checking `forge test` -use foundry_common::rpc; -use foundry_config::Config; -use foundry_test_utils::util::{OutputExt, OTHER_SOLC_VERSION, SOLC_VERSION}; +//! Contains various tests for `forge test`. + +use alloy_primitives::U256; +use foundry_config::{Config, FuzzConfig}; +use foundry_test_utils::{ + rpc, + util::{OutputExt, OTHER_SOLC_VERSION, SOLC_VERSION}, +}; use std::{path::PathBuf, str::FromStr}; // tests that test filters are handled correctly @@ -262,6 +266,7 @@ contract ContractTest is DSTest { // tests that libraries are handled correctly in multiforking mode forgetest_init!(can_use_libs_in_multi_fork, |prj, cmd| { prj.wipe_contracts(); + prj.add_source( "Contract.sol", r" @@ -358,10 +363,10 @@ interface IERC20 { function name() external view returns (string memory); } -contract USDCCallingTest is Test { +contract USDTCallingTest is Test { function test() public { vm.createSelectFork(""); - IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48).name(); + IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7).name(); } } "# @@ -490,3 +495,102 @@ contract TransientTest is Test { cmd.args(["test", "-vvvv", "--isolate", "--evm-version", "cancun"]).assert_success(); }); + +forgetest_init!(can_disable_block_gas_limit, |prj, cmd| { + prj.wipe_contracts(); + + let endpoint = rpc::next_http_archive_rpc_endpoint(); + + prj.add_test( + "Contract.t.sol", + &r#"pragma solidity 0.8.24; +import {Test} from "forge-std/Test.sol"; + +contract C is Test {} + +contract GasWaster { + function waste() public { + for (uint256 i = 0; i < 100; i++) { + new C(); + } + } +} + +contract GasLimitTest is Test { + function test() public { + vm.createSelectFork(""); + + GasWaster waster = new GasWaster(); + waster.waste(); + } +} + "# + .replace("", &endpoint), + ) + .unwrap(); + + cmd.args(["test", "-vvvv", "--isolate", "--disable-block-gas-limit"]).assert_success(); +}); + +forgetest!(test_match_path, |prj, cmd| { + prj.add_source( + "dummy", + r" +contract Dummy { + function testDummy() public {} +} +", + ) + .unwrap(); + + cmd.args(["test", "--match-path", "src/dummy.sol"]); + cmd.assert_success() +}); + +forgetest_init!(should_not_shrink_fuzz_failure, |prj, cmd| { + prj.wipe_contracts(); + + // deterministic test so we always have 54 runs until test fails with overflow + let config = Config { + fuzz: { FuzzConfig { runs: 256, seed: Some(U256::from(100)), ..Default::default() } }, + ..Default::default() + }; + prj.write_config(config); + + prj.add_test( + "CounterFuzz.t.sol", + r#"pragma solidity 0.8.24; +import {Test} from "forge-std/Test.sol"; + +contract Counter { + uint256 public number = 0; + + function addOne(uint256 x) external pure returns (uint256) { + return x + 100_000_000; + } +} + +contract CounterTest is Test { + Counter public counter; + + function setUp() public { + counter = new Counter(); + } + + function testAddOne(uint256 x) public view { + assertEq(counter.addOne(x), x + 100_000_000); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test"]); + let (stderr, _) = cmd.unchecked_output_lossy(); + let runs = stderr.find("runs:").and_then(|start_runs| { + let runs_split = &stderr[start_runs + 6..]; + runs_split.find(',').map(|end_runs| &runs_split[..end_runs]) + }); + // make sure there are only 61 runs (with proptest shrinking same test results in 298 runs) + assert_eq!(runs.unwrap().parse::().unwrap(), 61); +}); diff --git a/crates/forge/tests/cli/utils.rs b/crates/forge/tests/cli/utils.rs index 12b0fe134..094255195 100644 --- a/crates/forge/tests/cli/utils.rs +++ b/crates/forge/tests/cli/utils.rs @@ -1,7 +1,8 @@ //! Various helper functions -use ethers_core::types::{Address, Chain}; -use ethers_signers::{LocalWallet, Signer}; +use alloy_chains::NamedChain; +use alloy_primitives::Address; +use alloy_signer_local::PrivateKeySigner; /// Returns the current millis since unix epoch. /// @@ -13,12 +14,12 @@ pub fn millis_since_epoch() -> u128 { .as_millis() } -pub fn etherscan_key(chain: Chain) -> Option { +pub fn etherscan_key(chain: NamedChain) -> Option { match chain { - Chain::Fantom | Chain::FantomTestnet => { + NamedChain::Fantom | NamedChain::FantomTestnet => { std::env::var("FTMSCAN_API_KEY").or_else(|_| std::env::var("FANTOMSCAN_API_KEY")).ok() } - Chain::OptimismKovan => std::env::var("OP_KOVAN_API_KEY").ok(), + NamedChain::OptimismKovan => std::env::var("OP_KOVAN_API_KEY").ok(), _ => std::env::var("ETHERSCAN_API_KEY").ok(), } } @@ -35,76 +36,75 @@ pub fn network_private_key(chain: &str) -> Option { /// Represents external input required for executing verification requests pub struct EnvExternalities { - pub chain: Chain, + pub chain: NamedChain, pub rpc: String, pub pk: String, pub etherscan: String, pub verifier: String, } -#[allow(dead_code)] impl EnvExternalities { pub fn address(&self) -> Option
{ - let pk: LocalWallet = self.pk.parse().ok()?; + let pk: PrivateKeySigner = self.pk.parse().ok()?; Some(pk.address()) } pub fn goerli() -> Option { Some(Self { - chain: Chain::Goerli, + chain: NamedChain::Goerli, rpc: network_rpc_key("goerli")?, pk: network_private_key("goerli")?, - etherscan: etherscan_key(Chain::Goerli)?, + etherscan: etherscan_key(NamedChain::Goerli)?, verifier: "etherscan".to_string(), }) } pub fn ftm_testnet() -> Option { Some(Self { - chain: Chain::FantomTestnet, + chain: NamedChain::FantomTestnet, rpc: network_rpc_key("ftm_testnet")?, pk: network_private_key("ftm_testnet")?, - etherscan: etherscan_key(Chain::FantomTestnet)?, + etherscan: etherscan_key(NamedChain::FantomTestnet)?, verifier: "etherscan".to_string(), }) } pub fn optimism_kovan() -> Option { Some(Self { - chain: Chain::OptimismKovan, + chain: NamedChain::OptimismKovan, rpc: network_rpc_key("op_kovan")?, pk: network_private_key("op_kovan")?, - etherscan: etherscan_key(Chain::OptimismKovan)?, + etherscan: etherscan_key(NamedChain::OptimismKovan)?, verifier: "etherscan".to_string(), }) } pub fn arbitrum_goerli() -> Option { Some(Self { - chain: Chain::ArbitrumGoerli, + chain: NamedChain::ArbitrumGoerli, rpc: network_rpc_key("arbitrum-goerli")?, pk: network_private_key("arbitrum-goerli")?, - etherscan: etherscan_key(Chain::ArbitrumGoerli)?, + etherscan: etherscan_key(NamedChain::ArbitrumGoerli)?, verifier: "blockscout".to_string(), }) } pub fn mumbai() -> Option { Some(Self { - chain: Chain::PolygonMumbai, + chain: NamedChain::PolygonMumbai, rpc: network_rpc_key("mumbai")?, pk: network_private_key("mumbai")?, - etherscan: etherscan_key(Chain::PolygonMumbai)?, + etherscan: etherscan_key(NamedChain::PolygonMumbai)?, verifier: "etherscan".to_string(), }) } pub fn sepolia() -> Option { Some(Self { - chain: Chain::Sepolia, + chain: NamedChain::Sepolia, rpc: network_rpc_key("sepolia")?, pk: network_private_key("sepolia")?, - etherscan: etherscan_key(Chain::Sepolia)?, + etherscan: etherscan_key(NamedChain::Sepolia)?, verifier: "etherscan".to_string(), }) } diff --git a/crates/forge/tests/cli/verify.rs b/crates/forge/tests/cli/verify.rs index 8874af953..2f1f1368d 100644 --- a/crates/forge/tests/cli/verify.rs +++ b/crates/forge/tests/cli/verify.rs @@ -55,13 +55,31 @@ function doStuff() external {{}} prj.add_source("Verify.sol", &contract).unwrap(); } +fn add_verify_target_with_constructor(prj: &TestProject) { + prj.add_source( + "Verify.sol", + r#" +import {Unique} from "./unique.sol"; +contract Verify is Unique { + struct SomeStruct { + uint256 a; + string str; + } + + constructor(SomeStruct memory st, address owner) {} +} +"#, + ) + .unwrap(); +} + fn parse_verification_result(cmd: &mut TestCommand, retries: u32) -> eyre::Result<()> { // give etherscan some time to verify the contract let retry = Retry::new(retries, Some(Duration::from_secs(30))); retry.run(|| -> eyre::Result<()> { let output = cmd.unchecked_output(); let out = String::from_utf8_lossy(&output.stdout); - println!("{}", out); + println!("{out}"); if out.contains("Contract successfully verified") { return Ok(()) } @@ -73,6 +91,39 @@ fn parse_verification_result(cmd: &mut TestCommand, retries: u32) -> eyre::Resul }) } +fn await_verification_response(info: EnvExternalities, mut cmd: TestCommand) { + let guid = { + // give etherscan some time to detect the transaction + let retry = Retry::new(5, Some(Duration::from_secs(60))); + retry + .run(|| -> eyre::Result { + let output = cmd.unchecked_output(); + let out = String::from_utf8_lossy(&output.stdout); + utils::parse_verification_guid(&out).ok_or_else(|| { + eyre::eyre!( + "Failed to get guid, stdout: {}, stderr: {}", + out, + String::from_utf8_lossy(&output.stderr) + ) + }) + }) + .expect("Failed to get verify guid") + }; + + // verify-check + cmd.forge_fuse() + .arg("verify-check") + .arg(guid) + .arg("--chain-id") + .arg(info.chain.to_string()) + .arg("--etherscan-api-key") + .arg(info.etherscan) + .arg("--verifier") + .arg(info.verifier); + + parse_verification_result(&mut cmd, 6).expect("Failed to verify check") +} + fn verify_on_chain(info: Option, prj: TestProject, mut cmd: TestCommand) { // only execute if keys present if let Some(info) = info { @@ -98,37 +149,41 @@ fn verify_on_chain(info: Option, prj: TestProject, mut cmd: Te info.verifier.to_string(), ]); - // `verify-contract` - let guid = { - // give etherscan some time to detect the transaction - let retry = Retry::new(5, Some(Duration::from_secs(60))); - retry - .run(|| -> eyre::Result { - let output = cmd.unchecked_output(); - let out = String::from_utf8_lossy(&output.stdout); - utils::parse_verification_guid(&out).ok_or_else(|| { - eyre::eyre!( - "Failed to get guid, stdout: {}, stderr: {}", - out, - String::from_utf8_lossy(&output.stderr) - ) - }) - }) - .expect("Failed to get verify guid") - }; - - // verify-check - cmd.forge_fuse() - .arg("verify-check") - .arg(guid) - .arg("--chain-id") - .arg(info.chain.to_string()) - .arg("--etherscan-api-key") - .arg(info.etherscan) - .arg("--verifier") - .arg(info.verifier); - - parse_verification_result(&mut cmd, 6).expect("Failed to verify check") + await_verification_response(info, cmd) + } +} + +fn guess_constructor_args(info: Option, prj: TestProject, mut cmd: TestCommand) { + // only execute if keys present + if let Some(info) = info { + println!("verifying on {}", info.chain); + add_unique(&prj); + add_verify_target_with_constructor(&prj); + + let contract_path = "src/Verify.sol:Verify"; + cmd.arg("create").args(info.create_args()).arg(contract_path).args(vec![ + "--constructor-args", + "(239,SomeString)", + "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", + ]); + + let out = cmd.stdout_lossy(); + let address = utils::parse_deployed_address(out.as_str()) + .unwrap_or_else(|| panic!("Failed to parse deployer {out}")); + + cmd.forge_fuse().arg("verify-contract").root_arg().args([ + "--rpc-url".to_string(), + info.rpc.to_string(), + address, + contract_path.to_string(), + "--etherscan-api-key".to_string(), + info.etherscan.to_string(), + "--verifier".to_string(), + info.verifier.to_string(), + "--guess-constructor-args".to_string(), + ]); + + await_verification_response(info, cmd) } } @@ -174,3 +229,9 @@ forgetest!(can_verify_random_contract_sepolia, |prj, cmd| { forgetest!(can_create_verify_random_contract_sepolia, |prj, cmd| { create_verify_on_chain(EnvExternalities::sepolia(), prj, cmd); }); + +// tests `create && contract-verify --guess-constructor-args && verify-check` on Goerli testnet if +// correct env vars are set +forgetest!(can_guess_constructor_args, |prj, cmd| { + guess_constructor_args(EnvExternalities::goerli(), prj, cmd); +}); diff --git a/crates/forge/tests/fixtures/can_build_path_with_one_file.stdout b/crates/forge/tests/fixtures/can_build_path_with_one_file.stdout new file mode 100644 index 000000000..3213db81a --- /dev/null +++ b/crates/forge/tests/fixtures/can_build_path_with_one_file.stdout @@ -0,0 +1,3 @@ +Compiling 1 files with 0.8.23 +Solc 0.8.23 finished in 33.25ms +Compiler run successful! diff --git a/crates/forge/tests/fixtures/can_build_path_with_three_files.stdout b/crates/forge/tests/fixtures/can_build_path_with_three_files.stdout new file mode 100644 index 000000000..4235acf29 --- /dev/null +++ b/crates/forge/tests/fixtures/can_build_path_with_three_files.stdout @@ -0,0 +1,3 @@ +Compiling 3 files with 0.8.23 +Solc 0.8.23 finished in 33.25ms +Compiler run successful! diff --git a/crates/forge/tests/fixtures/can_build_path_with_two_files.stdout b/crates/forge/tests/fixtures/can_build_path_with_two_files.stdout new file mode 100644 index 000000000..cf5900340 --- /dev/null +++ b/crates/forge/tests/fixtures/can_build_path_with_two_files.stdout @@ -0,0 +1,3 @@ +Compiling 2 files with 0.8.23 +Solc 0.8.23 finished in 33.25ms +Compiler run successful! diff --git a/crates/forge/tests/fixtures/can_create_template_contract.stdout b/crates/forge/tests/fixtures/can_create_template_contract.stdout index 622c81ac4..533c92727 100644 --- a/crates/forge/tests/fixtures/can_create_template_contract.stdout +++ b/crates/forge/tests/fixtures/can_create_template_contract.stdout @@ -1,4 +1,4 @@ -Compiling 24 files with 0.8.23 +Compiling 1 files with 0.8.23 Solc 0.8.23 finished in 2.27s Compiler run successful! Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 diff --git a/crates/forge/tests/fixtures/can_create_using_unlocked.stdout b/crates/forge/tests/fixtures/can_create_using_unlocked.stdout index a4132c617..1f8b60d6f 100644 --- a/crates/forge/tests/fixtures/can_create_using_unlocked.stdout +++ b/crates/forge/tests/fixtures/can_create_using_unlocked.stdout @@ -1,4 +1,4 @@ -Compiling 24 files with 0.8.23 +Compiling 1 files with 0.8.23 Solc 0.8.23 finished in 1.95s Compiler run successful! Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 diff --git a/crates/forge/tests/fixtures/can_create_with_constructor_args.stdout b/crates/forge/tests/fixtures/can_create_with_constructor_args.stdout index 8cb09c22c..299ad2f2d 100644 --- a/crates/forge/tests/fixtures/can_create_with_constructor_args.stdout +++ b/crates/forge/tests/fixtures/can_create_with_constructor_args.stdout @@ -1,4 +1,4 @@ -Compiling 25 files with 0.8.23 +Compiling 1 files with 0.8.23 Solc 0.8.23 finished in 2.82s Compiler run successful! Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 diff --git a/crates/forge/tests/fixtures/can_test_repeatedly.stdout b/crates/forge/tests/fixtures/can_test_repeatedly.stdout index dbab28125..7095a50f0 100644 --- a/crates/forge/tests/fixtures/can_test_repeatedly.stdout +++ b/crates/forge/tests/fixtures/can_test_repeatedly.stdout @@ -2,7 +2,7 @@ No files changed, compilation skipped Ran 2 tests for test/Counter.t.sol:CounterTest [PASS] testFuzz_SetNumber(uint256) (runs: 256, μ: 26521, ~: 28387) -[PASS] test_Increment() (gas: 28379) +[PASS] test_Increment() (gas: 31325) Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 9.42ms Ran 1 test suite: 2 tests passed, 0 failed, 0 skipped (2 total tests) diff --git a/crates/forge/tests/fixtures/compile_json.stdout b/crates/forge/tests/fixtures/compile_json.stdout index f3eb33ea0..eff78e60c 100644 --- a/crates/forge/tests/fixtures/compile_json.stdout +++ b/crates/forge/tests/fixtures/compile_json.stdout @@ -1,4 +1,5 @@ { + "contracts": {}, "errors": [ { "sourceLocation": { @@ -11,10 +12,8 @@ "severity": "error", "errorCode": "7576", "message": "Undeclared identifier. Did you mean \"newNumber\"?", - "formattedMessage": "DeclarationError: Undeclared identifier. Did you mean \"newNumber\"?\n --> src/dummy.sol:7:18:\n |\n7 | number = newnumber; // error here\n | ^^^^^^^^^\n\n" + "formattedMessage": "DeclarationError: Undeclared identifier. Did you mean \"newNumber\"?\n --> src/jsonError.sol:7:18:\n |\n7 | number = newnumber; // error here\n | ^^^^^^^^^\n\n" } ], - "sources": {}, - "contracts": {}, - "build_infos": {} -} + "sources": {} +} \ No newline at end of file diff --git a/crates/forge/tests/fixtures/include_custom_types_in_traces.stdout b/crates/forge/tests/fixtures/include_custom_types_in_traces.stdout index 786679a67..571cc6927 100644 --- a/crates/forge/tests/fixtures/include_custom_types_in_traces.stdout +++ b/crates/forge/tests/fixtures/include_custom_types_in_traces.stdout @@ -6,13 +6,13 @@ Ran 2 tests for test/Contract.t.sol:CustomTypesTest [FAIL. Reason: PoolNotInitialized()] testErr() (gas: 231) Traces: [231] CustomTypesTest::testErr() - └─ ← PoolNotInitialized() + └─ ← [Revert] PoolNotInitialized() [PASS] testEvent() (gas: 1312) Traces: [1312] CustomTypesTest::testEvent() ├─ emit MyEvent(a: 100) - └─ ← () + └─ ← [Stop] Suite result: FAILED. 1 passed; 1 failed; 0 skipped; finished in 3.88ms diff --git a/crates/forge/tests/fixtures/repro_6531.stdout b/crates/forge/tests/fixtures/repro_6531.stdout index 159c6476e..35c27c948 100644 --- a/crates/forge/tests/fixtures/repro_6531.stdout +++ b/crates/forge/tests/fixtures/repro_6531.stdout @@ -2,17 +2,15 @@ Compiling 1 files with 0.8.23 Compiler run successful! -Ran 1 test for test/Contract.t.sol:USDCCallingTest -[PASS] test() (gas: 16799) +Ran 1 test for test/Contract.t.sol:USDTCallingTest +[PASS] test() (gas: 9559) Traces: - [16799] USDCCallingTest::test() + [9559] USDTCallingTest::test() ├─ [0] VM::createSelectFork("") - │ └─ ← 0 - ├─ [10350] 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48::name() [staticcall] - │ ├─ [3061] 0x43506849D7C04F9138D1A2050bbF3A0c054402dd::name() [delegatecall] - │ │ └─ ← "USD Coin" - │ └─ ← "USD Coin" - └─ ← () + │ └─ ← [Return] 0 + ├─ [3110] 0xdAC17F958D2ee523a2206206994597C13D831ec7::name() [staticcall] + │ └─ ← [Return] "Tether USD" + └─ ← [Stop] Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.43s diff --git a/crates/forge/tests/fixtures/suggest_when_no_tests_match.stdout b/crates/forge/tests/fixtures/suggest_when_no_tests_match.stdout index 1c27d8005..1cf6ad73f 100644 --- a/crates/forge/tests/fixtures/suggest_when_no_tests_match.stdout +++ b/crates/forge/tests/fixtures/suggest_when_no_tests_match.stdout @@ -1,7 +1,3 @@ -Compiling 1 files with 0.8.23 -Solc 0.8.23 finished in 185.25ms -Compiler run successful! - No tests match the provided pattern: match-test: `testA.*` no-match-test: `testB.*` diff --git a/crates/forge/tests/fixtures/warn_no_tests.stdout b/crates/forge/tests/fixtures/warn_no_tests.stdout index 9b2b8bff4..a9a7e7fc6 100644 --- a/crates/forge/tests/fixtures/warn_no_tests.stdout +++ b/crates/forge/tests/fixtures/warn_no_tests.stdout @@ -1,5 +1 @@ -Compiling 1 files with 0.8.23 -Solc 0.8.23 -Compiler run successful! - No tests found in project! Forge looks for functions that starts with `test`. diff --git a/crates/forge/tests/fixtures/warn_no_tests_match.stdout b/crates/forge/tests/fixtures/warn_no_tests_match.stdout index 56f068238..4b4080f15 100644 --- a/crates/forge/tests/fixtures/warn_no_tests_match.stdout +++ b/crates/forge/tests/fixtures/warn_no_tests_match.stdout @@ -1,7 +1,3 @@ -Compiling 1 files with 0.8.23 -Solc 0.8.23 -Compiler run successful! - No tests match the provided pattern: match-test: `testA.*` no-match-test: `testB.*` diff --git a/crates/forge/tests/it/cheats.rs b/crates/forge/tests/it/cheats.rs index 078361fab..47d6ebbb9 100644 --- a/crates/forge/tests/it/cheats.rs +++ b/crates/forge/tests/it/cheats.rs @@ -2,23 +2,59 @@ use crate::{ config::*, - test_helpers::{PROJECT, RE_PATH_SEPARATOR}, + test_helpers::{ + ForgeTestData, RE_PATH_SEPARATOR, TEST_DATA_CANCUN, TEST_DATA_DEFAULT, + TEST_DATA_MULTI_VERSION, + }, }; -use foundry_config::{fs_permissions::PathPermission, Config, FsPermissions}; +use foundry_config::{fs_permissions::PathPermission, FsPermissions}; use foundry_test_utils::Filter; -/// Executes all cheat code tests but not fork cheat codes -#[tokio::test(flavor = "multi_thread")] -async fn test_cheats_local() { - let mut config = Config::with_root(PROJECT.root()); +/// Executes all cheat code tests but not fork cheat codes or tests that require isolation mode +async fn test_cheats_local(test_data: &ForgeTestData) { + let mut filter = Filter::new(".*", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}*")) + .exclude_paths("Fork") + .exclude_contracts("Isolated"); + + // Exclude FFI tests on Windows because no `echo`, and file tests that expect certain file paths + if cfg!(windows) { + filter = filter.exclude_tests("(Ffi|File|Line|Root)"); + } + + let mut config = test_data.config.clone(); config.fs_permissions = FsPermissions::new(vec![PathPermission::read_write("./")]); - let runner = runner_with_config(config); - let filter = - Filter::new(".*", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}*")).exclude_paths("Fork"); + let runner = test_data.runner_with_config(config); + + TestConfig::with_filter(runner, filter).run().await; +} + +/// Executes subset of all cheat code tests in isolation mode +async fn test_cheats_local_isolated(test_data: &ForgeTestData) { + let filter = Filter::new(".*", ".*(Isolated)", &format!(".*cheats{RE_PATH_SEPARATOR}*")); + + let mut config = test_data.config.clone(); + config.isolate = true; + let runner = test_data.runner_with_config(config); + + TestConfig::with_filter(runner, filter).run().await; +} - // on windows exclude ffi tests since no echo and file test that expect a certain file path - #[cfg(windows)] - let filter = filter.exclude_tests("(Ffi|File|Line|Root)"); +#[tokio::test(flavor = "multi_thread")] +async fn test_cheats_local_default() { + test_cheats_local(&TEST_DATA_DEFAULT).await +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_cheats_local_default_isolated() { + test_cheats_local_isolated(&TEST_DATA_DEFAULT).await +} - TestConfig::with_filter(runner.await, filter).run().await; +#[tokio::test(flavor = "multi_thread")] +async fn test_cheats_local_multi_version() { + test_cheats_local(&TEST_DATA_MULTI_VERSION).await +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_cheats_local_cancun() { + test_cheats_local(&TEST_DATA_CANCUN).await } diff --git a/crates/forge/tests/it/config.rs b/crates/forge/tests/it/config.rs index dd9c4e1bf..1b2a1398d 100644 --- a/crates/forge/tests/it/config.rs +++ b/crates/forge/tests/it/config.rs @@ -1,33 +1,24 @@ //! Test config. -use crate::test_helpers::{COMPILED, COMPILED_ZK, EVM_OPTS, PROJECT, TESTDATA}; use forge::{ result::{SuiteResult, TestStatus}, - MultiContractRunner, MultiContractRunnerBuilder, TestOptions, TestOptionsBuilder, -}; -use foundry_compilers::ProjectPathsConfig; -use foundry_config::{ - fs_permissions::PathPermission, Config, FsPermissions, FuzzConfig, FuzzDictionaryConfig, - InvariantConfig, RpcEndpoint, RpcEndpoints, + MultiContractRunner, }; use foundry_evm::{ decode::decode_console_logs, - inspectors::CheatsConfig, revm::primitives::SpecId, traces::{render_trace_arena, CallTraceDecoderBuilder}, }; use foundry_test_utils::{init_tracing, Filter}; -use foundry_zksync_compiler::DualCompiledContracts; use futures::future::join_all; use itertools::Itertools; -use std::{collections::BTreeMap, path::Path}; +use std::collections::BTreeMap; /// How to execute a test run. pub struct TestConfig { pub runner: MultiContractRunner, pub should_fail: bool, pub filter: Filter, - pub opts: TestOptions, } impl TestConfig { @@ -35,13 +26,9 @@ impl TestConfig { Self::with_filter(runner, Filter::matches_all()) } - pub async fn filter(filter: Filter) -> Self { - Self::with_filter(runner().await, filter) - } - pub fn with_filter(runner: MultiContractRunner, filter: Filter) -> Self { init_tracing(); - Self { runner, should_fail: false, filter, opts: test_opts() } + Self { runner, should_fail: false, filter } } pub fn evm_spec(mut self, spec: SpecId) -> Self { @@ -59,8 +46,8 @@ impl TestConfig { } /// Executes the test runner - pub async fn test(&mut self) -> BTreeMap { - self.runner.test_collect(&self.filter, self.opts.clone()).await + pub fn test(&mut self) -> BTreeMap { + self.runner.test_collect(&self.filter) } pub async fn run(&mut self) { @@ -73,7 +60,7 @@ impl TestConfig { /// * filter matched 0 test cases /// * a test results deviates from the configured `should_fail` setting pub async fn try_run(&mut self) -> eyre::Result<()> { - let suite_result = self.test().await; + let suite_result = self.test(); if suite_result.is_empty() { eyre::bail!("empty test result"); } @@ -112,162 +99,6 @@ impl TestConfig { } } -/// Returns the [`TestOptions`] used by the tests. -pub fn test_opts() -> TestOptions { - TestOptionsBuilder::default() - .fuzz(FuzzConfig { - runs: 256, - max_test_rejects: 65536, - seed: None, - dictionary: FuzzDictionaryConfig { - include_storage: true, - include_push_bytes: true, - dictionary_weight: 40, - max_fuzz_dictionary_addresses: 10_000, - max_fuzz_dictionary_values: 10_000, - }, - no_zksync_reserved_addresses: false, - }) - .invariant(InvariantConfig { - runs: 256, - depth: 15, - fail_on_revert: false, - call_override: false, - dictionary: FuzzDictionaryConfig { - dictionary_weight: 80, - include_storage: true, - include_push_bytes: true, - max_fuzz_dictionary_addresses: 10_000, - max_fuzz_dictionary_values: 10_000, - }, - shrink_sequence: true, - shrink_run_limit: 2usize.pow(18u32), - no_zksync_reserved_addresses: false, - }) - .build(&COMPILED, &PROJECT.paths.root) - .expect("Config loaded") -} - -pub fn manifest_root() -> &'static Path { - let mut root = Path::new(env!("CARGO_MANIFEST_DIR")); - // need to check here where we're executing the test from, if in `forge` we need to also allow - // `testdata` - if root.ends_with("forge") { - root = root.parent().unwrap(); - } - root -} - -/// Builds a base runner -pub fn base_runner() -> MultiContractRunnerBuilder { - init_tracing(); - MultiContractRunnerBuilder::default().sender(EVM_OPTS.sender) -} - -/// Builds a non-tracing runner -pub async fn runner() -> MultiContractRunner { - let mut config = Config::with_root(PROJECT.root()); - config.fs_permissions = FsPermissions::new(vec![PathPermission::read_write(manifest_root())]); - runner_with_config(config).await -} - -/// Builds a non-tracing runner -pub async fn runner_with_config(mut config: Config) -> MultiContractRunner { - config.rpc_endpoints = rpc_endpoints(); - config.allow_paths.push(manifest_root().to_path_buf()); - - let root = &PROJECT.paths.root; - let opts = &*EVM_OPTS; - let env = opts.evm_env().await.expect("could not instantiate fork environment"); - let output = COMPILED.clone(); - base_runner() - .with_test_options(test_opts()) - .with_cheats_config(CheatsConfig::new( - &config, - opts.clone(), - None, - Default::default(), - false, - )) - .sender(config.sender) - .build(root, output, None, env, opts.clone()) - .unwrap() -} - -/// Builds a non-tracing zk runner -pub async fn runner_with_config_and_zk(mut config: Config) -> MultiContractRunner { - config.rpc_endpoints = rpc_endpoints(); - config.allow_paths.push(manifest_root().to_path_buf()); - - let root = &PROJECT.paths.root; - let opts = &*EVM_OPTS; - let env = opts.evm_env().await.expect("could not instantiate fork environment"); - let output = COMPILED.clone(); - let zk_output = COMPILED_ZK.clone(); - - let mut paths = ProjectPathsConfig::builder().root(TESTDATA).sources(TESTDATA).build().unwrap(); - paths.zksync_artifacts = format!("{TESTDATA}/zkout").into(); - // Dual compiled contracts - let dual_compiled_contracts = DualCompiledContracts::new(&output, &zk_output, &paths); - - base_runner() - .with_test_options(test_opts()) - .with_cheats_config(CheatsConfig::new( - &config, - opts.clone(), - None, - dual_compiled_contracts, - false, - )) - .sender(config.sender) - .build(root, output, Some(zk_output), env, opts.clone()) - .unwrap() -} - -/// Builds a tracing runner -pub async fn tracing_runner() -> MultiContractRunner { - let mut opts = EVM_OPTS.clone(); - opts.verbosity = 5; - base_runner() - .build( - &PROJECT.paths.root, - (*COMPILED).clone(), - None, - EVM_OPTS.evm_env().await.expect("Could not instantiate fork environment"), - opts, - ) - .unwrap() -} - -// Builds a runner that runs against forked state -pub async fn forked_runner(rpc: &str) -> MultiContractRunner { - let mut opts = EVM_OPTS.clone(); - - opts.env.chain_id = None; // clear chain id so the correct one gets fetched from the RPC - opts.fork_url = Some(rpc.to_string()); - - let env = opts.evm_env().await.expect("Could not instantiate fork environment"); - let fork = opts.get_fork(&Default::default(), env.clone()); - - base_runner() - .with_fork(fork) - .build(&PROJECT.paths.root, (*COMPILED).clone(), None, env, opts) - .unwrap() -} - -/// the RPC endpoints used during tests -pub fn rpc_endpoints() -> RpcEndpoints { - RpcEndpoints::new([ - ( - "rpcAlias", - RpcEndpoint::Url( - "https://eth-mainnet.alchemyapi.io/v2/Lc7oIGYeL_QvInzI0Wiu_pOZZDEKBrdf".to_string(), - ), - ), - ("rpcEnvAlias", RpcEndpoint::Env("${RPC_ENV_ALIAS}".to_string())), - ]) -} - /// A helper to assert the outcome of multiple tests with helpful assert messages #[track_caller] #[allow(clippy::type_complexity)] diff --git a/crates/forge/tests/it/core.rs b/crates/forge/tests/it/core.rs index 660ab0677..4c6fae77c 100644 --- a/crates/forge/tests/it/core.rs +++ b/crates/forge/tests/it/core.rs @@ -1,6 +1,6 @@ //! Forge tests for core functionality. -use crate::config::*; +use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; use forge::result::SuiteResult; use foundry_evm::traces::TraceKind; use foundry_test_utils::Filter; @@ -8,14 +8,15 @@ use std::{collections::BTreeMap, env}; #[tokio::test(flavor = "multi_thread")] async fn test_core() { - let mut runner = runner().await; - let results = runner.test_collect(&Filter::new(".*", ".*", ".*core"), test_opts()).await; + let filter = Filter::new(".*", ".*", ".*core"); + let mut runner = TEST_DATA_DEFAULT.runner(); + let results = runner.test_collect(&filter); assert_multiple( &results, BTreeMap::from([ ( - "core/FailingSetup.t.sol:FailingSetupTest", + "default/core/FailingSetup.t.sol:FailingSetupTest", vec![( "setUp()", false, @@ -25,7 +26,7 @@ async fn test_core() { )], ), ( - "core/MultipleSetup.t.sol:MultipleSetup", + "default/core/MultipleSetup.t.sol:MultipleSetup", vec![( "setUp()", false, @@ -35,34 +36,37 @@ async fn test_core() { )], ), ( - "core/Reverting.t.sol:RevertingTest", + "default/core/Reverting.t.sol:RevertingTest", vec![("testFailRevert()", true, None, None, None)], ), ( - "core/SetupConsistency.t.sol:SetupConsistencyCheck", + "default/core/SetupConsistency.t.sol:SetupConsistencyCheck", vec![ ("testAdd()", true, None, None, None), ("testMultiply()", true, None, None, None), ], ), ( - "core/DSStyle.t.sol:DSStyleTest", + "default/core/DSStyle.t.sol:DSStyleTest", vec![("testFailingAssertions()", true, None, None, None)], ), ( - "core/ContractEnvironment.t.sol:ContractEnvironmentTest", + "default/core/ContractEnvironment.t.sol:ContractEnvironmentTest", vec![ ("testAddresses()", true, None, None, None), ("testEnvironment()", true, None, None, None), ], ), ( - "core/PaymentFailure.t.sol:PaymentFailureTest", + "default/core/PaymentFailure.t.sol:PaymentFailureTest", vec![("testCantPay()", false, Some("EvmError: Revert".to_string()), None, None)], ), - ("core/Abstract.t.sol:AbstractTest", vec![("testSomething()", true, None, None, None)]), ( - "core/FailingTestAfterFailedSetup.t.sol:FailingTestAfterFailedSetupTest", + "default/core/Abstract.t.sol:AbstractTest", + vec![("testSomething()", true, None, None, None)], + ), + ( + "default/core/FailingTestAfterFailedSetup.t.sol:FailingTestAfterFailedSetupTest", vec![( "setUp()", false, @@ -71,31 +75,46 @@ async fn test_core() { None, )], ), + ( + "default/core/MultipleAfterInvariant.t.sol:MultipleAfterInvariant", + vec![( + "afterInvariant()", + false, + Some("multiple afterInvariant functions".to_string()), + None, + None, + )], + ), + ( + "default/core/BadSigAfterInvariant.t.sol:BadSigAfterInvariant", + vec![("testShouldPassWithWarning()", true, None, None, None)], + ), ]), ); } #[tokio::test(flavor = "multi_thread")] async fn test_linking() { - let mut runner = runner().await; - let results = runner.test_collect(&Filter::new(".*", ".*", ".*linking"), test_opts()).await; + let filter = Filter::new(".*", ".*", ".*linking"); + let mut runner = TEST_DATA_DEFAULT.runner(); + let results = runner.test_collect(&filter); assert_multiple( &results, BTreeMap::from([ ( - "linking/simple/Simple.t.sol:SimpleLibraryLinkingTest", + "default/linking/simple/Simple.t.sol:SimpleLibraryLinkingTest", vec![("testCall()", true, None, None, None)], ), ( - "linking/nested/Nested.t.sol:NestedLibraryLinkingTest", + "default/linking/nested/Nested.t.sol:NestedLibraryLinkingTest", vec![ ("testDirect()", true, None, None, None), ("testNested()", true, None, None, None), ], ), ( - "linking/duplicate/Duplicate.t.sol:DuplicateLibraryLinkingTest", + "default/linking/duplicate/Duplicate.t.sol:DuplicateLibraryLinkingTest", vec![ ("testA()", true, None, None, None), ("testB()", true, None, None, None), @@ -110,14 +129,15 @@ async fn test_linking() { #[tokio::test(flavor = "multi_thread")] async fn test_logs() { - let mut runner = runner().await; - let results = runner.test_collect(&Filter::new(".*", ".*", ".*logs"), test_opts()).await; + let filter = Filter::new(".*", ".*", ".*logs"); + let mut runner = TEST_DATA_DEFAULT.runner(); + let results = runner.test_collect(&filter); assert_multiple( &results, BTreeMap::from([ ( - "logs/DebugLogs.t.sol:DebugLogsTest", + "default/logs/DebugLogs.t.sol:DebugLogsTest", vec![ ( "test1()", @@ -286,7 +306,7 @@ async fn test_logs() { ], ), ( - "logs/HardhatLogs.t.sol:HardhatLogsTest", + "default/logs/HardhatLogs.t.sol:HardhatLogsTest", vec![ ( "testInts()", @@ -670,38 +690,31 @@ async fn test_logs() { #[tokio::test(flavor = "multi_thread")] async fn test_env_vars() { - let mut runner = runner().await; - - // test `setEnv` first, and confirm that it can correctly set environment variables, - // so that we can use it in subsequent `env*` tests - let _ = runner.test_collect(&Filter::new("testSetEnv", ".*", ".*"), test_opts()).await; let env_var_key = "_foundryCheatcodeSetEnvTestKey"; let env_var_val = "_foundryCheatcodeSetEnvTestVal"; - let res = env::var(env_var_key); - assert!( - res.is_ok() && res.unwrap() == env_var_val, - "Test `testSetEnv` did not pass as expected. -Reason: `setEnv` failed to set an environment variable `{env_var_key}={env_var_val}`" - ); + env::remove_var(env_var_key); + + let filter = Filter::new("testSetEnv", ".*", ".*"); + let mut runner = TEST_DATA_DEFAULT.runner(); + let _ = runner.test_collect(&filter); + + assert_eq!(env::var(env_var_key).unwrap(), env_var_val); } #[tokio::test(flavor = "multi_thread")] async fn test_doesnt_run_abstract_contract() { - let mut runner = runner().await; - let results = runner - .test_collect( - &Filter::new(".*", ".*", ".*Abstract.t.sol".to_string().as_str()), - test_opts(), - ) - .await; - assert!(results.get("core/Abstract.t.sol:AbstractTestBase").is_none()); - assert!(results.get("core/Abstract.t.sol:AbstractTest").is_some()); + let filter = Filter::new(".*", ".*", ".*Abstract.t.sol".to_string().as_str()); + let mut runner = TEST_DATA_DEFAULT.runner(); + let results = runner.test_collect(&filter); + assert!(!results.contains_key("default/core/Abstract.t.sol:AbstractTestBase")); + assert!(results.contains_key("default/core/Abstract.t.sol:AbstractTest")); } #[tokio::test(flavor = "multi_thread")] async fn test_trace() { - let mut runner = tracing_runner().await; - let suite_result = runner.test_collect(&Filter::new(".*", ".*", ".*trace"), test_opts()).await; + let filter = Filter::new(".*", ".*", ".*trace"); + let mut runner = TEST_DATA_DEFAULT.tracing_runner(); + let suite_result = runner.test_collect(&filter); // TODO: This trace test is very basic - it is probably a good candidate for snapshot // testing. @@ -711,12 +724,12 @@ async fn test_trace() { result.traces.iter().filter(|(kind, _)| *kind == TraceKind::Deployment); let setup_traces = result.traces.iter().filter(|(kind, _)| *kind == TraceKind::Setup); let execution_traces = - result.traces.iter().filter(|(kind, _)| *kind == TraceKind::Deployment); + result.traces.iter().filter(|(kind, _)| *kind == TraceKind::Execution); assert_eq!( deployment_traces.count(), - 1, - "Test {test_name} did not have exactly 1 deployment trace." + 12, + "Test {test_name} did not have exactly 12 deployment trace." ); assert!(setup_traces.count() <= 1, "Test {test_name} had more than 1 setup trace."); assert_eq!( diff --git a/crates/forge/tests/it/fork.rs b/crates/forge/tests/it/fork.rs index f34cc59bb..a6b215624 100644 --- a/crates/forge/tests/it/fork.rs +++ b/crates/forge/tests/it/fork.rs @@ -2,26 +2,24 @@ use crate::{ config::*, - test_helpers::{PROJECT, RE_PATH_SEPARATOR}, + test_helpers::{RE_PATH_SEPARATOR, TEST_DATA_DEFAULT}, }; +use alloy_chains::Chain; use forge::result::SuiteResult; use foundry_config::{fs_permissions::PathPermission, Config, FsPermissions}; use foundry_test_utils::Filter; +use std::fs; /// Executes reverting fork test #[tokio::test(flavor = "multi_thread")] async fn test_cheats_fork_revert() { - let mut runner = runner().await; - let suite_result = runner - .test_collect( - &Filter::new( - "testNonExistingContractRevert", - ".*", - &format!(".*cheats{RE_PATH_SEPARATOR}Fork"), - ), - test_opts(), - ) - .await; + let filter = Filter::new( + "testNonExistingContractRevert", + ".*", + &format!(".*cheats{RE_PATH_SEPARATOR}Fork"), + ); + let mut runner = TEST_DATA_DEFAULT.runner(); + let suite_result = runner.test_collect(&filter); assert_eq!(suite_result.len(), 1); for (_, SuiteResult { test_results, .. }) in suite_result { @@ -37,41 +35,41 @@ async fn test_cheats_fork_revert() { /// Executes all non-reverting fork cheatcodes #[tokio::test(flavor = "multi_thread")] async fn test_cheats_fork() { - let mut config = Config::with_root(PROJECT.root()); + let mut config = TEST_DATA_DEFAULT.config.clone(); config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); - let runner = runner_with_config(config); + let runner = TEST_DATA_DEFAULT.runner_with_config(config); let filter = Filter::new(".*", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}Fork")) .exclude_tests(".*Revert"); - TestConfig::with_filter(runner.await, filter).run().await; + TestConfig::with_filter(runner, filter).run().await; } /// Executes eth_getLogs cheatcode #[tokio::test(flavor = "multi_thread")] async fn test_get_logs_fork() { - let mut config = Config::with_root(PROJECT.root()); + let mut config = TEST_DATA_DEFAULT.config.clone(); config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); - let runner = runner_with_config(config); + let runner = TEST_DATA_DEFAULT.runner_with_config(config); let filter = Filter::new("testEthGetLogs", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}Fork")) .exclude_tests(".*Revert"); - TestConfig::with_filter(runner.await, filter).run().await; + TestConfig::with_filter(runner, filter).run().await; } /// Executes rpc cheatcode #[tokio::test(flavor = "multi_thread")] async fn test_rpc_fork() { - let mut config = Config::with_root(PROJECT.root()); + let mut config = TEST_DATA_DEFAULT.config.clone(); config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); - let runner = runner_with_config(config); + let runner = TEST_DATA_DEFAULT.runner_with_config(config); let filter = Filter::new("testRpc", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}Fork")) .exclude_tests(".*Revert"); - TestConfig::with_filter(runner.await, filter).run().await; + TestConfig::with_filter(runner, filter).run().await; } /// Tests that we can launch in forking mode #[tokio::test(flavor = "multi_thread")] async fn test_launch_fork() { - let rpc_url = foundry_common::rpc::next_http_archive_rpc_endpoint(); - let runner = forked_runner(&rpc_url).await; + let rpc_url = foundry_test_utils::rpc::next_http_archive_rpc_endpoint(); + let runner = TEST_DATA_DEFAULT.forked_runner(&rpc_url).await; let filter = Filter::new(".*", ".*", &format!(".*fork{RE_PATH_SEPARATOR}Launch")); TestConfig::with_filter(runner, filter).run().await; } @@ -79,8 +77,8 @@ async fn test_launch_fork() { /// Smoke test that forking workings with websockets #[tokio::test(flavor = "multi_thread")] async fn test_launch_fork_ws() { - let rpc_url = foundry_common::rpc::next_ws_archive_rpc_endpoint(); - let runner = forked_runner(&rpc_url).await; + let rpc_url = foundry_test_utils::rpc::next_ws_archive_rpc_endpoint(); + let runner = TEST_DATA_DEFAULT.forked_runner(&rpc_url).await; let filter = Filter::new(".*", ".*", &format!(".*fork{RE_PATH_SEPARATOR}Launch")); TestConfig::with_filter(runner, filter).run().await; } @@ -88,13 +86,44 @@ async fn test_launch_fork_ws() { /// Tests that we can transact transactions in forking mode #[tokio::test(flavor = "multi_thread")] async fn test_transact_fork() { + let runner = TEST_DATA_DEFAULT.runner(); let filter = Filter::new(".*", ".*", &format!(".*fork{RE_PATH_SEPARATOR}Transact")); - TestConfig::filter(filter).await.run().await; + TestConfig::with_filter(runner, filter).run().await; } -/// Tests that we can create the same fork (provider,block) concurretnly in different tests +/// Tests that we can create the same fork (provider,block) concurrently in different tests #[tokio::test(flavor = "multi_thread")] async fn test_create_same_fork() { + let runner = TEST_DATA_DEFAULT.runner(); let filter = Filter::new(".*", ".*", &format!(".*fork{RE_PATH_SEPARATOR}ForkSame")); - TestConfig::filter(filter).await.run().await; + TestConfig::with_filter(runner, filter).run().await; +} + +/// Test that `no_storage_caching` config is properly applied +#[tokio::test(flavor = "multi_thread")] +async fn test_storage_caching_config() { + // no_storage_caching set to true: storage should not be cached + let mut config = TEST_DATA_DEFAULT.config.clone(); + config.no_storage_caching = true; + let runner = TEST_DATA_DEFAULT.runner_with_config(config); + let filter = + Filter::new("testStorageCaching", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}Fork")) + .exclude_tests(".*Revert"); + TestConfig::with_filter(runner, filter).run().await; + let cache_dir = Config::foundry_block_cache_dir(Chain::mainnet(), 19800000); + assert!(!cache_dir.unwrap().exists()); + + // no_storage_caching set to false: storage should be cached + let mut config = TEST_DATA_DEFAULT.config.clone(); + config.no_storage_caching = false; + let runner = TEST_DATA_DEFAULT.runner_with_config(config); + let filter = + Filter::new("testStorageCaching", ".*", &format!(".*cheats{RE_PATH_SEPARATOR}Fork")) + .exclude_tests(".*Revert"); + TestConfig::with_filter(runner, filter).run().await; + let cache_dir = Config::foundry_block_cache_dir(Chain::mainnet(), 19800000).unwrap(); + assert!(cache_dir.exists()); + + // cleanup cached storage so subsequent tests does not fail + let _ = fs::remove_file(cache_dir); } diff --git a/crates/forge/tests/it/fs.rs b/crates/forge/tests/it/fs.rs index 29affe05c..5bb0b59fb 100644 --- a/crates/forge/tests/it/fs.rs +++ b/crates/forge/tests/it/fs.rs @@ -1,23 +1,23 @@ //! Filesystem tests. -use crate::{config::*, test_helpers::PROJECT}; -use foundry_config::{fs_permissions::PathPermission, Config, FsPermissions}; +use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; +use foundry_config::{fs_permissions::PathPermission, FsPermissions}; use foundry_test_utils::Filter; #[tokio::test(flavor = "multi_thread")] async fn test_fs_disabled() { - let mut config = Config::with_root(PROJECT.root()); + let mut config = TEST_DATA_DEFAULT.config.clone(); config.fs_permissions = FsPermissions::new(vec![PathPermission::none("./")]); - let runner = runner_with_config(config).await; + let runner = TEST_DATA_DEFAULT.runner_with_config(config); let filter = Filter::new(".*", ".*", ".*fs/Disabled"); TestConfig::with_filter(runner, filter).run().await; } #[tokio::test(flavor = "multi_thread")] async fn test_fs_default() { - let mut config = Config::with_root(PROJECT.root()); + let mut config = TEST_DATA_DEFAULT.config.clone(); config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures")]); - let runner = runner_with_config(config); + let runner = TEST_DATA_DEFAULT.runner_with_config(config); let filter = Filter::new(".*", ".*", ".*fs/Default"); - TestConfig::with_filter(runner.await, filter).run().await; + TestConfig::with_filter(runner, filter).run().await; } diff --git a/crates/forge/tests/it/fuzz.rs b/crates/forge/tests/it/fuzz.rs index 2eefa530d..4f8a6d412 100644 --- a/crates/forge/tests/it/fuzz.rs +++ b/crates/forge/tests/it/fuzz.rs @@ -1,23 +1,21 @@ //! Fuzz tests. -use crate::config::*; -use alloy_primitives::U256; -use forge::result::{SuiteResult, TestStatus}; +use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; +use alloy_primitives::{Bytes, U256}; +use forge::{ + fuzz::CounterExample, + result::{SuiteResult, TestStatus}, +}; use foundry_test_utils::Filter; use std::collections::BTreeMap; #[tokio::test(flavor = "multi_thread")] async fn test_fuzz() { - let mut runner = runner().await; - - let suite_result = runner - .test_collect( - &Filter::new(".*", ".*", ".*fuzz/") - .exclude_tests(r"invariantCounter|testIncrement\(address\)|testNeedle\(uint256\)|testSuccessChecker\(uint256\)|testSuccessChecker2\(int256\)|testSuccessChecker3\(uint32\)") - .exclude_paths("invariant"), - test_opts(), - ) - .await; + let filter = Filter::new(".*", ".*", ".*fuzz/") + .exclude_tests(r"invariantCounter|testIncrement\(address\)|testNeedle\(uint256\)|testSuccessChecker\(uint256\)|testSuccessChecker2\(int256\)|testSuccessChecker3\(uint32\)|testStorageOwner\(address\)|testImmutableOwner\(address\)") + .exclude_paths("invariant"); + let mut runner = TEST_DATA_DEFAULT.runner(); + let suite_result = runner.test_collect(&filter); assert!(!suite_result.is_empty()); @@ -27,15 +25,17 @@ async fn test_fuzz() { "testPositive(uint256)" | "testPositive(int256)" | "testSuccessfulFuzz(uint128,uint128)" | - "testToStringFuzz(bytes32)" => assert!( - result.status == TestStatus::Success, + "testToStringFuzz(bytes32)" => assert_eq!( + result.status, + TestStatus::Success, "Test {} did not pass as expected.\nReason: {:?}\nLogs:\n{}", test_name, result.reason, result.decoded_logs.join("\n") ), - _ => assert!( - result.status == TestStatus::Failure, + _ => assert_eq!( + result.status, + TestStatus::Failure, "Test {} did not fail as expected.\nReason: {:?}\nLogs:\n{}", test_name, result.reason, @@ -48,16 +48,11 @@ async fn test_fuzz() { #[tokio::test(flavor = "multi_thread")] async fn test_successful_fuzz_cases() { - let mut runner = runner().await; - - let suite_result = runner - .test_collect( - &Filter::new(".*", ".*", ".*fuzz/FuzzPositive") - .exclude_tests(r"invariantCounter|testIncrement\(address\)|testNeedle\(uint256\)") - .exclude_paths("invariant"), - test_opts(), - ) - .await; + let filter = Filter::new(".*", ".*", ".*fuzz/FuzzPositive") + .exclude_tests(r"invariantCounter|testIncrement\(address\)|testNeedle\(uint256\)") + .exclude_paths("invariant"); + let mut runner = TEST_DATA_DEFAULT.runner(); + let suite_result = runner.test_collect(&filter); assert!(!suite_result.is_empty()); @@ -66,8 +61,9 @@ async fn test_successful_fuzz_cases() { match test_name.as_str() { "testSuccessChecker(uint256)" | "testSuccessChecker2(int256)" | - "testSuccessChecker3(uint32)" => assert!( - result.status == TestStatus::Success, + "testSuccessChecker3(uint32)" => assert_eq!( + result.status, + TestStatus::Success, "Test {} did not pass as expected.\nReason: {:?}\nLogs:\n{}", test_name, result.reason, @@ -84,22 +80,18 @@ async fn test_successful_fuzz_cases() { #[tokio::test(flavor = "multi_thread")] #[ignore] async fn test_fuzz_collection() { - let mut runner = runner().await; - - let mut opts = test_opts(); - opts.invariant.depth = 100; - opts.invariant.runs = 1000; - opts.fuzz.runs = 1000; - opts.fuzz.seed = Some(U256::from(6u32)); - runner.test_options = opts.clone(); - - let results = - runner.test_collect(&Filter::new(".*", ".*", ".*fuzz/FuzzCollection.t.sol"), opts).await; + let filter = Filter::new(".*", ".*", ".*fuzz/FuzzCollection.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options.invariant.depth = 100; + runner.test_options.invariant.runs = 1000; + runner.test_options.fuzz.runs = 1000; + runner.test_options.fuzz.seed = Some(U256::from(6u32)); + let results = runner.test_collect(&filter); assert_multiple( &results, BTreeMap::from([( - "fuzz/FuzzCollection.t.sol:SampleContractTest", + "default/fuzz/FuzzCollection.t.sol:SampleContractTest", vec![ ("invariantCounter", false, Some("broken counter.".into()), None, None), ( @@ -114,3 +106,72 @@ async fn test_fuzz_collection() { )]), ); } + +#[tokio::test(flavor = "multi_thread")] +async fn test_persist_fuzz_failure() { + let filter = Filter::new(".*", ".*", ".*fuzz/FuzzFailurePersist.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options.fuzz.runs = 1000; + + macro_rules! get_failure_result { + () => { + runner + .test_collect(&filter) + .get("default/fuzz/FuzzFailurePersist.t.sol:FuzzFailurePersistTest") + .unwrap() + .test_results + .get("test_persist_fuzzed_failure(uint256,int256,address,bool,string,(address,uint256),address[])") + .unwrap() + .counterexample + .clone() + }; + } + + // record initial counterexample calldata + let initial_counterexample = get_failure_result!(); + let initial_calldata = match initial_counterexample { + Some(CounterExample::Single(counterexample)) => counterexample.calldata, + _ => Bytes::new(), + }; + + // run several times and compare counterexamples calldata + for i in 0..10 { + let new_calldata = match get_failure_result!() { + Some(CounterExample::Single(counterexample)) => counterexample.calldata, + _ => Bytes::new(), + }; + // calldata should be the same with the initial one + assert_eq!(initial_calldata, new_calldata, "run {i}"); + } + + // write new failure in different file + runner.test_options.fuzz.failure_persist_file = Some("failure1".to_string()); + let new_calldata = match get_failure_result!() { + Some(CounterExample::Single(counterexample)) => counterexample.calldata, + _ => Bytes::new(), + }; + // empty file is used to load failure so new calldata is generated + assert_ne!(initial_calldata, new_calldata); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_scrape_bytecode() { + let filter = Filter::new(".*", ".*", ".*fuzz/FuzzScrapeBytecode.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options.fuzz.runs = 2000; + runner.test_options.fuzz.seed = Some(U256::from(6u32)); + let suite_result = runner.test_collect(&filter); + + assert!(!suite_result.is_empty()); + + for (_, SuiteResult { test_results, .. }) in suite_result { + for (test_name, result) in test_results { + match test_name.as_str() { + "testImmutableOwner(address)" | "testStorageOwner(address)" => { + assert_eq!(result.status, TestStatus::Failure) + } + _ => {} + } + } + } +} diff --git a/crates/forge/tests/it/inline.rs b/crates/forge/tests/it/inline.rs index 0001bea29..09d4fb323 100644 --- a/crates/forge/tests/it/inline.rs +++ b/crates/forge/tests/it/inline.rs @@ -1,111 +1,72 @@ //! Inline configuration tests. -use crate::{ - config::runner, - test_helpers::{COMPILED, PROJECT}, -}; -use forge::{ - result::{SuiteResult, TestKind, TestResult}, - TestOptions, TestOptionsBuilder, -}; +use crate::test_helpers::TEST_DATA_DEFAULT; +use forge::{result::TestKind, TestOptionsBuilder}; use foundry_config::{FuzzConfig, InvariantConfig}; use foundry_test_utils::Filter; #[tokio::test(flavor = "multi_thread")] async fn inline_config_run_fuzz() { - let opts = default_test_options(); - let filter = Filter::new(".*", ".*", ".*inline/FuzzInlineConf.t.sol"); - - let mut runner = runner().await; - runner.test_options = opts.clone(); - - let result = runner.test_collect(&filter, opts).await; - let suite_result: &SuiteResult = - result.get("inline/FuzzInlineConf.t.sol:FuzzInlineConf").unwrap(); - let test_result: &TestResult = - suite_result.test_results.get("testInlineConfFuzz(uint8)").unwrap(); - match &test_result.kind { - TestKind::Fuzz { runs, .. } => { - assert_eq!(runs, &1024); - } - _ => { - unreachable!() - } + let mut runner = TEST_DATA_DEFAULT.runner(); + let result = runner.test_collect(&filter); + let suite_result = result.get("default/inline/FuzzInlineConf.t.sol:FuzzInlineConf").unwrap(); + let test_result = suite_result.test_results.get("testInlineConfFuzz(uint8)").unwrap(); + match test_result.kind { + TestKind::Fuzz { runs, .. } => assert_eq!(runs, 1024), + _ => unreachable!(), } } #[tokio::test(flavor = "multi_thread")] async fn inline_config_run_invariant() { - const ROOT: &str = "inline/InvariantInlineConf.t.sol"; + const ROOT: &str = "default/inline/InvariantInlineConf.t.sol"; - let opts = default_test_options(); let filter = Filter::new(".*", ".*", ".*inline/InvariantInlineConf.t.sol"); - let mut runner = runner().await; - runner.test_options = opts.clone(); - - let result = runner.test_collect(&filter, opts).await; + let mut runner = TEST_DATA_DEFAULT.runner(); + let result = runner.test_collect(&filter); let suite_result_1 = result.get(&format!("{ROOT}:InvariantInlineConf")).expect("Result exists"); let suite_result_2 = result.get(&format!("{ROOT}:InvariantInlineConf2")).expect("Result exists"); let test_result_1 = suite_result_1.test_results.get("invariant_neverFalse()").unwrap(); - let test_result_2 = suite_result_2.test_results.get("invariant_neverFalse()").unwrap(); - - match &test_result_1.kind { - TestKind::Invariant { runs, .. } => { - assert_eq!(runs, &333); - } - _ => { - unreachable!() - } + match test_result_1.kind { + TestKind::Invariant { runs, .. } => assert_eq!(runs, 333), + _ => unreachable!(), } - match &test_result_2.kind { - TestKind::Invariant { runs, .. } => { - assert_eq!(runs, &42); - } - _ => { - unreachable!() - } + let test_result_2 = suite_result_2.test_results.get("invariant_neverFalse()").unwrap(); + match test_result_2.kind { + TestKind::Invariant { runs, .. } => assert_eq!(runs, 42), + _ => unreachable!(), } } #[test] fn build_test_options() { - let root = &PROJECT.paths.root; + let root = &TEST_DATA_DEFAULT.project.paths.root; let profiles = vec!["default".to_string(), "ci".to_string()]; let build_result = TestOptionsBuilder::default() .fuzz(FuzzConfig::default()) .invariant(InvariantConfig::default()) .profiles(profiles) - .build(&COMPILED, root); + .build(&TEST_DATA_DEFAULT.output, root); assert!(build_result.is_ok()); } #[test] fn build_test_options_just_one_valid_profile() { - let root = &PROJECT.paths.root; + let root = &TEST_DATA_DEFAULT.project.root(); let valid_profiles = vec!["profile-sheldon-cooper".to_string()]; let build_result = TestOptionsBuilder::default() .fuzz(FuzzConfig::default()) .invariant(InvariantConfig::default()) .profiles(valid_profiles) - .build(&COMPILED, root); + .build(&TEST_DATA_DEFAULT.output, root); // We expect an error, since COMPILED contains in-line // per-test configs for "default" and "ci" profiles assert!(build_result.is_err()); } - -/// Returns the [TestOptions] for the testing [PROJECT]. -pub fn default_test_options() -> TestOptions { - let root = &PROJECT.paths.root; - TestOptionsBuilder::default() - .fuzz(FuzzConfig::default()) - .invariant(InvariantConfig::default()) - .build(&COMPILED, root) - .expect("Config loaded") -} diff --git a/crates/forge/tests/it/invariant.rs b/crates/forge/tests/it/invariant.rs index efc4364fc..45bcda9d5 100644 --- a/crates/forge/tests/it/invariant.rs +++ b/crates/forge/tests/it/invariant.rs @@ -1,31 +1,46 @@ //! Invariant tests. -use crate::config::*; +use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; use alloy_primitives::U256; -use forge::fuzz::CounterExample; +use forge::{fuzz::CounterExample, TestOptions}; use foundry_test_utils::Filter; use std::collections::BTreeMap; +macro_rules! get_counterexample { + ($runner:ident, $filter:expr) => { + $runner + .test_collect($filter) + .values() + .last() + .expect("Invariant contract should be testable.") + .test_results + .values() + .last() + .expect("Invariant contract should be testable.") + .counterexample + .as_ref() + .expect("Invariant contract should have failed with a counterexample.") + }; +} + #[tokio::test(flavor = "multi_thread")] async fn test_invariant() { - let mut runner = runner().await; - - let results = runner - .test_collect( - &Filter::new(".*", ".*", ".*fuzz/invariant/(target|targetAbi|common)"), - test_opts(), - ) - .await; + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/(target|targetAbi|common)"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options = TEST_DATA_DEFAULT.test_opts.clone(); + runner.test_options.invariant.failure_persist_dir = + Some(tempfile::tempdir().unwrap().into_path()); + let results = runner.test_collect(&filter); assert_multiple( &results, BTreeMap::from([ ( - "fuzz/invariant/common/InvariantHandlerFailure.t.sol:InvariantHandlerFailure", + "default/fuzz/invariant/common/InvariantHandlerFailure.t.sol:InvariantHandlerFailure", vec![("statefulFuzz_BrokenInvariant()", true, None, None, None)], ), ( - "fuzz/invariant/common/InvariantInnerContract.t.sol:InvariantInnerContract", + "default/fuzz/invariant/common/InvariantInnerContract.t.sol:InvariantInnerContract", vec![( "invariantHideJesus()", false, @@ -35,11 +50,11 @@ async fn test_invariant() { )], ), ( - "fuzz/invariant/common/InvariantReentrancy.t.sol:InvariantReentrancy", + "default/fuzz/invariant/common/InvariantReentrancy.t.sol:InvariantReentrancy", vec![("invariantNotStolen()", true, None, None, None)], ), ( - "fuzz/invariant/common/InvariantTest1.t.sol:InvariantTest", + "default/fuzz/invariant/common/InvariantTest1.t.sol:InvariantTest", vec![ ("invariant_neverFalse()", false, Some("revert: false".into()), None, None), ( @@ -52,15 +67,15 @@ async fn test_invariant() { ], ), ( - "fuzz/invariant/target/ExcludeContracts.t.sol:ExcludeContracts", + "default/fuzz/invariant/target/ExcludeContracts.t.sol:ExcludeContracts", vec![("invariantTrueWorld()", true, None, None, None)], ), ( - "fuzz/invariant/target/TargetContracts.t.sol:TargetContracts", + "default/fuzz/invariant/target/TargetContracts.t.sol:TargetContracts", vec![("invariantTrueWorld()", true, None, None, None)], ), ( - "fuzz/invariant/target/TargetSenders.t.sol:TargetSenders", + "default/fuzz/invariant/target/TargetSenders.t.sol:TargetSenders", vec![( "invariantTrueWorld()", false, @@ -70,7 +85,7 @@ async fn test_invariant() { )], ), ( - "fuzz/invariant/target/TargetInterfaces.t.sol:TargetWorldInterfaces", + "default/fuzz/invariant/target/TargetInterfaces.t.sol:TargetWorldInterfaces", vec![( "invariantTrueWorld()", false, @@ -80,19 +95,23 @@ async fn test_invariant() { )], ), ( - "fuzz/invariant/target/ExcludeSenders.t.sol:ExcludeSenders", + "default/fuzz/invariant/target/ExcludeSenders.t.sol:ExcludeSenders", vec![("invariantTrueWorld()", true, None, None, None)], ), ( - "fuzz/invariant/target/TargetSelectors.t.sol:TargetSelectors", + "default/fuzz/invariant/target/TargetSelectors.t.sol:TargetSelectors", vec![("invariantTrueWorld()", true, None, None, None)], ), ( - "fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol:ExcludeArtifacts", + "default/fuzz/invariant/target/ExcludeSelectors.t.sol:ExcludeSelectors", + vec![("invariantFalseWorld()", true, None, None, None)], + ), + ( + "default/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol:ExcludeArtifacts", vec![("invariantShouldPass()", true, None, None, None)], ), ( - "fuzz/invariant/targetAbi/TargetArtifacts.t.sol:TargetArtifacts", + "default/fuzz/invariant/targetAbi/TargetArtifacts.t.sol:TargetArtifacts", vec![ ("invariantShouldPass()", true, None, None, None), ( @@ -105,11 +124,11 @@ async fn test_invariant() { ], ), ( - "fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol:TargetArtifactSelectors", + "default/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol:TargetArtifactSelectors", vec![("invariantShouldPass()", true, None, None, None)], ), ( - "fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol:TargetArtifactSelectors2", + "default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol:TargetArtifactSelectors2", vec![( "invariantShouldFail()", false, @@ -118,29 +137,158 @@ async fn test_invariant() { None, )], ), + ( + "default/fuzz/invariant/common/InvariantShrinkWithAssert.t.sol:InvariantShrinkWithAssert", + vec![( + "invariant_with_assert()", + false, + Some("".into()), + None, + None, + )], + ), + ( + "default/fuzz/invariant/common/InvariantShrinkWithAssert.t.sol:InvariantShrinkWithRequire", + vec![( + "invariant_with_require()", + false, + Some("revert: wrong counter".into()), + None, + None, + )], + ), + ( + "default/fuzz/invariant/common/InvariantPreserveState.t.sol:InvariantPreserveState", + vec![("invariant_preserve_state()", true, None, None, None)], + ), + ( + "default/fuzz/invariant/common/InvariantCalldataDictionary.t.sol:InvariantCalldataDictionary", + vec![( + "invariant_owner_never_changes()", + false, + Some("".into()), + None, + None, + )], + ), + ( + "default/fuzz/invariant/common/InvariantAssume.t.sol:InvariantAssume", + vec![("invariant_dummy()", true, None, None, None)], + ), + ( + "default/fuzz/invariant/common/InvariantCustomError.t.sol:InvariantCustomError", + vec![("invariant_decode_error()", true, None, None, None)], + ), + ( + "default/fuzz/invariant/target/FuzzedTargetContracts.t.sol:ExplicitTargetContract", + vec![("invariant_explicit_target()", true, None, None, None)], + ), + ( + "default/fuzz/invariant/target/FuzzedTargetContracts.t.sol:DynamicTargetContract", + vec![("invariant_dynamic_targets()", true, None, None, None)], + ), + ( + "default/fuzz/invariant/common/InvariantFixtures.t.sol:InvariantFixtures", + vec![( + "invariant_target_not_compromised()", + false, + Some("".into()), + None, + None, + )], + ), + ( + "default/fuzz/invariant/common/InvariantShrinkBigSequence.t.sol:ShrinkBigSequenceTest", + vec![("invariant_shrink_big_sequence()", true, None, None, None)], + ), + ( + "default/fuzz/invariant/common/InvariantShrinkFailOnRevert.t.sol:ShrinkFailOnRevertTest", + vec![("invariant_shrink_fail_on_revert()", true, None, None, None)], + ), + ( + "default/fuzz/invariant/common/InvariantScrapeValues.t.sol:FindFromReturnValueTest", + vec![( + "invariant_value_not_found()", + false, + Some("revert: value from return found".into()), + None, + None, + )], + ), + ( + "default/fuzz/invariant/common/InvariantScrapeValues.t.sol:FindFromLogValueTest", + vec![( + "invariant_value_not_found()", + false, + Some("revert: value from logs found".into()), + None, + None, + )], + ), + ( + "default/fuzz/invariant/common/InvariantRollFork.t.sol:InvariantRollForkBlockTest", + vec![( + "invariant_fork_handler_block()", + false, + Some("revert: too many blocks mined".into()), + None, + None, + )], + ), + ( + "default/fuzz/invariant/common/InvariantRollFork.t.sol:InvariantRollForkStateTest", + vec![( + "invariant_fork_handler_state()", + false, + Some("revert: wrong supply".into()), + None, + None, + )], + ), + ( + "default/fuzz/invariant/common/InvariantExcludedSenders.t.sol:InvariantExcludedSendersTest", + vec![("invariant_check_sender()", true, None, None, None)], + ), + ( + "default/fuzz/invariant/common/InvariantAfterInvariant.t.sol:InvariantAfterInvariantTest", + vec![ + ( + "invariant_after_invariant_failure()", + false, + Some("revert: afterInvariant failure".into()), + None, + None, + ), + ( + "invariant_failure()", + false, + Some("revert: invariant failure".into()), + None, + None, + ), + ("invariant_success()", true, None, None, None), + ], + ), + ( + "default/fuzz/invariant/common/InvariantSelectorsWeight.t.sol:InvariantSelectorsWeightTest", + vec![("invariant_selectors_weight()", true, None, None, None)], + ) ]), ); } #[tokio::test(flavor = "multi_thread")] async fn test_invariant_override() { - let mut runner = runner().await; - - let mut opts = test_opts(); - opts.invariant.call_override = true; - runner.test_options = opts.clone(); - - let results = runner - .test_collect( - &Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantReentrancy.t.sol"), - opts, - ) - .await; + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantReentrancy.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options.invariant.fail_on_revert = false; + runner.test_options.invariant.call_override = true; + let results = runner.test_collect(&filter); assert_multiple( &results, BTreeMap::from([( - "fuzz/invariant/common/InvariantReentrancy.t.sol:InvariantReentrancy", + "default/fuzz/invariant/common/InvariantReentrancy.t.sol:InvariantReentrancy", vec![("invariantNotStolen()", false, Some("revert: stolen".into()), None, None)], )]), ); @@ -148,25 +296,17 @@ async fn test_invariant_override() { #[tokio::test(flavor = "multi_thread")] async fn test_invariant_fail_on_revert() { - let mut runner = runner().await; - - let mut opts = test_opts(); - opts.invariant.fail_on_revert = true; - opts.invariant.runs = 1; - opts.invariant.depth = 10; - runner.test_options = opts.clone(); - - let results = runner - .test_collect( - &Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantHandlerFailure.t.sol"), - opts, - ) - .await; + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantHandlerFailure.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options.invariant.fail_on_revert = true; + runner.test_options.invariant.runs = 1; + runner.test_options.invariant.depth = 10; + let results = runner.test_collect(&filter); assert_multiple( &results, BTreeMap::from([( - "fuzz/invariant/common/InvariantHandlerFailure.t.sol:InvariantHandlerFailure", + "default/fuzz/invariant/common/InvariantHandlerFailure.t.sol:InvariantHandlerFailure", vec![( "statefulFuzz_BrokenInvariant()", false, @@ -181,24 +321,16 @@ async fn test_invariant_fail_on_revert() { #[tokio::test(flavor = "multi_thread")] #[ignore] async fn test_invariant_storage() { - let mut runner = runner().await; - - let mut opts = test_opts(); - opts.invariant.depth = 100 + (50 * cfg!(windows) as u32); - opts.fuzz.seed = Some(U256::from(6u32)); - runner.test_options = opts.clone(); - - let results = runner - .test_collect( - &Filter::new(".*", ".*", ".*fuzz/invariant/storage/InvariantStorageTest.t.sol"), - opts, - ) - .await; + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/storage/InvariantStorageTest.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options.invariant.depth = 100 + (50 * cfg!(windows) as u32); + runner.test_options.fuzz.seed = Some(U256::from(6u32)); + let results = runner.test_collect(&filter); assert_multiple( &results, BTreeMap::from([( - "fuzz/invariant/storage/InvariantStorageTest.t.sol:InvariantStorageTest", + "default/fuzz/invariant/storage/InvariantStorageTest.t.sol:InvariantStorageTest", vec![ ("invariantChangeAddress()", false, Some("changedAddr".to_string()), None, None), ("invariantChangeString()", false, Some("changedString".to_string()), None, None), @@ -212,50 +344,455 @@ async fn test_invariant_storage() { #[tokio::test(flavor = "multi_thread")] #[cfg_attr(windows, ignore = "for some reason there's different rng")] async fn test_invariant_shrink() { - let mut runner = runner().await; + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantInnerContract.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options.fuzz.seed = Some(U256::from(119u32)); - let mut opts = test_opts(); + match get_counterexample!(runner, &filter) { + CounterExample::Single(_) => panic!("CounterExample should be a sequence."), + // `fuzz_seed` at 119 makes this sequence shrinkable from 4 to 2. + CounterExample::Sequence(sequence) => { + assert!(sequence.len() <= 3); + + if sequence.len() == 2 { + // call order should always be preserved + let create_fren_sequence = sequence[0].clone(); + assert_eq!( + create_fren_sequence.contract_name.unwrap(), + "default/fuzz/invariant/common/InvariantInnerContract.t.sol:Jesus" + ); + assert_eq!(create_fren_sequence.signature.unwrap(), "create_fren()"); + + let betray_sequence = sequence[1].clone(); + assert_eq!( + betray_sequence.contract_name.unwrap(), + "default/fuzz/invariant/common/InvariantInnerContract.t.sol:Judas" + ); + assert_eq!(betray_sequence.signature.unwrap(), "betray()"); + } + } + }; +} + +#[tokio::test(flavor = "multi_thread")] +#[cfg_attr(windows, ignore = "for some reason there's different rng")] +async fn test_invariant_assert_shrink() { + let mut opts = TEST_DATA_DEFAULT.test_opts.clone(); opts.fuzz.seed = Some(U256::from(119u32)); + + // ensure assert and require shrinks to same sequence of 3 or less + test_shrink(opts.clone(), "InvariantShrinkWithAssert").await; + test_shrink(opts.clone(), "InvariantShrinkWithRequire").await; +} + +async fn test_shrink(opts: TestOptions, contract_pattern: &str) { + let filter = Filter::new( + ".*", + contract_pattern, + ".*fuzz/invariant/common/InvariantShrinkWithAssert.t.sol", + ); + let mut runner = TEST_DATA_DEFAULT.runner(); runner.test_options = opts.clone(); - let results = runner - .test_collect( - &Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantInnerContract.t.sol"), - opts, - ) - .await; + match get_counterexample!(runner, &filter) { + CounterExample::Single(_) => panic!("CounterExample should be a sequence."), + CounterExample::Sequence(sequence) => { + assert!(sequence.len() <= 3); + } + }; +} - let results = - results.values().last().expect("`InvariantInnerContract.t.sol` should be testable."); +#[tokio::test(flavor = "multi_thread")] +#[cfg_attr(windows, ignore = "for some reason there's different rng")] +async fn test_shrink_big_sequence() { + let mut opts = TEST_DATA_DEFAULT.test_opts.clone(); + opts.fuzz.seed = Some(U256::from(119u32)); - let result = - results.test_results.values().last().expect("`InvariantInnerContract` should be testable."); + let filter = + Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantShrinkBigSequence.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options = opts.clone(); + runner.test_options.invariant.runs = 1; + runner.test_options.invariant.depth = 500; - let counter = result + let initial_counterexample = runner + .test_collect(&filter) + .values() + .last() + .expect("Invariant contract should be testable.") + .test_results + .values() + .last() + .expect("Invariant contract should be testable.") .counterexample - .as_ref() - .expect("`InvariantInnerContract` should have failed with a counterexample."); + .clone() + .unwrap(); - match counter { + let initial_sequence = match initial_counterexample { + CounterExample::Single(_) => panic!("CounterExample should be a sequence."), + CounterExample::Sequence(sequence) => sequence, + }; + // ensure shrinks to same sequence of 77 + assert_eq!(initial_sequence.len(), 77); + + // test failure persistence + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantShrinkBigSequence.t.sol:ShrinkBigSequenceTest", + vec![( + "invariant_shrink_big_sequence()", + false, + Some("invariant_shrink_big_sequence replay failure".into()), + None, + None, + )], + )]), + ); + let new_sequence = match results + .values() + .last() + .expect("Invariant contract should be testable.") + .test_results + .values() + .last() + .expect("Invariant contract should be testable.") + .counterexample + .clone() + .unwrap() + { + CounterExample::Single(_) => panic!("CounterExample should be a sequence."), + CounterExample::Sequence(sequence) => sequence, + }; + // ensure shrinks to same sequence of 77 + assert_eq!(new_sequence.len(), 77); + // ensure calls within failed sequence are the same as initial one + for index in 0..77 { + let new_call = new_sequence.get(index).unwrap(); + let initial_call = initial_sequence.get(index).unwrap(); + assert_eq!(new_call.sender, initial_call.sender); + assert_eq!(new_call.addr, initial_call.addr); + assert_eq!(new_call.calldata, initial_call.calldata); + } +} + +#[tokio::test(flavor = "multi_thread")] +#[cfg_attr(windows, ignore = "for some reason there's different rng")] +async fn test_shrink_fail_on_revert() { + let mut opts = TEST_DATA_DEFAULT.test_opts.clone(); + opts.fuzz.seed = Some(U256::from(119u32)); + + let filter = + Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantShrinkFailOnRevert.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options = opts.clone(); + runner.test_options.invariant.fail_on_revert = true; + runner.test_options.invariant.runs = 1; + runner.test_options.invariant.depth = 100; + + match get_counterexample!(runner, &filter) { CounterExample::Single(_) => panic!("CounterExample should be a sequence."), - // `fuzz_seed` at 119 makes this sequence shrinkable from 4 to 2. CounterExample::Sequence(sequence) => { - assert_eq!(sequence.len(), 2); - - // call order should always be preserved - let create_fren_sequence = sequence[0].clone(); - assert_eq!( - create_fren_sequence.contract_name.unwrap(), - "fuzz/invariant/common/InvariantInnerContract.t.sol:Jesus" - ); - assert_eq!(create_fren_sequence.signature.unwrap(), "create_fren()"); - - let betray_sequence = sequence[1].clone(); - assert_eq!( - betray_sequence.contract_name.unwrap(), - "fuzz/invariant/common/InvariantInnerContract.t.sol:Judas" - ); - assert_eq!(betray_sequence.signature.unwrap(), "betray()"); + // ensure shrinks to sequence of 10 + assert_eq!(sequence.len(), 10); } }; } + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_preserve_state() { + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantPreserveState.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options.invariant.fail_on_revert = true; + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantPreserveState.t.sol:InvariantPreserveState", + vec![( + "invariant_preserve_state()", + false, + Some("EvmError: Revert".into()), + None, + None, + )], + )]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_with_address_fixture() { + let mut runner = TEST_DATA_DEFAULT.runner(); + let results = runner.test_collect(&Filter::new( + ".*", + ".*", + ".*fuzz/invariant/common/InvariantCalldataDictionary.t.sol", + )); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantCalldataDictionary.t.sol:InvariantCalldataDictionary", + vec![( + "invariant_owner_never_changes()", + false, + Some("".into()), + None, + None, + )], + )]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_assume_does_not_revert() { + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantAssume.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + // Should not treat vm.assume as revert. + runner.test_options.invariant.fail_on_revert = true; + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantAssume.t.sol:InvariantAssume", + vec![("invariant_dummy()", true, None, None, None)], + )]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_assume_respects_restrictions() { + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantAssume.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options.invariant.runs = 1; + runner.test_options.invariant.depth = 10; + runner.test_options.invariant.max_assume_rejects = 1; + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantAssume.t.sol:InvariantAssume", + vec![( + "invariant_dummy()", + false, + Some("The `vm.assume` cheatcode rejected too many inputs (1 allowed)".into()), + None, + None, + )], + )]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_decode_custom_error() { + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantCustomError.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options.invariant.fail_on_revert = true; + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantCustomError.t.sol:InvariantCustomError", + vec![( + "invariant_decode_error()", + false, + Some("InvariantCustomError(111, \"custom\")".into()), + None, + None, + )], + )]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_fuzzed_selected_targets() { + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/target/FuzzedTargetContracts.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options.invariant.fail_on_revert = true; + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([ + ( + "default/fuzz/invariant/target/FuzzedTargetContracts.t.sol:ExplicitTargetContract", + vec![("invariant_explicit_target()", true, None, None, None)], + ), + ( + "default/fuzz/invariant/target/FuzzedTargetContracts.t.sol:DynamicTargetContract", + vec![( + "invariant_dynamic_targets()", + false, + Some("revert: wrong target selector called".into()), + None, + None, + )], + ), + ]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_fixtures() { + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantFixtures.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options.invariant.runs = 1; + runner.test_options.invariant.depth = 100; + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantFixtures.t.sol:InvariantFixtures", + vec![( + "invariant_target_not_compromised()", + false, + Some("".into()), + None, + None, + )], + )]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_scrape_values() { + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantScrapeValues.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([ + ( + "default/fuzz/invariant/common/InvariantScrapeValues.t.sol:FindFromReturnValueTest", + vec![( + "invariant_value_not_found()", + false, + Some("revert: value from return found".into()), + None, + None, + )], + ), + ( + "default/fuzz/invariant/common/InvariantScrapeValues.t.sol:FindFromLogValueTest", + vec![( + "invariant_value_not_found()", + false, + Some("revert: value from logs found".into()), + None, + None, + )], + ), + ]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_roll_fork_handler() { + let mut opts = TEST_DATA_DEFAULT.test_opts.clone(); + opts.fuzz.seed = Some(U256::from(119u32)); + + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantRollFork.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options = opts.clone(); + runner.test_options.invariant.failure_persist_dir = + Some(tempfile::tempdir().unwrap().into_path()); + + let results = runner.test_collect(&filter); + + assert_multiple( + &results, + BTreeMap::from([ + ( + "default/fuzz/invariant/common/InvariantRollFork.t.sol:InvariantRollForkBlockTest", + vec![( + "invariant_fork_handler_block()", + false, + Some("revert: too many blocks mined".into()), + None, + None, + )], + ), + ( + "default/fuzz/invariant/common/InvariantRollFork.t.sol:InvariantRollForkStateTest", + vec![( + "invariant_fork_handler_state()", + false, + Some("revert: wrong supply".into()), + None, + None, + )], + ), + ]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_excluded_senders() { + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantExcludedSenders.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options.invariant.fail_on_revert = true; + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantExcludedSenders.t.sol:InvariantExcludedSendersTest", + vec![("invariant_check_sender()", true, None, None, None)], + )]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_after_invariant() { + // Check failure on passing invariant and failed `afterInvariant` condition + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantAfterInvariant.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options.invariant.failure_persist_dir = + Some(tempfile::tempdir().unwrap().into_path()); + + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantAfterInvariant.t.sol:InvariantAfterInvariantTest", + vec![ + ( + "invariant_after_invariant_failure()", + false, + Some("revert: afterInvariant failure".into()), + None, + None, + ), + ( + "invariant_failure()", + false, + Some("revert: invariant failure".into()), + None, + None, + ), + ("invariant_success()", true, None, None, None), + ], + )]), + ); +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_invariant_selectors_weight() { + let mut opts = TEST_DATA_DEFAULT.test_opts.clone(); + opts.fuzz.seed = Some(U256::from(100u32)); + + let filter = Filter::new(".*", ".*", ".*fuzz/invariant/common/InvariantSelectorsWeight.t.sol"); + let mut runner = TEST_DATA_DEFAULT.runner(); + runner.test_options = opts.clone(); + runner.test_options.invariant.runs = 1; + runner.test_options.invariant.depth = 30; + runner.test_options.invariant.failure_persist_dir = + Some(tempfile::tempdir().unwrap().into_path()); + + let results = runner.test_collect(&filter); + assert_multiple( + &results, + BTreeMap::from([( + "default/fuzz/invariant/common/InvariantSelectorsWeight.t.sol:InvariantSelectorsWeightTest", + vec![("invariant_selectors_weight()", true, None, None, None)], + )]), + ) +} diff --git a/crates/forge/tests/it/repros.rs b/crates/forge/tests/it/repros.rs index 8506a248d..9150da5f0 100644 --- a/crates/forge/tests/it/repros.rs +++ b/crates/forge/tests/it/repros.rs @@ -1,10 +1,15 @@ //! Regression tests for previous issues. -use crate::{config::*, test_helpers::PROJECT}; -use alloy_primitives::{address, Address}; -use ethers_core::abi::{Event, EventParam, Log, LogParam, ParamType, RawLog, Token}; +use std::sync::Arc; + +use crate::{ + config::*, + test_helpers::{ForgeTestData, TEST_DATA_DEFAULT}, +}; +use alloy_dyn_abi::{DecodedEvent, DynSolValue, EventExt}; +use alloy_json_abi::Event; +use alloy_primitives::{address, Address, U256}; use forge::result::TestStatus; -use foundry_common::types::ToEthers; use foundry_config::{fs_permissions::PathPermission, Config, FsPermissions}; use foundry_evm::{ constants::HARDHAT_CONSOLE_ADDRESS, @@ -24,7 +29,7 @@ macro_rules! test_repro { paste::paste! { #[tokio::test(flavor = "multi_thread")] async fn [< issue_ $issue_number >]() { - repro_config($issue_number, $should_fail, $sender.into()).await.run().await; + repro_config($issue_number, $should_fail, $sender.into(), &*TEST_DATA_DEFAULT).await.run().await; } } }; @@ -32,7 +37,7 @@ macro_rules! test_repro { paste::paste! { #[tokio::test(flavor = "multi_thread")] async fn [< issue_ $issue_number >]() { - let mut $res = repro_config($issue_number, $should_fail, $sender.into()).await.test().await; + let mut $res = repro_config($issue_number, $should_fail, $sender.into(), &*TEST_DATA_DEFAULT).await.test(); $e } } @@ -41,7 +46,7 @@ macro_rules! test_repro { paste::paste! { #[tokio::test(flavor = "multi_thread")] async fn [< issue_ $issue_number >]() { - let mut $config = repro_config($issue_number, false, None).await; + let mut $config = repro_config($issue_number, false, None, &*TEST_DATA_DEFAULT).await; $e $config.run().await; } @@ -49,18 +54,23 @@ macro_rules! test_repro { }; } -async fn repro_config(issue: usize, should_fail: bool, sender: Option
) -> TestConfig { +async fn repro_config( + issue: usize, + should_fail: bool, + sender: Option
, + test_data: &ForgeTestData, +) -> TestConfig { foundry_test_utils::init_tracing(); let filter = Filter::path(&format!(".*repros/Issue{issue}.t.sol")); - let mut config = Config::with_root(PROJECT.root()); + let mut config = test_data.config.clone(); config.fs_permissions = FsPermissions::new(vec![PathPermission::read("./fixtures"), PathPermission::read("out")]); if let Some(sender) = sender { config.sender = sender; } - let runner = runner_with_config(config).await; + let runner = TEST_DATA_DEFAULT.runner_with_config(config); TestConfig::with_filter(runner, filter).set_should_fail(should_fail) } @@ -114,28 +124,18 @@ test_repro!(3223, false, address!("F0959944122fb1ed4CfaBA645eA06EED30427BAA")); // https://github.com/foundry-rs/foundry/issues/3347 test_repro!(3347, false, None, |res| { - let mut res = res.remove("repros/Issue3347.t.sol:Issue3347Test").unwrap(); + let mut res = res.remove("default/repros/Issue3347.t.sol:Issue3347Test").unwrap(); let test = res.test_results.remove("test()").unwrap(); assert_eq!(test.logs.len(), 1); - let event = Event { - name: "log2".to_string(), - inputs: vec![ - EventParam { name: "x".to_string(), kind: ParamType::Uint(256), indexed: false }, - EventParam { name: "y".to_string(), kind: ParamType::Uint(256), indexed: false }, - ], - anonymous: false, - }; - let raw_log = RawLog { - topics: test.logs[0].data.topics().iter().map(|t| t.to_ethers()).collect(), - data: test.logs[0].data.data.clone().to_vec(), - }; - let log = event.parse_log(raw_log).unwrap(); + let event = Event::parse("event log2(uint256, uint256)").unwrap(); + let decoded = event.decode_log(&test.logs[0].data, false).unwrap(); assert_eq!( - log, - Log { - params: vec![ - LogParam { name: "x".to_string(), value: Token::Uint(1u64.into()) }, - LogParam { name: "y".to_string(), value: Token::Uint(2u64.into()) } + decoded, + DecodedEvent { + indexed: vec![], + body: vec![ + DynSolValue::Uint(U256::from(1), 256), + DynSolValue::Uint(U256::from(2), 256) ] } ); @@ -176,6 +176,9 @@ test_repro!(3753); // https://github.com/foundry-rs/foundry/issues/3792 test_repro!(3792); +// https://github.com/foundry-rs/foundry/issues/4402 +test_repro!(4402); + // https://github.com/foundry-rs/foundry/issues/4586 test_repro!(4586); @@ -218,7 +221,7 @@ test_repro!(6115); // https://github.com/foundry-rs/foundry/issues/6170 test_repro!(6170, false, None, |res| { - let mut res = res.remove("repros/Issue6170.t.sol:Issue6170Test").unwrap(); + let mut res = res.remove("default/repros/Issue6170.t.sol:Issue6170Test").unwrap(); let test = res.test_results.remove("test()").unwrap(); assert_eq!(test.status, TestStatus::Failure); assert_eq!(test.reason, Some("log != expected log".to_string())); @@ -232,7 +235,7 @@ test_repro!(6180); // https://github.com/foundry-rs/foundry/issues/6355 test_repro!(6355, false, None, |res| { - let mut res = res.remove("repros/Issue6355.t.sol:Issue6355Test").unwrap(); + let mut res = res.remove("default/repros/Issue6355.t.sol:Issue6355Test").unwrap(); let test = res.test_results.remove("test_shouldFail()").unwrap(); assert_eq!(test.status, TestStatus::Failure); @@ -246,12 +249,12 @@ test_repro!(6437); // Test we decode Hardhat console logs AND traces correctly. // https://github.com/foundry-rs/foundry/issues/6501 test_repro!(6501, false, None, |res| { - let mut res = res.remove("repros/Issue6501.t.sol:Issue6501Test").unwrap(); + let mut res = res.remove("default/repros/Issue6501.t.sol:Issue6501Test").unwrap(); let test = res.test_results.remove("test_hhLogs()").unwrap(); assert_eq!(test.status, TestStatus::Success); assert_eq!(test.decoded_logs, ["a".to_string(), "1".to_string(), "b 2".to_string()]); - let (kind, traces) = test.traces[1].clone(); + let (kind, traces) = test.traces.last().unwrap().clone(); let nodes = traces.into_nodes(); assert_eq!(kind, TraceKind::Execution); @@ -289,10 +292,12 @@ test_repro!(6538); // https://github.com/foundry-rs/foundry/issues/6554 test_repro!(6554; |config| { - let mut cheats_config = config.runner.cheats_config.as_ref().clone(); - let path = cheats_config.root.join("out/Issue6554.t.sol"); - cheats_config.fs_permissions.add(PathPermission::read_write(path)); - config.runner.cheats_config = std::sync::Arc::new(cheats_config); + let path = config.runner.config.root.0.join("out/default/Issue6554.t.sol"); + + let mut prj_config = Config::clone(&config.runner.config); + prj_config.fs_permissions.add(PathPermission::read_write(path)); + config.runner.config = Arc::new(prj_config); + }); // https://github.com/foundry-rs/foundry/issues/6759 @@ -306,14 +311,34 @@ test_repro!(6616); // https://github.com/foundry-rs/foundry/issues/5529 test_repro!(5529; |config| { - let mut cheats_config = config.runner.cheats_config.as_ref().clone(); - cheats_config.always_use_create_2_factory = true; - config.runner.cheats_config = std::sync::Arc::new(cheats_config); + let mut prj_config = Config::clone(&config.runner.config); + prj_config.always_use_create_2_factory = true; + config.runner.evm_opts.always_use_create_2_factory = true; + config.runner.config = Arc::new(prj_config); }); // https://github.com/foundry-rs/foundry/issues/6634 test_repro!(6634; |config| { - let mut cheats_config = config.runner.cheats_config.as_ref().clone(); - cheats_config.always_use_create_2_factory = true; - config.runner.cheats_config = std::sync::Arc::new(cheats_config); + let mut prj_config = Config::clone(&config.runner.config); + prj_config.always_use_create_2_factory = true; + config.runner.evm_opts.always_use_create_2_factory = true; + config.runner.config = Arc::new(prj_config); }); + +test_repro!(7481); + +// https://github.com/foundry-rs/foundry/issues/5739 +test_repro!(5739); + +// https://github.com/foundry-rs/foundry/issues/8004 +test_repro!(8004); + +// https://github.com/foundry-rs/foundry/issues/2851 +test_repro!(2851, false, None, |res| { + let mut res = res.remove("default/repros/Issue2851.t.sol:Issue2851Test").unwrap(); + let test = res.test_results.remove("invariantNotZero()").unwrap(); + assert_eq!(test.status, TestStatus::Failure); +}); + +// https://github.com/foundry-rs/foundry/issues/8006 +test_repro!(8006); diff --git a/crates/forge/tests/it/spec.rs b/crates/forge/tests/it/spec.rs index 724aaa0ff..db98a15d1 100644 --- a/crates/forge/tests/it/spec.rs +++ b/crates/forge/tests/it/spec.rs @@ -1,11 +1,14 @@ //! Integration tests for EVM specifications. -use crate::config::*; +use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; use foundry_evm::revm::primitives::SpecId; use foundry_test_utils::Filter; #[tokio::test(flavor = "multi_thread")] async fn test_shanghai_compat() { let filter = Filter::new("", "ShanghaiCompat", ".*spec"); - TestConfig::filter(filter).await.evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(TEST_DATA_DEFAULT.runner(), filter) + .evm_spec(SpecId::SHANGHAI) + .run() + .await; } diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index c058156e1..b1b00b7aa 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -1,51 +1,324 @@ //! Test helpers for Forge integration tests. use alloy_primitives::U256; +use forge::{ + revm::primitives::SpecId, MultiContractRunner, MultiContractRunnerBuilder, TestOptions, + TestOptionsBuilder, +}; use foundry_compilers::{ - artifacts::{Libraries, Settings}, + artifacts::{EvmVersion, Libraries, Settings}, zksync::compile::output::ProjectCompileOutput as ZkProjectCompileOutput, - Project, ProjectCompileOutput, ProjectPathsConfig, SolcConfig, + Project, ProjectCompileOutput, SolcConfig, +}; +use foundry_config::{ + fs_permissions::PathPermission, Config, FsPermissions, FuzzConfig, FuzzDictionaryConfig, + InvariantConfig, RpcEndpoint, RpcEndpoints, }; -use foundry_config::Config; use foundry_evm::{ constants::CALLER, - executors::{Executor, FuzzedExecutor}, opts::{Env, EvmOpts}, - revm::db::DatabaseRef, }; -use foundry_test_utils::fd_lock; +use foundry_test_utils::{fd_lock, init_tracing}; use once_cell::sync::Lazy; -use std::{env, io::Write}; +use std::{ + env, fmt, + io::Write, + path::{Path, PathBuf}, + sync::Arc, +}; pub const RE_PATH_SEPARATOR: &str = "/"; +const TESTDATA: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../testdata"); -pub const TESTDATA: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../testdata"); +/// Profile for the tests group. Used to configure separate configurations for test runs. +pub enum ForgeTestProfile { + Default, + Cancun, + MultiVersion, +} -pub static PROJECT: Lazy = Lazy::new(|| { - let paths = ProjectPathsConfig::builder().root(TESTDATA).sources(TESTDATA).build().unwrap(); +impl fmt::Display for ForgeTestProfile { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Default => write!(f, "default"), + Self::Cancun => write!(f, "cancun"), + Self::MultiVersion => write!(f, "multi-version"), + } + } +} - let libs = - ["fork/Fork.t.sol:DssExecLib:0xfD88CeE74f7D78697775aBDAE53f9Da1559728E4".to_string()]; - let settings = Settings { libraries: Libraries::parse(&libs).unwrap(), ..Default::default() }; - let solc_config = SolcConfig::builder().settings(settings).build(); +impl ForgeTestProfile { + /// Returns true if the profile is Cancun. + pub fn is_cancun(&self) -> bool { + matches!(self, Self::Cancun) + } - Project::builder().paths(paths).solc_config(solc_config).build().unwrap() -}); + pub fn root(&self) -> PathBuf { + PathBuf::from(TESTDATA) + } + + /// Configures the solc settings for the test profile. + pub fn solc_config(&self) -> SolcConfig { + let libs = + ["fork/Fork.t.sol:DssExecLib:0xfD88CeE74f7D78697775aBDAE53f9Da1559728E4".to_string()]; + + let mut settings = + Settings { libraries: Libraries::parse(&libs).unwrap(), ..Default::default() }; + + if matches!(self, Self::Cancun) { + settings.evm_version = Some(EvmVersion::Cancun); + } + + SolcConfig::builder().settings(settings).build() + } + + pub fn project(&self) -> Project { + self.config().project().expect("Failed to build project") + } + + pub fn test_opts(&self, output: &ProjectCompileOutput) -> TestOptions { + TestOptionsBuilder::default() + .fuzz(FuzzConfig { + runs: 256, + max_test_rejects: 65536, + seed: None, + dictionary: FuzzDictionaryConfig { + include_storage: true, + include_push_bytes: true, + dictionary_weight: 40, + max_fuzz_dictionary_addresses: 10_000, + max_fuzz_dictionary_values: 10_000, + }, + gas_report_samples: 256, + failure_persist_dir: Some(tempfile::tempdir().unwrap().into_path()), + failure_persist_file: Some("testfailure".to_string()), + no_zksync_reserved_addresses: false, + }) + .invariant(InvariantConfig { + runs: 256, + depth: 15, + fail_on_revert: false, + call_override: false, + dictionary: FuzzDictionaryConfig { + dictionary_weight: 80, + include_storage: true, + include_push_bytes: true, + max_fuzz_dictionary_addresses: 10_000, + max_fuzz_dictionary_values: 10_000, + }, + shrink_run_limit: 5000, + max_assume_rejects: 65536, + gas_report_samples: 256, + failure_persist_dir: Some(tempfile::tempdir().unwrap().into_path()), + no_zksync_reserved_addresses: false, + }) + .build(output, Path::new(self.project().root())) + .expect("Config loaded") + } -pub static COMPILED: Lazy = Lazy::new(|| { - const LOCK: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../testdata/.lock"); + pub fn evm_opts(&self) -> EvmOpts { + EvmOpts { + env: Env { + gas_limit: u64::MAX, + chain_id: None, + tx_origin: CALLER, + block_number: 1, + block_timestamp: 1, + ..Default::default() + }, + sender: CALLER, + initial_balance: U256::MAX, + ffi: true, + verbosity: 3, + memory_limit: 1 << 26, + ..Default::default() + } + } + + /// Build [Config] for test profile. + /// + /// Project source files are read from testdata/{profile_name} + /// Project output files are written to testdata/out/{profile_name} + /// Cache is written to testdata/cache/{profile_name} + /// + /// AST output is enabled by default to support inline configs. + pub fn config(&self) -> Config { + let mut config = Config::with_root(self.root()); + + config.ast = true; + config.src = self.root().join(self.to_string()); + config.out = self.root().join("out").join(self.to_string()); + config.cache_path = self.root().join("cache").join(self.to_string()); + config.libraries = vec![ + "fork/Fork.t.sol:DssExecLib:0xfD88CeE74f7D78697775aBDAE53f9Da1559728E4".to_string(), + ]; + + if self.is_cancun() { + config.evm_version = EvmVersion::Cancun; + } + + config + } +} + +/// Container for test data for a specific test profile. +pub struct ForgeTestData { + pub project: Project, + pub output: ProjectCompileOutput, + pub test_opts: TestOptions, + pub evm_opts: EvmOpts, + pub config: Config, + pub profile: ForgeTestProfile, +} - let project = &*PROJECT; - assert!(project.cached); +impl ForgeTestData { + /// Builds [ForgeTestData] for the given [ForgeTestProfile]. + /// + /// Uses [get_compiled] to lazily compile the project. + pub fn new(profile: ForgeTestProfile) -> Self { + let project = profile.project(); + let output = get_compiled(&project); + let test_opts = profile.test_opts(&output); + let config = profile.config(); + let evm_opts = profile.evm_opts(); + + Self { project, output, test_opts, evm_opts, config, profile } + } + + /// Builds a base runner + pub fn base_runner(&self) -> MultiContractRunnerBuilder { + init_tracing(); + let mut runner = MultiContractRunnerBuilder::new(Arc::new(self.config.clone())) + .sender(self.evm_opts.sender) + .with_test_options(self.test_opts.clone()); + if self.profile.is_cancun() { + runner = runner.evm_spec(SpecId::CANCUN); + } + + runner + } + + /// Builds a non-tracing runner + pub fn runner(&self) -> MultiContractRunner { + let mut config = self.config.clone(); + config.fs_permissions = + FsPermissions::new(vec![PathPermission::read_write(manifest_root())]); + self.runner_with_config(config) + } + + /// Builds a non-tracing zksync runner + /// TODO: This needs to be implemented as currently it is a copy of the original function + pub fn runner_zksync(&self) -> MultiContractRunner { + let mut config = self.config.clone(); + config.fs_permissions = + FsPermissions::new(vec![PathPermission::read_write(manifest_root())]); + self.runner_with_zksync_config(config) + } + /// Builds a non-tracing runner + pub fn runner_with_config(&self, mut config: Config) -> MultiContractRunner { + config.rpc_endpoints = rpc_endpoints(); + config.allow_paths.push(manifest_root().to_path_buf()); + + // no prompt testing + config.prompt_timeout = 0; + + let root = self.project.root(); + let mut opts = self.evm_opts.clone(); + + if config.isolate { + opts.isolate = true; + } + + let env = opts.local_evm_env(); + let output = self.output.clone(); + + let sender = config.sender; + + let mut builder = self.base_runner(); + builder.config = Arc::new(config); + builder + .enable_isolation(opts.isolate) + .sender(sender) + .with_test_options(self.test_opts.clone()) + .build(root, output, None, env, opts.clone(), Default::default()) + .unwrap() + } + + /// Builds a non-tracing runner with zksync + /// TODO: This needs to be added as currently it is a copy of the original function + pub fn runner_with_zksync_config(&self, mut config: Config) -> MultiContractRunner { + config.rpc_endpoints = rpc_endpoints(); + config.allow_paths.push(manifest_root().to_path_buf()); + + // no prompt testing + config.prompt_timeout = 0; + + let root = self.project.root(); + let mut opts = self.evm_opts.clone(); + + if config.isolate { + opts.isolate = true; + } + + let env = opts.local_evm_env(); + let output = self.output.clone(); + + let sender = config.sender; + + let mut builder = self.base_runner(); + builder.config = Arc::new(config); + builder + .enable_isolation(opts.isolate) + .sender(sender) + .with_test_options(self.test_opts.clone()) + .build(root, output, None, env, opts.clone(), Default::default()) + .unwrap() + } + + /// Builds a tracing runner + pub fn tracing_runner(&self) -> MultiContractRunner { + let mut opts = self.evm_opts.clone(); + opts.verbosity = 5; + self.base_runner() + .build( + self.project.root(), + self.output.clone(), + None, + opts.local_evm_env(), + opts, + Default::default(), + ) + .unwrap() + } + + /// Builds a runner that runs against forked state + pub async fn forked_runner(&self, rpc: &str) -> MultiContractRunner { + let mut opts = self.evm_opts.clone(); + + opts.env.chain_id = None; // clear chain id so the correct one gets fetched from the RPC + opts.fork_url = Some(rpc.to_string()); + + let env = opts.evm_env().await.expect("Could not instantiate fork environment"); + let fork = opts.get_fork(&Default::default(), env.clone()); + + self.base_runner() + .with_fork(fork) + .build(self.project.root(), self.output.clone(), None, env, opts, Default::default()) + .unwrap() + } +} + +pub fn get_compiled(project: &Project) -> ProjectCompileOutput { + let lock_file_path = project.sources_path().join(".lock"); // Compile only once per test run. // We need to use a file lock because `cargo-nextest` runs tests in different processes. // This is similar to [`foundry_test_utils::util::initialize`], see its comments for more // details. - let mut lock = fd_lock::new_lock(LOCK); + let mut lock = fd_lock::new_lock(&lock_file_path); let read = lock.read().unwrap(); let out; - if project.cache_path().exists() && std::fs::read(LOCK).unwrap() == b"1" { + if project.cache_path().exists() && std::fs::read(&lock_file_path).unwrap() == b"1" { out = project.compile(); drop(read); } else { @@ -54,62 +327,26 @@ pub static COMPILED: Lazy = Lazy::new(|| { write.write_all(b"1").unwrap(); out = project.compile(); drop(write); - }; + } let out = out.unwrap(); if out.has_compiler_errors() { panic!("Compiled with errors:\n{out}"); } out -}); - -/// Compile ZK project -fn zk_compile(project: Project) -> ZkProjectCompileOutput { - // let compiler_path = - // futures::executor::block_on(setup_zksolc_manager(DEFAULT_ZKSOLC_VERSION.to_owned())) - // .expect("failed setting up zksolc"); - - // let mut zksolc_config = ZkSolcConfigBuilder::new() - // .compiler_path(compiler_path) - // .settings(ZkSettings { - // optimizer: Optimizer { - // enabled: Some(true), - // mode: Some(String::from("3")), - // fallback_to_optimizing_for_size: Some(false), - // disable_system_request_memoization: true, - // ..Default::default() - // }, - // ..Default::default() - // }) - // .build() - // .expect("failed building zksolc config"); - // zksolc_config.contracts_to_compile = Some(vec![ - // globset::Glob::new("zk/*").unwrap().compile_matcher(), - // globset::Glob::new("lib/*").unwrap().compile_matcher(), - // globset::Glob::new("cheats/Vm.sol").unwrap().compile_matcher(), - // ]); - - // let mut zksolc = ZkSolc::new(zksolc_config, project); - // let (zk_out, _) = zksolc.compile().unwrap(); - // zk_out - - project.zksync_compile().expect("failed compiling with zksolc") } pub static COMPILED_ZK: Lazy = Lazy::new(|| { const LOCK: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../testdata/.lock-zk"); - // let project = &*PROJECT; - let mut paths = ProjectPathsConfig::builder().root(TESTDATA).sources(TESTDATA).build().unwrap(); - paths.zksync_artifacts = format!("{TESTDATA}/zkout").into(); + // let libs = + // ["fork/Fork.t.sol:DssExecLib:0xfD88CeE74f7D78697775aBDAE53f9Da1559728E4".to_string()]; - let libs = - ["fork/Fork.t.sol:DssExecLib:0xfD88CeE74f7D78697775aBDAE53f9Da1559728E4".to_string()]; - let settings = Settings { libraries: Libraries::parse(&libs).unwrap(), ..Default::default() }; - let solc_config = SolcConfig::builder().settings(settings).build(); - - let project = Project::builder().paths(paths).solc_config(solc_config).build().unwrap(); - assert!(project.cached); + // TODO: fix this to adapt to new way of testing + let config = ForgeTestData::new(ForgeTestProfile::Default).config; + let zk_project = foundry_zksync_compiler::create_project(&config, true, false).unwrap(); + let zk_compiler = foundry_common::compile::ProjectCompiler::new(); + assert!(zk_project.cached); // Compile only once per test run. // We need to use a file lock because `cargo-nextest` runs tests in different processes. @@ -118,14 +355,14 @@ pub static COMPILED_ZK: Lazy = Lazy::new(|| { let mut lock = fd_lock::new_lock(LOCK); let read = lock.read().unwrap(); let out; - if project.cache_path().exists() && std::fs::read(LOCK).unwrap() == b"1" { - out = zk_compile(project); + if zk_project.cache_path().exists() && std::fs::read(LOCK).unwrap() == b"1" { + out = zk_compiler.zksync_compile(&zk_project, None).unwrap(); drop(read); } else { drop(read); let mut write = lock.write().unwrap(); write.write_all(b"1").unwrap(); - out = zk_compile(project); + out = zk_compiler.zksync_compile(&zk_project, None).unwrap(); drop(write); }; @@ -152,13 +389,43 @@ pub static EVM_OPTS: Lazy = Lazy::new(|| EvmOpts { ..Default::default() }); -pub fn fuzz_executor(executor: Executor) -> FuzzedExecutor { - let cfg = proptest::test_runner::Config { failure_persistence: None, ..Default::default() }; +/// Default data for the tests group. +pub static TEST_DATA_DEFAULT: Lazy = + Lazy::new(|| ForgeTestData::new(ForgeTestProfile::Default)); + +/// Data for tests requiring Cancun support on Solc and EVM level. +pub static TEST_DATA_CANCUN: Lazy = + Lazy::new(|| ForgeTestData::new(ForgeTestProfile::Cancun)); + +/// Data for tests requiring Cancun support on Solc and EVM level. +pub static TEST_DATA_MULTI_VERSION: Lazy = + Lazy::new(|| ForgeTestData::new(ForgeTestProfile::MultiVersion)); + +pub fn manifest_root() -> &'static Path { + let mut root = Path::new(env!("CARGO_MANIFEST_DIR")); + // need to check here where we're executing the test from, if in `forge` we need to also allow + // `testdata` + if root.ends_with("forge") { + root = root.parent().unwrap(); + } + root +} - FuzzedExecutor::new( - executor, - proptest::test_runner::TestRunner::new(cfg), - CALLER, - crate::config::test_opts().fuzz, - ) +/// the RPC endpoints used during tests +pub fn rpc_endpoints() -> RpcEndpoints { + RpcEndpoints::new([ + ( + "rpcAlias", + RpcEndpoint::Url( + "https://eth-mainnet.alchemyapi.io/v2/Lc7oIGYeL_QvInzI0Wiu_pOZZDEKBrdf".to_string(), + ), + ), + ( + "rpcAliasSepolia", + RpcEndpoint::Url( + "https://eth-sepolia.g.alchemy.com/v2/Lc7oIGYeL_QvInzI0Wiu_pOZZDEKBrdf".to_string(), + ), + ), + ("rpcEnvAlias", RpcEndpoint::Env("${RPC_ENV_ALIAS}".to_string())), + ]) } diff --git a/crates/forge/tests/it/zk.rs b/crates/forge/tests/it/zk.rs index 1439b866f..da988fcdd 100644 --- a/crates/forge/tests/it/zk.rs +++ b/crates/forge/tests/it/zk.rs @@ -4,55 +4,54 @@ use std::collections::BTreeMap; use crate::{ config::*, - test_helpers::{PROJECT, RE_PATH_SEPARATOR}, + test_helpers::{RE_PATH_SEPARATOR, TEST_DATA_DEFAULT}, }; use forge::revm::primitives::SpecId; -use foundry_config::{fs_permissions::PathPermission, Config, FsPermissions}; +use foundry_config::{fs_permissions::PathPermission, FsPermissions}; use foundry_test_utils::Filter; /// Executes all zk basic tests #[tokio::test(flavor = "multi_thread")] async fn test_zk_basic() { - let mut config = Config::with_root(PROJECT.root()); + let mut config = TEST_DATA_DEFAULT.config.clone(); config.fs_permissions = FsPermissions::new(vec![PathPermission::read_write("./")]); - let runner = runner_with_config_and_zk(config); + let runner = TEST_DATA_DEFAULT.runner_with_zksync_config(config); let filter = Filter::new(".*", "ZkBasicTest", &format!(".*zk{RE_PATH_SEPARATOR}*")); - TestConfig::with_filter(runner.await, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; } /// Executes all zk contract tests #[tokio::test(flavor = "multi_thread")] async fn test_zk_contracts() { - let mut config = Config::with_root(PROJECT.root()); + let mut config = TEST_DATA_DEFAULT.config.clone(); config.fs_permissions = FsPermissions::new(vec![PathPermission::read_write("./")]); - let runner = runner_with_config_and_zk(config); + let runner = TEST_DATA_DEFAULT.runner_with_zksync_config(config); let filter = Filter::new(".*", "ZkContractsTest", &format!(".*zk{RE_PATH_SEPARATOR}*")); - TestConfig::with_filter(runner.await, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; } /// Executes all zk cheatcode tests #[tokio::test(flavor = "multi_thread")] async fn test_zk_cheats() { - let mut config = Config::with_root(PROJECT.root()); + let mut config = TEST_DATA_DEFAULT.config.clone(); config.fs_permissions = FsPermissions::new(vec![PathPermission::read_write("./")]); - let runner = runner_with_config_and_zk(config); + let runner = TEST_DATA_DEFAULT.runner_with_zksync_config(config); let filter = Filter::new(".*", "ZkCheatcodesTest", &format!(".*zk{RE_PATH_SEPARATOR}*")); - TestConfig::with_filter(runner.await, filter).evm_spec(SpecId::SHANGHAI).run().await; + TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).run().await; } /// Executes all zk console tests #[tokio::test(flavor = "multi_thread")] async fn test_zk_logs() { - let mut config = Config::with_root(PROJECT.root()); + let mut config = TEST_DATA_DEFAULT.config.clone(); config.fs_permissions = FsPermissions::new(vec![PathPermission::read_write("./")]); - let runner = runner_with_config_and_zk(config); + let runner = TEST_DATA_DEFAULT.runner_with_zksync_config(config); let filter = Filter::new(".*", "ZkConsoleTest", &format!(".*zk{RE_PATH_SEPARATOR}*")); - let results = - TestConfig::with_filter(runner.await, filter).evm_spec(SpecId::SHANGHAI).test().await; + let results = TestConfig::with_filter(runner, filter).evm_spec(SpecId::SHANGHAI).test(); assert_multiple( &results, diff --git a/crates/linking/Cargo.toml b/crates/linking/Cargo.toml new file mode 100644 index 000000000..15d0d113b --- /dev/null +++ b/crates/linking/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "foundry-linking" +description = "Smart contract linking tools" + +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-compilers = { workspace = true, features = ["full"] } +semver.workspace = true +alloy-primitives = { workspace = true, features = ["rlp"] } +thiserror.workspace = true diff --git a/crates/forge/src/link.rs b/crates/linking/src/lib.rs similarity index 51% rename from crates/forge/src/link.rs rename to crates/linking/src/lib.rs index 55f3f5487..25ef8fe19 100644 --- a/crates/forge/src/link.rs +++ b/crates/linking/src/lib.rs @@ -1,12 +1,19 @@ -use alloy_primitives::{Address, Bytes}; +//! # foundry-linking +//! +//! EVM bytecode linker. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +use alloy_primitives::{Address, Bytes, B256}; use foundry_compilers::{ - artifacts::{CompactContractBytecode, Libraries}, + artifacts::{CompactContractBytecodeCow, Libraries}, contracts::ArtifactContracts, Artifact, ArtifactId, }; use semver::Version; use std::{ - collections::BTreeSet, + collections::{BTreeMap, BTreeSet}, path::{Path, PathBuf}, str::FromStr, }; @@ -20,13 +27,15 @@ pub enum LinkerError { MissingTargetArtifact, #[error(transparent)] InvalidAddress(
::Err), + #[error("cyclic dependency found, can't link libraries via CREATE2")] + CyclicDependency, } -pub struct Linker { +pub struct Linker<'a> { /// Root of the project, used to determine whether artifact/library path can be stripped. pub root: PathBuf, /// Compilation artifacts. - pub contracts: ArtifactContracts, + pub contracts: ArtifactContracts>, } /// Output of the `link_with_nonce_or_address` @@ -39,8 +48,11 @@ pub struct LinkOutput { pub libs_to_deploy: Vec, } -impl Linker { - pub fn new(root: impl Into, contracts: ArtifactContracts) -> Self { +impl<'a> Linker<'a> { + pub fn new( + root: impl Into, + contracts: ArtifactContracts>, + ) -> Self { Linker { root: root.into(), contracts } } @@ -60,7 +72,7 @@ impl Linker { /// library path in the form of "./path/to/Lib.sol:Lib" /// /// Optionally accepts solc version, and if present, only compares artifacts with given version. - fn find_artifact_id_by_library_path<'a>( + fn find_artifact_id_by_library_path( &'a self, file: &str, name: &str, @@ -83,16 +95,23 @@ impl Linker { } /// Performs DFS on the graph of link references, and populates `deps` with all found libraries. - fn collect_dependencies<'a>( + fn collect_dependencies( &'a self, target: &'a ArtifactId, deps: &mut BTreeSet<&'a ArtifactId>, ) -> Result<(), LinkerError> { - let references = self - .contracts - .get(target) - .ok_or(LinkerError::MissingTargetArtifact)? - .all_link_references(); + let contract = self.contracts.get(target).ok_or(LinkerError::MissingTargetArtifact)?; + + let mut references = BTreeMap::new(); + if let Some(bytecode) = &contract.bytecode { + references.extend(bytecode.link_references.clone()); + } + if let Some(deployed_bytecode) = &contract.deployed_bytecode { + if let Some(bytecode) = &deployed_bytecode.bytecode { + references.extend(bytecode.link_references.clone()); + } + } + for (file, libs) in &references { for contract in libs.keys() { let id = self @@ -119,19 +138,21 @@ impl Linker { /// When calling for `target` being an external library itself, you should check that `target` /// does not appear in `libs_to_deploy` to avoid deploying it twice. It may happen in cases /// when there is a dependency cycle including `target`. - pub fn link_with_nonce_or_address<'a>( + pub fn link_with_nonce_or_address( &'a self, libraries: Libraries, sender: Address, mut nonce: u64, - target: &'a ArtifactId, + targets: impl IntoIterator, ) -> Result { // Library paths in `link_references` keys are always stripped, so we have to strip // user-provided paths to be able to match them correctly. let mut libraries = libraries.with_stripped_file_prefixes(self.root.as_path()); let mut needed_libraries = BTreeSet::new(); - self.collect_dependencies(target, &mut needed_libraries)?; + for target in targets { + self.collect_dependencies(target, &mut needed_libraries)?; + } let mut libs_to_deploy = Vec::new(); @@ -160,22 +181,82 @@ impl Linker { Ok(LinkOutput { libraries, libs_to_deploy }) } + pub fn link_with_create2( + &'a self, + libraries: Libraries, + sender: Address, + salt: B256, + target: &'a ArtifactId, + ) -> Result { + // Library paths in `link_references` keys are always stripped, so we have to strip + // user-provided paths to be able to match them correctly. + let mut libraries = libraries.with_stripped_file_prefixes(self.root.as_path()); + + let mut needed_libraries = BTreeSet::new(); + self.collect_dependencies(target, &mut needed_libraries)?; + + let mut needed_libraries = needed_libraries + .into_iter() + .filter(|id| { + // Filter out already provided libraries. + let (file, name) = self.convert_artifact_id_to_lib_path(id); + !libraries.libs.contains_key(&file) || !libraries.libs[&file].contains_key(&name) + }) + .map(|id| { + // Link library with provided libs and extract bytecode object (possibly unlinked). + let bytecode = self.link(id, &libraries).unwrap().bytecode.unwrap(); + (id, bytecode) + }) + .collect::>(); + + let mut libs_to_deploy = Vec::new(); + + // Iteratively compute addresses and link libraries until we have no unlinked libraries + // left. + while !needed_libraries.is_empty() { + // Find any library which is fully linked. + let deployable = needed_libraries + .iter() + .enumerate() + .find(|(_, (_, bytecode))| !bytecode.object.is_unlinked()); + + // If we haven't found any deployable library, it means we have a cyclic dependency. + let Some((index, &(id, _))) = deployable else { + return Err(LinkerError::CyclicDependency); + }; + let (_, bytecode) = needed_libraries.swap_remove(index); + let code = bytecode.bytes().unwrap(); + let address = sender.create2_from_code(salt, code); + libs_to_deploy.push(code.clone()); + + let (file, name) = self.convert_artifact_id_to_lib_path(id); + + for (_, bytecode) in &mut needed_libraries { + bytecode.to_mut().link(file.to_string_lossy(), name.clone(), address); + } + + libraries.libs.entry(file).or_default().insert(name, address.to_checksum(None)); + } + + Ok(LinkOutput { libraries, libs_to_deploy }) + } + /// Links given artifact with given libraries. pub fn link( &self, target: &ArtifactId, libraries: &Libraries, - ) -> Result { + ) -> Result, LinkerError> { let mut contract = self.contracts.get(target).ok_or(LinkerError::MissingTargetArtifact)?.clone(); for (file, libs) in &libraries.libs { for (name, address) in libs { let address = Address::from_str(address).map_err(LinkerError::InvalidAddress)?; if let Some(bytecode) = contract.bytecode.as_mut() { - bytecode.link(file.to_string_lossy(), name, address); + bytecode.to_mut().link(file.to_string_lossy(), name, address); } if let Some(deployed_bytecode) = - contract.deployed_bytecode.as_mut().and_then(|b| b.bytecode.as_mut()) + contract.deployed_bytecode.as_mut().and_then(|b| b.to_mut().bytecode.as_mut()) { deployed_bytecode.link(file.to_string_lossy(), name, address); } @@ -190,17 +271,25 @@ impl Linker { ) -> Result { self.contracts.keys().map(|id| Ok((id.clone(), self.link(id, libraries)?))).collect() } + + pub fn get_linked_artifacts_cow( + &self, + libraries: &Libraries, + ) -> Result>, LinkerError> { + self.contracts.keys().map(|id| Ok((id.clone(), self.link(id, libraries)?))).collect() + } } #[cfg(test)] mod tests { use super::*; - use foundry_compilers::{Project, ProjectPathsConfig}; + use alloy_primitives::fixed_bytes; + use foundry_compilers::{Project, ProjectCompileOutput, ProjectPathsConfig}; use std::collections::HashMap; struct LinkerTest { project: Project, - linker: Linker, + output: ProjectCompileOutput, dependency_assertions: HashMap>, } @@ -208,30 +297,27 @@ mod tests { fn new(path: impl Into, strip_prefixes: bool) -> Self { let path = path.into(); let paths = ProjectPathsConfig::builder() - .root("../../testdata/linking") + .root("../../testdata") .lib("../../testdata/lib") .sources(path.clone()) .tests(path) .build() .unwrap(); - let project = - Project::builder().paths(paths).ephemeral().no_artifacts().build().unwrap(); + let project = Project::builder() + .paths(paths) + .ephemeral() + .no_artifacts() + .build(Default::default()) + .unwrap(); - let mut contracts = project.compile().unwrap(); + let mut output = project.compile().unwrap(); if strip_prefixes { - contracts = contracts.with_stripped_file_prefixes(project.root()); + output = output.with_stripped_file_prefixes(project.root()); } - let contracts = contracts - .into_artifacts() - .map(|(id, c)| (id, c.into_contract_bytecode())) - .collect::(); - - let linker = Linker::new(project.root(), contracts); - - Self { project, linker, dependency_assertions: HashMap::new() } + Self { project, output, dependency_assertions: HashMap::new() } } fn assert_dependencies( @@ -244,7 +330,30 @@ mod tests { } fn test_with_sender_and_nonce(self, sender: Address, initial_nonce: u64) { - for id in self.linker.contracts.keys() { + let linker = Linker::new(self.project.root(), self.output.artifact_ids().collect()); + for (id, identifier) in self.iter_linking_targets(&linker) { + let output = linker + .link_with_nonce_or_address(Default::default(), sender, initial_nonce, [id]) + .expect("Linking failed"); + self.validate_assertions(identifier, output); + } + } + + fn test_with_create2(self, sender: Address, salt: B256) { + let linker = Linker::new(self.project.root(), self.output.artifact_ids().collect()); + for (id, identifier) in self.iter_linking_targets(&linker) { + let output = linker + .link_with_create2(Default::default(), sender, salt, id) + .expect("Linking failed"); + self.validate_assertions(identifier, output); + } + } + + fn iter_linking_targets<'a>( + &'a self, + linker: &'a Linker<'_>, + ) -> impl IntoIterator + 'a { + linker.contracts.keys().filter_map(move |id| { // If we didn't strip paths, artifacts will have absolute paths. // That's expected and we want to ensure that only `libraries` object has relative // paths, artifacts should be kept as is. @@ -258,37 +367,42 @@ mod tests { // Skip ds-test as it always has no dependencies etc. (and the path is outside root // so is not sanitized) if identifier.contains("DSTest") { - continue; + return None; } - let LinkOutput { libs_to_deploy, libraries, .. } = self - .linker - .link_with_nonce_or_address(Default::default(), sender, initial_nonce, id) - .expect("Linking failed"); - - let assertions = self - .dependency_assertions - .get(&identifier) - .unwrap_or_else(|| panic!("Unexpected artifact: {identifier}")); - - assert_eq!( - libs_to_deploy.len(), - assertions.len(), - "artifact {identifier} has more/less dependencies than expected ({} vs {}): {:#?}", - libs_to_deploy.len(), - assertions.len(), - libs_to_deploy - ); + Some((id, identifier)) + }) + } - for (dep_identifier, address) in assertions { - let (file, name) = dep_identifier.split_once(':').unwrap(); - if let Some(lib_address) = - libraries.libs.get(&PathBuf::from(file)).and_then(|libs| libs.get(name)) - { - assert_eq!(*lib_address, address.to_string(), "incorrect library address for dependency {dep_identifier} of {identifier}"); - } else { - panic!("Library not found") - } + fn validate_assertions(&self, identifier: String, output: LinkOutput) { + let LinkOutput { libs_to_deploy, libraries } = output; + + let assertions = self + .dependency_assertions + .get(&identifier) + .unwrap_or_else(|| panic!("Unexpected artifact: {identifier}")); + + assert_eq!( + libs_to_deploy.len(), + assertions.len(), + "artifact {identifier} has more/less dependencies than expected ({} vs {}): {:#?}", + libs_to_deploy.len(), + assertions.len(), + libs_to_deploy + ); + + for (dep_identifier, address) in assertions { + let (file, name) = dep_identifier.split_once(':').unwrap(); + if let Some(lib_address) = + libraries.libs.get(&PathBuf::from(file)).and_then(|libs| libs.get(name)) + { + assert_eq!( + *lib_address, + address.to_string(), + "incorrect library address for dependency {dep_identifier} of {identifier}" + ); + } else { + panic!("Library {dep_identifier} not found"); } } } @@ -302,20 +416,20 @@ mod tests { #[test] fn link_simple() { - link_test("../../testdata/linking/simple", |linker| { + link_test("../../testdata/default/linking/simple", |linker| { linker - .assert_dependencies("simple/Simple.t.sol:Lib".to_string(), vec![]) + .assert_dependencies("default/linking/simple/Simple.t.sol:Lib".to_string(), vec![]) .assert_dependencies( - "simple/Simple.t.sol:LibraryConsumer".to_string(), + "default/linking/simple/Simple.t.sol:LibraryConsumer".to_string(), vec![( - "simple/Simple.t.sol:Lib".to_string(), + "default/linking/simple/Simple.t.sol:Lib".to_string(), Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), )], ) .assert_dependencies( - "simple/Simple.t.sol:SimpleLibraryLinkingTest".to_string(), + "default/linking/simple/Simple.t.sol:SimpleLibraryLinkingTest".to_string(), vec![( - "simple/Simple.t.sol:Lib".to_string(), + "default/linking/simple/Simple.t.sol:Lib".to_string(), Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), )], ) @@ -325,43 +439,43 @@ mod tests { #[test] fn link_nested() { - link_test("../../testdata/linking/nested", |linker| { + link_test("../../testdata/default/linking/nested", |linker| { linker - .assert_dependencies("nested/Nested.t.sol:Lib".to_string(), vec![]) + .assert_dependencies("default/linking/nested/Nested.t.sol:Lib".to_string(), vec![]) .assert_dependencies( - "nested/Nested.t.sol:NestedLib".to_string(), + "default/linking/nested/Nested.t.sol:NestedLib".to_string(), vec![( - "nested/Nested.t.sol:Lib".to_string(), + "default/linking/nested/Nested.t.sol:Lib".to_string(), Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), )], ) .assert_dependencies( - "nested/Nested.t.sol:LibraryConsumer".to_string(), + "default/linking/nested/Nested.t.sol:LibraryConsumer".to_string(), vec![ // Lib shows up here twice, because the linker sees it twice, but it should // have the same address and nonce. ( - "nested/Nested.t.sol:Lib".to_string(), + "default/linking/nested/Nested.t.sol:Lib".to_string(), Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") .unwrap(), ), ( - "nested/Nested.t.sol:NestedLib".to_string(), + "default/linking/nested/Nested.t.sol:NestedLib".to_string(), Address::from_str("0x47e9Fbef8C83A1714F1951F142132E6e90F5fa5D") .unwrap(), ), ], ) .assert_dependencies( - "nested/Nested.t.sol:NestedLibraryLinkingTest".to_string(), + "default/linking/nested/Nested.t.sol:NestedLibraryLinkingTest".to_string(), vec![ ( - "nested/Nested.t.sol:Lib".to_string(), + "default/linking/nested/Nested.t.sol:Lib".to_string(), Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") .unwrap(), ), ( - "nested/Nested.t.sol:NestedLib".to_string(), + "default/linking/nested/Nested.t.sol:NestedLib".to_string(), Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d") .unwrap(), ), @@ -373,94 +487,101 @@ mod tests { #[test] fn link_duplicate() { - link_test("../../testdata/linking/duplicate", |linker| { + link_test("../../testdata/default/linking/duplicate", |linker| { linker - .assert_dependencies("duplicate/Duplicate.t.sol:A".to_string(), vec![]) - .assert_dependencies("duplicate/Duplicate.t.sol:B".to_string(), vec![]) .assert_dependencies( - "duplicate/Duplicate.t.sol:C".to_string(), + "default/linking/duplicate/Duplicate.t.sol:A".to_string(), + vec![], + ) + .assert_dependencies( + "default/linking/duplicate/Duplicate.t.sol:B".to_string(), + vec![], + ) + .assert_dependencies( + "default/linking/duplicate/Duplicate.t.sol:C".to_string(), vec![( - "duplicate/Duplicate.t.sol:A".to_string(), + "default/linking/duplicate/Duplicate.t.sol:A".to_string(), Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), )], ) .assert_dependencies( - "duplicate/Duplicate.t.sol:D".to_string(), + "default/linking/duplicate/Duplicate.t.sol:D".to_string(), vec![( - "duplicate/Duplicate.t.sol:B".to_string(), + "default/linking/duplicate/Duplicate.t.sol:B".to_string(), Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3").unwrap(), )], ) .assert_dependencies( - "duplicate/Duplicate.t.sol:E".to_string(), + "default/linking/duplicate/Duplicate.t.sol:E".to_string(), vec![ ( - "duplicate/Duplicate.t.sol:A".to_string(), + "default/linking/duplicate/Duplicate.t.sol:A".to_string(), Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") .unwrap(), ), ( - "duplicate/Duplicate.t.sol:C".to_string(), + "default/linking/duplicate/Duplicate.t.sol:C".to_string(), Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d") .unwrap(), ), ], ) .assert_dependencies( - "duplicate/Duplicate.t.sol:LibraryConsumer".to_string(), + "default/linking/duplicate/Duplicate.t.sol:LibraryConsumer".to_string(), vec![ ( - "duplicate/Duplicate.t.sol:A".to_string(), + "default/linking/duplicate/Duplicate.t.sol:A".to_string(), Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") .unwrap(), ), ( - "duplicate/Duplicate.t.sol:B".to_string(), + "default/linking/duplicate/Duplicate.t.sol:B".to_string(), Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d") .unwrap(), ), ( - "duplicate/Duplicate.t.sol:C".to_string(), + "default/linking/duplicate/Duplicate.t.sol:C".to_string(), Address::from_str("0x8be503bcded90ed42eff31f56199399b2b0154ca") .unwrap(), ), ( - "duplicate/Duplicate.t.sol:D".to_string(), + "default/linking/duplicate/Duplicate.t.sol:D".to_string(), Address::from_str("0x47c5e40890bce4a473a49d7501808b9633f29782") .unwrap(), ), ( - "duplicate/Duplicate.t.sol:E".to_string(), + "default/linking/duplicate/Duplicate.t.sol:E".to_string(), Address::from_str("0x29b2440db4a256b0c1e6d3b4cdcaa68e2440a08f") .unwrap(), ), ], ) .assert_dependencies( - "duplicate/Duplicate.t.sol:DuplicateLibraryLinkingTest".to_string(), + "default/linking/duplicate/Duplicate.t.sol:DuplicateLibraryLinkingTest" + .to_string(), vec![ ( - "duplicate/Duplicate.t.sol:A".to_string(), + "default/linking/duplicate/Duplicate.t.sol:A".to_string(), Address::from_str("0x5a443704dd4b594b382c22a083e2bd3090a6fef3") .unwrap(), ), ( - "duplicate/Duplicate.t.sol:B".to_string(), + "default/linking/duplicate/Duplicate.t.sol:B".to_string(), Address::from_str("0x47e9fbef8c83a1714f1951f142132e6e90f5fa5d") .unwrap(), ), ( - "duplicate/Duplicate.t.sol:C".to_string(), + "default/linking/duplicate/Duplicate.t.sol:C".to_string(), Address::from_str("0x8be503bcded90ed42eff31f56199399b2b0154ca") .unwrap(), ), ( - "duplicate/Duplicate.t.sol:D".to_string(), + "default/linking/duplicate/Duplicate.t.sol:D".to_string(), Address::from_str("0x47c5e40890bce4a473a49d7501808b9633f29782") .unwrap(), ), ( - "duplicate/Duplicate.t.sol:E".to_string(), + "default/linking/duplicate/Duplicate.t.sol:E".to_string(), Address::from_str("0x29b2440db4a256b0c1e6d3b4cdcaa68e2440a08f") .unwrap(), ), @@ -472,33 +593,33 @@ mod tests { #[test] fn link_cycle() { - link_test("../../testdata/linking/cycle", |linker| { + link_test("../../testdata/default/linking/cycle", |linker| { linker .assert_dependencies( - "cycle/Cycle.t.sol:Foo".to_string(), + "default/linking/cycle/Cycle.t.sol:Foo".to_string(), vec![ ( - "cycle/Cycle.t.sol:Foo".to_string(), + "default/linking/cycle/Cycle.t.sol:Foo".to_string(), Address::from_str("0x47e9Fbef8C83A1714F1951F142132E6e90F5fa5D") .unwrap(), ), ( - "cycle/Cycle.t.sol:Bar".to_string(), + "default/linking/cycle/Cycle.t.sol:Bar".to_string(), Address::from_str("0x5a443704dd4B594B382c22a083e2BD3090A6feF3") .unwrap(), ), ], ) .assert_dependencies( - "cycle/Cycle.t.sol:Bar".to_string(), + "default/linking/cycle/Cycle.t.sol:Bar".to_string(), vec![ ( - "cycle/Cycle.t.sol:Foo".to_string(), + "default/linking/cycle/Cycle.t.sol:Foo".to_string(), Address::from_str("0x47e9Fbef8C83A1714F1951F142132E6e90F5fa5D") .unwrap(), ), ( - "cycle/Cycle.t.sol:Bar".to_string(), + "default/linking/cycle/Cycle.t.sol:Bar".to_string(), Address::from_str("0x5a443704dd4B594B382c22a083e2BD3090A6feF3") .unwrap(), ), @@ -507,4 +628,57 @@ mod tests { .test_with_sender_and_nonce(Address::default(), 1); }); } + + #[test] + fn link_create2_nested() { + link_test("../../testdata/default/linking/nested", |linker| { + linker + .assert_dependencies("default/linking/nested/Nested.t.sol:Lib".to_string(), vec![]) + .assert_dependencies( + "default/linking/nested/Nested.t.sol:NestedLib".to_string(), + vec![( + "default/linking/nested/Nested.t.sol:Lib".to_string(), + Address::from_str("0xCD3864eB2D88521a5477691EE589D9994b796834").unwrap(), + )], + ) + .assert_dependencies( + "default/linking/nested/Nested.t.sol:LibraryConsumer".to_string(), + vec![ + // Lib shows up here twice, because the linker sees it twice, but it should + // have the same address and nonce. + ( + "default/linking/nested/Nested.t.sol:Lib".to_string(), + Address::from_str("0xCD3864eB2D88521a5477691EE589D9994b796834") + .unwrap(), + ), + ( + "default/linking/nested/Nested.t.sol:NestedLib".to_string(), + Address::from_str("0x023d9a6bfA39c45997572dC4F87b3E2713b6EBa4") + .unwrap(), + ), + ], + ) + .assert_dependencies( + "default/linking/nested/Nested.t.sol:NestedLibraryLinkingTest".to_string(), + vec![ + ( + "default/linking/nested/Nested.t.sol:Lib".to_string(), + Address::from_str("0xCD3864eB2D88521a5477691EE589D9994b796834") + .unwrap(), + ), + ( + "default/linking/nested/Nested.t.sol:NestedLib".to_string(), + Address::from_str("0x023d9a6bfA39c45997572dC4F87b3E2713b6EBa4") + .unwrap(), + ), + ], + ) + .test_with_create2( + Address::default(), + fixed_bytes!( + "19bf59b7b67ae8edcbc6e53616080f61fa99285c061450ad601b0bc40c9adfc9" + ), + ); + }); + } } diff --git a/crates/macros/Cargo.toml b/crates/macros/Cargo.toml index a2c070c53..671d37be8 100644 --- a/crates/macros/Cargo.toml +++ b/crates/macros/Cargo.toml @@ -9,6 +9,9 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [lib] proc-macro = true # proc-macro tests aren't fully supported by cargo-nextest archives diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs index 136b65f51..dbdaa208c 100644 --- a/crates/macros/src/lib.rs +++ b/crates/macros/src/lib.rs @@ -1,4 +1,9 @@ -#![warn(unused_crate_dependencies)] +//! # foundry-macros +//! +//! Internal Foundry proc-macros. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #[macro_use] extern crate proc_macro_error; diff --git a/crates/script/Cargo.toml b/crates/script/Cargo.toml new file mode 100644 index 000000000..d035e6930 --- /dev/null +++ b/crates/script/Cargo.toml @@ -0,0 +1,64 @@ +[package] +name = "forge-script" +description = "Solidity scripting" + +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] +forge-verify.workspace = true +foundry-cli.workspace = true +foundry-config.workspace = true +foundry-common.workspace = true +foundry-evm.workspace = true +foundry-debugger.workspace = true +foundry-cheatcodes.workspace = true +foundry-wallets.workspace = true +foundry-linking.workspace = true +foundry-zksync-core.workspace = true +foundry-zksync-compiler.workspace = true + +hex.workspace = true +serde.workspace = true +eyre.workspace = true +serde_json.workspace = true +dunce.workspace = true +foundry-compilers = { workspace = true, features = ["full"] } +tracing.workspace = true +clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } +semver.workspace = true +futures.workspace = true +async-recursion = "1.0.5" + +itertools.workspace = true +parking_lot.workspace = true +yansi.workspace = true +revm-inspectors.workspace = true +alloy-rpc-types.workspace = true +alloy-json-abi.workspace = true +dialoguer = { version = "0.11", default-features = false } +indicatif = "0.17" + +alloy-signer.workspace = true +alloy-serde.workspace = true +alloy-network.workspace = true +alloy-provider.workspace = true +alloy-chains.workspace = true +alloy-dyn-abi.workspace = true +alloy-primitives.workspace = true +alloy-eips.workspace = true +alloy-transport.workspace = true + +# zksync +zksync-web3-rs.workspace = true + +[dev-dependencies] +tempfile.workspace = true diff --git a/crates/script/src/broadcast.rs b/crates/script/src/broadcast.rs new file mode 100644 index 000000000..102f4f4ea --- /dev/null +++ b/crates/script/src/broadcast.rs @@ -0,0 +1,482 @@ +use crate::{ + build::LinkedBuildData, progress::ScriptProgress, sequence::ScriptSequenceKind, + transaction::ZkTransaction, verify::BroadcastedState, ScriptArgs, ScriptConfig, +}; +use alloy_chains::Chain; +use alloy_eips::eip2718::Encodable2718; +use alloy_network::{AnyNetwork, EthereumWallet, TransactionBuilder}; +use alloy_primitives::{utils::format_units, Address, TxHash}; +use alloy_provider::{utils::Eip1559Estimation, Provider}; +use alloy_rpc_types::TransactionRequest; +use alloy_serde::WithOtherFields; +use alloy_transport::Transport; +use eyre::{bail, Context, Result}; +use forge_verify::provider::VerificationProviderType; +use foundry_cheatcodes::ScriptWallets; +use foundry_cli::utils::{has_batch_support, has_different_gas_calc}; +use foundry_common::{ + provider::{get_http_provider, try_get_http_provider, RetryProvider}, + shell, +}; +use foundry_config::Config; +use foundry_zksync_core::convert::{ConvertAddress, ConvertBytes, ConvertSignature, ToSignable}; +use futures::{future::join_all, StreamExt}; +use itertools::Itertools; +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; +use zksync_web3_rs::eip712::{Eip712Meta, Eip712Transaction, Eip712TransactionRequest}; + +pub async fn estimate_gas( + tx: &mut WithOtherFields, + provider: &P, + estimate_multiplier: u64, +) -> Result<()> +where + P: Provider, + T: Transport + Clone, +{ + // if already set, some RPC endpoints might simply return the gas value that is already + // set in the request and omit the estimate altogether, so we remove it here + tx.gas = None; + + tx.set_gas_limit( + provider.estimate_gas(tx).await.wrap_err("Failed to estimate gas for tx")? * + estimate_multiplier as u128 / + 100, + ); + Ok(()) +} + +pub async fn next_nonce(caller: Address, provider_url: &str) -> eyre::Result { + let provider = try_get_http_provider(provider_url) + .wrap_err_with(|| format!("bad fork_url provider: {provider_url}"))?; + Ok(provider.get_transaction_count(caller).await?) +} + +async fn convert_to_zksync( + provider: &Arc, + tx: WithOtherFields, + zk: &ZkTransaction, +) -> Result<(Eip712TransactionRequest, Eip712Transaction)> { + let custom_data = Eip712Meta::new().factory_deps(zk.factory_deps.clone()); + + let gas_price = match tx.gas_price() { + Some(price) => price, + None => provider.get_gas_price().await?, + }; + + let mut deploy_request = Eip712TransactionRequest::new() + .r#type(zksync_web3_rs::zks_utils::EIP712_TX_TYPE) + .from(Address(*tx.from().unwrap()).to_h160()) + .to(tx.to().map(|to| to.to_h160()).unwrap()) + .chain_id(tx.chain_id().unwrap()) + .nonce(tx.nonce().unwrap()) + .data(tx.input().cloned().unwrap_or_default().to_ethers()) + .gas_price(gas_price) + .custom_data(custom_data); + + let fee: zksync_web3_rs::zks_provider::types::Fee = + provider.raw_request("zks_estimateFee".into(), [deploy_request.clone()]).await.unwrap(); + deploy_request = deploy_request + .gas_limit(fee.gas_limit) + .max_fee_per_gas(fee.max_fee_per_gas) + .max_priority_fee_per_gas(fee.max_priority_fee_per_gas); + deploy_request.custom_data.gas_per_pubdata = fee.gas_per_pubdata_limit; + + // TODO: This is a work around as try_into is not propagating + // gas_per_pubdata_byte_limit. It always set the default We would need to + // fix that library or add EIP712 to alloy with correct implementation. + let mut signable: Eip712Transaction = + deploy_request.clone().try_into().wrap_err("converting deploy request")?; + signable.gas_per_pubdata_byte_limit = deploy_request.custom_data.gas_per_pubdata; + + Ok((deploy_request, signable)) +} + +#[allow(clippy::too_many_arguments)] +pub async fn send_transaction( + provider: Arc, + mut tx: WithOtherFields, + zk: Option<&ZkTransaction>, + kind: SendTransactionKind<'_>, + sequential_broadcast: bool, + is_fixed_gas_limit: bool, + estimate_via_rpc: bool, + estimate_multiplier: u64, +) -> Result { + let from = tx.from.expect("no sender"); + + if sequential_broadcast { + let nonce = provider.get_transaction_count(from).await?; + + let tx_nonce = tx.nonce.expect("no nonce"); + if nonce != tx_nonce { + bail!("EOA nonce changed unexpectedly while sending transactions. Expected {tx_nonce} got {nonce} from provider.") + } + } + + // Chains which use `eth_estimateGas` are being sent sequentially and require their + // gas to be re-estimated right before broadcasting. + if !is_fixed_gas_limit && estimate_via_rpc { + estimate_gas(&mut tx, &provider, estimate_multiplier).await?; + } + + let pending = match kind { + SendTransactionKind::Unlocked(addr) => { + debug!("sending transaction from unlocked account {:?}: {:?}", addr, tx); + + // Submit the transaction + provider.send_transaction(tx).await? + } + SendTransactionKind::Raw(signer) => { + debug!("sending transaction: {:?}", tx); + + let signed = if let Some(zk) = zk { + let signer = + signer.signer_by_address(from).ok_or(eyre::eyre!("Signer not found"))?; + + let (deploy_request, signable) = convert_to_zksync(&provider, tx, zk).await?; + let mut signable = signable.to_signable_tx(); + + let signature = signer + .sign_transaction(&mut signable) + .await + .wrap_err("Failed to sign typed data")?; + + let encoded = &*deploy_request + .rlp_signed(signature.to_ethers()) + .wrap_err("able to rlp encode deploy request")?; + + [&[zksync_web3_rs::zks_utils::EIP712_TX_TYPE], encoded].concat() + } else { + tx.build(signer).await?.encoded_2718() + }; + + // Submit the raw transaction + provider.send_raw_transaction(signed.as_ref()).await? + } + }; + + Ok(*pending.tx_hash()) +} + +/// How to send a single transaction +#[derive(Clone)] +pub enum SendTransactionKind<'a> { + Unlocked(Address), + Raw(&'a EthereumWallet), +} + +/// Represents how to send _all_ transactions +pub enum SendTransactionsKind { + /// Send via `eth_sendTransaction` and rely on the `from` address being unlocked. + Unlocked(HashSet
), + /// Send a signed transaction via `eth_sendRawTransaction` + Raw(HashMap), +} + +impl SendTransactionsKind { + /// Returns the [`SendTransactionKind`] for the given address + /// + /// Returns an error if no matching signer is found or the address is not unlocked + pub fn for_sender(&self, addr: &Address) -> Result> { + match self { + Self::Unlocked(unlocked) => { + if !unlocked.contains(addr) { + bail!("Sender address {:?} is not unlocked", addr) + } + Ok(SendTransactionKind::Unlocked(*addr)) + } + Self::Raw(wallets) => { + if let Some(wallet) = wallets.get(addr) { + Ok(SendTransactionKind::Raw(wallet)) + } else { + bail!("No matching signer for {:?} found", addr) + } + } + } + } + + /// How many signers are set + pub fn signers_count(&self) -> usize { + match self { + Self::Unlocked(addr) => addr.len(), + Self::Raw(signers) => signers.len(), + } + } +} + +/// State after we have bundled all +/// [`TransactionWithMetadata`](crate::transaction::TransactionWithMetadata) objects into a single +/// [`ScriptSequenceKind`] object containing one or more script sequences. +pub struct BundledState { + pub args: ScriptArgs, + pub script_config: ScriptConfig, + pub script_wallets: ScriptWallets, + pub build_data: LinkedBuildData, + pub sequence: ScriptSequenceKind, +} + +impl BundledState { + pub async fn wait_for_pending(mut self) -> Result { + let progress = ScriptProgress::default(); + let progress_ref = &progress; + let futs = self + .sequence + .sequences_mut() + .iter_mut() + .enumerate() + .map(|(sequence_idx, sequence)| async move { + let rpc_url = sequence.rpc_url(); + let provider = Arc::new(get_http_provider(rpc_url)); + progress_ref.wait_for_pending(sequence_idx, sequence, &provider).await + }) + .collect::>(); + + let errors = join_all(futs).await.into_iter().filter_map(Result::err).collect::>(); + + self.sequence.save(true, false)?; + + if !errors.is_empty() { + return Err(eyre::eyre!("{}", errors.iter().format("\n"))); + } + + Ok(self) + } + + /// Broadcasts transactions from all sequences. + pub async fn broadcast(mut self) -> Result { + let required_addresses = self + .sequence + .sequences() + .iter() + .flat_map(|sequence| { + sequence + .transactions() + .map(|tx| (tx.from().expect("No sender for onchain transaction!"))) + }) + .collect::>(); + + if required_addresses.contains(&Config::DEFAULT_SENDER) { + eyre::bail!( + "You seem to be using Foundry's default sender. Be sure to set your own --sender." + ); + } + + let send_kind = if self.args.unlocked { + SendTransactionsKind::Unlocked(required_addresses) + } else { + let signers = self.script_wallets.into_multi_wallet().into_signers()?; + let mut missing_addresses = Vec::new(); + + for addr in &required_addresses { + if !signers.contains_key(addr) { + missing_addresses.push(addr); + } + } + + if !missing_addresses.is_empty() { + eyre::bail!( + "No associated wallet for addresses: {:?}. Unlocked wallets: {:?}", + missing_addresses, + signers.keys().collect::>() + ); + } + + let signers = signers + .into_iter() + .map(|(addr, signer)| (addr, EthereumWallet::new(signer))) + .collect(); + + SendTransactionsKind::Raw(signers) + }; + + let progress = ScriptProgress::default(); + + for i in 0..self.sequence.sequences().len() { + let mut sequence = self.sequence.sequences_mut().get_mut(i).unwrap(); + + let provider = Arc::new(try_get_http_provider(sequence.rpc_url())?); + let already_broadcasted = sequence.receipts.len(); + + let seq_progress = progress.get_sequence_progress(i, sequence); + + if already_broadcasted < sequence.transactions.len() { + let is_legacy = Chain::from(sequence.chain).is_legacy() || self.args.legacy; + // Make a one-time gas price estimation + let (gas_price, eip1559_fees) = match ( + is_legacy, + self.args.with_gas_price, + self.args.priority_gas_price, + ) { + (true, Some(gas_price), _) => (Some(gas_price.to()), None), + (true, None, _) => (Some(provider.get_gas_price().await?), None), + (false, Some(max_fee_per_gas), Some(max_priority_fee_per_gas)) => ( + None, + Some(Eip1559Estimation { + max_fee_per_gas: max_fee_per_gas.to(), + max_priority_fee_per_gas: max_priority_fee_per_gas.to(), + }), + ), + (false, _, _) => { + let mut fees = provider.estimate_eip1559_fees(None).await.wrap_err("Failed to estimate EIP1559 fees. This chain might not support EIP1559, try adding --legacy to your command.")?; + + if let Some(gas_price) = self.args.with_gas_price { + fees.max_fee_per_gas = gas_price.to(); + } + + if let Some(priority_gas_price) = self.args.priority_gas_price { + fees.max_priority_fee_per_gas = priority_gas_price.to(); + } + + (None, Some(fees)) + } + }; + + // Iterate through transactions, matching the `from` field with the associated + // wallet. Then send the transaction. Panics if we find a unknown `from` + let transactions = sequence + .transactions + .iter() + .skip(already_broadcasted) + .map(|tx_with_metadata| { + let tx = tx_with_metadata.tx(); + let from = tx.from().expect("No sender for onchain transaction!"); + + let kind = send_kind.for_sender(&from)?; + let is_fixed_gas_limit = tx_with_metadata.is_fixed_gas_limit; + let zk = tx_with_metadata.zk.clone(); + + let mut tx = tx.clone(); + tx.set_chain_id(sequence.chain); + + // Set TxKind::Create explicityly to satify `check_reqd_fields` in alloy + if tx.to().is_none() { + tx.set_create(); + } + + if let Some(gas_price) = gas_price { + tx.set_gas_price(gas_price); + } else { + let eip1559_fees = eip1559_fees.expect("was set above"); + tx.set_max_priority_fee_per_gas(eip1559_fees.max_priority_fee_per_gas); + tx.set_max_fee_per_gas(eip1559_fees.max_fee_per_gas); + } + + Ok((tx, zk, kind, is_fixed_gas_limit)) + }) + .collect::>>()?; + + let estimate_via_rpc = + has_different_gas_calc(sequence.chain) || self.args.skip_simulation; + + // We only wait for a transaction receipt before sending the next transaction, if + // there is more than one signer. There would be no way of assuring + // their order otherwise. + // Or if the chain does not support batched transactions (eg. Arbitrum). + // Or if we need to invoke eth_estimateGas before sending transactions. + let sequential_broadcast = estimate_via_rpc || + self.args.slow || + send_kind.signers_count() != 1 || + !has_batch_support(sequence.chain); + + // We send transactions and wait for receipts in batches. + let batch_size = if sequential_broadcast { 1 } else { self.args.batch_size }; + let mut index = already_broadcasted; + + for (batch_number, batch) in transactions.chunks(batch_size).enumerate() { + let mut pending_transactions = vec![]; + + seq_progress.inner.write().set_status(&format!( + "Sending transactions [{} - {}]", + batch_number * batch_size, + batch_number * batch_size + std::cmp::min(batch_size, batch.len()) - 1 + )); + for (tx, zk, kind, is_fixed_gas_limit) in batch { + let fut = send_transaction( + provider.clone(), + tx.clone(), + zk.as_ref(), + kind.clone(), + sequential_broadcast, + *is_fixed_gas_limit, + estimate_via_rpc, + self.args.gas_estimate_multiplier, + ); + pending_transactions.push(fut); + } + + if !pending_transactions.is_empty() { + let mut buffer = futures::stream::iter(pending_transactions).buffered(7); + + while let Some(tx_hash) = buffer.next().await { + let tx_hash = tx_hash.wrap_err("Failed to send transaction")?; + sequence.add_pending(index, tx_hash); + + // Checkpoint save + self.sequence.save(true, false)?; + sequence = self.sequence.sequences_mut().get_mut(i).unwrap(); + + seq_progress.inner.write().tx_sent(tx_hash); + index += 1; + } + + // Checkpoint save + self.sequence.save(true, false)?; + sequence = self.sequence.sequences_mut().get_mut(i).unwrap(); + + progress.wait_for_pending(i, sequence, &provider).await? + } + // Checkpoint save + self.sequence.save(true, false)?; + sequence = self.sequence.sequences_mut().get_mut(i).unwrap(); + } + } + + let (total_gas, total_gas_price, total_paid) = + sequence.receipts.iter().fold((0, 0, 0), |acc, receipt| { + let gas_used = receipt.gas_used; + let gas_price = receipt.effective_gas_price; + (acc.0 + gas_used, acc.1 + gas_price, acc.2 + gas_used * gas_price) + }); + let paid = format_units(total_paid, 18).unwrap_or_else(|_| "N/A".to_string()); + let avg_gas_price = format_units(total_gas_price / sequence.receipts.len() as u128, 9) + .unwrap_or_else(|_| "N/A".to_string()); + + seq_progress.inner.write().set_status(&format!( + "Total Paid: {} ETH ({} gas * avg {} gwei)\n", + paid.trim_end_matches('0'), + total_gas, + avg_gas_price.trim_end_matches('0').trim_end_matches('.') + )); + seq_progress.inner.write().finish(); + } + + shell::println("\n\n==========================")?; + shell::println("\nONCHAIN EXECUTION COMPLETE & SUCCESSFUL.")?; + + Ok(BroadcastedState { + args: self.args, + script_config: self.script_config, + build_data: self.build_data, + sequence: self.sequence, + }) + } + + pub fn verify_preflight_check(&self) -> Result<()> { + for sequence in self.sequence.sequences() { + if self.args.verifier.verifier == VerificationProviderType::Etherscan && + self.script_config + .config + .get_etherscan_api_key(Some(sequence.chain.into())) + .is_none() + { + eyre::bail!("Missing etherscan key for chain {}", sequence.chain); + } + } + + Ok(()) + } +} diff --git a/crates/script/src/build.rs b/crates/script/src/build.rs new file mode 100644 index 000000000..c01c4c52a --- /dev/null +++ b/crates/script/src/build.rs @@ -0,0 +1,388 @@ +use crate::{ + broadcast::BundledState, + execute::LinkedState, + multi_sequence::MultiChainSequence, + sequence::{ScriptSequence, ScriptSequenceKind}, + ScriptArgs, ScriptConfig, +}; +use alloy_primitives::{Bytes, B256}; +use alloy_provider::Provider; +use eyre::{OptionExt, Result}; +use foundry_cheatcodes::ScriptWallets; +use foundry_common::{ + compile::{ContractSources, ProjectCompiler}, + provider::try_get_http_provider, + ContractData, ContractsByArtifact, +}; +use foundry_compilers::{ + artifacts::{BytecodeObject, Libraries}, + compilers::{multi::MultiCompilerLanguage, Language}, + info::ContractInfo, + solc::SolcLanguage, + utils::source_files_iter, + ArtifactId, ProjectCompileOutput, +}; +use foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER; +use foundry_linking::Linker; +use foundry_zksync_compiler::DualCompiledContracts; +use std::{path::PathBuf, str::FromStr, sync::Arc}; + +/// Container for the compiled contracts. +#[derive(Debug)] +pub struct BuildData { + /// Root of the project. + pub project_root: PathBuf, + /// The compiler output. + pub output: ProjectCompileOutput, + /// ID of target contract artifact. + pub target: ArtifactId, + pub dual_compiled_contracts: Option, +} + +impl BuildData { + pub fn get_linker(&self) -> Linker<'_> { + Linker::new(self.project_root.clone(), self.output.artifact_ids().collect()) + } + + /// Links contracts. Uses CREATE2 linking when possible, otherwise falls back to + /// default linking with sender nonce and address. + pub async fn link(self, script_config: &ScriptConfig) -> Result { + let can_use_create2 = if let Some(fork_url) = &script_config.evm_opts.fork_url { + let provider = try_get_http_provider(fork_url)?; + let deployer_code = provider.get_code_at(DEFAULT_CREATE2_DEPLOYER).await?; + + !deployer_code.is_empty() + } else { + // If --fork-url is not provided, we are just simulating the script. + true + }; + + let known_libraries = script_config.config.libraries_with_remappings()?; + + let maybe_create2_link_output = can_use_create2 + .then(|| { + self.get_linker() + .link_with_create2( + known_libraries.clone(), + DEFAULT_CREATE2_DEPLOYER, + script_config.config.create2_library_salt, + &self.target, + ) + .ok() + }) + .flatten(); + + let (libraries, predeploy_libs) = if let Some(output) = maybe_create2_link_output { + ( + output.libraries, + ScriptPredeployLibraries::Create2( + output.libs_to_deploy, + script_config.config.create2_library_salt, + ), + ) + } else { + let output = self.get_linker().link_with_nonce_or_address( + known_libraries, + script_config.evm_opts.sender, + script_config.sender_nonce, + [&self.target], + )?; + + (output.libraries, ScriptPredeployLibraries::Default(output.libs_to_deploy)) + }; + + LinkedBuildData::new(libraries, predeploy_libs, self) + } + + /// Links the build data with the given libraries. Expects supplied libraries set being enough + /// to fully link target contract. + pub fn link_with_libraries(self, libraries: Libraries) -> Result { + LinkedBuildData::new(libraries, ScriptPredeployLibraries::Default(Vec::new()), self) + } +} + +#[derive(Debug)] +pub enum ScriptPredeployLibraries { + Default(Vec), + Create2(Vec, B256), +} + +impl ScriptPredeployLibraries { + pub fn libraries_count(&self) -> usize { + match self { + Self::Default(libs) => libs.len(), + Self::Create2(libs, _) => libs.len(), + } + } +} + +/// Container for the linked contracts and their dependencies +#[derive(Debug)] +pub struct LinkedBuildData { + /// Original build data, might be used to relink this object with different libraries. + pub build_data: BuildData, + /// Known fully linked contracts. + pub known_contracts: ContractsByArtifact, + /// Libraries used to link the contracts. + pub libraries: Libraries, + /// Libraries that need to be deployed by sender before script execution. + pub predeploy_libraries: ScriptPredeployLibraries, + /// Source files of the contracts. Used by debugger. + pub sources: ContractSources, +} + +impl LinkedBuildData { + pub fn new( + libraries: Libraries, + predeploy_libraries: ScriptPredeployLibraries, + build_data: BuildData, + ) -> Result { + let sources = ContractSources::from_project_output( + &build_data.output, + &build_data.project_root, + Some(&libraries), + )?; + + //TODO: zk contracts? + let known_contracts = + ContractsByArtifact::new(build_data.get_linker().get_linked_artifacts(&libraries)?); + + Ok(Self { build_data, known_contracts, libraries, predeploy_libraries, sources }) + } + + /// Fetches target bytecode from linked contracts. + pub fn get_target_contract(&self) -> Result<&ContractData> { + self.known_contracts + .get(&self.build_data.target) + .ok_or_eyre("target not found in linked artifacts") + } +} + +/// First state basically containing only inputs of the user. +pub struct PreprocessedState { + pub args: ScriptArgs, + pub script_config: ScriptConfig, + pub script_wallets: ScriptWallets, +} + +impl PreprocessedState { + /// Parses user input and compiles the contracts depending on script target. + /// After compilation, finds exact [ArtifactId] of the target contract. + pub fn compile(self) -> Result { + let Self { args, script_config, script_wallets } = self; + let project = script_config.config.project()?; + + let mut target_name = args.target_contract.clone(); + + // If we've received correct path, use it as target_path + // Otherwise, parse input as : and use the path from the contract info, if + // present. + let target_path = if let Ok(path) = dunce::canonicalize(&args.path) { + path + } else { + let contract = ContractInfo::from_str(&args.path)?; + target_name = Some(contract.name.clone()); + if let Some(path) = contract.path { + dunce::canonicalize(path)? + } else { + project.find_contract_path(contract.name.as_str())? + } + }; + + let sources_to_compile = source_files_iter( + project.paths.sources.as_path(), + MultiCompilerLanguage::FILE_EXTENSIONS, + ) + .chain([target_path.to_path_buf()]); + + let output = ProjectCompiler::new() + .quiet_if(args.opts.silent) + .files(sources_to_compile) + .compile(&project)?; + + // ZK + let dual_compiled_contracts = if script_config.config.zksync.should_compile() { + let zk_project = foundry_zksync_compiler::create_project( + &script_config.config, + script_config.config.cache, + false, + )?; + let sources_to_compile = + source_files_iter(project.paths.sources.as_path(), SolcLanguage::FILE_EXTENSIONS) + .chain([target_path.to_path_buf()]); + + let zk_compiler = + ProjectCompiler::new().quiet_if(args.opts.silent).files(sources_to_compile); + + let zk_output = zk_compiler + .zksync_compile(&zk_project, script_config.config.zksync.avoid_contracts())?; + Some(DualCompiledContracts::new(&output, &zk_output, &project.paths)) + } else { + None + }; + + let mut target_id: Option = None; + + // Find target artfifact id by name and path in compilation artifacts. + for (id, contract) in output.artifact_ids().filter(|(id, _)| id.source == target_path) { + if let Some(name) = &target_name { + if id.name != *name { + continue; + } + } else if contract.abi.as_ref().map_or(true, |abi| abi.is_empty()) || + contract.bytecode.as_ref().map_or(true, |b| match &b.object { + BytecodeObject::Bytecode(b) => b.is_empty(), + BytecodeObject::Unlinked(_) => false, + }) + { + // Ignore contracts with empty abi or linked bytecode of length 0 which are + // interfaces/abstract contracts/libraries. + continue; + } + + if let Some(target) = target_id { + // We might have multiple artifacts for the same contract but with different + // solc versions. Their names will have form of {name}.0.X.Y, so we are + // stripping versions off before comparing them. + let target_name = target.name.split('.').next().unwrap(); + let id_name = id.name.split('.').next().unwrap(); + if target_name != id_name { + eyre::bail!("Multiple contracts in the target path. Please specify the contract name with `--tc ContractName`") + } + } + target_id = Some(id); + } + + let target = target_id.ok_or_eyre("Could not find target contract")?; + + Ok(CompiledState { + args, + script_config, + script_wallets, + build_data: BuildData { + output, + target, + project_root: project.root().clone(), + dual_compiled_contracts, + }, + }) + } +} + +/// State after we have determined and compiled target contract to be executed. +pub struct CompiledState { + pub args: ScriptArgs, + pub script_config: ScriptConfig, + pub script_wallets: ScriptWallets, + pub build_data: BuildData, +} + +impl CompiledState { + /// Uses provided sender address to compute library addresses and link contracts with them. + pub async fn link(self) -> Result { + let Self { args, script_config, script_wallets, build_data } = self; + + let build_data = build_data.link(&script_config).await?; + + Ok(LinkedState { args, script_config, script_wallets, build_data }) + } + + /// Tries loading the resumed state from the cache files, skipping simulation stage. + pub async fn resume(self) -> Result { + let chain = if self.args.multi { + None + } else { + let fork_url = self.script_config.evm_opts.fork_url.clone().ok_or_eyre("Missing --fork-url field, if you were trying to broadcast a multi-chain sequence, please use --multi flag")?; + let provider = Arc::new(try_get_http_provider(fork_url)?); + Some(provider.get_chain_id().await?) + }; + + let sequence = match self.try_load_sequence(chain, false) { + Ok(sequence) => sequence, + Err(_) => { + // If the script was simulated, but there was no attempt to broadcast yet, + // try to read the script sequence from the `dry-run/` folder + let mut sequence = self.try_load_sequence(chain, true)?; + + // If sequence was in /dry-run, Update its paths so it is not saved into /dry-run + // this time as we are about to broadcast it. + sequence.update_paths_to_broadcasted( + &self.script_config.config, + &self.args.sig, + &self.build_data.target, + )?; + + sequence.save(true, true)?; + sequence + } + }; + + let (args, build_data, script_wallets, script_config) = if !self.args.unlocked { + let mut froms = sequence.sequences().iter().flat_map(|s| { + s.transactions + .iter() + .skip(s.receipts.len()) + .map(|t| t.transaction.from.expect("from is missing in script artifact")) + }); + + let available_signers = self + .script_wallets + .signers() + .map_err(|e| eyre::eyre!("Failed to get available signers: {}", e))?; + + if !froms.all(|from| available_signers.contains(&from)) { + // IF we are missing required signers, execute script as we might need to collect + // private keys from the execution. + let executed = self.link().await?.prepare_execution().await?.execute().await?; + ( + executed.args, + executed.build_data.build_data, + executed.script_wallets, + executed.script_config, + ) + } else { + (self.args, self.build_data, self.script_wallets, self.script_config) + } + } else { + (self.args, self.build_data, self.script_wallets, self.script_config) + }; + + // Collect libraries from sequence and link contracts with them. + let libraries = match sequence { + ScriptSequenceKind::Single(ref seq) => Libraries::parse(&seq.libraries)?, + // Library linking is not supported for multi-chain sequences + ScriptSequenceKind::Multi(_) => Libraries::default(), + }; + + let linked_build_data = build_data.link_with_libraries(libraries)?; + + Ok(BundledState { + args, + script_config, + script_wallets, + build_data: linked_build_data, + sequence, + }) + } + + fn try_load_sequence(&self, chain: Option, dry_run: bool) -> Result { + if let Some(chain) = chain { + let sequence = ScriptSequence::load( + &self.script_config.config, + &self.args.sig, + &self.build_data.target, + chain, + dry_run, + )?; + Ok(ScriptSequenceKind::Single(sequence)) + } else { + let sequence = MultiChainSequence::load( + &self.script_config.config, + &self.args.sig, + &self.build_data.target, + dry_run, + )?; + Ok(ScriptSequenceKind::Multi(sequence)) + } + } +} diff --git a/crates/script/src/execute.rs b/crates/script/src/execute.rs new file mode 100644 index 000000000..af4111bd0 --- /dev/null +++ b/crates/script/src/execute.rs @@ -0,0 +1,505 @@ +use crate::{ + build::{CompiledState, LinkedBuildData}, + simulate::PreSimulationState, + ScriptArgs, ScriptConfig, +}; + +use super::{runner::ScriptRunner, JsonResult, NestedValue, ScriptResult}; +use alloy_dyn_abi::FunctionExt; +use alloy_json_abi::{Function, InternalType, JsonAbi}; +use alloy_primitives::{Address, Bytes}; +use alloy_provider::Provider; +use async_recursion::async_recursion; +use eyre::{OptionExt, Result}; +use foundry_cheatcodes::ScriptWallets; +use foundry_cli::utils::{ensure_clean_constructor, needs_setup}; +use foundry_common::{ + fmt::{format_token, format_token_raw}, + provider::get_http_provider, + shell, ContractsByArtifact, +}; +use foundry_config::{Config, NamedChain}; +use foundry_debugger::Debugger; +use foundry_evm::{ + decode::decode_console_logs, + inspectors::cheatcodes::BroadcastableTransactions, + traces::{ + identifier::{SignaturesIdentifier, TraceIdentifiers}, + render_trace_arena, CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, + }, +}; +use futures::future::join_all; +use itertools::Itertools; +use std::collections::{HashMap, HashSet}; +use yansi::Paint; + +/// State after linking, contains the linked build data along with library addresses and optional +/// array of libraries that need to be predeployed. +pub struct LinkedState { + pub args: ScriptArgs, + pub script_config: ScriptConfig, + pub script_wallets: ScriptWallets, + pub build_data: LinkedBuildData, +} + +/// Container for data we need for execution which can only be obtained after linking stage. +#[derive(Debug)] +pub struct ExecutionData { + /// Function to call. + pub func: Function, + /// Calldata to pass to the target contract. + pub calldata: Bytes, + /// Bytecode of the target contract. + pub bytecode: Bytes, + /// ABI of the target contract. + pub abi: JsonAbi, +} + +impl LinkedState { + /// Given linked and compiled artifacts, prepares data we need for execution. + /// This includes the function to call and the calldata to pass to it. + pub async fn prepare_execution(self) -> Result { + let Self { args, script_config, script_wallets, build_data } = self; + + let target_contract = build_data.get_target_contract()?; + + //TODO: zk bytecode? factory deps? + let bytecode = target_contract.bytecode().ok_or_eyre("target contract has no bytecode")?; + + let (func, calldata) = args.get_method_and_calldata(&target_contract.abi)?; + + ensure_clean_constructor(&target_contract.abi)?; + + Ok(PreExecutionState { + args, + script_config, + script_wallets, + execution_data: ExecutionData { + func, + calldata, + bytecode: bytecode.clone(), + abi: target_contract.abi.clone(), + }, + build_data, + }) + } +} + +/// Same as [LinkedState], but also contains [ExecutionData]. +#[derive(Debug)] +pub struct PreExecutionState { + pub args: ScriptArgs, + pub script_config: ScriptConfig, + pub script_wallets: ScriptWallets, + pub build_data: LinkedBuildData, + pub execution_data: ExecutionData, +} + +impl PreExecutionState { + /// Executes the script and returns the state after execution. + /// Might require executing script twice in cases when we determine sender from execution. + #[async_recursion] + pub async fn execute(mut self) -> Result { + let mut runner = self + .script_config + .get_runner_with_cheatcodes( + self.build_data.known_contracts.clone(), + self.script_wallets.clone(), + self.args.debug, + self.build_data.build_data.target.clone(), + self.build_data.build_data.dual_compiled_contracts.clone().unwrap_or_default(), + ) + .await?; + let result = self.execute_with_runner(&mut runner).await?; + + // If we have a new sender from execution, we need to use it to deploy libraries and relink + // contracts. + if let Some(new_sender) = self.maybe_new_sender(result.transactions.as_ref())? { + self.script_config.update_sender(new_sender).await?; + + // Rollback to rerun linking with the new sender. + let state = CompiledState { + args: self.args, + script_config: self.script_config, + script_wallets: self.script_wallets, + build_data: self.build_data.build_data, + }; + + return state.link().await?.prepare_execution().await?.execute().await; + } + + Ok(ExecutedState { + args: self.args, + script_config: self.script_config, + script_wallets: self.script_wallets, + build_data: self.build_data, + execution_data: self.execution_data, + execution_result: result, + }) + } + + /// Executes the script using the provided runner and returns the [ScriptResult]. + pub async fn execute_with_runner(&self, runner: &mut ScriptRunner) -> Result { + let (address, mut setup_result) = runner.setup( + &self.build_data.predeploy_libraries, + self.execution_data.bytecode.clone(), + needs_setup(&self.execution_data.abi), + self.script_config.sender_nonce, + self.args.broadcast, + self.script_config.evm_opts.fork_url.is_none(), + )?; + + if setup_result.success { + let script_result = runner.script(address, self.execution_data.calldata.clone())?; + + setup_result.success &= script_result.success; + setup_result.gas_used = script_result.gas_used; + setup_result.logs.extend(script_result.logs); + setup_result.traces.extend(script_result.traces); + setup_result.debug = script_result.debug; + setup_result.labeled_addresses.extend(script_result.labeled_addresses); + setup_result.returned = script_result.returned; + setup_result.breakpoints = script_result.breakpoints; + + match (&mut setup_result.transactions, script_result.transactions) { + (Some(txs), Some(new_txs)) => { + txs.extend(new_txs); + } + (None, Some(new_txs)) => { + setup_result.transactions = Some(new_txs); + } + _ => {} + } + } + + Ok(setup_result) + } + + /// It finds the deployer from the running script and uses it to predeploy libraries. + /// + /// If there are multiple candidate addresses, it skips everything and lets `--sender` deploy + /// them instead. + fn maybe_new_sender( + &self, + transactions: Option<&BroadcastableTransactions>, + ) -> Result> { + let mut new_sender = None; + + if let Some(txs) = transactions { + // If the user passed a `--sender` don't check anything. + if self.build_data.predeploy_libraries.libraries_count() > 0 && + self.args.evm_opts.sender.is_none() + { + for tx in txs.iter() { + if tx.transaction.to.is_none() { + let sender = tx.transaction.from.expect("no sender"); + if let Some(ns) = new_sender { + if sender != ns { + shell::println("You have more than one deployer who could predeploy libraries. Using `--sender` instead.")?; + return Ok(None); + } + } else if sender != self.script_config.evm_opts.sender { + new_sender = Some(sender); + } + } + } + } + } + Ok(new_sender) + } +} + +/// Container for information about RPC-endpoints used during script execution. +pub struct RpcData { + /// Unique list of rpc urls present. + pub total_rpcs: HashSet, + /// If true, one of the transactions did not have a rpc. + pub missing_rpc: bool, +} + +impl RpcData { + /// Iterates over script transactions and collects RPC urls. + fn from_transactions(txs: &BroadcastableTransactions) -> Self { + let missing_rpc = txs.iter().any(|tx| tx.rpc.is_none()); + let total_rpcs = + txs.iter().filter_map(|tx| tx.rpc.as_ref().cloned()).collect::>(); + + Self { total_rpcs, missing_rpc } + } + + /// Returns true if script might be multi-chain. + /// Returns false positive in case when missing rpc is the same as the only rpc present. + pub fn is_multi_chain(&self) -> bool { + self.total_rpcs.len() > 1 || (self.missing_rpc && !self.total_rpcs.is_empty()) + } + + /// Checks if all RPCs support EIP-3855. Prints a warning if not. + async fn check_shanghai_support(&self) -> Result<()> { + let chain_ids = self.total_rpcs.iter().map(|rpc| async move { + let provider = get_http_provider(rpc); + let id = provider.get_chain_id().await.ok()?; + NamedChain::try_from(id).ok() + }); + + let chains = join_all(chain_ids).await; + let iter = chains.iter().flatten().map(|c| (c.supports_shanghai(), c)); + if iter.clone().any(|(s, _)| !s) { + let msg = format!( + "\ +EIP-3855 is not supported in one or more of the RPCs used. +Unsupported Chain IDs: {}. +Contracts deployed with a Solidity version equal or higher than 0.8.20 might not work properly. +For more information, please see https://eips.ethereum.org/EIPS/eip-3855", + iter.filter(|(supported, _)| !supported) + .map(|(_, chain)| *chain as u64) + .format(", ") + ); + shell::println(msg.yellow())?; + } + Ok(()) + } +} + +/// Container for data being collected after execution. +pub struct ExecutionArtifacts { + /// Trace decoder used to decode traces. + pub decoder: CallTraceDecoder, + /// Return values from the execution result. + pub returns: HashMap, + /// Information about RPC endpoints used during script execution. + pub rpc_data: RpcData, +} + +/// State after the script has been executed. +pub struct ExecutedState { + pub args: ScriptArgs, + pub script_config: ScriptConfig, + pub script_wallets: ScriptWallets, + pub build_data: LinkedBuildData, + pub execution_data: ExecutionData, + pub execution_result: ScriptResult, +} + +impl ExecutedState { + /// Collects the data we need for simulation and various post-execution tasks. + pub async fn prepare_simulation(self) -> Result { + let returns = self.get_returns()?; + + let decoder = self.build_trace_decoder(&self.build_data.known_contracts).await?; + + let txs = self.execution_result.transactions.clone().unwrap_or_default(); + let rpc_data = RpcData::from_transactions(&txs); + + if rpc_data.is_multi_chain() { + shell::eprintln(format!( + "{}", + "Multi chain deployment is still under development. Use with caution.".yellow() + ))?; + if !self.build_data.libraries.is_empty() { + eyre::bail!( + "Multi chain deployment does not support library linking at the moment." + ) + } + } + rpc_data.check_shanghai_support().await?; + + Ok(PreSimulationState { + args: self.args, + script_config: self.script_config, + script_wallets: self.script_wallets, + build_data: self.build_data, + execution_data: self.execution_data, + execution_result: self.execution_result, + execution_artifacts: ExecutionArtifacts { decoder, returns, rpc_data }, + }) + } + + /// Builds [CallTraceDecoder] from the execution result and known contracts. + async fn build_trace_decoder( + &self, + known_contracts: &ContractsByArtifact, + ) -> Result { + let mut decoder = CallTraceDecoderBuilder::new() + .with_labels(self.execution_result.labeled_addresses.clone()) + .with_verbosity(self.script_config.evm_opts.verbosity) + .with_known_contracts(known_contracts) + .with_signature_identifier(SignaturesIdentifier::new( + Config::foundry_cache_dir(), + self.script_config.config.offline, + )?) + .build(); + + let mut identifier = TraceIdentifiers::new().with_local(known_contracts).with_etherscan( + &self.script_config.config, + self.script_config.evm_opts.get_remote_chain_id().await, + )?; + + // Decoding traces using etherscan is costly as we run into rate limits, + // causing scripts to run for a very long time unnecessarily. + // Therefore, we only try and use etherscan if the user has provided an API key. + let should_use_etherscan_traces = self.script_config.config.etherscan_api_key.is_some(); + if !should_use_etherscan_traces { + identifier.etherscan = None; + } + + for (_, trace) in &self.execution_result.traces { + decoder.identify(trace, &mut identifier); + } + + Ok(decoder) + } + + /// Collects the return values from the execution result. + fn get_returns(&self) -> Result> { + let mut returns = HashMap::new(); + let returned = &self.execution_result.returned; + let func = &self.execution_data.func; + + match func.abi_decode_output(returned, false) { + Ok(decoded) => { + for (index, (token, output)) in decoded.iter().zip(&func.outputs).enumerate() { + let internal_type = + output.internal_type.clone().unwrap_or(InternalType::Other { + contract: None, + ty: "unknown".to_string(), + }); + + let label = if !output.name.is_empty() { + output.name.to_string() + } else { + index.to_string() + }; + + returns.insert( + label, + NestedValue { + internal_type: internal_type.to_string(), + value: format_token_raw(token), + }, + ); + } + } + Err(_) => { + shell::println(format!("{returned:?}"))?; + } + } + + Ok(returns) + } +} + +impl PreSimulationState { + pub fn show_json(&self) -> Result<()> { + let result = &self.execution_result; + + let console_logs = decode_console_logs(&result.logs); + let output = JsonResult { + logs: console_logs, + gas_used: result.gas_used, + returns: self.execution_artifacts.returns.clone(), + }; + let j = serde_json::to_string(&output)?; + shell::println(j)?; + + if !self.execution_result.success { + return Err(eyre::eyre!( + "script failed: {}", + &self.execution_artifacts.decoder.revert_decoder.decode(&result.returned[..], None) + )); + } + + Ok(()) + } + + pub async fn show_traces(&self) -> Result<()> { + let verbosity = self.script_config.evm_opts.verbosity; + let func = &self.execution_data.func; + let result = &self.execution_result; + let decoder = &self.execution_artifacts.decoder; + + if !result.success || verbosity > 3 { + if result.traces.is_empty() { + warn!(verbosity, "no traces"); + } + + shell::println("Traces:")?; + for (kind, trace) in &result.traces { + let should_include = match kind { + TraceKind::Setup => verbosity >= 5, + TraceKind::Execution => verbosity > 3, + _ => false, + } || !result.success; + + if should_include { + shell::println(render_trace_arena(trace, decoder).await?)?; + } + } + shell::println(String::new())?; + } + + if result.success { + shell::println(format!("{}", "Script ran successfully.".green()))?; + } + + if self.script_config.evm_opts.fork_url.is_none() { + shell::println(format!("Gas used: {}", result.gas_used))?; + } + + if result.success && !result.returned.is_empty() { + shell::println("\n== Return ==")?; + match func.abi_decode_output(&result.returned, false) { + Ok(decoded) => { + for (index, (token, output)) in decoded.iter().zip(&func.outputs).enumerate() { + let internal_type = + output.internal_type.clone().unwrap_or(InternalType::Other { + contract: None, + ty: "unknown".to_string(), + }); + + let label = if !output.name.is_empty() { + output.name.to_string() + } else { + index.to_string() + }; + shell::println(format!( + "{}: {internal_type} {}", + label.trim_end(), + format_token(token) + ))?; + } + } + Err(_) => { + shell::println(format!("{:x?}", (&result.returned)))?; + } + } + } + + let console_logs = decode_console_logs(&result.logs); + if !console_logs.is_empty() { + shell::println("\n== Logs ==")?; + for log in console_logs { + shell::println(format!(" {log}"))?; + } + } + + if !result.success { + return Err(eyre::eyre!( + "script failed: {}", + &self.execution_artifacts.decoder.revert_decoder.decode(&result.returned[..], None) + )); + } + + Ok(()) + } + + pub fn run_debugger(&self) -> Result<()> { + let mut debugger = Debugger::builder() + .debug_arenas(self.execution_result.debug.as_deref().unwrap_or_default()) + .decoder(&self.execution_artifacts.decoder) + .sources(self.build_data.sources.clone()) + .breakpoints(self.execution_result.breakpoints.clone()) + .build(); + debugger.try_run()?; + Ok(()) + } +} diff --git a/crates/forge/bin/cmd/script/mod.rs b/crates/script/src/lib.rs similarity index 53% rename from crates/forge/bin/cmd/script/mod.rs rename to crates/script/src/lib.rs index ad5a6b851..388de5f22 100644 --- a/crates/forge/bin/cmd/script/mod.rs +++ b/crates/script/src/lib.rs @@ -1,65 +1,68 @@ -use super::{build::BuildArgs, retry::RetryArgs}; -use alloy_dyn_abi::FunctionExt; -use alloy_json_abi::{Function, InternalType, JsonAbi}; -use alloy_primitives::{Address, Bytes, Log, U256, U64}; -use alloy_rpc_types::request::TransactionRequest; +//! # foundry-script +//! +//! Smart contract scripting. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +#[macro_use] +extern crate tracing; + +use self::transaction::AdditionalContract; +use crate::runner::ScriptRunner; +use alloy_json_abi::{Function, JsonAbi}; +use alloy_primitives::{Address, Bytes, Log, TxKind, U256}; +use alloy_signer::Signer; +use broadcast::next_nonce; +use build::PreprocessedState; use clap::{Parser, ValueHint}; use dialoguer::Confirm; -use ethers_providers::{Http, Middleware}; -use eyre::{ContextCompat, Result, WrapErr}; -use forge::{ - backend::Backend, - debug::DebugArena, - decode::decode_console_logs, - opts::EvmOpts, - traces::{ - identifier::{EtherscanIdentifier, LocalTraceIdentifier, SignaturesIdentifier}, - render_trace_arena, CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, Traces, - }, -}; +use eyre::{ContextCompat, Result}; +use forge_verify::RetryArgs; +use foundry_cli::{opts::CoreBuildArgs, utils::LoadConfig}; use foundry_common::{ abi::{encode_function_args, get_func}, - errors::UnlinkedByteCode, evm::{Breakpoints, EvmArgs}, - fmt::{format_token, format_token_raw}, - provider::ethers::RpcUrl, shell, ContractsByArtifact, CONTRACT_MAX_SIZE, SELECTOR_LEN, }; -use foundry_compilers::{ - artifacts::{ContractBytecodeSome, Libraries}, - ArtifactId, -}; +use foundry_compilers::ArtifactId; use foundry_config::{ figment, figment::{ value::{Dict, Map}, Metadata, Profile, Provider, }, - Config, NamedChain, + Config, }; use foundry_evm::{ + backend::Backend, constants::DEFAULT_CREATE2_DEPLOYER, - decode::RevertDecoder, - inspectors::cheatcodes::{BroadcastableTransaction, BroadcastableTransactions}, + debug::DebugArena, + executors::ExecutorBuilder, + inspectors::{ + cheatcodes::{BroadcastableTransactions, ScriptWallets}, + CheatsConfig, + }, + opts::EvmOpts, + traces::Traces, }; use foundry_wallets::MultiWalletOpts; -use futures::future; -use itertools::Itertools; +use foundry_zksync_compiler::DualCompiledContracts; use serde::{Deserialize, Serialize}; -use std::collections::{BTreeMap, HashMap, HashSet}; +use std::collections::HashMap; use yansi::Paint; -mod artifacts; mod broadcast; mod build; -mod cmd; -mod executor; -mod multi; +mod execute; +mod multi_sequence; +mod progress; mod providers; mod receipts; mod runner; mod sequence; -pub mod transaction; +mod simulate; +mod transaction; mod verify; // Loads project's figment and merges the build cli arguments into it @@ -72,22 +75,22 @@ pub struct ScriptArgs { /// /// If multiple contracts exist in the same file you must specify the target contract with /// --target-contract. - #[clap(value_hint = ValueHint::FilePath)] + #[arg(value_hint = ValueHint::FilePath)] pub path: String, /// Arguments to pass to the script function. pub args: Vec, /// The name of the contract you want to run. - #[clap(long, visible_alias = "tc", value_name = "CONTRACT_NAME")] + #[arg(long, visible_alias = "tc", value_name = "CONTRACT_NAME")] pub target_contract: Option, /// The signature of the function you want to call in the contract, or raw calldata. - #[clap(long, short, default_value = "run()")] + #[arg(long, short, default_value = "run()")] pub sig: String, /// Max priority fee per gas for EIP1559 transactions. - #[clap( + #[arg( long, env = "ETH_PRIORITY_GAS_PRICE", value_parser = foundry_cli::utils::parse_ether_value, @@ -98,25 +101,30 @@ pub struct ScriptArgs { /// Use legacy transactions instead of EIP1559 ones. /// /// This is auto-enabled for common networks without EIP1559. - #[clap(long)] + #[arg(long)] pub legacy: bool, /// Broadcasts the transactions. - #[clap(long)] + #[arg(long)] pub broadcast: bool, + /// Batch size of transactions. + /// + /// This is ignored and set to 1 if batching is not available or `--slow` is enabled. + #[arg(long, default_value = "100")] + pub batch_size: usize, + /// Skips on-chain simulation. - #[clap(long)] + #[arg(long)] pub skip_simulation: bool, /// Relative percentage to multiply gas estimates by. - #[clap(long, short, default_value = "130")] + #[arg(long, short, default_value = "130")] pub gas_estimate_multiplier: u64, /// Send via `eth_sendTransaction` using the `--from` argument or `$ETH_FROM` as sender - #[clap( + #[arg( long, - requires = "sender", conflicts_with_all = &["private_key", "private_keys", "froms", "ledger", "trezor", "aws"], )] pub unlocked: bool, @@ -127,44 +135,44 @@ pub struct ScriptArgs { /// /// Example: If transaction N has a nonce of 22, then the account should have a nonce of 22, /// otherwise it fails. - #[clap(long)] + #[arg(long)] pub resume: bool, /// If present, --resume or --verify will be assumed to be a multi chain deployment. - #[clap(long)] + #[arg(long)] pub multi: bool, /// Open the script in the debugger. /// /// Takes precedence over broadcast. - #[clap(long)] + #[arg(long)] pub debug: bool, /// Makes sure a transaction is sent, /// only after its previous one has been confirmed and succeeded. - #[clap(long)] + #[arg(long)] pub slow: bool, /// Disables interactive prompts that might appear when deploying big contracts. /// /// For more info on the contract size limit, see EIP-170: - #[clap(long)] + #[arg(long)] pub non_interactive: bool, /// The Etherscan (or equivalent) API key - #[clap(long, env = "ETHERSCAN_API_KEY", value_name = "KEY")] + #[arg(long, env = "ETHERSCAN_API_KEY", value_name = "KEY")] pub etherscan_api_key: Option, /// Verifies all the contracts found in the receipts of a script, if any. - #[clap(long)] + #[arg(long)] pub verify: bool, /// Output results in JSON format. - #[clap(long)] + #[arg(long)] pub json: bool, /// Gas price for legacy transactions, or max fee per gas for EIP1559 transactions. - #[clap( + #[arg( long, env = "ETH_GAS_PRICE", value_parser = foundry_cli::utils::parse_ether_value, @@ -172,259 +180,126 @@ pub struct ScriptArgs { )] pub with_gas_price: Option, - #[clap(flatten)] - pub opts: BuildArgs, + #[command(flatten)] + pub opts: CoreBuildArgs, - #[clap(flatten)] + #[command(flatten)] pub wallets: MultiWalletOpts, - #[clap(flatten)] + #[command(flatten)] pub evm_opts: EvmArgs, - #[clap(flatten)] - pub verifier: super::verify::VerifierArgs, + #[command(flatten)] + pub verifier: forge_verify::VerifierArgs, - #[clap(flatten)] + #[command(flatten)] pub retry: RetryArgs, } -// === impl ScriptArgs === - impl ScriptArgs { - fn decode_traces( - &self, - script_config: &ScriptConfig, - result: &mut ScriptResult, - known_contracts: &ContractsByArtifact, - ) -> Result { - let verbosity = script_config.evm_opts.verbosity; - let mut etherscan_identifier = EtherscanIdentifier::new( - &script_config.config, - script_config.evm_opts.get_remote_chain_id(), - )?; - - let mut local_identifier = LocalTraceIdentifier::new(known_contracts); - let mut decoder = CallTraceDecoderBuilder::new() - .with_labels(result.labeled_addresses.clone()) - .with_verbosity(verbosity) - .with_local_identifier_abis(&local_identifier) - .with_signature_identifier(SignaturesIdentifier::new( - Config::foundry_cache_dir(), - script_config.config.offline, - )?) - .build(); - - // Decoding traces using etherscan is costly as we run into rate limits, - // causing scripts to run for a very long time unnecessarily. - // Therefore, we only try and use etherscan if the user has provided an API key. - let should_use_etherscan_traces = script_config.config.etherscan_api_key.is_some(); - - for (_, trace) in &mut result.traces { - decoder.identify(trace, &mut local_identifier); - if should_use_etherscan_traces { - decoder.identify(trace, &mut etherscan_identifier); - } - } - Ok(decoder) - } + async fn preprocess(self) -> Result { + let script_wallets = + ScriptWallets::new(self.wallets.get_multi_wallet().await?, self.evm_opts.sender); - fn get_returns( - &self, - script_config: &ScriptConfig, - returned: &Bytes, - ) -> Result> { - let func = script_config.called_function.as_ref().expect("There should be a function."); - let mut returns = HashMap::new(); - - match func.abi_decode_output(returned, false) { - Ok(decoded) => { - for (index, (token, output)) in decoded.iter().zip(&func.outputs).enumerate() { - let internal_type = - output.internal_type.clone().unwrap_or(InternalType::Other { - contract: None, - ty: "unknown".to_string(), - }); + let (config, mut evm_opts) = self.load_config_and_evm_opts_emit_warnings()?; - let label = if !output.name.is_empty() { - output.name.to_string() - } else { - index.to_string() - }; - - returns.insert( - label, - NestedValue { - internal_type: internal_type.to_string(), - value: format_token_raw(token), - }, - ); - } - } - Err(_) => { - shell::println(format!("{returned:?}"))?; - } + if let Some(sender) = self.maybe_load_private_key()? { + evm_opts.sender = sender; } - Ok(returns) + let script_config = ScriptConfig::new(config, evm_opts).await?; + + Ok(PreprocessedState { args: self, script_config, script_wallets }) } - async fn show_traces( - &self, - script_config: &ScriptConfig, - decoder: &CallTraceDecoder, - result: &mut ScriptResult, - ) -> Result<()> { - let verbosity = script_config.evm_opts.verbosity; - let func = script_config.called_function.as_ref().expect("There should be a function."); + /// Executes the script + pub async fn run_script(self) -> Result<()> { + trace!(target: "script", "executing script command"); + + let compiled = self.preprocess().await?.compile()?; - if !result.success || verbosity > 3 { - if result.traces.is_empty() { - warn!(verbosity, "no traces"); + // Move from `CompiledState` to `BundledState` either by resuming or executing and + // simulating script. + let bundled = if compiled.args.resume || (compiled.args.verify && !compiled.args.broadcast) + { + compiled.resume().await? + } else { + // Drive state machine to point at which we have everything needed for simulation. + let pre_simulation = compiled + .link() + .await? + .prepare_execution() + .await? + .execute() + .await? + .prepare_simulation() + .await?; + + if pre_simulation.args.debug { + pre_simulation.run_debugger()?; } - shell::println("Traces:")?; - for (kind, trace) in &result.traces { - let should_include = match kind { - TraceKind::Setup => verbosity >= 5, - TraceKind::Execution => verbosity > 3, - _ => false, - } || !result.success; + if pre_simulation.args.json { + pre_simulation.show_json()?; + } else { + pre_simulation.show_traces().await?; + } - if should_include { - shell::println(render_trace_arena(trace, decoder).await?)?; - } + // Ensure that we have transactions to simulate/broadcast, otherwise exit early to avoid + // hard error. + if pre_simulation + .execution_result + .transactions + .as_ref() + .map_or(true, |txs| txs.is_empty()) + { + return Ok(()); } - shell::println(String::new())?; - } - if result.success { - shell::println(format!("{}", Paint::green("Script ran successfully.")))?; - } + // Check if there are any missing RPCs and exit early to avoid hard error. + if pre_simulation.execution_artifacts.rpc_data.missing_rpc { + shell::println("\nIf you wish to simulate on-chain transactions pass a RPC URL.")?; + return Ok(()); + } - if script_config.evm_opts.fork_url.is_none() { - shell::println(format!("Gas used: {}", result.gas_used))?; - } + pre_simulation.args.check_contract_sizes( + &pre_simulation.execution_result, + &pre_simulation.build_data.known_contracts, + )?; - if result.success && !result.returned.is_empty() { - shell::println("\n== Return ==")?; - match func.abi_decode_output(&result.returned, false) { - Ok(decoded) => { - for (index, (token, output)) in decoded.iter().zip(&func.outputs).enumerate() { - let internal_type = - output.internal_type.clone().unwrap_or(InternalType::Other { - contract: None, - ty: "unknown".to_string(), - }); - - let label = if !output.name.is_empty() { - output.name.to_string() - } else { - index.to_string() - }; - shell::println(format!( - "{}: {internal_type} {}", - label.trim_end(), - format_token(token) - ))?; - } - } - Err(_) => { - shell::println(format!("{:x?}", (&result.returned)))?; - } - } - } + pre_simulation.fill_metadata().await?.bundle().await? + }; - let console_logs = decode_console_logs(&result.logs); - if !console_logs.is_empty() { - shell::println("\n== Logs ==")?; - for log in console_logs { - shell::println(format!(" {log}"))?; - } + // Exit early in case user didn't provide any broadcast/verify related flags. + if !bundled.args.broadcast && !bundled.args.resume && !bundled.args.verify { + shell::println("\nSIMULATION COMPLETE. To broadcast these transactions, add --broadcast and wallet configuration(s) to the previous command. See forge script --help for more.")?; + return Ok(()); } - if !result.success { - return Err(eyre::eyre!( - "script failed: {}", - RevertDecoder::new().decode(&result.returned[..], None) - )); + // Exit early if something is wrong with verification options. + if bundled.args.verify { + bundled.verify_preflight_check()?; } - Ok(()) - } - - fn show_json(&self, script_config: &ScriptConfig, result: &ScriptResult) -> Result<()> { - let returns = self.get_returns(script_config, &result.returned)?; - - let console_logs = decode_console_logs(&result.logs); - let output = JsonResult { logs: console_logs, gas_used: result.gas_used, returns }; - let j = serde_json::to_string(&output)?; - shell::println(j)?; + // Wait for pending txes and broadcast others. + let broadcasted = bundled.wait_for_pending().await?.broadcast().await?; - if !result.success { - return Err(eyre::eyre!( - "script failed: {}", - RevertDecoder::new().decode(&result.returned[..], None) - )); + if broadcasted.args.verify { + broadcasted.verify().await?; } Ok(()) } - /// It finds the deployer from the running script and uses it to predeploy libraries. - /// - /// If there are multiple candidate addresses, it skips everything and lets `--sender` deploy - /// them instead. - fn maybe_new_sender( - &self, - evm_opts: &EvmOpts, - transactions: Option<&BroadcastableTransactions>, - predeploy_libraries: &[Bytes], - ) -> Result> { - let mut new_sender = None; - - if let Some(txs) = transactions { - // If the user passed a `--sender` don't check anything. - if !predeploy_libraries.is_empty() && self.evm_opts.sender.is_none() { - for tx in txs.iter() { - if tx.transaction.to.is_none() { - let sender = tx.transaction.from.expect("no sender"); - if let Some(ns) = new_sender { - if sender != ns { - shell::println("You have more than one deployer who could predeploy libraries. Using `--sender` instead.")?; - return Ok(None); - } - } else if sender != evm_opts.sender { - new_sender = Some(sender); - } - } - } - } - } - Ok(new_sender) - } - - /// Helper for building the transactions for any libraries that need to be deployed ahead of - /// linking - fn create_deploy_transactions( - &self, - from: Address, - nonce: u64, - data: &[Bytes], - fork_url: &Option, - ) -> BroadcastableTransactions { - data.iter() - .enumerate() - .map(|(i, bytes)| BroadcastableTransaction { - rpc: fork_url.clone(), - transaction: TransactionRequest { - from: Some(from), - input: Some(bytes.clone()).into(), - nonce: Some(U64::from(nonce + i as u64)), - ..Default::default() - }, - zk_tx: None, - }) - .collect() + /// In case the user has loaded *only* one private-key, we can assume that he's using it as the + /// `--sender` + fn maybe_load_private_key(&self) -> Result> { + let maybe_sender = self + .wallets + .private_keys()? + .filter(|pks| pks.len() == 1) + .map(|pks| pks.first().unwrap().address()); + Ok(maybe_sender) } /// Returns the Function and calldata based on the signature @@ -435,30 +310,38 @@ impl ScriptArgs { /// /// Note: We assume that the `sig` is already stripped of its prefix, See [`ScriptArgs`] fn get_method_and_calldata(&self, abi: &JsonAbi) -> Result<(Function, Bytes)> { - let (func, data) = if let Ok(func) = get_func(&self.sig) { - ( - abi.functions().find(|&abi_func| abi_func.selector() == func.selector()).wrap_err( - format!("Function `{}` is not implemented in your script.", self.sig), - )?, - encode_function_args(&func, &self.args)?.into(), - ) - } else { - let decoded = hex::decode(&self.sig).wrap_err("Invalid hex calldata")?; + if let Ok(decoded) = hex::decode(&self.sig) { let selector = &decoded[..SELECTOR_LEN]; - ( - abi.functions().find(|&func| selector == &func.selector()[..]).ok_or_else( - || { - eyre::eyre!( - "Function selector `{}` not found in the ABI", - hex::encode(selector) - ) - }, - )?, - decoded.into(), - ) + let func = + abi.functions().find(|func| selector == &func.selector()[..]).ok_or_else(|| { + eyre::eyre!( + "Function selector `{}` not found in the ABI", + hex::encode(selector) + ) + })?; + return Ok((func.clone(), decoded.into())); + } + + let func = if self.sig.contains('(') { + let func = get_func(&self.sig)?; + abi.functions() + .find(|&abi_func| abi_func.selector() == func.selector()) + .wrap_err(format!("Function `{}` is not implemented in your script.", self.sig))? + } else { + let matching_functions = + abi.functions().filter(|func| func.name == self.sig).collect::>(); + match matching_functions.len() { + 0 => eyre::bail!("Function `{}` not found in the ABI", self.sig), + 1 => matching_functions[0], + 2.. => eyre::bail!( + "Multiple functions with the same name `{}` found in the ABI", + self.sig + ), + } }; + let data = encode_function_args(func, &self.args)?; - Ok((func.clone(), data)) + Ok((func.clone(), data.into())) } /// Checks if the transaction is a deployment with either a size above the `CONTRACT_MAX_SIZE` @@ -469,25 +352,18 @@ impl ScriptArgs { fn check_contract_sizes( &self, result: &ScriptResult, - known_contracts: &BTreeMap, + known_contracts: &ContractsByArtifact, ) -> Result<()> { + //TODO: zk mode contract size check + // (name, &init, &deployed)[] let mut bytecodes: Vec<(String, &[u8], &[u8])> = vec![]; // From artifacts - for (artifact, bytecode) in known_contracts.iter() { - if bytecode.bytecode.object.is_unlinked() { - return Err(UnlinkedByteCode::Bytecode(artifact.identifier()).into()); - } - let init_code = bytecode.bytecode.object.as_bytes().unwrap(); - // Ignore abstract contracts - if let Some(ref deployed_code) = bytecode.deployed_bytecode.bytecode { - if deployed_code.object.is_unlinked() { - return Err(UnlinkedByteCode::DeployedBytecode(artifact.identifier()).into()); - } - let deployed_code = deployed_code.object.as_bytes().unwrap(); - bytecodes.push((artifact.name.clone(), init_code, deployed_code)); - } + for (artifact, contract) in known_contracts.iter() { + let Some(bytecode) = contract.bytecode() else { continue }; + let Some(deployed_bytecode) = contract.deployed_bytecode() else { continue }; + bytecodes.push((artifact.name.clone(), bytecode, deployed_bytecode)); } // From traces @@ -524,13 +400,15 @@ impl ScriptArgs { let mut offset = 0; // Find if it's a CREATE or CREATE2. Otherwise, skip transaction. - if let Some(to) = to { + if let Some(TxKind::Call(to)) = to { if to == DEFAULT_CREATE2_DEPLOYER { // Size of the salt prefix. offset = 32; + } else { + continue; } - } else if to.is_some() { - continue; + } else if let Some(TxKind::Create) = to { + // Pass } // Find artifact with a deployment code same as the data. @@ -543,9 +421,9 @@ impl ScriptArgs { prompt_user = self.broadcast; shell::println(format!( "{}", - Paint::red(format!( + format!( "`{name}` is above the contract size limit ({deployment_size} > {max_size})." - )) + ).red() ))?; } } @@ -596,6 +474,26 @@ pub struct ScriptResult { pub breakpoints: Breakpoints, } +impl ScriptResult { + pub fn get_created_contracts(&self) -> Vec { + self.traces + .iter() + .flat_map(|(_, traces)| { + traces.nodes().iter().filter_map(|node| { + if node.trace.kind.is_any_create() { + return Some(AdditionalContract { + opcode: node.trace.kind, + address: node.trace.address, + init_code: node.trace.data.clone(), + }); + } + None + }) + }) + .collect() + } +} + #[derive(Serialize, Deserialize)] struct JsonResult { logs: Vec, @@ -609,99 +507,126 @@ pub struct NestedValue { pub value: String, } -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] pub struct ScriptConfig { pub config: Config, pub evm_opts: EvmOpts, pub sender_nonce: u64, /// Maps a rpc url to a backend - pub backends: HashMap, - /// Script target contract - pub target_contract: Option, - /// Function called by the script - pub called_function: Option, - /// Unique list of rpc urls present - pub total_rpcs: HashSet, - /// If true, one of the transactions did not have a rpc - pub missing_rpc: bool, - /// Should return some debug information - pub debug: bool, + pub backends: HashMap, } impl ScriptConfig { - fn collect_rpcs(&mut self, txs: &BroadcastableTransactions) { - self.missing_rpc = txs.iter().any(|tx| tx.rpc.is_none()); - - self.total_rpcs - .extend(txs.iter().filter_map(|tx| tx.rpc.as_ref().cloned()).collect::>()); - - if let Some(rpc) = &self.evm_opts.fork_url { - self.total_rpcs.insert(rpc.clone()); - } + pub async fn new(config: Config, evm_opts: EvmOpts) -> Result { + let sender_nonce = if let Some(fork_url) = evm_opts.fork_url.as_ref() { + next_nonce(evm_opts.sender, fork_url).await? + } else { + // dapptools compatibility + 1 + }; + Ok(Self { config, evm_opts, sender_nonce, backends: HashMap::new() }) } - fn has_multiple_rpcs(&self) -> bool { - self.total_rpcs.len() > 1 + pub async fn update_sender(&mut self, sender: Address) -> Result<()> { + self.sender_nonce = if let Some(fork_url) = self.evm_opts.fork_url.as_ref() { + next_nonce(sender, fork_url).await? + } else { + // dapptools compatibility + 1 + }; + self.evm_opts.sender = sender; + Ok(()) } - /// Certain features are disabled for multi chain deployments, and if tried, will return - /// error. [library support] - fn check_multi_chain_constraints(&self, libraries: &Libraries) -> Result<()> { - if self.has_multiple_rpcs() || (self.missing_rpc && !self.total_rpcs.is_empty()) { - shell::eprintln(format!( - "{}", - Paint::yellow( - "Multi chain deployment is still under development. Use with caution." - ) - ))?; - if !libraries.libs.is_empty() { - eyre::bail!( - "Multi chain deployment does not support library linking at the moment." - ) - } - } - Ok(()) + async fn get_runner(&mut self) -> Result { + self._get_runner(None, false).await } - /// Returns the script target contract - fn target_contract(&self) -> &ArtifactId { - self.target_contract.as_ref().expect("should exist after building") + async fn get_runner_with_cheatcodes( + &mut self, + known_contracts: ContractsByArtifact, + script_wallets: ScriptWallets, + debug: bool, + target: ArtifactId, + dual_compiled_contracts: DualCompiledContracts, + ) -> Result { + self._get_runner( + Some((known_contracts, script_wallets, target, dual_compiled_contracts)), + debug, + ) + .await } - /// Checks if the RPCs used point to chains that support EIP-3855. - /// If not, warns the user. - async fn check_shanghai_support(&self) -> Result<()> { - let chain_ids = self.total_rpcs.iter().map(|rpc| async move { - let provider = ethers_providers::Provider::::try_from(rpc).ok()?; - let id = provider.get_chainid().await.ok()?; - let id_u64: u64 = id.try_into().ok()?; - NamedChain::try_from(id_u64).ok() - }); + async fn _get_runner( + &mut self, + cheats_data: Option<( + ContractsByArtifact, + ScriptWallets, + ArtifactId, + DualCompiledContracts, + )>, + debug: bool, + ) -> Result { + trace!("preparing script runner"); + let env = self.evm_opts.evm_env().await?; + + let db = if let Some(fork_url) = self.evm_opts.fork_url.as_ref() { + match self.backends.get(fork_url) { + Some(db) => db.clone(), + None => { + let fork = self.evm_opts.get_fork(&self.config, env.clone()); + let backend = Backend::spawn(fork); + self.backends.insert(fork_url.clone(), backend.clone()); + backend + } + } + } else { + // It's only really `None`, when we don't pass any `--fork-url`. And if so, there is + // no need to cache it, since there won't be any onchain simulation that we'd need + // to cache the backend for. + Backend::spawn(None) + }; - let chains = future::join_all(chain_ids).await; - let iter = chains.iter().flatten().map(|c| (c.supports_shanghai(), c)); - if iter.clone().any(|(s, _)| !s) { - let msg = format!( - "\ -EIP-3855 is not supported in one or more of the RPCs used. -Unsupported Chain IDs: {}. -Contracts deployed with a Solidity version equal or higher than 0.8.20 might not work properly. -For more information, please see https://eips.ethereum.org/EIPS/eip-3855", - iter.filter(|(supported, _)| !supported) - .map(|(_, chain)| *chain as u64) - .format(", ") - ); - shell::println(Paint::yellow(msg))?; + // We need to enable tracing to decode contract names: local or external. + let mut builder = ExecutorBuilder::new() + .inspectors(|stack| stack.trace(true)) + .spec(self.config.evm_spec_id()) + .gas_limit(self.evm_opts.gas_limit()); + + let use_zk = self.config.zksync.run_in_zk_mode(); + if let Some((known_contracts, script_wallets, target, dual_compiled_contracts)) = + cheats_data + { + builder = builder + .inspectors(|stack| { + stack + .debug(debug) + .cheatcodes( + CheatsConfig::new( + &self.config, + self.evm_opts.clone(), + Some(known_contracts), + Some(script_wallets), + Some(target.version), + dual_compiled_contracts, + use_zk, + ) + .into(), + ) + .enable_isolation(self.evm_opts.isolate) + }) + .use_zk_vm(use_zk); } - Ok(()) + + let executor = builder.build(env, db); + Ok(ScriptRunner::new(executor, self.evm_opts.clone())) } } #[cfg(test)] mod tests { use super::*; - use foundry_cli::utils::LoadConfig; - use foundry_config::UnresolvedEnvVarError; + use foundry_config::{NamedChain, UnresolvedEnvVarError}; use std::fs; use tempfile::tempdir; diff --git a/crates/script/src/multi_sequence.rs b/crates/script/src/multi_sequence.rs new file mode 100644 index 000000000..24490bf01 --- /dev/null +++ b/crates/script/src/multi_sequence.rs @@ -0,0 +1,154 @@ +use super::sequence::{sig_to_file_name, ScriptSequence, SensitiveScriptSequence, DRY_RUN_DIR}; +use eyre::{ContextCompat, Result, WrapErr}; +use foundry_cli::utils::now; +use foundry_common::fs; +use foundry_compilers::ArtifactId; +use foundry_config::Config; +use serde::{Deserialize, Serialize}; +use std::{ + io::{BufWriter, Write}, + path::PathBuf, +}; + +/// Holds the sequences of multiple chain deployments. +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct MultiChainSequence { + pub deployments: Vec, + #[serde(skip)] + pub path: PathBuf, + #[serde(skip)] + pub sensitive_path: PathBuf, + pub timestamp: u64, +} + +/// Sensitive values from script sequences. +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct SensitiveMultiChainSequence { + pub deployments: Vec, +} + +impl SensitiveMultiChainSequence { + fn from_multi_sequence(sequence: MultiChainSequence) -> Self { + Self { + deployments: sequence.deployments.into_iter().map(|sequence| sequence.into()).collect(), + } + } +} + +impl MultiChainSequence { + pub fn new( + deployments: Vec, + sig: &str, + target: &ArtifactId, + config: &Config, + dry_run: bool, + ) -> Result { + let (path, sensitive_path) = Self::get_paths(config, sig, target, dry_run)?; + + Ok(Self { deployments, path, sensitive_path, timestamp: now().as_secs() }) + } + + /// Gets paths in the formats + /// ./broadcast/multi/contract_filename[-timestamp]/sig.json and + /// ./cache/multi/contract_filename[-timestamp]/sig.json + pub fn get_paths( + config: &Config, + sig: &str, + target: &ArtifactId, + dry_run: bool, + ) -> Result<(PathBuf, PathBuf)> { + let mut broadcast = config.broadcast.to_path_buf(); + let mut cache = config.cache_path.to_path_buf(); + let mut common = PathBuf::new(); + + common.push("multi"); + + if dry_run { + common.push(DRY_RUN_DIR); + } + + let target_fname = target + .source + .file_name() + .wrap_err_with(|| format!("No filename for {:?}", target.source))? + .to_string_lossy(); + + common.push(format!("{target_fname}-latest")); + + broadcast.push(common.clone()); + cache.push(common); + + fs::create_dir_all(&broadcast)?; + fs::create_dir_all(&cache)?; + + let filename = format!("{}.json", sig_to_file_name(sig)); + + broadcast.push(filename.clone()); + cache.push(filename); + + Ok((broadcast, cache)) + } + + /// Loads the sequences for the multi chain deployment. + pub fn load(config: &Config, sig: &str, target: &ArtifactId, dry_run: bool) -> Result { + let (path, sensitive_path) = Self::get_paths(config, sig, target, dry_run)?; + let mut sequence: Self = foundry_compilers::utils::read_json_file(&path) + .wrap_err("Multi-chain deployment not found.")?; + let sensitive_sequence: SensitiveMultiChainSequence = + foundry_compilers::utils::read_json_file(&sensitive_path) + .wrap_err("Multi-chain deployment sensitive details not found.")?; + + sequence.deployments.iter_mut().enumerate().for_each(|(i, sequence)| { + sequence.fill_sensitive(&sensitive_sequence.deployments[i]); + }); + + sequence.path = path; + sequence.sensitive_path = sensitive_path; + + Ok(sequence) + } + + /// Saves the transactions as file if it's a standalone deployment. + pub fn save(&mut self, silent: bool, save_ts: bool) -> Result<()> { + self.deployments.iter_mut().for_each(|sequence| sequence.sort_receipts()); + + self.timestamp = now().as_secs(); + + let sensitive_sequence = SensitiveMultiChainSequence::from_multi_sequence(self.clone()); + + // broadcast writes + //../Contract-latest/run.json + let mut writer = BufWriter::new(fs::create_file(&self.path)?); + serde_json::to_writer_pretty(&mut writer, &self)?; + writer.flush()?; + + if save_ts { + //../Contract-[timestamp]/run.json + let path = self.path.to_string_lossy(); + let file = PathBuf::from(&path.replace("-latest", &format!("-{}", self.timestamp))); + fs::create_dir_all(file.parent().unwrap())?; + fs::copy(&self.path, &file)?; + } + + // cache writes + //../Contract-latest/run.json + let mut writer = BufWriter::new(fs::create_file(&self.sensitive_path)?); + serde_json::to_writer_pretty(&mut writer, &sensitive_sequence)?; + writer.flush()?; + + if save_ts { + //../Contract-[timestamp]/run.json + let path = self.sensitive_path.to_string_lossy(); + let file = PathBuf::from(&path.replace("-latest", &format!("-{}", self.timestamp))); + fs::create_dir_all(file.parent().unwrap())?; + fs::copy(&self.sensitive_path, &file)?; + } + + if !silent { + println!("\nTransactions saved to: {}\n", self.path.display()); + println!("Sensitive details saved to: {}\n", self.sensitive_path.display()); + } + + Ok(()) + } +} diff --git a/crates/script/src/progress.rs b/crates/script/src/progress.rs new file mode 100644 index 000000000..f97e162a3 --- /dev/null +++ b/crates/script/src/progress.rs @@ -0,0 +1,238 @@ +use crate::{ + receipts::{check_tx_status, format_receipt, TxStatus}, + sequence::ScriptSequence, +}; +use alloy_chains::Chain; +use alloy_primitives::B256; +use eyre::Result; +use foundry_cli::utils::init_progress; +use foundry_common::provider::RetryProvider; +use futures::StreamExt; +use indicatif::{MultiProgress, ProgressBar, ProgressStyle}; +use parking_lot::RwLock; +use std::{collections::HashMap, fmt::Write, sync::Arc, time::Duration}; +use yansi::Paint; + +/// State of [ProgressBar]s displayed for the given [ScriptSequence]. +#[derive(Debug)] +pub struct SequenceProgressState { + /// The top spinner with containt of the format "Sequence #{id} on {network} | {status}"" + top_spinner: ProgressBar, + /// Progress bar with the count of transactions. + txs: ProgressBar, + /// Progress var with the count of confirmed transactions. + receipts: ProgressBar, + /// Standalone spinners for pending transactions. + tx_spinners: HashMap, + /// Copy of the main [MultiProgress] instance. + multi: MultiProgress, +} + +impl SequenceProgressState { + pub fn new(sequence_idx: usize, sequence: &ScriptSequence, multi: MultiProgress) -> Self { + let mut template = "{spinner:.green}".to_string(); + write!(template, " Sequence #{} on {}", sequence_idx + 1, Chain::from(sequence.chain)) + .unwrap(); + template.push_str("{msg}"); + + let top_spinner = ProgressBar::new_spinner() + .with_style(ProgressStyle::with_template(&template).unwrap().tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈✅")); + let top_spinner = multi.add(top_spinner); + + let txs = multi.insert_after( + &top_spinner, + init_progress(sequence.transactions.len() as u64, "txes").with_prefix(" "), + ); + + let receipts = multi.insert_after( + &txs, + init_progress(sequence.transactions.len() as u64, "receipts").with_prefix(" "), + ); + + top_spinner.enable_steady_tick(Duration::from_millis(100)); + txs.enable_steady_tick(Duration::from_millis(1000)); + receipts.enable_steady_tick(Duration::from_millis(1000)); + + txs.set_position(sequence.receipts.len() as u64); + receipts.set_position(sequence.receipts.len() as u64); + + let mut state = Self { top_spinner, txs, receipts, tx_spinners: Default::default(), multi }; + + for tx_hash in sequence.pending.iter() { + state.tx_sent(*tx_hash); + } + + state + } + + /// Called when a new transaction is sent. Displays a spinner with a hash of the transaction and + /// advances the sent transactions progress bar. + pub fn tx_sent(&mut self, tx_hash: B256) { + // Avoid showing more than 10 spinners. + if self.tx_spinners.len() < 10 { + let spinner = ProgressBar::new_spinner() + .with_style( + ProgressStyle::with_template(" {spinner:.green} {msg}") + .unwrap() + .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈"), + ) + .with_message(format!("{} {}", "[Pending]".yellow(), tx_hash)); + + let spinner = self.multi.insert_before(&self.txs, spinner); + spinner.enable_steady_tick(Duration::from_millis(100)); + + self.tx_spinners.insert(tx_hash, spinner); + } + self.txs.inc(1); + } + + /// Removes the pending transaction spinner and advances confirmed transactions progress bar. + pub fn finish_tx_spinner(&mut self, tx_hash: B256) { + if let Some(spinner) = self.tx_spinners.remove(&tx_hash) { + spinner.finish_and_clear(); + } + self.receipts.inc(1); + } + + /// Same as finish_tx_spinner but also prints a message to stdout above all other progress bars. + pub fn finish_tx_spinner_with_msg(&mut self, tx_hash: B256, msg: &str) -> std::io::Result<()> { + self.finish_tx_spinner(tx_hash); + self.multi.println(msg)?; + + Ok(()) + } + + /// Sets status for the current sequence progress. + pub fn set_status(&mut self, status: &str) { + self.top_spinner.set_message(format!(" | {status}")); + } + + /// Hides transactions and receipts progress bar, leaving only top line with the latest set + /// status. + pub fn finish(&self) { + self.top_spinner.finish(); + self.txs.finish_and_clear(); + self.receipts.finish_and_clear(); + } +} + +/// Clonable wrapper around [SequenceProgressState]. +#[derive(Debug, Clone)] +pub struct SequenceProgress { + pub inner: Arc>, +} + +impl SequenceProgress { + pub fn new(sequence_idx: usize, sequence: &ScriptSequence, multi: MultiProgress) -> Self { + Self { + inner: Arc::new(RwLock::new(SequenceProgressState::new(sequence_idx, sequence, multi))), + } + } +} + +/// Container for multiple [SequenceProgress] instances keyed by sequence index. +#[derive(Debug, Clone, Default)] +pub struct ScriptProgress { + state: Arc>>, + multi: MultiProgress, +} + +impl ScriptProgress { + /// Returns a [SequenceProgress] instance for the given sequence index. If it doesn't exist, + /// creates one. + pub fn get_sequence_progress( + &self, + sequence_idx: usize, + sequence: &ScriptSequence, + ) -> SequenceProgress { + if let Some(progress) = self.state.read().get(&sequence_idx) { + return progress.clone(); + } + let progress = SequenceProgress::new(sequence_idx, sequence, self.multi.clone()); + self.state.write().insert(sequence_idx, progress.clone()); + progress + } + + /// Traverses a set of pendings and either finds receipts, or clears them from + /// the deployment sequence. + /// + /// For each `tx_hash`, we check if it has confirmed. If it has + /// confirmed, we push the receipt (if successful) or push an error (if + /// revert). If the transaction has not confirmed, but can be found in the + /// node's mempool, we wait for its receipt to be available. If the transaction + /// has not confirmed, and cannot be found in the mempool, we remove it from + /// the `deploy_sequence.pending` vector so that it will be rebroadcast in + /// later steps. + pub async fn wait_for_pending( + &self, + sequence_idx: usize, + deployment_sequence: &mut ScriptSequence, + provider: &RetryProvider, + ) -> Result<()> { + if deployment_sequence.pending.is_empty() { + return Ok(()); + } + let count = deployment_sequence.pending.len(); + let seq_progress = self.get_sequence_progress(sequence_idx, deployment_sequence); + + seq_progress.inner.write().set_status("Waiting for pending transactions"); + + trace!("Checking status of {count} pending transactions"); + + let futs = + deployment_sequence.pending.clone().into_iter().map(|tx| check_tx_status(provider, tx)); + let mut tasks = futures::stream::iter(futs).buffer_unordered(10); + + let mut errors: Vec = vec![]; + + while let Some((tx_hash, result)) = tasks.next().await { + match result { + Err(err) => { + errors.push(format!("Failure on receiving a receipt for {tx_hash:?}:\n{err}")); + + seq_progress.inner.write().finish_tx_spinner(tx_hash); + } + Ok(TxStatus::Dropped) => { + // We want to remove it from pending so it will be re-broadcast. + deployment_sequence.remove_pending(tx_hash); + errors.push(format!("Transaction dropped from the mempool: {tx_hash:?}")); + + seq_progress.inner.write().finish_tx_spinner(tx_hash); + } + Ok(TxStatus::Success(receipt)) => { + trace!(tx_hash=?tx_hash, "received tx receipt"); + + let msg = format_receipt(deployment_sequence.chain.into(), &receipt); + seq_progress.inner.write().finish_tx_spinner_with_msg(tx_hash, &msg)?; + + deployment_sequence.remove_pending(receipt.transaction_hash); + deployment_sequence.add_receipt(receipt); + } + Ok(TxStatus::Revert(receipt)) => { + // consider: + // if this is not removed from pending, then the script becomes + // un-resumable. Is this desirable on reverts? + warn!(tx_hash=?tx_hash, "Transaction Failure"); + deployment_sequence.remove_pending(receipt.transaction_hash); + + let msg = format_receipt(deployment_sequence.chain.into(), &receipt); + seq_progress.inner.write().finish_tx_spinner_with_msg(tx_hash, &msg)?; + + errors.push(format!("Transaction Failure: {:?}", receipt.transaction_hash)); + } + } + } + + // print any errors + if !errors.is_empty() { + let mut error_msg = errors.join("\n"); + if !deployment_sequence.pending.is_empty() { + error_msg += "\n\n Add `--resume` to your command to try and continue broadcasting + the transactions." + } + eyre::bail!(error_msg); + } + + Ok(()) + } +} diff --git a/crates/forge/bin/cmd/script/providers.rs b/crates/script/src/providers.rs similarity index 61% rename from crates/forge/bin/cmd/script/providers.rs rename to crates/script/src/providers.rs index f29a72629..7f1aa0eb3 100644 --- a/crates/forge/bin/cmd/script/providers.rs +++ b/crates/script/src/providers.rs @@ -1,11 +1,6 @@ -use alloy_primitives::U256; -use ethers_providers::{Middleware, Provider}; +use alloy_provider::{utils::Eip1559Estimation, Provider}; use eyre::{Result, WrapErr}; -use foundry_common::{ - provider::ethers::{get_http_provider, RpcUrl}, - runtime_client::RuntimeClient, - types::ToAlloy, -}; +use foundry_common::provider::{get_http_provider, RetryProvider}; use foundry_config::Chain; use std::{ collections::{hash_map::Entry, HashMap}, @@ -16,7 +11,7 @@ use std::{ /// Contains a map of RPC urls to single instances of [`ProviderInfo`]. #[derive(Default)] pub struct ProvidersManager { - pub inner: HashMap, + pub inner: HashMap, } impl ProvidersManager { @@ -37,7 +32,7 @@ impl ProvidersManager { } impl Deref for ProvidersManager { - type Target = HashMap; + type Target = HashMap; fn deref(&self) -> &Self::Target { &self.inner @@ -47,23 +42,22 @@ impl Deref for ProvidersManager { /// Holds related metadata to each provider RPC. #[derive(Debug)] pub struct ProviderInfo { - pub provider: Arc>, + pub provider: Arc, pub chain: u64, pub gas_price: GasPrice, - pub is_legacy: bool, } /// Represents the outcome of a gas price request #[derive(Debug)] pub enum GasPrice { - Legacy(Result), - EIP1559(Result<(U256, U256)>), + Legacy(Result), + EIP1559(Result), } impl ProviderInfo { - pub async fn new(rpc: &str, mut is_legacy: bool) -> Result { + pub async fn new(rpc: &str, mut is_legacy: bool) -> Result { let provider = Arc::new(get_http_provider(rpc)); - let chain = provider.get_chainid().await?.as_u64(); + let chain = provider.get_chain_id().await?; if let Some(chain) = Chain::from(chain).named() { is_legacy |= chain.is_legacy(); @@ -71,30 +65,22 @@ impl ProviderInfo { let gas_price = if is_legacy { GasPrice::Legacy( - provider - .get_gas_price() - .await - .wrap_err("Failed to get legacy gas price") - .map(|p| p.to_alloy()), + provider.get_gas_price().await.wrap_err("Failed to get legacy gas price"), ) } else { GasPrice::EIP1559( - provider - .estimate_eip1559_fees(None) - .await - .wrap_err("Failed to get EIP-1559 fees") - .map(|p| (p.0.to_alloy(), p.1.to_alloy())), + provider.estimate_eip1559_fees(None).await.wrap_err("Failed to get EIP-1559 fees"), ) }; - Ok(ProviderInfo { provider, chain, gas_price, is_legacy }) + Ok(Self { provider, chain, gas_price }) } /// Returns the gas price to use - pub fn gas_price(&self) -> Result { + pub fn gas_price(&self) -> Result { let res = match &self.gas_price { GasPrice::Legacy(res) => res.as_ref(), - GasPrice::EIP1559(res) => res.as_ref().map(|res| &res.0), + GasPrice::EIP1559(res) => res.as_ref().map(|res| &res.max_fee_per_gas), }; match res { Ok(val) => Ok(*val), diff --git a/crates/script/src/receipts.rs b/crates/script/src/receipts.rs new file mode 100644 index 000000000..a080e2e0d --- /dev/null +++ b/crates/script/src/receipts.rs @@ -0,0 +1,85 @@ +use alloy_chains::Chain; +use alloy_primitives::{utils::format_units, TxHash, U256}; +use alloy_provider::{PendingTransactionBuilder, Provider}; +use alloy_rpc_types::AnyTransactionReceipt; +use eyre::Result; +use foundry_common::provider::RetryProvider; +use std::time::Duration; + +/// Convenience enum for internal signalling of transaction status +pub enum TxStatus { + Dropped, + Success(AnyTransactionReceipt), + Revert(AnyTransactionReceipt), +} + +impl From for TxStatus { + fn from(receipt: AnyTransactionReceipt) -> Self { + if !receipt.inner.inner.inner.receipt.status.coerce_status() { + Self::Revert(receipt) + } else { + Self::Success(receipt) + } + } +} + +/// Checks the status of a txhash by first polling for a receipt, then for +/// mempool inclusion. Returns the tx hash, and a status +pub async fn check_tx_status( + provider: &RetryProvider, + hash: TxHash, +) -> (TxHash, Result) { + // We use the inner future so that we can use ? operator in the future, but + // still neatly return the tuple + let result = async move { + // First check if there's a receipt + let receipt_opt = provider.get_transaction_receipt(hash).await?; + if let Some(receipt) = receipt_opt { + return Ok(receipt.into()); + } + + // If the tx is present in the mempool, run the pending tx future, and + // assume the next drop is really really real + Ok(PendingTransactionBuilder::new(provider, hash) + .with_timeout(Some(Duration::from_secs(120))) + .get_receipt() + .await + .map_or(TxStatus::Dropped, |r| r.into())) + } + .await; + + (hash, result) +} + +/// Prints parts of the receipt to stdout +pub fn format_receipt(chain: Chain, receipt: &AnyTransactionReceipt) -> String { + let gas_used = receipt.gas_used; + let gas_price = receipt.effective_gas_price; + format!( + "\n##### {chain}\n{status}Hash: {tx_hash:?}{caddr}\nBlock: {bn}\n{gas}\n\n", + status = if !receipt.inner.inner.inner.receipt.status.coerce_status() { + "❌ [Failed]" + } else { + "✅ [Success]" + }, + tx_hash = receipt.transaction_hash, + caddr = if let Some(addr) = &receipt.contract_address { + format!("\nContract Address: {}", addr.to_checksum(None)) + } else { + String::new() + }, + bn = receipt.block_number.unwrap_or_default(), + gas = if gas_price == 0 { + format!("Gas Used: {gas_used}") + } else { + let paid = format_units(gas_used.saturating_mul(gas_price), 18) + .unwrap_or_else(|_| "N/A".into()); + let gas_price = format_units(U256::from(gas_price), 9).unwrap_or_else(|_| "N/A".into()); + format!( + "Paid: {} ETH ({gas_used} gas * {} gwei)", + paid.trim_end_matches('0'), + gas_price.trim_end_matches('0').trim_end_matches('.') + ) + }, + ) +} diff --git a/crates/forge/bin/cmd/script/runner.rs b/crates/script/src/runner.rs similarity index 62% rename from crates/forge/bin/cmd/script/runner.rs rename to crates/script/src/runner.rs index 809494511..aa0af6bb3 100644 --- a/crates/forge/bin/cmd/script/runner.rs +++ b/crates/script/src/runner.rs @@ -1,39 +1,37 @@ use super::ScriptResult; -use alloy_primitives::{Address, Bytes, U256}; +use crate::build::ScriptPredeployLibraries; +use alloy_primitives::{Address, Bytes, TxKind, U256}; +use alloy_rpc_types::TransactionRequest; use eyre::Result; -use forge::{ - constants::CALLER, - executors::{CallResult, DeployResult, EvmError, ExecutionErr, Executor, RawCallResult}, +use foundry_cheatcodes::BroadcastableTransaction; +use foundry_config::Config; +use foundry_evm::{ + constants::{CALLER, DEFAULT_CREATE2_DEPLOYER}, + executors::{DeployResult, EvmError, ExecutionErr, Executor, RawCallResult}, + opts::EvmOpts, revm::interpreter::{return_ok, InstructionResult}, traces::{TraceKind, Traces}, }; -use foundry_config::Config; use foundry_zksync_core::ZkTransactionMetadata; +use std::collections::VecDeque; use yansi::Paint; -/// Represents which simulation stage is the script execution at. -pub enum SimulationStage { - Local, - OnChain, -} - /// Drives script execution #[derive(Debug)] pub struct ScriptRunner { pub executor: Executor, - pub initial_balance: U256, - pub sender: Address, + pub evm_opts: EvmOpts, } impl ScriptRunner { - pub fn new(executor: Executor, initial_balance: U256, sender: Address) -> Self { - Self { executor, initial_balance, sender } + pub fn new(executor: Executor, evm_opts: EvmOpts) -> Self { + Self { executor, evm_opts } } /// Deploys the libraries and broadcast contract. Calls setUp method if requested. pub fn setup( &mut self, - libraries: &[Bytes], + libraries: &ScriptPredeployLibraries, code: Bytes, setup: bool, sender_nonce: u64, @@ -43,9 +41,9 @@ impl ScriptRunner { info!(target: "script", "executing setUP()"); if !is_broadcast { - if self.sender == Config::DEFAULT_SENDER { + if self.evm_opts.sender == Config::DEFAULT_SENDER { // We max out their balance so that they can deploy and make calls. - self.executor.set_balance(self.sender, U256::MAX)?; + self.executor.set_balance(self.evm_opts.sender, U256::MAX)?; } if need_create2_deployer { @@ -53,38 +51,94 @@ impl ScriptRunner { } } - self.executor.set_nonce(self.sender, sender_nonce)?; + self.executor.set_nonce(self.evm_opts.sender, sender_nonce)?; // We max out their balance so that they can deploy and make calls. self.executor.set_balance(CALLER, U256::MAX)?; + let mut library_transactions = VecDeque::new(); + let mut traces = Traces::default(); + // Deploy libraries - let mut traces: Traces = libraries - .iter() - .filter_map(|code| { - let DeployResult { traces, .. } = self + match libraries { + ScriptPredeployLibraries::Default(libraries) => libraries.iter().for_each(|code| { + let result = self .executor - .deploy(self.sender, code.clone(), U256::ZERO, None) - .expect("couldn't deploy library"); + .deploy(self.evm_opts.sender, code.clone(), U256::ZERO, None) + .expect("couldn't deploy library") + .raw; - traces - }) - .map(|traces| (TraceKind::Deployment, traces)) - .collect(); + if let Some(deploy_traces) = result.traces { + traces.push((TraceKind::Deployment, deploy_traces)); + } + + library_transactions.push_back(BroadcastableTransaction { + rpc: self.evm_opts.fork_url.clone(), + transaction: TransactionRequest { + from: Some(self.evm_opts.sender), + input: Some(code.clone()).into(), + nonce: Some(sender_nonce + library_transactions.len() as u64), + ..Default::default() + }, + zk_tx: None, + }) + }), + ScriptPredeployLibraries::Create2(libraries, salt) => { + for library in libraries { + let address = + DEFAULT_CREATE2_DEPLOYER.create2_from_code(salt, library.as_ref()); + // Skip if already deployed + if !self.executor.is_empty_code(address)? { + continue; + } + let calldata = [salt.as_ref(), library.as_ref()].concat(); + let result = self + .executor + .transact_raw( + self.evm_opts.sender, + DEFAULT_CREATE2_DEPLOYER, + calldata.clone().into(), + U256::from(0), + ) + .expect("couldn't deploy library"); + + if let Some(deploy_traces) = result.traces { + traces.push((TraceKind::Deployment, deploy_traces)); + } + + library_transactions.push_back(BroadcastableTransaction { + rpc: self.evm_opts.fork_url.clone(), + transaction: TransactionRequest { + from: Some(self.evm_opts.sender), + input: Some(calldata.into()).into(), + nonce: Some(sender_nonce + library_transactions.len() as u64), + to: Some(TxKind::Call(DEFAULT_CREATE2_DEPLOYER)), + ..Default::default() + }, + zk_tx: None, + }); + } + + // Sender nonce is not incremented when performing CALLs. We need to manually + // increase it. + self.executor.set_nonce( + self.evm_opts.sender, + sender_nonce + library_transactions.len() as u64, + )?; + } + }; let address = CALLER.create(self.executor.get_nonce(CALLER)?); // Set the contracts initial balance before deployment, so it is available during the // construction - self.executor.set_balance(address, self.initial_balance)?; + self.executor.set_balance(address, self.evm_opts.initial_balance)?; // Deploy an instance of the contract let DeployResult { address, - mut logs, - traces: constructor_traces, - debug: constructor_debug, - .. + raw: + RawCallResult { mut logs, traces: constructor_traces, debug: constructor_debug, .. }, } = self .executor .deploy(CALLER, code, U256::ZERO, None) @@ -95,34 +149,42 @@ impl ScriptRunner { // Optionally call the `setUp` function let (success, gas_used, labeled_addresses, transactions, debug) = if !setup { self.executor.backend.set_test_contract(address); - (true, 0, Default::default(), None, vec![constructor_debug].into_iter().collect()) + ( + true, + 0, + Default::default(), + Some(library_transactions), + vec![constructor_debug].into_iter().collect(), + ) } else { - match self.executor.setup(Some(self.sender), address) { - Ok(CallResult { + match self.executor.setup(Some(self.evm_opts.sender), address, None) { + Ok(RawCallResult { reverted, traces: setup_traces, labels, logs: setup_logs, debug, gas_used, - transactions, + transactions: setup_transactions, .. }) => { traces.extend(setup_traces.map(|traces| (TraceKind::Setup, traces))); logs.extend_from_slice(&setup_logs); - self.maybe_correct_nonce(sender_nonce, libraries.len())?; + if let Some(txs) = setup_transactions { + library_transactions.extend(txs); + } ( !reverted, gas_used, labels, - transactions, + Some(library_transactions), vec![constructor_debug, debug].into_iter().collect(), ) } Err(EvmError::Execution(err)) => { - let ExecutionErr { + let RawCallResult { reverted, traces: setup_traces, labels, @@ -131,17 +193,19 @@ impl ScriptRunner { gas_used, transactions, .. - } = *err; + } = err.raw; traces.extend(setup_traces.map(|traces| (TraceKind::Setup, traces))); logs.extend_from_slice(&setup_logs); - self.maybe_correct_nonce(sender_nonce, libraries.len())?; + if let Some(txs) = transactions { + library_transactions.extend(txs); + } ( !reverted, gas_used, labels, - transactions, + Some(library_transactions), vec![constructor_debug, debug].into_iter().collect(), ) } @@ -166,27 +230,9 @@ impl ScriptRunner { )) } - /// We call the `setUp()` function with self.sender, and if there haven't been - /// any broadcasts, then the EVM cheatcode module hasn't corrected the nonce. - /// So we have to. - fn maybe_correct_nonce( - &mut self, - sender_initial_nonce: u64, - libraries_len: usize, - ) -> Result<()> { - if let Some(cheatcodes) = &self.executor.inspector.cheatcodes { - if !cheatcodes.corrected_nonce { - self.executor - .set_nonce(self.sender, sender_initial_nonce + libraries_len as u64)?; - } - self.executor.inspector.cheatcodes.as_mut().unwrap().corrected_nonce = false; - } - Ok(()) - } - /// Executes the method that will collect all broadcastable transactions. pub fn script(&mut self, address: Address, calldata: Bytes) -> Result { - self.call(self.sender, address, calldata, U256::ZERO, false) + self.call(self.evm_opts.sender, address, calldata, U256::ZERO, false) } /// Runs a broadcastable transaction locally and persists its state. @@ -206,20 +252,18 @@ impl ScriptRunner { if let Some(to) = to { self.call(from, to, calldata.unwrap_or_default(), value.unwrap_or(U256::ZERO), true) } else if to.is_none() { - let (address, gas_used, logs, traces, debug) = match self.executor.deploy( + let res = self.executor.deploy( from, calldata.expect("No data for create transaction"), value.unwrap_or(U256::ZERO), None, - ) { - Ok(DeployResult { address, gas_used, logs, traces, debug, .. }) => { - (address, gas_used, logs, traces, debug) - } + ); + let (address, RawCallResult { gas_used, logs, traces, debug, .. }) = match res { + Ok(DeployResult { address, raw }) => (address, raw), Err(EvmError::Execution(err)) => { - let ExecutionErr { reason, traces, gas_used, logs, debug, .. } = *err; - println!("{}", Paint::red(format!("\nFailed with `{reason}`:\n"))); - - (Address::ZERO, gas_used, logs, traces, debug) + let ExecutionErr { raw, reason } = *err; + println!("{}", format!("\nFailed with `{reason}`:\n").red()); + (Address::ZERO, raw) } Err(e) => eyre::bail!("Failed deploying contract: {e:?}"), }; @@ -229,14 +273,11 @@ impl ScriptRunner { success: address != Address::ZERO, gas_used, logs, + // Manually adjust gas for the trace to add back the stipend/real used gas traces: traces - .map(|traces| { - // Manually adjust gas for the trace to add back the stipend/real used gas - - vec![(TraceKind::Execution, traces)] - }) + .map(|traces| vec![(TraceKind::Execution, traces)]) .unwrap_or_default(), - debug: vec![debug].into_iter().collect(), + debug: debug.map(|debug| vec![debug]), address: Some(address), ..Default::default() }) @@ -269,7 +310,7 @@ impl ScriptRunner { // Otherwise don't re-execute, or some usecases might be broken: https://github.com/foundry-rs/foundry/issues/3921 if commit { gas_used = self.search_optimal_gas_usage(&res, from, to, &calldata, value)?; - res = self.executor.call_raw_committing(from, to, calldata, value)?; + res = self.executor.transact_raw(from, to, calldata, value)?; } let RawCallResult { result, reverted, logs, traces, labels, debug, transactions, .. } = res; @@ -324,7 +365,7 @@ impl ScriptRunner { match res.exit_reason { InstructionResult::Revert | InstructionResult::OutOfGas | - InstructionResult::OutOfFund => { + InstructionResult::OutOfFunds => { lowest_gas_limit = mid_gas_limit; } _ => { diff --git a/crates/forge/bin/cmd/script/sequence.rs b/crates/script/src/sequence.rs similarity index 65% rename from crates/forge/bin/cmd/script/sequence.rs rename to crates/script/src/sequence.rs index 6223bd104..0eee95951 100644 --- a/crates/forge/bin/cmd/script/sequence.rs +++ b/crates/script/src/sequence.rs @@ -1,22 +1,16 @@ -use super::NestedValue; -use crate::cmd::{ - init::get_commit_hash, - script::{ - transaction::{wrapper, AdditionalContract, TransactionWithMetadata}, - verify::VerifyBundle, - }, - verify::provider::VerificationProviderType, +use super::{multi_sequence::MultiChainSequence, NestedValue}; +use crate::{ + transaction::{AdditionalContract, TransactionWithMetadata}, + verify::VerifyBundle, }; use alloy_primitives::{Address, TxHash}; -use ethers_core::types::{transaction::eip2718::TypedTransaction, TransactionReceipt}; +use alloy_rpc_types::{AnyTransactionReceipt, TransactionRequest}; +use alloy_serde::WithOtherFields; use eyre::{ContextCompat, Result, WrapErr}; -use foundry_cli::utils::now; -use foundry_common::{ - fs, shell, - types::{ToAlloy, ToEthers}, - SELECTOR_LEN, -}; -use foundry_compilers::{artifacts::Libraries, ArtifactId}; +use forge_verify::provider::VerificationProviderType; +use foundry_cli::utils::{now, Git}; +use foundry_common::{fs, shell, SELECTOR_LEN}; +use foundry_compilers::ArtifactId; use foundry_config::Config; use serde::{Deserialize, Serialize}; use std::{ @@ -26,6 +20,67 @@ use std::{ }; use yansi::Paint; +/// Returns the commit hash of the project if it exists +pub fn get_commit_hash(root: &Path) -> Option { + Git::new(root).commit_hash(true, "HEAD").ok() +} + +pub enum ScriptSequenceKind { + Single(ScriptSequence), + Multi(MultiChainSequence), +} + +impl ScriptSequenceKind { + pub fn save(&mut self, silent: bool, save_ts: bool) -> Result<()> { + match self { + Self::Single(sequence) => sequence.save(silent, save_ts), + Self::Multi(sequence) => sequence.save(silent, save_ts), + } + } + + pub fn sequences(&self) -> &[ScriptSequence] { + match self { + Self::Single(sequence) => std::slice::from_ref(sequence), + Self::Multi(sequence) => &sequence.deployments, + } + } + + pub fn sequences_mut(&mut self) -> &mut [ScriptSequence] { + match self { + Self::Single(sequence) => std::slice::from_mut(sequence), + Self::Multi(sequence) => &mut sequence.deployments, + } + } + /// Updates underlying sequence paths to not be under /dry-run directory. + pub fn update_paths_to_broadcasted( + &mut self, + config: &Config, + sig: &str, + target: &ArtifactId, + ) -> Result<()> { + match self { + Self::Single(sequence) => { + sequence.paths = + Some(ScriptSequence::get_paths(config, sig, target, sequence.chain, false)?); + } + Self::Multi(sequence) => { + (sequence.path, sequence.sensitive_path) = + MultiChainSequence::get_paths(config, sig, target, false)?; + } + }; + + Ok(()) + } +} + +impl Drop for ScriptSequenceKind { + fn drop(&mut self) { + if let Err(err) = self.save(false, true) { + error!(?err, "could not save deployment sequence"); + } + } +} + pub const DRY_RUN_DIR: &str = "dry-run"; /// Helper that saves the transactions sequence and its state on which transactions have been @@ -33,26 +88,23 @@ pub const DRY_RUN_DIR: &str = "dry-run"; #[derive(Clone, Default, Serialize, Deserialize)] pub struct ScriptSequence { pub transactions: VecDeque, - #[serde(serialize_with = "wrapper::serialize_receipts")] - pub receipts: Vec, + pub receipts: Vec, pub libraries: Vec, pub pending: Vec, #[serde(skip)] - pub path: PathBuf, - #[serde(skip)] - pub sensitive_path: PathBuf, + /// Contains paths to the sequence files + /// None if sequence should not be saved to disk (e.g. part of a multi-chain sequence) + pub paths: Option<(PathBuf, PathBuf)>, pub returns: HashMap, pub timestamp: u64, pub chain: u64, - /// If `True`, the sequence belongs to a `MultiChainSequence` and won't save to disk as usual. - pub multi: bool, pub commit: Option, } /// Sensitive values from the transactions in a script sequence #[derive(Clone, Default, Serialize, Deserialize)] pub struct SensitiveTransactionMetadata { - pub rpc: Option, + pub rpc: String, } /// Sensitive info from the script sequence which is saved into the cache folder @@ -61,9 +113,9 @@ pub struct SensitiveScriptSequence { pub transactions: VecDeque, } -impl From<&mut ScriptSequence> for SensitiveScriptSequence { - fn from(sequence: &mut ScriptSequence) -> Self { - SensitiveScriptSequence { +impl From for SensitiveScriptSequence { + fn from(sequence: ScriptSequence) -> Self { + Self { transactions: sequence .transactions .iter() @@ -74,59 +126,15 @@ impl From<&mut ScriptSequence> for SensitiveScriptSequence { } impl ScriptSequence { - pub fn new( - transactions: VecDeque, - returns: HashMap, - sig: &str, - target: &ArtifactId, - config: &Config, - broadcasted: bool, - is_multi: bool, - ) -> Result { - let chain = config.chain.unwrap_or_default().id(); - - let (path, sensitive_path) = ScriptSequence::get_paths( - &config.broadcast, - &config.cache_path, - sig, - target, - chain, - broadcasted && !is_multi, - )?; - - let commit = get_commit_hash(&config.__root.0); - - Ok(ScriptSequence { - transactions, - returns, - receipts: vec![], - pending: vec![], - path, - sensitive_path, - timestamp: now().as_secs(), - libraries: vec![], - chain, - multi: is_multi, - commit, - }) - } - /// Loads The sequence for the corresponding json file pub fn load( config: &Config, sig: &str, target: &ArtifactId, chain_id: u64, - broadcasted: bool, + dry_run: bool, ) -> Result { - let (path, sensitive_path) = ScriptSequence::get_paths( - &config.broadcast, - &config.cache_path, - sig, - target, - chain_id, - broadcasted, - )?; + let (path, sensitive_path) = Self::get_paths(config, sig, target, chain_id, dry_run)?; let mut script_sequence: Self = foundry_compilers::utils::read_json_file(&path) .wrap_err(format!("Deployment not found for chain `{chain_id}`."))?; @@ -138,52 +146,63 @@ impl ScriptSequence { script_sequence.fill_sensitive(&sensitive_script_sequence); - script_sequence.path = path; - script_sequence.sensitive_path = sensitive_path; + script_sequence.paths = Some((path, sensitive_path)); Ok(script_sequence) } /// Saves the transactions as file if it's a standalone deployment. - pub fn save(&mut self) -> Result<()> { - if self.multi || self.transactions.is_empty() { + /// `save_ts` should be set to true for checkpoint updates, which might happen many times and + /// could result in us saving many identical files. + pub fn save(&mut self, silent: bool, save_ts: bool) -> Result<()> { + self.sort_receipts(); + + if self.transactions.is_empty() { return Ok(()) } + let Some((path, sensitive_path)) = self.paths.clone() else { return Ok(()) }; + self.timestamp = now().as_secs(); let ts_name = format!("run-{}.json", self.timestamp); - let sensitive_script_sequence: SensitiveScriptSequence = self.into(); + let sensitive_script_sequence: SensitiveScriptSequence = self.clone().into(); // broadcast folder writes //../run-latest.json - let mut writer = BufWriter::new(fs::create_file(&self.path)?); + let mut writer = BufWriter::new(fs::create_file(&path)?); serde_json::to_writer_pretty(&mut writer, &self)?; writer.flush()?; - //../run-[timestamp].json - fs::copy(&self.path, self.path.with_file_name(&ts_name))?; + if save_ts { + //../run-[timestamp].json + fs::copy(&path, path.with_file_name(&ts_name))?; + } // cache folder writes //../run-latest.json - let mut writer = BufWriter::new(fs::create_file(&self.sensitive_path)?); + let mut writer = BufWriter::new(fs::create_file(&sensitive_path)?); serde_json::to_writer_pretty(&mut writer, &sensitive_script_sequence)?; writer.flush()?; - //../run-[timestamp].json - fs::copy(&self.sensitive_path, self.sensitive_path.with_file_name(&ts_name))?; + if save_ts { + //../run-[timestamp].json + fs::copy(&sensitive_path, sensitive_path.with_file_name(&ts_name))?; + } - shell::println(format!("\nTransactions saved to: {}\n", self.path.display()))?; - shell::println(format!("Sensitive values saved to: {}\n", self.sensitive_path.display()))?; + if !silent { + shell::println(format!("\nTransactions saved to: {}\n", path.display()))?; + shell::println(format!("Sensitive values saved to: {}\n", sensitive_path.display()))?; + } Ok(()) } - pub fn add_receipt(&mut self, receipt: TransactionReceipt) { + pub fn add_receipt(&mut self, receipt: AnyTransactionReceipt) { self.receipts.push(receipt); } /// Sorts all receipts with ascending transaction index pub fn sort_receipts(&mut self) { - self.receipts.sort_unstable() + self.receipts.sort_by_key(|r| (r.block_number, r.transaction_index)); } pub fn add_pending(&mut self, index: usize, tx_hash: TxHash) { @@ -197,36 +216,24 @@ impl ScriptSequence { self.pending.retain(|element| element != &tx_hash); } - pub fn add_libraries(&mut self, libraries: Libraries) { - self.libraries = libraries - .libs - .iter() - .flat_map(|(file, libs)| { - libs.iter() - .map(|(name, address)| format!("{}:{name}:{address}", file.to_string_lossy())) - }) - .collect(); - } - /// Gets paths in the formats - /// ./broadcast/[contract_filename]/[chain_id]/[sig]-[timestamp].json and - /// ./cache/[contract_filename]/[chain_id]/[sig]-[timestamp].json + /// `./broadcast/[contract_filename]/[chain_id]/[sig]-[timestamp].json` and + /// `./cache/[contract_filename]/[chain_id]/[sig]-[timestamp].json`. pub fn get_paths( - broadcast: &Path, - cache: &Path, + config: &Config, sig: &str, target: &ArtifactId, chain_id: u64, - broadcasted: bool, + dry_run: bool, ) -> Result<(PathBuf, PathBuf)> { - let mut broadcast = broadcast.to_path_buf(); - let mut cache = cache.to_path_buf(); + let mut broadcast = config.broadcast.to_path_buf(); + let mut cache = config.cache_path.to_path_buf(); let mut common = PathBuf::new(); let target_fname = target.source.file_name().wrap_err("No filename.")?; common.push(target_fname); common.push(chain_id.to_string()); - if !broadcasted { + if dry_run { common.push(DRY_RUN_DIR); } @@ -245,20 +252,6 @@ impl ScriptSequence { Ok((broadcast, cache)) } - /// Checks that there is an Etherscan key for the chain id of this sequence. - pub fn verify_preflight_check(&self, config: &Config, verify: &VerifyBundle) -> Result<()> { - if config.get_etherscan_api_key(Some(self.chain.into())).is_none() && - verify.verifier.verifier == VerificationProviderType::Etherscan - { - eyre::bail!( - "Etherscan API key wasn't found for chain id {}. On-chain execution aborted", - self.chain - ) - } - - Ok(()) - } - /// Given the broadcast log, it matches transactions with receipts, and tries to verify any /// created contract on etherscan. pub async fn verify_contracts( @@ -286,13 +279,13 @@ impl ScriptSequence { let mut offset = 0; if tx.is_create2() { - receipt.contract_address = tx.contract_address.map(|a| a.to_ethers()); + receipt.contract_address = tx.contract_address; offset = 32; } // Verify contract created directly from the transaction if let (Some(address), Some(data)) = - (receipt.contract_address.map(|h| h.to_alloy()), tx.typed_tx().data()) + (receipt.contract_address, tx.tx().input.input()) { match verify.get_verify_args(address, offset, &data.0, &self.libraries) { Some(verify) => future_verifications.push(verify.run()), @@ -302,7 +295,7 @@ impl ScriptSequence { // Verify potential contracts created during the transaction execution for AdditionalContract { address, init_code, .. } in &tx.additional_contracts { - match verify.get_verify_args(*address, 0, init_code, &self.libraries) { + match verify.get_verify_args(*address, 0, init_code.as_ref(), &self.libraries) { Some(verify) => future_verifications.push(verify.run()), None => unverifiable_contracts.push(*address), }; @@ -331,11 +324,12 @@ impl ScriptSequence { if !unverifiable_contracts.is_empty() { println!( "\n{}", - Paint::yellow(format!( + format!( "We haven't found any matching bytecode for the following contracts: {:?}.\n\n{}", unverifiable_contracts, "This may occur when resuming a verification, but the underlying source code or compiler version has changed." - )) + ) + .yellow() .bold(), ); @@ -354,27 +348,20 @@ impl ScriptSequence { } /// Returns the first RPC URL of this sequence. - pub fn rpc_url(&self) -> Option<&str> { - self.transactions.front().and_then(|tx| tx.rpc.as_deref()) + pub fn rpc_url(&self) -> &str { + self.transactions.front().expect("empty sequence").rpc.as_str() } /// Returns the list of the transactions without the metadata. - pub fn typed_transactions(&self) -> impl Iterator { - self.transactions.iter().map(|tx| tx.typed_tx()) + pub fn transactions(&self) -> impl Iterator> { + self.transactions.iter().map(|tx| tx.tx()) } pub fn fill_sensitive(&mut self, sensitive: &SensitiveScriptSequence) { self.transactions .iter_mut() .enumerate() - .for_each(|(i, tx)| tx.rpc = sensitive.transactions[i].rpc.clone()); - } -} - -impl Drop for ScriptSequence { - fn drop(&mut self) { - self.sort_receipts(); - self.save().expect("not able to save deployment sequence"); + .for_each(|(i, tx)| tx.rpc.clone_from(&sensitive.transactions[i].rpc)); } } diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs new file mode 100644 index 000000000..aff08d466 --- /dev/null +++ b/crates/script/src/simulate.rs @@ -0,0 +1,436 @@ +use super::{ + multi_sequence::MultiChainSequence, + providers::ProvidersManager, + runner::ScriptRunner, + sequence::{ScriptSequence, ScriptSequenceKind}, + transaction::TransactionWithMetadata, +}; +use crate::{ + broadcast::{estimate_gas, BundledState}, + build::LinkedBuildData, + execute::{ExecutionArtifacts, ExecutionData}, + sequence::get_commit_hash, + transaction::ZkTransaction, + ScriptArgs, ScriptConfig, ScriptResult, +}; +use alloy_network::TransactionBuilder; +use alloy_primitives::{utils::format_units, Address, TxKind, U256}; +use eyre::{Context, Result}; +use foundry_cheatcodes::{BroadcastableTransactions, ScriptWallets}; +use foundry_cli::utils::{has_different_gas_calc, now}; +use foundry_common::{get_contract_name, shell, ContractData}; +use foundry_evm::traces::render_trace_arena; +use futures::future::{join_all, try_join_all}; +use parking_lot::RwLock; +use std::{ + collections::{BTreeMap, HashMap, VecDeque}, + sync::Arc, +}; + +/// Same as [ExecutedState](crate::execute::ExecutedState), but also contains [ExecutionArtifacts] +/// which are obtained from [ScriptResult]. +/// +/// Can be either converted directly to [BundledState] or driven to it through +/// [FilledTransactionsState]. +pub struct PreSimulationState { + pub args: ScriptArgs, + pub script_config: ScriptConfig, + pub script_wallets: ScriptWallets, + pub build_data: LinkedBuildData, + pub execution_data: ExecutionData, + pub execution_result: ScriptResult, + pub execution_artifacts: ExecutionArtifacts, +} + +impl PreSimulationState { + /// If simulation is enabled, simulates transactions against fork and fills gas estimation and + /// metadata. Otherwise, metadata (e.g. additional contracts, created contract names) is + /// left empty. + /// + /// Both modes will panic if any of the transactions have None for the `rpc` field. + pub async fn fill_metadata(self) -> Result { + let transactions = if let Some(txs) = self.execution_result.transactions.as_ref() { + if self.args.skip_simulation { + shell::println("\nSKIPPING ON CHAIN SIMULATION.")?; + self.no_simulation(txs.clone())? + } else { + self.onchain_simulation(txs.clone()).await? + } + } else { + VecDeque::new() + }; + + Ok(FilledTransactionsState { + args: self.args, + script_config: self.script_config, + script_wallets: self.script_wallets, + build_data: self.build_data, + execution_artifacts: self.execution_artifacts, + transactions, + }) + } + + /// Builds separate runners and environments for each RPC used in script and executes all + /// transactions in those environments. + /// + /// Collects gas usage and metadata for each transaction. + pub async fn onchain_simulation( + &self, + transactions: BroadcastableTransactions, + ) -> Result> { + trace!(target: "script", "executing onchain simulation"); + + let runners = Arc::new( + self.build_runners() + .await? + .into_iter() + .map(|(rpc, runner)| (rpc, Arc::new(RwLock::new(runner)))) + .collect::>(), + ); + + let address_to_abi = self.build_address_to_abi_map(); + + let mut final_txs = VecDeque::new(); + + // Executes all transactions from the different forks concurrently. + let futs = transactions + .into_iter() + .map(|transaction| async { + let rpc = transaction.rpc.expect("missing broadcastable tx rpc url"); + let mut runner = runners.get(&rpc).expect("invalid rpc url").write(); + + let zk = transaction.zk_tx; + let mut tx = transaction.transaction; + let to = if let Some(TxKind::Call(to)) = tx.to { Some(to) } else { None }; + let result = runner + .simulate( + tx.from + .expect("transaction doesn't have a `from` address at execution time"), + to, + tx.input.clone().into_input(), + tx.value, + (self.script_config.config.zksync.run_in_zk_mode(), zk.clone()), + ) + .wrap_err("Internal EVM error during simulation")?; + + if !result.success { + return Ok((None, result.traces)); + } + + let created_contracts = result.get_created_contracts(); + + // Simulate mining the transaction if the user passes `--slow`. + if self.args.slow { + runner.executor.env.block.number += U256::from(1); + } + + let is_fixed_gas_limit = tx.gas.is_some(); + match tx.gas { + // If tx.gas is already set that means it was specified in script + Some(gas) => { + println!("Gas limit was set in script to {gas}"); + } + // We inflate the gas used by the user specified percentage + None => { + let gas = result.gas_used * self.args.gas_estimate_multiplier / 100; + tx.gas = Some(gas as u128); + } + } + let tx = TransactionWithMetadata::new( + tx, + rpc, + &result, + &address_to_abi, + &self.execution_artifacts.decoder, + created_contracts, + is_fixed_gas_limit, + zk.map(|zk_tx| ZkTransaction { factory_deps: zk_tx.factory_deps }), + )?; + + eyre::Ok((Some(tx), result.traces)) + }) + .collect::>(); + + if self.script_config.evm_opts.verbosity > 3 { + println!("=========================="); + println!("Simulated On-chain Traces:\n"); + } + + let mut abort = false; + for res in join_all(futs).await { + let (tx, traces) = res?; + + // Transaction will be `None`, if execution didn't pass. + if tx.is_none() || self.script_config.evm_opts.verbosity > 3 { + for (_, trace) in &traces { + println!( + "{}", + render_trace_arena(trace, &self.execution_artifacts.decoder).await? + ); + } + } + + if let Some(tx) = tx { + final_txs.push_back(tx); + } else { + abort = true; + } + } + + if abort { + eyre::bail!("Simulated execution failed.") + } + + Ok(final_txs) + } + + /// Build mapping from contract address to its ABI, code and contract name. + fn build_address_to_abi_map(&self) -> BTreeMap { + self.execution_artifacts + .decoder + .contracts + .iter() + .filter_map(move |(addr, contract_id)| { + let contract_name = get_contract_name(contract_id); + if let Ok(Some((_, data))) = + self.build_data.known_contracts.find_by_name_or_identifier(contract_name) + { + return Some((*addr, data)); + } + None + }) + .collect() + } + + /// Build [ScriptRunner] forking given RPC for each RPC used in the script. + async fn build_runners(&self) -> Result> { + let rpcs = self.execution_artifacts.rpc_data.total_rpcs.clone(); + if !shell::verbosity().is_silent() { + let n = rpcs.len(); + let s = if n != 1 { "s" } else { "" }; + println!("\n## Setting up {n} EVM{s}."); + } + + let futs = rpcs.into_iter().map(|rpc| async move { + let mut script_config = self.script_config.clone(); + script_config.evm_opts.fork_url = Some(rpc.clone()); + let runner = script_config.get_runner().await?; + Ok((rpc.clone(), runner)) + }); + try_join_all(futs).await + } + + /// If simulation is disabled, converts transactions into [TransactionWithMetadata] type + /// skipping metadata filling. + fn no_simulation( + &self, + transactions: BroadcastableTransactions, + ) -> Result> { + Ok(transactions + .into_iter() + .map(|btx| { + let mut tx = TransactionWithMetadata::from_tx_request(btx.transaction); + tx.zk = + btx.zk_tx.map(|metadata| ZkTransaction { factory_deps: metadata.factory_deps }); + tx.rpc = btx.rpc.expect("missing broadcastable tx rpc url"); + tx + }) + .collect()) + } +} + +/// At this point we have converted transactions collected during script execution to +/// [TransactionWithMetadata] objects which contain additional metadata needed for broadcasting and +/// verification. +pub struct FilledTransactionsState { + pub args: ScriptArgs, + pub script_config: ScriptConfig, + pub script_wallets: ScriptWallets, + pub build_data: LinkedBuildData, + pub execution_artifacts: ExecutionArtifacts, + pub transactions: VecDeque, +} + +impl FilledTransactionsState { + /// Bundles all transactions of the [`TransactionWithMetadata`] type in a list of + /// [`ScriptSequence`]. List length will be higher than 1, if we're dealing with a multi + /// chain deployment. + /// + /// Each transaction will be added with the correct transaction type and gas estimation. + pub async fn bundle(self) -> Result { + let is_multi_deployment = self.execution_artifacts.rpc_data.total_rpcs.len() > 1; + + if is_multi_deployment && !self.build_data.libraries.is_empty() { + eyre::bail!("Multi-chain deployment is not supported with libraries."); + } + + let mut total_gas_per_rpc: HashMap = HashMap::new(); + + // Batches sequence of transactions from different rpcs. + let mut new_sequence = VecDeque::new(); + let mut manager = ProvidersManager::default(); + let mut sequences = vec![]; + + // Peeking is used to check if the next rpc url is different. If so, it creates a + // [`ScriptSequence`] from all the collected transactions up to this point. + let mut txes_iter = self.transactions.clone().into_iter().peekable(); + + while let Some(mut tx) = txes_iter.next() { + let tx_rpc = tx.rpc.clone(); + let provider_info = manager.get_or_init_provider(&tx.rpc, self.args.legacy).await?; + + // Handles chain specific requirements. + tx.transaction.set_chain_id(provider_info.chain); + + if !self.args.skip_simulation { + let tx = tx.tx_mut(); + + if has_different_gas_calc(provider_info.chain) { + trace!("estimating with different gas calculation"); + let gas = tx.gas.expect("gas is set by simulation."); + + // We are trying to show the user an estimation of the total gas usage. + // + // However, some transactions might depend on previous ones. For + // example, tx1 might deploy a contract that tx2 uses. That + // will result in the following `estimate_gas` call to fail, + // since tx1 hasn't been broadcasted yet. + // + // Not exiting here will not be a problem when actually broadcasting, because + // for chains where `has_different_gas_calc` returns true, + // we await each transaction before broadcasting the next + // one. + if let Err(err) = + estimate_gas(tx, &provider_info.provider, self.args.gas_estimate_multiplier) + .await + { + trace!("gas estimation failed: {err}"); + + // Restore gas value, since `estimate_gas` will remove it. + tx.set_gas_limit(gas); + } + } + + let total_gas = total_gas_per_rpc.entry(tx_rpc.clone()).or_insert(0); + *total_gas += tx.gas.expect("gas is set"); + } + + new_sequence.push_back(tx); + // We only create a [`ScriptSequence`] object when we collect all the rpc related + // transactions. + if let Some(next_tx) = txes_iter.peek() { + if next_tx.rpc == tx_rpc { + continue; + } + } + + let sequence = + self.create_sequence(is_multi_deployment, provider_info.chain, new_sequence)?; + + sequences.push(sequence); + + new_sequence = VecDeque::new(); + } + + if !self.args.skip_simulation { + // Present gas information on a per RPC basis. + for (rpc, total_gas) in total_gas_per_rpc { + let provider_info = manager.get(&rpc).expect("provider is set."); + + // We don't store it in the transactions, since we want the most updated value. + // Right before broadcasting. + let per_gas = if let Some(gas_price) = self.args.with_gas_price { + gas_price.to() + } else { + provider_info.gas_price()? + }; + + shell::println("\n==========================")?; + shell::println(format!("\nChain {}", provider_info.chain))?; + + shell::println(format!( + "\nEstimated gas price: {} gwei", + format_units(per_gas, 9) + .unwrap_or_else(|_| "[Could not calculate]".to_string()) + .trim_end_matches('0') + .trim_end_matches('.') + ))?; + shell::println(format!("\nEstimated total gas used for script: {total_gas}"))?; + shell::println(format!( + "\nEstimated amount required: {} ETH", + format_units(total_gas.saturating_mul(per_gas), 18) + .unwrap_or_else(|_| "[Could not calculate]".to_string()) + .trim_end_matches('0') + ))?; + shell::println("\n==========================")?; + } + } + + let sequence = if sequences.len() == 1 { + ScriptSequenceKind::Single(sequences.pop().expect("empty sequences")) + } else { + ScriptSequenceKind::Multi(MultiChainSequence::new( + sequences, + &self.args.sig, + &self.build_data.build_data.target, + &self.script_config.config, + !self.args.broadcast, + )?) + }; + + Ok(BundledState { + args: self.args, + script_config: self.script_config, + script_wallets: self.script_wallets, + build_data: self.build_data, + sequence, + }) + } + + /// Creates a [ScriptSequence] object from the given transactions. + fn create_sequence( + &self, + multi: bool, + chain: u64, + transactions: VecDeque, + ) -> Result { + // Paths are set to None for multi-chain sequences parts, because they don't need to be + // saved to a separate file. + let paths = if multi { + None + } else { + Some(ScriptSequence::get_paths( + &self.script_config.config, + &self.args.sig, + &self.build_data.build_data.target, + chain, + !self.args.broadcast, + )?) + }; + + let commit = get_commit_hash(&self.script_config.config.root.0); + + let libraries = self + .build_data + .libraries + .libs + .iter() + .flat_map(|(file, libs)| { + libs.iter() + .map(|(name, address)| format!("{}:{name}:{address}", file.to_string_lossy())) + }) + .collect(); + + Ok(ScriptSequence { + transactions, + returns: self.execution_artifacts.returns.clone(), + receipts: vec![], + pending: vec![], + paths, + timestamp: now().as_secs(), + libraries, + chain, + commit, + }) + } +} diff --git a/crates/script/src/transaction.rs b/crates/script/src/transaction.rs new file mode 100644 index 000000000..6eb6bf17d --- /dev/null +++ b/crates/script/src/transaction.rs @@ -0,0 +1,235 @@ +use super::ScriptResult; +use alloy_dyn_abi::JsonAbiExt; +use alloy_primitives::{Address, Bytes, TxKind, B256}; +use alloy_rpc_types::request::TransactionRequest; +use alloy_serde::WithOtherFields; +use eyre::{ContextCompat, Result, WrapErr}; +use foundry_common::{fmt::format_token_raw, ContractData, SELECTOR_LEN}; +use foundry_evm::{constants::DEFAULT_CREATE2_DEPLOYER, traces::CallTraceDecoder}; +use itertools::Itertools; +use revm_inspectors::tracing::types::CallKind; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AdditionalContract { + #[serde(rename = "transactionType")] + pub opcode: CallKind, + pub address: Address, + pub init_code: Bytes, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ZkTransaction { + pub factory_deps: Vec>, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TransactionWithMetadata { + pub hash: Option, + #[serde(rename = "transactionType")] + pub opcode: CallKind, + #[serde(default = "default_string")] + pub contract_name: Option, + #[serde(default = "default_address")] + pub contract_address: Option
, + #[serde(default = "default_string")] + pub function: Option, + #[serde(default = "default_vec_of_strings")] + pub arguments: Option>, + #[serde(skip)] + pub rpc: String, + pub transaction: WithOtherFields, + pub additional_contracts: Vec, + pub is_fixed_gas_limit: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub zk: Option, +} + +fn default_string() -> Option { + Some(String::new()) +} + +fn default_address() -> Option
{ + Some(Address::ZERO) +} + +fn default_vec_of_strings() -> Option> { + Some(vec![]) +} + +impl TransactionWithMetadata { + pub fn from_tx_request(transaction: TransactionRequest) -> Self { + Self { transaction: WithOtherFields::new(transaction), ..Default::default() } + } + + #[allow(clippy::too_many_arguments)] + pub fn new( + transaction: TransactionRequest, + rpc: String, + result: &ScriptResult, + local_contracts: &BTreeMap, + decoder: &CallTraceDecoder, + additional_contracts: Vec, + is_fixed_gas_limit: bool, + zk: Option, + ) -> Result { + let mut metadata = Self::from_tx_request(transaction); + metadata.rpc = rpc; + metadata.is_fixed_gas_limit = is_fixed_gas_limit; + metadata.zk = zk; + + // Specify if any contract was directly created with this transaction + if let Some(TxKind::Call(to)) = metadata.transaction.to { + if to == DEFAULT_CREATE2_DEPLOYER { + metadata.set_create( + true, + Address::from_slice(&result.returned), + local_contracts, + )?; + } else { + metadata + .set_call(to, local_contracts, decoder) + .wrap_err("Could not decode transaction type.")?; + } + } else { + metadata.set_create( + false, + result.address.wrap_err("There should be a contract address from CREATE.")?, + local_contracts, + )?; + } + + // Add the additional contracts created in this transaction, so we can verify them later. + if let Some(tx_address) = metadata.contract_address { + metadata.additional_contracts = additional_contracts + .into_iter() + .filter_map(|contract| { + // Filter out the transaction contract repeated init_code. + if contract.address != tx_address { + Some(contract) + } else { + None + } + }) + .collect(); + } + + Ok(metadata) + } + + /// Populate the transaction as CREATE tx + /// + /// If this is a CREATE2 transaction this attempt to decode the arguments from the CREATE2 + /// deployer's function + fn set_create( + &mut self, + is_create2: bool, + address: Address, + contracts: &BTreeMap, + ) -> Result<()> { + if is_create2 { + self.opcode = CallKind::Create2; + } else { + self.opcode = CallKind::Create; + } + + let info = contracts.get(&address); + self.contract_name = info.map(|info| info.name.clone()); + self.contract_address = Some(address); + + let Some(data) = self.transaction.input.input() else { return Ok(()) }; + let Some(info) = info else { return Ok(()) }; + let Some(bytecode) = info.bytecode() else { return Ok(()) }; + + // `create2` transactions are prefixed by a 32 byte salt. + let creation_code = if is_create2 { + if data.len() < 32 { + return Ok(()) + } + &data[32..] + } else { + data + }; + + // The constructor args start after bytecode. + let contains_constructor_args = creation_code.len() > bytecode.len(); + if !contains_constructor_args { + return Ok(()); + } + let constructor_args = &creation_code[bytecode.len()..]; + + let Some(constructor) = info.abi.constructor() else { return Ok(()) }; + let values = constructor.abi_decode_input(constructor_args, false).map_err(|e| { + error!( + contract=?self.contract_name, + signature=%format!("constructor({})", constructor.inputs.iter().map(|p| &p.ty).format(",")), + is_create2, + constructor_args=%hex::encode(constructor_args), + "Failed to decode constructor arguments", + ); + debug!(full_data=%hex::encode(data), bytecode=%hex::encode(creation_code)); + e + })?; + self.arguments = Some(values.iter().map(format_token_raw).collect()); + + Ok(()) + } + + /// Populate the transaction as CALL tx + fn set_call( + &mut self, + target: Address, + local_contracts: &BTreeMap, + decoder: &CallTraceDecoder, + ) -> Result<()> { + self.opcode = CallKind::Call; + self.contract_address = Some(target); + + let Some(data) = self.transaction.input.input() else { return Ok(()) }; + if data.len() < SELECTOR_LEN { + return Ok(()); + } + let (selector, data) = data.split_at(SELECTOR_LEN); + + let function = if let Some(info) = local_contracts.get(&target) { + // This CALL is made to a local contract. + self.contract_name = Some(info.name.clone()); + info.abi.functions().find(|function| function.selector() == selector) + } else { + // This CALL is made to an external contract; try to decode it from the given decoder. + decoder.functions.get(selector).and_then(|v| v.first()) + }; + if let Some(function) = function { + self.function = Some(function.signature()); + + let values = function.abi_decode_input(data, false).map_err(|e| { + error!( + contract=?self.contract_name, + signature=?function, + data=hex::encode(data), + "Failed to decode function arguments", + ); + e + })?; + self.arguments = Some(values.iter().map(format_token_raw).collect()); + } + + Ok(()) + } + + pub fn tx(&self) -> &WithOtherFields { + &self.transaction + } + + pub fn tx_mut(&mut self) -> &mut WithOtherFields { + &mut self.transaction + } + + pub fn is_create2(&self) -> bool { + self.opcode == CallKind::Create2 + } +} diff --git a/crates/forge/bin/cmd/script/verify.rs b/crates/script/src/verify.rs similarity index 72% rename from crates/forge/bin/cmd/script/verify.rs rename to crates/script/src/verify.rs index 8c23c25dd..b3bd6c0fb 100644 --- a/crates/forge/bin/cmd/script/verify.rs +++ b/crates/script/src/verify.rs @@ -1,14 +1,44 @@ -use crate::cmd::{ - retry::RetryArgs, - verify::{VerifierArgs, VerifyArgs}, -}; +use crate::{build::LinkedBuildData, sequence::ScriptSequenceKind, ScriptArgs, ScriptConfig}; + use alloy_primitives::Address; +use eyre::Result; +use forge_verify::{RetryArgs, VerifierArgs, VerifyArgs}; use foundry_cli::opts::{EtherscanOpts, ProjectPathsArgs}; use foundry_common::ContractsByArtifact; use foundry_compilers::{info::ContractInfo, Project}; use foundry_config::{Chain, Config}; use semver::Version; +/// State after we have broadcasted the script. +/// It is assumed that at this point [BroadcastedState::sequence] contains receipts for all +/// broadcasted transactions. +pub struct BroadcastedState { + pub args: ScriptArgs, + pub script_config: ScriptConfig, + pub build_data: LinkedBuildData, + pub sequence: ScriptSequenceKind, +} + +impl BroadcastedState { + pub async fn verify(self) -> Result<()> { + let Self { args, script_config, build_data, mut sequence, .. } = self; + + let verify = VerifyBundle::new( + &script_config.config.project()?, + &script_config.config, + build_data.known_contracts, + args.retry, + args.verifier, + ); + + for sequence in sequence.sequences_mut() { + sequence.verify_contracts(&script_config.config, verify.clone()).await?; + } + + Ok(()) + } +} + /// Data struct to help `ScriptSequence` verify contracts on `etherscan`. #[derive(Clone)] pub struct VerifyBundle { @@ -49,7 +79,7 @@ impl VerifyBundle { let via_ir = config.via_ir; let zksync = config.zksync.should_compile(); - VerifyBundle { + Self { num_of_optimizations, known_contracts, etherscan: Default::default(), @@ -78,16 +108,19 @@ impl VerifyBundle { data: &[u8], libraries: &[String], ) -> Option { - for (artifact, (_contract, bytecode)) in self.known_contracts.iter() { + for (artifact, contract) in self.known_contracts.iter() { + let Some(bytecode) = contract.bytecode() else { continue }; // If it's a CREATE2, the tx.data comes with a 32-byte salt in the beginning // of the transaction if data.split_at(create2_offset).1.starts_with(bytecode) { let constructor_args = data.split_at(create2_offset + bytecode.len()).1.to_vec(); + if artifact.source.extension().map_or(false, |e| e.to_str() == Some("vy")) { + warn!("Skipping verification of Vyper contract: {}", artifact.name); + } + let contract = ContractInfo { - path: Some( - artifact.source.to_str().expect("There should be an artifact.").to_string(), - ), + path: Some(artifact.source.to_string_lossy().to_string()), name: artifact.name.clone(), }; @@ -102,12 +135,13 @@ impl VerifyBundle { let verify = VerifyArgs { address: contract_address, - contract, + contract: Some(contract), compiler_version: Some(version.to_string()), constructor_args: Some(hex::encode(constructor_args)), constructor_args_path: None, num_of_optimizations: self.num_of_optimizations, etherscan: self.etherscan.clone(), + rpc: Default::default(), flatten: false, force: false, skip_is_verified_check: true, @@ -119,6 +153,7 @@ impl VerifyBundle { via_ir: self.via_ir, evm_version: None, show_standard_json_input: false, + guess_constructor_args: false, zksync: self.zksync, }; diff --git a/crates/sol-macro-gen/Cargo.toml b/crates/sol-macro-gen/Cargo.toml new file mode 100644 index 000000000..c4da94041 --- /dev/null +++ b/crates/sol-macro-gen/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "forge-sol-macro-gen" +description = "Contains types and methods for generating rust bindings using sol!" +publish = false + +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +alloy-json-abi.workspace = true +alloy-sol-macro-input.workspace = true +alloy-sol-macro-expander = { workspace = true, features = ["json"] } +foundry-common.workspace = true + +proc-macro2.workspace = true +quote.workspace = true +syn.workspace = true +prettyplease.workspace = true + +serde_json.workspace = true +eyre.workspace = true diff --git a/crates/sol-macro-gen/src/lib.rs b/crates/sol-macro-gen/src/lib.rs new file mode 100644 index 000000000..0202827f2 --- /dev/null +++ b/crates/sol-macro-gen/src/lib.rs @@ -0,0 +1,5 @@ +//! This crate constains the logic for Rust bindings generating from Solidity contracts + +pub mod sol_macro_gen; + +pub use sol_macro_gen::*; diff --git a/crates/sol-macro-gen/src/sol_macro_gen.rs b/crates/sol-macro-gen/src/sol_macro_gen.rs new file mode 100644 index 000000000..7867b6673 --- /dev/null +++ b/crates/sol-macro-gen/src/sol_macro_gen.rs @@ -0,0 +1,337 @@ +//! SolMacroGen and MultiSolMacroGen +//! +//! This type encapsulates the logic for expansion of a Rust TokenStream from Solidity tokens. It +//! uses the `expand` method from `alloy_sol_macro_expander` underneath. +//! +//! It holds info such as `path` to the ABI file, `name` of the file and the rust binding being +//! generated, and lastly the `expansion` itself, i.e the Rust binding for the provided ABI. +//! +//! It contains methods to read the json abi, generate rust bindings from the abi and ultimately +//! write the bindings to a crate or modules. + +use alloy_sol_macro_expander::expand::expand; +use alloy_sol_macro_input::{SolInput, SolInputKind}; +use eyre::{Context, Ok, OptionExt, Result}; +use foundry_common::fs; +use proc_macro2::{Span, TokenStream}; +use std::{ + fmt::Write, + path::{Path, PathBuf}, + str::FromStr, +}; + +pub struct SolMacroGen { + pub path: PathBuf, + pub name: String, + pub expansion: Option, +} + +impl SolMacroGen { + pub fn new(path: PathBuf, name: String) -> Self { + Self { path, name, expansion: None } + } + + pub fn get_sol_input(&self) -> Result { + let path = self.path.to_string_lossy().into_owned(); + let name = proc_macro2::Ident::new(&self.name, Span::call_site()); + let tokens = quote::quote! { + #name, + #path + }; + + let sol_input: SolInput = syn::parse2(tokens).wrap_err("Failed to parse SolInput {e}")?; + + Ok(sol_input) + } +} + +pub struct MultiSolMacroGen { + pub artifacts_path: PathBuf, + pub instances: Vec, +} + +impl MultiSolMacroGen { + pub fn new(artifacts_path: &Path, instances: Vec) -> Self { + Self { artifacts_path: artifacts_path.to_path_buf(), instances } + } + + pub fn populate_expansion(&mut self, bindings_path: &Path) -> Result<()> { + for instance in &mut self.instances { + let path = bindings_path.join(format!("{}.rs", instance.name.to_lowercase())); + let expansion = fs::read_to_string(path).wrap_err("Failed to read file")?; + + let tokens = TokenStream::from_str(&expansion) + .map_err(|e| eyre::eyre!("Failed to parse TokenStream: {e}"))?; + instance.expansion = Some(tokens); + } + Ok(()) + } + + pub fn generate_bindings(&mut self) -> Result<()> { + for instance in &mut self.instances { + let input = instance.get_sol_input()?.normalize_json()?; + + let SolInput { attrs: _attrs, path: _path, kind } = input; + + let tokens = match kind { + SolInputKind::Sol(mut file) => { + let sol_attr: syn::Attribute = syn::parse_quote! { + #[sol(rpc, alloy_sol_types = alloy::sol_types, alloy_contract = alloy::contract)] + }; + file.attrs.push(sol_attr); + expand(file).wrap_err("Failed to expand SolInput")? + } + _ => unreachable!(), + }; + + instance.expansion = Some(tokens); + } + + Ok(()) + } + + pub fn write_to_crate( + &mut self, + name: &str, + version: &str, + bindings_path: &Path, + single_file: bool, + alloy_version: Option, + ) -> Result<()> { + self.generate_bindings()?; + + let src = bindings_path.join("src"); + + let _ = fs::create_dir_all(&src); + + // Write Cargo.toml + let cargo_toml_path = bindings_path.join("Cargo.toml"); + let mut toml_contents = format!( + r#"[package] +name = "{}" +version = "{}" +edition = "2021" + +[dependencies] +"#, + name, version + ); + + let alloy_dep = if let Some(alloy_version) = alloy_version { + format!( + r#"alloy = {{ git = "https://github.com/alloy-rs/alloy", rev = "{}", features = ["sol-types", "contract"] }}"#, + alloy_version + ) + } else { + r#"alloy = { git = "https://github.com/alloy-rs/alloy", features = ["sol-types", "contract"] }"#.to_string() + }; + write!(toml_contents, "{}", alloy_dep)?; + + fs::write(cargo_toml_path, toml_contents).wrap_err("Failed to write Cargo.toml")?; + + let mut lib_contents = String::new(); + write!( + &mut lib_contents, + r#"#![allow(unused_imports, clippy::all, rustdoc::all)] + //! This module contains the sol! generated bindings for solidity contracts. + //! This is autogenerated code. + //! Do not manually edit these files. + //! These files may be overwritten by the codegen system at any time. + "# + )?; + + // Write src + for instance in &self.instances { + let name = instance.name.to_lowercase(); + let contents = instance.expansion.as_ref().unwrap().to_string(); + + if !single_file { + let path = src.join(format!("{}.rs", name)); + let file = syn::parse_file(&contents)?; + let contents = prettyplease::unparse(&file); + + fs::write(path.clone(), contents).wrap_err("Failed to write file")?; + writeln!(&mut lib_contents, "pub mod {};", name)?; + } else { + write!(&mut lib_contents, "{}", contents)?; + } + } + + let lib_path = src.join("lib.rs"); + let lib_file = syn::parse_file(&lib_contents)?; + + let lib_contents = prettyplease::unparse(&lib_file); + + fs::write(lib_path, lib_contents).wrap_err("Failed to write lib.rs")?; + + Ok(()) + } + + pub fn write_to_module(&mut self, bindings_path: &Path, single_file: bool) -> Result<()> { + self.generate_bindings()?; + + let _ = fs::create_dir_all(bindings_path); + + let mut mod_contents = r#"#![allow(unused_imports, clippy::all, rustdoc::all)] + //! This module contains the sol! generated bindings for solidity contracts. + //! This is autogenerated code. + //! Do not manually edit these files. + //! These files may be overwritten by the codegen system at any time. + "# + .to_string(); + + for instance in &self.instances { + let name = instance.name.to_lowercase(); + if !single_file { + // Module + write!( + mod_contents, + r#"pub mod {}; + "#, + instance.name.to_lowercase() + )?; + let mut contents = String::new(); + + write!(contents, "{}", instance.expansion.as_ref().unwrap())?; + let file = syn::parse_file(&contents)?; + + let contents = prettyplease::unparse(&file); + fs::write(bindings_path.join(format!("{}.rs", name)), contents) + .wrap_err("Failed to write file")?; + } else { + // Single File + let mut contents = String::new(); + write!(contents, "{}\n\n", instance.expansion.as_ref().unwrap())?; + write!(mod_contents, "{}", contents)?; + } + } + + let mod_path = bindings_path.join("mod.rs"); + let mod_file = syn::parse_file(&mod_contents)?; + let mod_contents = prettyplease::unparse(&mod_file); + + fs::write(mod_path, mod_contents).wrap_err("Failed to write mod.rs")?; + + Ok(()) + } + + /// Checks that the generated bindings are up to date with the latest version of + /// `sol!`. + /// + /// Returns `Ok(())` if the generated bindings are up to date, otherwise it returns + /// `Err(_)`. + #[allow(clippy::too_many_arguments)] + pub fn check_consistency( + &self, + name: &str, + version: &str, + crate_path: &Path, + single_file: bool, + check_cargo_toml: bool, + is_mod: bool, + alloy_version: Option, + ) -> Result<()> { + if check_cargo_toml { + self.check_cargo_toml(name, version, crate_path, alloy_version)?; + } + + let mut super_contents = String::new(); + write!( + &mut super_contents, + r#"#![allow(unused_imports, clippy::all, rustdoc::all)] + //! This module contains the sol! generated bindings for solidity contracts. + //! This is autogenerated code. + //! Do not manually edit these files. + //! These files may be overwritten by the codegen system at any time. + "# + )?; + if !single_file { + for instance in &self.instances { + let name = instance.name.to_lowercase(); + let path = if is_mod { + crate_path.join(format!("{}.rs", name)) + } else { + crate_path.join(format!("src/{}.rs", name)) + }; + let tokens = instance + .expansion + .as_ref() + .ok_or_eyre(format!("TokenStream for {path:?} does not exist"))? + .to_string(); + + self.check_file_contents(&path, &tokens)?; + + write!( + &mut super_contents, + r#"pub mod {}; + "#, + name + )?; + } + + let super_path = + if is_mod { crate_path.join("mod.rs") } else { crate_path.join("src/lib.rs") }; + self.check_file_contents(&super_path, &super_contents)?; + } + + Ok(()) + } + + fn check_file_contents(&self, file_path: &Path, expected_contents: &str) -> Result<()> { + eyre::ensure!( + file_path.is_file() && file_path.exists(), + "{} is not a file", + file_path.display() + ); + let file_contents = &fs::read_to_string(file_path).wrap_err("Failed to read file")?; + + // Format both + let file_contents = syn::parse_file(file_contents)?; + let formatted_file = prettyplease::unparse(&file_contents); + + let expected_contents = syn::parse_file(expected_contents)?; + let formatted_exp = prettyplease::unparse(&expected_contents); + + eyre::ensure!( + formatted_file == formatted_exp, + "File contents do not match expected contents for {file_path:?}" + ); + Ok(()) + } + + fn check_cargo_toml( + &self, + name: &str, + version: &str, + crate_path: &Path, + alloy_version: Option, + ) -> Result<()> { + eyre::ensure!(crate_path.is_dir(), "Crate path must be a directory"); + + let cargo_toml_path = crate_path.join("Cargo.toml"); + + eyre::ensure!(cargo_toml_path.is_file(), "Cargo.toml must exist"); + let cargo_toml_contents = + fs::read_to_string(cargo_toml_path).wrap_err("Failed to read Cargo.toml")?; + + let name_check = format!("name = \"{name}\""); + let version_check = format!("version = \"{version}\""); + let alloy_dep_check = if let Some(version) = alloy_version { + format!( + r#"alloy = {{ git = "https://github.com/alloy-rs/alloy", rev = "{version}", features = ["sol-types", "contract"] }}"#, + ) + } else { + r#"alloy = { git = "https://github.com/alloy-rs/alloy", features = ["sol-types", "contract"] }"#.to_string() + }; + let toml_consistent = cargo_toml_contents.contains(&name_check) && + cargo_toml_contents.contains(&version_check) && + cargo_toml_contents.contains(&alloy_dep_check); + eyre::ensure!( + toml_consistent, + r#"The contents of Cargo.toml do not match the expected output of the latest `sol!` version. + This indicates that the existing bindings are outdated and need to be generated again."# + ); + + Ok(()) + } +} diff --git a/crates/test-utils/Cargo.toml b/crates/test-utils/Cargo.toml index 8e2b75931..95ef8d6c9 100644 --- a/crates/test-utils/Cargo.toml +++ b/crates/test-utils/Cargo.toml @@ -11,26 +11,28 @@ license.workspace = true homepage.workspace = true repository.workspace = true +[lints] +workspace = true + [dependencies] foundry-common.workspace = true foundry-compilers = { workspace = true, features = ["project-util"] } foundry-config.workspace = true alloy-primitives.workspace = true - -ethers-core.workspace = true -ethers-providers.workspace = true +alloy-provider.workspace = true eyre.workspace = true fd-lock = "4.0.0" -once_cell = "1" -parking_lot = "0.12" -pretty_assertions.workspace = true +once_cell.workspace = true +parking_lot.workspace = true +similar-asserts.workspace = true regex = "1" serde_json.workspace = true -tracing = "0.1" +tracing.workspace = true tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } -walkdir = "2" +walkdir.workspace = true +rand.workspace = true [features] # feature for integration tests that test external projects diff --git a/crates/test-utils/src/filter.rs b/crates/test-utils/src/filter.rs index fb07237f2..003b0170f 100644 --- a/crates/test-utils/src/filter.rs +++ b/crates/test-utils/src/filter.rs @@ -7,12 +7,13 @@ pub struct Filter { contract_regex: Regex, path_regex: Regex, exclude_tests: Option, + exclude_contracts: Option, exclude_paths: Option, } impl Filter { pub fn new(test_pattern: &str, contract_pattern: &str, path_pattern: &str) -> Self { - Filter { + Self { test_regex: Regex::new(test_pattern) .unwrap_or_else(|_| panic!("Failed to parse test pattern: `{test_pattern}`")), contract_regex: Regex::new(contract_pattern).unwrap_or_else(|_| { @@ -21,6 +22,7 @@ impl Filter { path_regex: Regex::new(path_pattern) .unwrap_or_else(|_| panic!("Failed to parse path pattern: `{path_pattern}`")), exclude_tests: None, + exclude_contracts: None, exclude_paths: None, } } @@ -41,6 +43,14 @@ impl Filter { self } + /// All contracts to also exclude + /// + /// This is a workaround since regex does not support negative look aheads + pub fn exclude_contracts(mut self, pattern: &str) -> Self { + self.exclude_contracts = Some(Regex::new(pattern).unwrap()); + self + } + /// All paths to also exclude /// /// This is a workaround since regex does not support negative look aheads @@ -50,11 +60,12 @@ impl Filter { } pub fn matches_all() -> Self { - Filter { + Self { test_regex: Regex::new(".*").unwrap(), contract_regex: Regex::new(".*").unwrap(), path_regex: Regex::new(".*").unwrap(), exclude_tests: None, + exclude_contracts: None, exclude_paths: None, } } @@ -71,6 +82,12 @@ impl TestFilter for Filter { } fn matches_contract(&self, contract_name: &str) -> bool { + if let Some(exclude) = &self.exclude_contracts { + if exclude.is_match(contract_name) { + return false; + } + } + self.contract_regex.is_match(contract_name) } diff --git a/crates/test-utils/src/lib.rs b/crates/test-utils/src/lib.rs index 042ff4aba..28bb4def1 100644 --- a/crates/test-utils/src/lib.rs +++ b/crates/test-utils/src/lib.rs @@ -1,4 +1,9 @@ -#![warn(unused_crate_dependencies, unreachable_pub)] +//! # foundry-test-utils +//! +//! Internal Foundry testing utilities. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #[macro_use] extern crate tracing; @@ -6,6 +11,8 @@ extern crate tracing; // Macros useful for testing. mod macros; +pub mod rpc; + pub mod fd_lock; mod filter; diff --git a/crates/test-utils/src/macros.rs b/crates/test-utils/src/macros.rs index f05c8e36b..1f19adffe 100644 --- a/crates/test-utils/src/macros.rs +++ b/crates/test-utils/src/macros.rs @@ -92,3 +92,20 @@ macro_rules! forgetest_init { } }; } + +/// Setup forge soldeer +#[macro_export] +macro_rules! forgesoldeer { + ($(#[$attr:meta])* $test:ident, |$prj:ident, $cmd:ident| $e:expr) => { + $crate::forgesoldeer!($(#[$attr])* $test, $crate::foundry_compilers::PathStyle::Dapptools, |$prj, $cmd| $e); + }; + ($(#[$attr:meta])* $test:ident, $style:expr, |$prj:ident, $cmd:ident| $e:expr) => { + #[test] + $(#[$attr])* + fn $test() { + let (mut $prj, mut $cmd) = $crate::util::setup_forge(stringify!($test), $style); + $crate::util::initialize($prj.root()); + $e + } + }; +} diff --git a/crates/common/src/rpc.rs b/crates/test-utils/src/rpc.rs similarity index 85% rename from crates/common/src/rpc.rs rename to crates/test-utils/src/rpc.rs index eb5321335..63a701a4f 100644 --- a/crates/common/src/rpc.rs +++ b/crates/test-utils/src/rpc.rs @@ -51,6 +51,22 @@ static ALCHEMY_MAINNET_KEYS: Lazy> = Lazy::new(|| { keys }); +// List of etherscan keys for mainnet +static ETHERSCAN_MAINNET_KEYS: Lazy> = Lazy::new(|| { + let mut keys = vec![ + "MCAUM7WPE9XP5UQMZPCKIBUJHPM1C24FP6", + "JW6RWCG2C5QF8TANH4KC7AYIF1CX7RB5D1", + "ZSMDY6BI2H55MBE3G9CUUQT4XYUDBB6ZSK", + "4FYHTY429IXYMJNS4TITKDMUKW5QRYDX61", + "QYKNT5RHASZ7PGQE68FNQWH99IXVTVVD2I", + "VXMQ117UN58Y4RHWUB8K1UGCEA7UQEWK55", + ]; + + keys.shuffle(&mut rand::thread_rng()); + + keys +}); + /// counts how many times a rpc endpoint was requested for _mainnet_ static NEXT_RPC_ENDPOINT: AtomicUsize = AtomicUsize::new(0); @@ -111,6 +127,12 @@ pub fn next_ws_archive_rpc_endpoint() -> String { format!("wss://eth-mainnet.alchemyapi.io/v2/{}", ALCHEMY_MAINNET_KEYS[idx]) } +/// Returns the next etherscan api key +pub fn next_etherscan_api_key() -> String { + let idx = next() % ETHERSCAN_MAINNET_KEYS.len(); + ETHERSCAN_MAINNET_KEYS[idx].to_string() +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/test-utils/src/script.rs b/crates/test-utils/src/script.rs index f4449a769..0bc4d399d 100644 --- a/crates/test-utils/src/script.rs +++ b/crates/test-utils/src/script.rs @@ -1,25 +1,54 @@ use crate::{init_tracing, TestCommand}; -use alloy_primitives::{Address, U256}; -use ethers_core::types::NameOrAddress; -use ethers_providers::Middleware; +use alloy_primitives::Address; +use alloy_provider::Provider; use eyre::Result; -use foundry_common::{ - provider::ethers::{get_http_provider, RetryProvider}, - types::{ToAlloy, ToEthers}, +use foundry_common::provider::{get_http_provider, RetryProvider}; +use std::{ + collections::BTreeMap, + fs, + path::{Path, PathBuf}, + str::FromStr, }; -use std::{collections::BTreeMap, fs, path::Path, str::FromStr}; const BROADCAST_TEST_PATH: &str = "src/Broadcast.t.sol"; const TESTDATA: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../testdata"); +fn init_script_cmd( + cmd: &mut TestCommand, + project_root: &Path, + target_contract: &str, + endpoint: Option<&str>, +) { + cmd.forge_fuse(); + cmd.set_current_dir(project_root); + + cmd.args([ + "script", + "-R", + "ds-test/=lib/", + "-R", + "cheats/=cheats/", + target_contract, + "--root", + project_root.to_str().unwrap(), + "-vvvvv", + ]); + + if let Some(rpc_url) = endpoint { + cmd.args(["--fork-url", rpc_url]); + } +} /// A helper struct to test forge script scenarios pub struct ScriptTester { pub accounts_pub: Vec
, pub accounts_priv: Vec, pub provider: Option, - pub nonces: BTreeMap, - pub address_nonces: BTreeMap, + pub nonces: BTreeMap, + pub address_nonces: BTreeMap, pub cmd: TestCommand, + pub project_root: PathBuf, + pub target_contract: String, + pub endpoint: Option, } impl ScriptTester { @@ -31,26 +60,15 @@ impl ScriptTester { target_contract: &str, ) -> Self { init_tracing(); - ScriptTester::copy_testdata(project_root).unwrap(); - cmd.set_current_dir(project_root); - - cmd.args([ - "script", - "-R", - "ds-test/=lib/", - target_contract, - "--root", - project_root.to_str().unwrap(), - "-vvvvv", - ]); + Self::copy_testdata(project_root).unwrap(); + init_script_cmd(&mut cmd, project_root, target_contract, endpoint); let mut provider = None; if let Some(endpoint) = endpoint { - cmd.args(["--fork-url", endpoint]); provider = Some(get_http_provider(endpoint)) } - ScriptTester { + Self { accounts_pub: vec![ Address::from_str("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266").unwrap(), Address::from_str("0x70997970C51812dc3A010C7d01b50e0d17dc79C8").unwrap(), @@ -65,6 +83,9 @@ impl ScriptTester { nonces: BTreeMap::default(), address_nonces: BTreeMap::default(), cmd, + project_root: project_root.to_path_buf(), + target_contract: target_contract.to_string(), + endpoint: endpoint.map(|s| s.to_string()), } } @@ -75,7 +96,7 @@ impl ScriptTester { // copy the broadcast test fs::copy( - Self::testdata_path().join("cheats/Broadcast.t.sol"), + Self::testdata_path().join("default/cheats/Broadcast.t.sol"), project_root.join(BROADCAST_TEST_PATH), ) .expect("Failed to initialize broadcast contract"); @@ -90,8 +111,11 @@ impl ScriptTester { // copy the broadcast test let testdata = Self::testdata_path(); - fs::copy(testdata.join("cheats/Broadcast.t.sol"), project_root.join(BROADCAST_TEST_PATH)) - .expect("Failed to initialize broadcast contract"); + fs::copy( + testdata.join("default/cheats/Broadcast.t.sol"), + project_root.join(BROADCAST_TEST_PATH), + ) + .expect("Failed to initialize broadcast contract"); Self::new(cmd, None, project_root, &target_contract) } @@ -104,7 +128,8 @@ impl ScriptTester { /// Initialises the test contracts by copying them into the workspace fn copy_testdata(current_dir: &Path) -> Result<()> { let testdata = Self::testdata_path(); - fs::copy(testdata.join("cheats/Vm.sol"), current_dir.join("src/Vm.sol"))?; + fs::create_dir_all(current_dir.join("cheats"))?; + fs::copy(testdata.join("cheats/Vm.sol"), current_dir.join("cheats/Vm.sol"))?; fs::copy(testdata.join("lib/ds-test/src/test.sol"), current_dir.join("lib/test.sol"))?; Ok(()) } @@ -115,13 +140,10 @@ impl ScriptTester { if let Some(provider) = &self.provider { let nonce = provider - .get_transaction_count( - NameOrAddress::Address(self.accounts_pub[index as usize].to_ethers()), - None, - ) + .get_transaction_count(self.accounts_pub[index as usize]) .await .unwrap(); - self.nonces.insert(index, nonce.to_alloy()); + self.nonces.insert(index, nonce); } } self @@ -129,14 +151,9 @@ impl ScriptTester { pub async fn load_addresses(&mut self, addresses: &[Address]) -> &mut Self { for &address in addresses { - let nonce = self - .provider - .as_ref() - .unwrap() - .get_transaction_count(NameOrAddress::Address(address.to_ethers()), None) - .await - .unwrap(); - self.address_nonces.insert(address, nonce.to_alloy()); + let nonce = + self.provider.as_ref().unwrap().get_transaction_count(address).await.unwrap(); + self.address_nonces.insert(address, nonce); } self } @@ -175,18 +192,12 @@ impl ScriptTester { pub async fn assert_nonce_increment(&mut self, keys_indexes: &[(u32, u32)]) -> &mut Self { for &(private_key_slot, expected_increment) in keys_indexes { let addr = self.accounts_pub[private_key_slot as usize]; - let nonce = self - .provider - .as_ref() - .unwrap() - .get_transaction_count(NameOrAddress::Address(addr.to_ethers()), None) - .await - .unwrap(); + let nonce = self.provider.as_ref().unwrap().get_transaction_count(addr).await.unwrap(); let prev_nonce = self.nonces.get(&private_key_slot).unwrap(); assert_eq!( nonce, - (prev_nonce + U256::from(expected_increment)).to_ethers(), + (*prev_nonce + expected_increment as u64), "nonce not incremented correctly for {addr}: \ {prev_nonce} + {expected_increment} != {nonce}" ); @@ -200,16 +211,11 @@ impl ScriptTester { address_indexes: &[(Address, u32)], ) -> &mut Self { for (address, expected_increment) in address_indexes { - let nonce = self - .provider - .as_ref() - .unwrap() - .get_transaction_count(NameOrAddress::Address(address.to_ethers()), None) - .await - .unwrap(); + let nonce = + self.provider.as_ref().unwrap().get_transaction_count(*address).await.unwrap(); let prev_nonce = self.address_nonces.get(address).unwrap(); - assert_eq!(nonce, (prev_nonce + U256::from(*expected_increment)).to_ethers()); + assert_eq!(nonce, *prev_nonce + *expected_increment as u64); } self } @@ -243,6 +249,17 @@ impl ScriptTester { self.cmd.args(args); self } + + pub fn clear(&mut self) { + init_script_cmd( + &mut self.cmd, + &self.project_root, + &self.target_contract, + self.endpoint.as_deref(), + ); + self.nonces.clear(); + self.address_nonces.clear(); + } } /// Various `forge` script results @@ -258,6 +275,7 @@ pub enum ScriptOutcome { ScriptFailed, UnsupportedLibraries, ErrorSelectForkOnBroadcast, + OkRun, } impl ScriptOutcome { @@ -273,21 +291,23 @@ impl ScriptOutcome { Self::ScriptFailed => "script failed: ", Self::UnsupportedLibraries => "Multi chain deployment does not support library linking at the moment.", Self::ErrorSelectForkOnBroadcast => "cannot select forks during a broadcast", + Self::OkRun => "Script ran successfully", } } pub fn is_err(&self) -> bool { match self { - ScriptOutcome::OkNoEndpoint | - ScriptOutcome::OkSimulation | - ScriptOutcome::OkBroadcast | - ScriptOutcome::WarnSpecifyDeployer => false, - ScriptOutcome::MissingSender | - ScriptOutcome::MissingWallet | - ScriptOutcome::StaticCallNotAllowed | - ScriptOutcome::UnsupportedLibraries | - ScriptOutcome::ErrorSelectForkOnBroadcast | - ScriptOutcome::ScriptFailed => true, + Self::OkNoEndpoint | + Self::OkSimulation | + Self::OkBroadcast | + Self::WarnSpecifyDeployer | + Self::OkRun => false, + Self::MissingSender | + Self::MissingWallet | + Self::StaticCallNotAllowed | + Self::UnsupportedLibraries | + Self::ErrorSelectForkOnBroadcast | + Self::ScriptFailed => true, } } } diff --git a/crates/test-utils/src/util.rs b/crates/test-utils/src/util.rs index a26936489..6d2d5233a 100644 --- a/crates/test-utils/src/util.rs +++ b/crates/test-utils/src/util.rs @@ -1,7 +1,9 @@ use crate::init_tracing; use eyre::{Result, WrapErr}; use foundry_compilers::{ - cache::SolFilesCache, + artifacts::Settings, + cache::CompilerCache, + compilers::multi::MultiCompiler, error::Result as SolcResult, project_util::{copy_dir, TempProject}, ArtifactOutput, ConfigurableArtifacts, PathStyle, ProjectPathsConfig, @@ -13,9 +15,8 @@ use regex::Regex; use std::{ env, ffi::OsStr, - fs, - fs::File, - io::{BufWriter, IsTerminal, Write}, + fs::{self, File}, + io::{BufWriter, IsTerminal, Read, Seek, Write}, path::{Path, PathBuf}, process::{ChildStdin, Command, Output, Stdio}, sync::{ @@ -51,7 +52,7 @@ pub const OTHER_SOLC_VERSION: &str = "0.8.22"; /// External test builder #[derive(Clone, Debug)] -#[must_use = "call run()"] +#[must_use = "ExtTester does nothing unless you `run` it"] pub struct ExtTester { pub org: &'static str, pub name: &'static str, @@ -195,10 +196,10 @@ impl ExtTester { test_cmd.envs(self.envs.iter().map(|(k, v)| (k, v))); if let Some(fork_block) = self.fork_block { - test_cmd - .env("FOUNDRY_ETH_RPC_URL", foundry_common::rpc::next_http_archive_rpc_endpoint()); + test_cmd.env("FOUNDRY_ETH_RPC_URL", crate::rpc::next_http_archive_rpc_endpoint()); test_cmd.env("FOUNDRY_FORK_BLOCK_NUMBER", fork_block.to_string()); } + test_cmd.env("FOUNDRY_INVARIANT_DEPTH", "15"); test_cmd.assert_non_empty_stdout(); } @@ -237,21 +238,31 @@ pub fn initialize(target: &Path) { // Release the read lock and acquire a write lock, initializing the lock file. _read = None; + let mut write = lock.write().unwrap(); - write.write_all(b"1").unwrap(); - // Initialize and build. - let (prj, mut cmd) = setup_forge("template", foundry_compilers::PathStyle::Dapptools); - eprintln!("- initializing template dir in {}", prj.root().display()); + let mut data = String::new(); + write.read_to_string(&mut data).unwrap(); + + if data != "1" { + // Initialize and build. + let (prj, mut cmd) = setup_forge("template", foundry_compilers::PathStyle::Dapptools); + eprintln!("- initializing template dir in {}", prj.root().display()); + + cmd.args(["init", "--force"]).assert_success(); + cmd.forge_fuse().args(["build", "--use", SOLC_VERSION]).assert_success(); - cmd.args(["init", "--force"]).assert_success(); - cmd.forge_fuse().args(["build", "--use", SOLC_VERSION]).assert_success(); + // Remove the existing template, if any. + let _ = fs::remove_dir_all(tpath); - // Remove the existing template, if any. - let _ = fs::remove_dir_all(tpath); + // Copy the template to the global template path. + pretty_err(tpath, copy_dir(prj.root(), tpath)); - // Copy the template to the global template path. - pretty_err(tpath, copy_dir(prj.root(), tpath)); + // Update lockfile to mark that template is initialized. + write.set_len(0).unwrap(); + write.seek(std::io::SeekFrom::Start(0)).unwrap(); + write.write_all(b"1").unwrap(); + } // Release the write lock and acquire a new read lock. drop(write); @@ -399,7 +410,7 @@ pub struct TestProject { /// The directory in which this test executable is running. exe_root: PathBuf, /// The project in which the test should run. - inner: Arc>, + inner: Arc>, } impl TestProject { @@ -444,6 +455,12 @@ impl TestProject { &self.paths().artifacts } + /// Removes the project's cache and artifacts directory. + pub fn clear(&self) { + self.clear_cache(); + self.clear_artifacts(); + } + /// Removes this project's cache file. pub fn clear_cache(&self) { let _ = fs::remove_file(self.cache()); @@ -518,7 +535,9 @@ impl TestProject { #[track_caller] pub fn assert_create_dirs_exists(&self) { self.paths().create_all().unwrap_or_else(|_| panic!("Failed to create project paths")); - SolFilesCache::default().write(&self.paths().cache).expect("Failed to create cache"); + CompilerCache::::default() + .write(&self.paths().cache) + .expect("Failed to create cache"); self.assert_all_paths_exist(); } @@ -562,7 +581,7 @@ impl TestProject { /// Adds `console.sol` as a source under "console.sol" pub fn insert_console(&self) -> PathBuf { - let s = include_str!("../../../testdata/logs/console.sol"); + let s = include_str!("../../../testdata/default/logs/console.sol"); self.add_source("console.sol", s).unwrap() } @@ -714,18 +733,18 @@ impl TestCommand { } /// Replaces the underlying command. - pub fn set_cmd(&mut self, cmd: Command) -> &mut TestCommand { + pub fn set_cmd(&mut self, cmd: Command) -> &mut Self { self.cmd = cmd; self } /// Resets the command to the default `forge` command. - pub fn forge_fuse(&mut self) -> &mut TestCommand { + pub fn forge_fuse(&mut self) -> &mut Self { self.set_cmd(self.project.forge_bin()) } /// Resets the command to the default `cast` command. - pub fn cast_fuse(&mut self) -> &mut TestCommand { + pub fn cast_fuse(&mut self) -> &mut Self { self.set_cmd(self.project.cast_bin()) } @@ -739,13 +758,13 @@ impl TestCommand { } /// Add an argument to pass to the command. - pub fn arg>(&mut self, arg: A) -> &mut TestCommand { + pub fn arg>(&mut self, arg: A) -> &mut Self { self.cmd.arg(arg); self } /// Add any number of arguments to the command. - pub fn args(&mut self, args: I) -> &mut TestCommand + pub fn args(&mut self, args: I) -> &mut Self where I: IntoIterator, A: AsRef, @@ -754,13 +773,13 @@ impl TestCommand { self } - pub fn stdin(&mut self, fun: impl FnOnce(ChildStdin) + 'static) -> &mut TestCommand { + pub fn stdin(&mut self, fun: impl FnOnce(ChildStdin) + 'static) -> &mut Self { self.stdin_fun = Some(Box::new(fun)); self } /// Convenience function to add `--root project.root()` argument - pub fn root_arg(&mut self) -> &mut TestCommand { + pub fn root_arg(&mut self) -> &mut Self { let root = self.project.root().to_path_buf(); self.arg("--root").arg(root) } @@ -790,7 +809,7 @@ impl TestCommand { /// Note that this does not need to be called normally, since the creation /// of this TestCommand causes its working directory to be set to the /// test's directory automatically. - pub fn current_dir>(&mut self, dir: P) -> &mut TestCommand { + pub fn current_dir>(&mut self, dir: P) -> &mut Self { self.cmd.current_dir(dir); self } @@ -942,7 +961,7 @@ impl TestCommand { fs::write(format!("{}.stderr", name.display()), &output.stderr).unwrap(); } - /// Runs the command and asserts that it resulted in an error exit code. + /// Runs the command and asserts that it **failed** (resulted in an error exit code). #[track_caller] pub fn assert_err(&mut self) { let out = self.execute(); @@ -951,7 +970,7 @@ impl TestCommand { } } - /// Runs the command and asserts that something was printed to stderr. + /// Runs the command and asserts that it **failed** and something was printed to stderr. #[track_caller] pub fn assert_non_empty_stderr(&mut self) { let out = self.execute(); @@ -960,7 +979,7 @@ impl TestCommand { } } - /// Runs the command and asserts that something was printed to stdout. + /// Runs the command and asserts that it **succeeded** and something was printed to stdout. #[track_caller] pub fn assert_non_empty_stdout(&mut self) { let out = self.execute(); @@ -969,7 +988,7 @@ impl TestCommand { } } - /// Runs the command and asserts that nothing was printed to stdout. + /// Runs the command and asserts that it **failed** nothing was printed to stdout. #[track_caller] pub fn assert_empty_stdout(&mut self) { let out = self.execute(); @@ -1050,7 +1069,7 @@ static IGNORE_IN_FIXTURES: Lazy = Lazy::new(|| { let re = &[ // solc version r" ?Solc(?: version)? \d+.\d+.\d+", - r" with \d+.\d+.\d+", + r" with(?: Solc)? \d+.\d+.\d+", // solc runs r"runs: \d+, μ: \d+, ~: \d+", // elapsed time @@ -1064,7 +1083,7 @@ static IGNORE_IN_FIXTURES: Lazy = Lazy::new(|| { Regex::new(&format!("({})", re.join("|"))).unwrap() }); -fn normalize_output(s: &str) -> String { +pub fn normalize_output(s: &str) -> String { let s = s.replace("\r\n", "\n").replace('\\', "/"); IGNORE_IN_FIXTURES.replace_all(&s, "").into_owned() } @@ -1073,7 +1092,7 @@ impl OutputExt for Output { #[track_caller] fn stdout_matches_content(&self, expected: &str) { let out = lossy_string(&self.stdout); - pretty_assertions::assert_eq!(normalize_output(&out), normalize_output(expected)); + similar_asserts::assert_eq!(normalize_output(&out), normalize_output(expected)); } #[track_caller] @@ -1086,7 +1105,7 @@ impl OutputExt for Output { fn stderr_matches_path(&self, expected_path: impl AsRef) { let expected = fs::read_to_string(expected_path).unwrap(); let err = lossy_string(&self.stderr); - pretty_assertions::assert_eq!(normalize_output(&err), normalize_output(&expected)); + similar_asserts::assert_eq!(normalize_output(&err), normalize_output(&expected)); } } diff --git a/crates/verify/Cargo.toml b/crates/verify/Cargo.toml new file mode 100644 index 000000000..29cd75770 --- /dev/null +++ b/crates/verify/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "forge-verify" +description = "Contract verification tools" + +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-config.workspace = true +foundry-cli.workspace = true +foundry-common.workspace = true +foundry-evm.workspace = true +serde_json.workspace = true +hex.workspace = true +alloy-json-abi.workspace = true +alloy-primitives.workspace = true +alloy-rpc-types.workspace = true +revm-primitives.workspace = true +serde.workspace = true +eyre.workspace = true +alloy-provider.workspace = true +tracing.workspace = true +foundry-compilers = { workspace = true, features = ["full"] } +foundry-block-explorers = { workspace = true, features = ["foundry-compilers"] } + +clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } +reqwest = { workspace = true, features = ["json"] } +async-trait.workspace = true +futures.workspace = true +semver.workspace = true +regex = { version = "1", default-features = false } +once_cell.workspace = true +yansi.workspace = true +itertools.workspace = true + +# zk +foundry-zksync-compiler.workspace = true + +[dev-dependencies] +tokio = { workspace = true, features = ["macros"] } +foundry-test-utils.workspace = true +tempfile.workspace = true diff --git a/crates/verify/src/bytecode.rs b/crates/verify/src/bytecode.rs new file mode 100644 index 000000000..44b1f5542 --- /dev/null +++ b/crates/verify/src/bytecode.rs @@ -0,0 +1,658 @@ +use alloy_primitives::{Address, Bytes, U256}; +use alloy_provider::Provider; +use alloy_rpc_types::{BlockId, BlockNumberOrTag}; +use clap::{Parser, ValueHint}; +use eyre::{OptionExt, Result}; +use foundry_block_explorers::{contract::Metadata, Client}; +use foundry_cli::{ + opts::EtherscanOpts, + utils::{self, read_constructor_args_file, LoadConfig}, +}; +use foundry_common::{compile::ProjectCompiler, provider::ProviderBuilder}; +use foundry_compilers::{ + artifacts::{BytecodeHash, BytecodeObject, CompactContractBytecode, EvmVersion}, + info::ContractInfo, + Artifact, +}; +use foundry_config::{figment, filter::SkipBuildFilter, impl_figment_convert, Chain, Config}; +use foundry_evm::{ + constants::DEFAULT_CREATE2_DEPLOYER, executors::TracingExecutor, utils::configure_tx_env, +}; +use revm_primitives::{db::Database, EnvWithHandlerCfg, HandlerCfg, SpecId}; +use semver::Version; +use serde::{Deserialize, Serialize}; +use std::{fmt, path::PathBuf, str::FromStr}; +use yansi::Paint; + +impl_figment_convert!(VerifyBytecodeArgs); + +/// CLI arguments for `forge verify-bytecode`. +#[derive(Clone, Debug, Parser)] +pub struct VerifyBytecodeArgs { + /// The address of the contract to verify. + pub address: Address, + + /// The contract identifier in the form `:`. + pub contract: ContractInfo, + + /// The block at which the bytecode should be verified. + #[clap(long, value_name = "BLOCK")] + pub block: Option, + + /// The constructor args to generate the creation code. + #[clap( + long, + conflicts_with = "constructor_args_path", + value_name = "ARGS", + visible_alias = "encoded-constructor-args" + )] + pub constructor_args: Option, + + /// The path to a file containing the constructor arguments. + #[clap(long, value_hint = ValueHint::FilePath, value_name = "PATH")] + pub constructor_args_path: Option, + + /// The rpc url to use for verification. + #[clap(short = 'r', long, value_name = "RPC_URL", env = "ETH_RPC_URL")] + pub rpc_url: Option, + + /// Verfication Type: `full` or `partial`. + /// Ref: + #[clap(long, default_value = "full", value_name = "TYPE")] + pub verification_type: VerificationType, + + #[clap(flatten)] + pub etherscan_opts: EtherscanOpts, + + /// Skip building files whose names contain the given filter. + /// + /// `test` and `script` are aliases for `.t.sol` and `.s.sol`. + #[arg(long, num_args(1..))] + pub skip: Option>, + + /// The path to the project's root directory. + pub root: Option, + + /// Suppress logs and emit json results to stdout + #[clap(long, default_value = "false")] + pub json: bool, +} + +impl figment::Provider for VerifyBytecodeArgs { + fn metadata(&self) -> figment::Metadata { + figment::Metadata::named("Verify Bytecode Provider") + } + + fn data( + &self, + ) -> Result, figment::Error> { + let mut dict = figment::value::Dict::new(); + if let Some(block) = &self.block { + dict.insert("block".into(), figment::value::Value::serialize(block)?); + } + if let Some(rpc_url) = &self.rpc_url { + dict.insert("eth_rpc_url".into(), rpc_url.to_string().into()); + } + dict.insert("verification_type".into(), self.verification_type.to_string().into()); + + Ok(figment::value::Map::from([(Config::selected_profile(), dict)])) + } +} + +impl VerifyBytecodeArgs { + /// Run the `verify-bytecode` command to verify the bytecode onchain against the locally built + /// bytecode. + pub async fn run(mut self) -> Result<()> { + // Setup + let config = self.load_config_emit_warnings(); + let provider = ProviderBuilder::new(&config.get_rpc_url_or_localhost_http()?).build()?; + + let code = provider.get_code_at(self.address).await?; + if code.is_empty() { + eyre::bail!("No bytecode found at address {}", self.address); + } + + if !self.json { + println!( + "Verifying bytecode for contract {} at address {}", + self.contract.name.clone().green(), + self.address.green() + ); + } + + // If chain is not set, we try to get it from the RPC + // If RPC is not set, the default chain is used + let chain = if config.get_rpc_url().is_some() { + let chain_id = provider.get_chain_id().await?; + Chain::from(chain_id) + } else { + config.chain.unwrap_or_default() + }; + + // Set Etherscan options + self.etherscan_opts.chain = Some(chain); + self.etherscan_opts.key = + config.get_etherscan_config_with_chain(Some(chain))?.map(|c| c.key); + + // If etherscan key is not set, we can't proceed with etherscan verification + let Some(key) = self.etherscan_opts.key.clone() else { + eyre::bail!("Etherscan API key is required for verification"); + }; + let etherscan = Client::new(chain, key)?; + + // Get the constructor args using `source_code` endpoint + let source_code = etherscan.contract_source_code(self.address).await?; + + // Check if the contract name matches + let name = source_code.items.first().map(|item| item.contract_name.to_owned()); + if name.as_ref() != Some(&self.contract.name) { + eyre::bail!("Contract name mismatch"); + } + + // Get the constructor args from etherscan + let constructor_args = if let Some(args) = source_code.items.first() { + args.constructor_arguments.clone() + } else { + eyre::bail!("No constructor arguments found for contract at address {}", self.address); + }; + + // Get user provided constructor args + let provided_constructor_args = if let Some(args) = self.constructor_args.to_owned() { + args + } else if let Some(path) = self.constructor_args_path.to_owned() { + // Read from file + let res = read_constructor_args_file(path)?; + // Convert res to Bytes + res.join("") + } else { + constructor_args.to_string() + }; + + // Constructor args mismatch + if provided_constructor_args != constructor_args.to_string() && !self.json { + println!( + "{}", + "The provided constructor args do not match the constructor args from etherscan. This will result in a mismatch - Using the args from etherscan".red().bold(), + ); + } + + // Get creation tx hash + let creation_data = etherscan.contract_creation_data(self.address).await?; + + let mut transaction = provider + .get_transaction_by_hash(creation_data.transaction_hash) + .await + .or_else(|e| eyre::bail!("Couldn't fetch transaction from RPC: {:?}", e))? + .ok_or_else(|| { + eyre::eyre!("Transaction not found for hash {}", creation_data.transaction_hash) + })?; + let receipt = provider + .get_transaction_receipt(creation_data.transaction_hash) + .await + .or_else(|e| eyre::bail!("Couldn't fetch transacrion receipt from RPC: {:?}", e))?; + + let receipt = if let Some(receipt) = receipt { + receipt + } else { + eyre::bail!( + "Receipt not found for transaction hash {}", + creation_data.transaction_hash + ); + }; + // Extract creation code + let maybe_creation_code = if receipt.contract_address == Some(self.address) { + &transaction.input + } else if transaction.to == Some(DEFAULT_CREATE2_DEPLOYER) { + &transaction.input[32..] + } else { + eyre::bail!( + "Could not extract the creation code for contract at address {}", + self.address + ); + }; + + // If bytecode_hash is disabled then its always partial verification + let (verification_type, has_metadata) = + match (&self.verification_type, config.bytecode_hash) { + (VerificationType::Full, BytecodeHash::None) => (VerificationType::Partial, false), + (VerificationType::Partial, BytecodeHash::None) => { + (VerificationType::Partial, false) + } + (VerificationType::Full, _) => (VerificationType::Full, true), + (VerificationType::Partial, _) => (VerificationType::Partial, true), + }; + + trace!(?verification_type, has_metadata); + // Etherscan compilation metadata + let etherscan_metadata = source_code.items.first().unwrap(); + + let local_bytecode = + if let Some(local_bytecode) = self.build_using_cache(etherscan_metadata, &config) { + trace!("using cache"); + local_bytecode + } else { + self.build_project(&config)? + }; + + // Append constructor args to the local_bytecode + let mut local_bytecode_vec = local_bytecode.to_vec(); + local_bytecode_vec.extend_from_slice(&constructor_args); + + // Cmp creation code with locally built bytecode and maybe_creation_code + let (did_match, with_status) = try_match( + local_bytecode_vec.as_slice(), + maybe_creation_code, + &constructor_args, + &verification_type, + false, + has_metadata, + )?; + + let mut json_results: Vec = vec![]; + self.print_result( + (did_match, with_status), + BytecodeType::Creation, + &mut json_results, + etherscan_metadata, + &config, + ); + + // Get contract creation block + let simulation_block = match self.block { + Some(BlockId::Number(BlockNumberOrTag::Number(block))) => block, + Some(_) => eyre::bail!("Invalid block number"), + None => { + let provider = utils::get_provider(&config)?; + provider + .get_transaction_by_hash(creation_data.transaction_hash) + .await.or_else(|e| eyre::bail!("Couldn't fetch transaction from RPC: {:?}", e))?.ok_or_else(|| { + eyre::eyre!("Transaction not found for hash {}", creation_data.transaction_hash) + })? + .block_number.ok_or_else(|| { + eyre::eyre!("Failed to get block number of the contract creation tx, specify using the --block flag") + })? + } + }; + + // Fork the chain at `simulation_block` + + let (mut fork_config, evm_opts) = config.clone().load_config_and_evm_opts()?; + fork_config.fork_block_number = Some(simulation_block - 1); + fork_config.evm_version = + etherscan_metadata.evm_version()?.unwrap_or(EvmVersion::default()); + let (mut env, fork, _chain) = + TracingExecutor::get_fork_material(&fork_config, evm_opts).await?; + + let mut executor = + TracingExecutor::new(env.clone(), fork, Some(fork_config.evm_version), false); + env.block.number = U256::from(simulation_block); + let block = provider.get_block(simulation_block.into(), true.into()).await?; + + // Workaround for the NonceTooHigh issue as we're not simulating prior txs of the same + // block. + let prev_block_id = BlockId::number(simulation_block - 1); + let prev_block_nonce = provider + .get_transaction_count(creation_data.contract_creator) + .block_id(prev_block_id) + .await?; + transaction.nonce = prev_block_nonce; + + if let Some(ref block) = block { + env.block.timestamp = U256::from(block.header.timestamp); + env.block.coinbase = block.header.miner; + env.block.difficulty = block.header.difficulty; + env.block.prevrandao = Some(block.header.mix_hash.unwrap_or_default()); + env.block.basefee = U256::from(block.header.base_fee_per_gas.unwrap_or_default()); + env.block.gas_limit = U256::from(block.header.gas_limit); + } + + configure_tx_env(&mut env, &transaction); + + let env_with_handler = + EnvWithHandlerCfg::new(Box::new(env.clone()), HandlerCfg::new(SpecId::LATEST)); + + let contract_address = if let Some(to) = transaction.to { + if to != DEFAULT_CREATE2_DEPLOYER { + eyre::bail!("Transaction `to` address is not the default create2 deployer i.e the tx is not a contract creation tx."); + } + let result = executor.transact_with_env(env_with_handler.clone())?; + + if result.result.len() != 20 { + eyre::bail!("Failed to deploy contract on fork at block {simulation_block}: call result is not exactly 20 bytes"); + } + + Address::from_slice(&result.result) + } else { + let deploy_result = executor.deploy_with_env(env_with_handler, None)?; + deploy_result.address + }; + + // State commited using deploy_with_env, now get the runtime bytecode from the db. + let fork_runtime_code = executor + .backend + .basic(contract_address)? + .ok_or_else(|| { + eyre::eyre!( + "Failed to get runtime code for contract deployed on fork at address {}", + contract_address + ) + })? + .code + .ok_or_else(|| { + eyre::eyre!( + "Bytecode does not exist for contract deployed on fork at address {}", + contract_address + ) + })?; + + let onchain_runtime_code = + provider.get_code_at(self.address).block_id(BlockId::number(simulation_block)).await?; + + // Compare the runtime bytecode with the locally built bytecode + let (did_match, with_status) = try_match( + fork_runtime_code.bytecode(), + &onchain_runtime_code, + &constructor_args, + &verification_type, + true, + has_metadata, + )?; + + self.print_result( + (did_match, with_status), + BytecodeType::Runtime, + &mut json_results, + etherscan_metadata, + &config, + ); + + if self.json { + println!("{}", serde_json::to_string(&json_results)?); + } + Ok(()) + } + + fn build_project(&self, config: &Config) -> Result { + let project = config.project()?; + let compiler = ProjectCompiler::new(); + + let output = compiler.compile(&project)?; + + let artifact = output + .find_contract(&self.contract) + .ok_or_eyre("Build Error: Contract artifact not found locally")?; + + let local_bytecode = artifact + .get_bytecode_object() + .ok_or_eyre("Contract artifact does not have bytecode")?; + + let local_bytecode = match local_bytecode.as_ref() { + BytecodeObject::Bytecode(bytes) => bytes, + BytecodeObject::Unlinked(_) => { + eyre::bail!("Unlinked bytecode is not supported for verification") + } + }; + + Ok(local_bytecode.to_owned()) + } + + fn build_using_cache(&self, etherscan_settings: &Metadata, config: &Config) -> Option { + let project = config.project().ok()?; + let cache = project.read_cache_file().ok()?; + let cached_artifacts = cache.read_artifacts::().ok()?; + + for (key, value) in cached_artifacts { + let name = self.contract.name.to_owned() + ".sol"; + let version = etherscan_settings.compiler_version.to_owned(); + // Ignores vyper + if version.starts_with("vyper:") { + return None; + } + // Parse etherscan version string + let version = + version.split('+').next().unwrap_or("").trim_start_matches('v').to_string(); + + // Check if `out/directory` name matches the contract name + if key.ends_with(name.as_str()) { + let artifacts = + value.iter().flat_map(|(_, artifacts)| artifacts.iter()).collect::>(); + let name = name.replace(".sol", ".json"); + for artifact in artifacts { + // Check if ABI file matches the name + if !artifact.file.ends_with(&name) { + continue; + } + + // Check if Solidity version matches + if let Ok(version) = Version::parse(&version) { + if !(artifact.version.major == version.major && + artifact.version.minor == version.minor && + artifact.version.patch == version.patch) + { + continue; + } + } + + return artifact + .artifact + .bytecode + .as_ref() + .and_then(|bytes| bytes.bytes().to_owned()) + .cloned(); + } + + return None + } + } + + None + } + + fn print_result( + &self, + res: (bool, Option), + bytecode_type: BytecodeType, + json_results: &mut Vec, + etherscan_config: &Metadata, + config: &Config, + ) { + if res.0 { + if !self.json { + println!( + "{} with status {}", + format!("{bytecode_type:?} code matched").green().bold(), + res.1.unwrap().green().bold() + ); + } else { + let json_res = JsonResult { + bytecode_type, + matched: true, + verification_type: res.1.unwrap(), + message: None, + }; + json_results.push(json_res); + } + } else if !res.0 && !self.json { + println!( + "{}", + format!( + "{bytecode_type:?} code did not match - this may be due to varying compiler settings" + ) + .red() + .bold() + ); + let mismatches = find_mismatch_in_settings(etherscan_config, config); + for mismatch in mismatches { + println!("{}", mismatch.red().bold()); + } + } else if !res.0 && self.json { + let json_res = JsonResult { + bytecode_type, + matched: false, + verification_type: self.verification_type, + message: Some(format!( + "{bytecode_type:?} code did not match - this may be due to varying compiler settings" + )), + }; + json_results.push(json_res); + } + } +} + +/// Enum to represent the type of verification: `full` or `partial`. +/// Ref: +#[derive(Debug, Clone, clap::ValueEnum, Default, PartialEq, Eq, Serialize, Deserialize, Copy)] +pub enum VerificationType { + #[default] + #[serde(rename = "full")] + Full, + #[serde(rename = "partial")] + Partial, +} + +impl FromStr for VerificationType { + type Err = eyre::Error; + + fn from_str(s: &str) -> Result { + match s { + "full" => Ok(Self::Full), + "partial" => Ok(Self::Partial), + _ => eyre::bail!("Invalid verification type"), + } + } +} + +impl From for String { + fn from(v: VerificationType) -> Self { + match v { + VerificationType::Full => "full".to_string(), + VerificationType::Partial => "partial".to_string(), + } + } +} + +impl fmt::Display for VerificationType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Full => write!(f, "full"), + Self::Partial => write!(f, "partial"), + } + } +} + +/// Enum to represent the type of bytecode being verified +#[derive(Debug, Serialize, Deserialize, Clone, Copy)] +pub enum BytecodeType { + #[serde(rename = "creation")] + Creation, + #[serde(rename = "runtime")] + Runtime, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct JsonResult { + pub bytecode_type: BytecodeType, + pub matched: bool, + pub verification_type: VerificationType, + #[serde(skip_serializing_if = "Option::is_none")] + pub message: Option, +} + +fn try_match( + local_bytecode: &[u8], + bytecode: &[u8], + constructor_args: &[u8], + match_type: &VerificationType, + is_runtime: bool, + has_metadata: bool, +) -> Result<(bool, Option)> { + // 1. Try full match + if *match_type == VerificationType::Full && local_bytecode.starts_with(bytecode) { + Ok((true, Some(VerificationType::Full))) + } else { + try_partial_match(local_bytecode, bytecode, constructor_args, is_runtime, has_metadata) + .map(|matched| (matched, matched.then_some(VerificationType::Partial))) + } +} + +fn try_partial_match( + mut local_bytecode: &[u8], + mut bytecode: &[u8], + constructor_args: &[u8], + is_runtime: bool, + has_metadata: bool, +) -> Result { + // 1. Check length of constructor args + if constructor_args.is_empty() { + // Assume metadata is at the end of the bytecode + if has_metadata { + local_bytecode = extract_metadata_hash(local_bytecode)?; + bytecode = extract_metadata_hash(bytecode)?; + } + + // Now compare the creation code and bytecode + return Ok(local_bytecode.starts_with(bytecode)); + } + + if is_runtime { + if has_metadata { + local_bytecode = extract_metadata_hash(local_bytecode)?; + bytecode = extract_metadata_hash(bytecode)?; + } + + // Now compare the local code and bytecode + return Ok(local_bytecode.starts_with(bytecode)); + } + + // If not runtime, extract constructor args from the end of the bytecode + bytecode = &bytecode[..bytecode.len() - constructor_args.len()]; + local_bytecode = &local_bytecode[..local_bytecode.len() - constructor_args.len()]; + + if has_metadata { + local_bytecode = extract_metadata_hash(local_bytecode)?; + bytecode = extract_metadata_hash(bytecode)?; + } + + Ok(local_bytecode.starts_with(bytecode)) +} + +/// @dev This assumes that the metadata is at the end of the bytecode +fn extract_metadata_hash(bytecode: &[u8]) -> Result<&[u8]> { + // Get the last two bytes of the bytecode to find the length of CBOR metadata + let metadata_len = &bytecode[bytecode.len() - 2..]; + let metadata_len = u16::from_be_bytes([metadata_len[0], metadata_len[1]]); + + // Now discard the metadata from the bytecode + Ok(&bytecode[..bytecode.len() - 2 - metadata_len as usize]) +} + +fn find_mismatch_in_settings( + etherscan_settings: &Metadata, + local_settings: &Config, +) -> Vec { + let mut mismatches: Vec = vec![]; + if etherscan_settings.evm_version != local_settings.evm_version.to_string().to_lowercase() { + let str = format!( + "EVM version mismatch: local={}, onchain={}", + local_settings.evm_version, etherscan_settings.evm_version + ); + mismatches.push(str); + } + let local_optimizer: u64 = if local_settings.optimizer { 1 } else { 0 }; + if etherscan_settings.optimization_used != local_optimizer { + let str = format!( + "Optimizer mismatch: local={}, onchain={}", + local_settings.optimizer, etherscan_settings.optimization_used + ); + mismatches.push(str); + } + if etherscan_settings.runs != local_settings.optimizer_runs as u64 { + let str = format!( + "Optimizer runs mismatch: local={}, onchain={}", + local_settings.optimizer_runs, etherscan_settings.runs + ); + mismatches.push(str); + } + + mismatches +} diff --git a/crates/forge/bin/cmd/verify/etherscan/flatten.rs b/crates/verify/src/etherscan/flatten.rs similarity index 64% rename from crates/forge/bin/cmd/verify/etherscan/flatten.rs rename to crates/verify/src/etherscan/flatten.rs index 1857967e9..785643315 100644 --- a/crates/forge/bin/cmd/verify/etherscan/flatten.rs +++ b/crates/verify/src/etherscan/flatten.rs @@ -1,13 +1,23 @@ use super::{EtherscanSourceProvider, VerifyArgs}; +use crate::provider::VerificationContext; use eyre::{Context, Result}; use foundry_block_explorers::verify::CodeFormat; use foundry_compilers::{ artifacts::{BytecodeHash, Source}, + buildinfo::RawBuildInfo, + compilers::{ + solc::{SolcCompiler, SolcLanguage, SolcVersionedInput}, + Compiler, CompilerInput, + }, + solc::Solc, + zksolc::{ + input::{ZkSolcInput, ZkSolcVersionedInput}, + ZkSolc, + }, zksync::{ - artifacts::{BytecodeHash as ZkBytecodeHash, CompilerInput as ZkCompilerInput}, - compile::{output::AggregatedCompilerOutput as ZkAggregatedCompilerOutput, ZkSolc}, + compile::output::AggregatedCompilerOutput as ZkAggregatedCompilerOutput, raw_build_info_new, }, - AggregatedCompilerOutput, CompilerInput, Project, Solc, + AggregatedCompilerOutput, }; use semver::{BuildMetadata, Version}; @@ -19,11 +29,9 @@ impl EtherscanSourceProvider for EtherscanFlattenedSource { fn source( &self, args: &VerifyArgs, - project: &Project, - target: &Path, - version: &Version, + context: &VerificationContext, ) -> Result<(String, String, CodeFormat)> { - let metadata = project.solc_config.settings.metadata.as_ref(); + let metadata = context.project.settings.solc.metadata.as_ref(); let bch = metadata.and_then(|m| m.bytecode_hash).unwrap_or_default(); eyre::ensure!( @@ -32,11 +40,18 @@ impl EtherscanSourceProvider for EtherscanFlattenedSource { bch, ); - let source = project.flatten(target).wrap_err("Failed to flatten contract")?; + let source = context + .project + .paths + .clone() + .with_language::() + .flatten(&context.target_path) + .wrap_err("Failed to flatten contract")?; if !args.force { // solc dry run of flattened code - self.check_flattened(source.clone(), version, target).map_err(|err| { + self.check_flattened(source.clone(), &context.compiler_version, &context.target_path) + .map_err(|err| { eyre::eyre!( "Failed to compile the flattened code locally: `{}`\ To skip this solc dry, have a look at the `--force` flag of this command.", @@ -45,31 +60,39 @@ impl EtherscanSourceProvider for EtherscanFlattenedSource { })?; } - let name = args.contract.name.clone(); - Ok((source, name, CodeFormat::SingleFile)) + Ok((source, context.target_name.clone(), CodeFormat::SingleFile)) } fn zk_source( &self, args: &VerifyArgs, - project: &Project, - target: &Path, - version: &Version, + context: &VerificationContext, ) -> Result<(String, String, CodeFormat)> { - let metadata = project.zksync_zksolc_config.settings.metadata.as_ref(); + let metadata = context.project.zksync_zksolc_config.settings.metadata.as_ref(); let bch = metadata.and_then(|m| m.bytecode_hash).unwrap_or_default(); eyre::ensure!( - bch == ZkBytecodeHash::Keccak256, + bch == foundry_compilers::zksolc::settings::BytecodeHash::Keccak256, "When using flattened source with zksync, bytecodeHash must be set to keccak256 because Etherscan uses Keccak256 in its Compiler Settings when re-compiling your code. BytecodeHash is currently: {}. Hint: Set the bytecodeHash key in your foundry.toml :)", bch, ); - let source = project.flatten(target).wrap_err("Failed to flatten contract")?; + let source = context + .project + .paths + .clone() + .with_language::() + .flatten(&context.target_path) + .wrap_err("Failed to flatten contract")?; if !args.force { // solc dry run of flattened code - self.zk_check_flattened(source.clone(), version, target).map_err(|err| { + self.zk_check_flattened( + source.clone(), + &context.compiler_version, + &context.target_path, + ) + .map_err(|err| { eyre::eyre!( "Failed to compile the flattened code locally: `{}`\ To skip this solc dry, have a look at the `--force` flag of this command.", @@ -78,15 +101,14 @@ impl EtherscanSourceProvider for EtherscanFlattenedSource { })?; } - let name = args.contract.name.clone(); - Ok((source, name, CodeFormat::SingleFile)) + Ok((source, context.target_name.clone(), CodeFormat::SingleFile)) } } impl EtherscanFlattenedSource { /// Attempts to compile the flattened content locally with the compiler version. /// - /// This expects the completely flattened `content´ and will try to compile it using the + /// This expects the completely flattened content and will try to compile it using the /// provided compiler. If the compiler is missing it will be installed. /// /// # Errors @@ -105,19 +127,19 @@ impl EtherscanFlattenedSource { contract_path: &Path, ) -> Result<()> { let version = strip_build_meta(version.clone()); - let solc = Solc::find_svm_installed_version(version.to_string())? - .unwrap_or(Solc::blocking_install(&version)?); + let solc = Solc::find_or_install(&version)?; - let input = CompilerInput { - language: "Solidity".to_string(), - sources: BTreeMap::from([("contract.sol".into(), Source::new(content))]), - settings: Default::default(), - }; + let input = SolcVersionedInput::build( + BTreeMap::from([("contract.sol".into(), Source::new(content))]), + Default::default(), + SolcLanguage::Solidity, + version.clone(), + ); - let out = solc.compile(&input)?; - if out.has_error() { - let mut o = AggregatedCompilerOutput::default(); - o.extend(version, out); + let out = SolcCompiler::Specific(solc).compile(&input)?; + if out.errors.iter().any(|e| e.is_error()) { + let mut o = AggregatedCompilerOutput::::default(); + o.extend(version.clone(), RawBuildInfo::new(&input, &out, false)?, out); let diags = o.diagnostics(&[], &[], Default::default()); eyre::bail!( @@ -157,16 +179,22 @@ Diagnostics: {diags}", let zksolc = ZkSolc::find_installed_version(&version)? .unwrap_or(ZkSolc::blocking_install(&version)?); - let input = ZkCompilerInput { - language: "Solidity".to_string(), - sources: BTreeMap::from([("contract.sol".into(), Source::new(content))]), - settings: Default::default(), + let mut input = ZkSolcVersionedInput { + input: ZkSolcInput { + language: SolcLanguage::Solidity, + sources: BTreeMap::from([("contract.sol".into(), Source::new(content))]), + ..Default::default() + }, + solc_version: version.clone(), + allow_paths: Default::default(), + base_path: Default::default(), + include_paths: Default::default(), }; - let (out, _) = zksolc.compile(&input)?; + let out = zksolc.compile(&mut input)?; if out.has_error() { let mut o = ZkAggregatedCompilerOutput::default(); - o.extend(version, out); + o.extend(version.clone(), raw_build_info_new(&input, &out, false)?, out); let diags = o.diagnostics(&[], &[], Default::default()); eyre::bail!( diff --git a/crates/forge/bin/cmd/verify/etherscan/mod.rs b/crates/verify/src/etherscan/mod.rs similarity index 65% rename from crates/forge/bin/cmd/verify/etherscan/mod.rs rename to crates/verify/src/etherscan/mod.rs index e2d06b9fb..7b1520fd6 100644 --- a/crates/forge/bin/cmd/verify/etherscan/mod.rs +++ b/crates/verify/src/etherscan/mod.rs @@ -1,30 +1,25 @@ use super::{provider::VerificationProvider, VerifyArgs, VerifyCheckArgs}; -use crate::cmd::retry::RETRY_CHECK_ON_VERIFY; +use crate::{provider::VerificationContext, retry::RETRY_CHECK_ON_VERIFY}; use alloy_json_abi::Function; -use eyre::{eyre, Context, Result}; -use forge::hashbrown::HashSet; +use alloy_provider::Provider; +use eyre::{eyre, Context, OptionExt, Result}; use foundry_block_explorers::{ errors::EtherscanError, utils::lookup_compiler_version, verify::{CodeFormat, VerifyContract}, Client, }; -use foundry_cli::utils::{get_cached_entry_by_name, read_constructor_args_file, LoadConfig}; -use foundry_common::{abi::encode_function_args, retry::Retry}; -use foundry_compilers::{ - artifacts::CompactContract, cache::CacheEntry, info::ContractInfo, Project, Solc, -}; -use foundry_config::{Chain, Config, SolcReq}; +use foundry_cli::utils::{self, read_constructor_args_file, LoadConfig}; +use foundry_common::{abi::encode_function_args, retry::Retry, shell}; +use foundry_compilers::{artifacts::BytecodeObject, solc::Solc, Artifact}; +use foundry_config::{Chain, Config}; +use foundry_evm::constants::DEFAULT_CREATE2_DEPLOYER; use futures::FutureExt; use once_cell::sync::Lazy; use regex::Regex; use semver::{BuildMetadata, Version}; -use std::{ - fmt::Debug, - path::{Path, PathBuf}, - str::FromStr, -}; +use std::fmt::Debug; mod flatten; mod standard_json; @@ -34,10 +29,7 @@ pub static RE_BUILD_COMMIT: Lazy = #[derive(Clone, Debug, Default)] #[non_exhaustive] -pub struct EtherscanVerificationProvider { - /// Memoized cached entry of the target contract - cached_entry: Option<(PathBuf, CacheEntry, CompactContract)>, -} +pub struct EtherscanVerificationProvider; /// The contract source provider for [EtherscanVerificationProvider] /// @@ -46,29 +38,29 @@ trait EtherscanSourceProvider: Send + Sync + Debug { fn source( &self, args: &VerifyArgs, - project: &Project, - target: &Path, - version: &Version, + context: &VerificationContext, ) -> Result<(String, String, CodeFormat)>; fn zk_source( &self, args: &VerifyArgs, - project: &Project, - target: &Path, - version: &Version, + context: &VerificationContext, ) -> Result<(String, String, CodeFormat)>; } #[async_trait::async_trait] impl VerificationProvider for EtherscanVerificationProvider { - async fn preflight_check(&mut self, args: VerifyArgs) -> Result<()> { - let _ = self.prepare_request(&args).await?; + async fn preflight_check( + &mut self, + args: VerifyArgs, + context: VerificationContext, + ) -> Result<()> { + let _ = self.prepare_request(&args, &context).await?; Ok(()) } - async fn verify(&mut self, args: VerifyArgs) -> Result<()> { - let (etherscan, verify_args) = self.prepare_request(&args).await?; + async fn verify(&mut self, args: VerifyArgs, context: VerificationContext) -> Result<()> { + let (etherscan, verify_args) = self.prepare_request(&args, &context).await?; if !args.skip_is_verified_check && self.is_contract_verified(ðerscan, &verify_args).await? @@ -223,38 +215,12 @@ impl EtherscanVerificationProvider { } } - /// Return the memoized cache entry for the target contract. - /// Read the artifact from cache on first access. - fn cache_entry( - &mut self, - project: &Project, - contract: &ContractInfo, - ) -> Result<&(PathBuf, CacheEntry, CompactContract)> { - if let Some(ref entry) = self.cached_entry { - return Ok(entry) - } - - let cache = project.read_cache_file()?; - let (path, entry) = if let Some(path) = contract.path.as_ref() { - let path = project.root().join(path); - ( - path.clone(), - cache - .entry(&path) - .ok_or_else(|| { - eyre::eyre!(format!("Cache entry not found for {}", path.display())) - })? - .to_owned(), - ) - } else { - get_cached_entry_by_name(&cache, &contract.name)? - }; - let contract: CompactContract = cache.read_artifact(path.clone(), &contract.name)?; - Ok(self.cached_entry.insert((path, entry, contract))) - } - /// Configures the API request to the etherscan API using the given [`VerifyArgs`]. - async fn prepare_request(&mut self, args: &VerifyArgs) -> Result<(Client, VerifyContract)> { + async fn prepare_request( + &mut self, + args: &VerifyArgs, + context: &VerificationContext, + ) -> Result<(Client, VerifyContract)> { let config = args.try_load_config_emit_warnings()?; let etherscan = self.client( @@ -263,7 +229,7 @@ impl EtherscanVerificationProvider { args.etherscan.key().as_deref(), &config, )?; - let verify_args = self.create_verify_request(args, Some(config)).await?; + let verify_args = self.create_verify_request(args, context).await?; Ok((etherscan, verify_args)) } @@ -314,7 +280,10 @@ impl EtherscanVerificationProvider { builder = if let Some(api_url) = api_url { // we don't want any trailing slashes because this can cause cloudflare issues: let api_url = api_url.trim_end_matches('/'); - builder.with_api_url(api_url)?.with_url(base_url.unwrap_or(api_url))? + builder + .with_chain_id(chain) + .with_api_url(api_url)? + .with_url(base_url.unwrap_or(api_url))? } else { builder.chain(chain)? }; @@ -332,26 +301,23 @@ impl EtherscanVerificationProvider { pub async fn create_verify_request( &mut self, args: &VerifyArgs, - config: Option, + context: &VerificationContext, ) -> Result { - let mut config = - if let Some(config) = config { config } else { args.try_load_config_emit_warnings()? }; - - config.libraries.extend(args.libraries.clone()); - - let project = config.project()?; - - let contract_path = self.contract_path(args, &project)?; - let mut compiler_version = self.compiler_version(args, &config, &project)?; - let zk_compiler_version = self.zk_compiler_version(args, &config, &project)?; - - let source_provider = self.source_provider(args); + let zk_compiler_version = self.zk_compiler_version(args, context)?; let (source, contract_name, code_format) = if let Some(zk) = &zk_compiler_version { - source_provider.zk_source(args, &project, &contract_path, &zk.zksolc) + let mut zk_context = context.clone(); + zk_context.compiler_version = zk.zksolc.clone(); + self.source_provider(args).zk_source(args, &zk_context) } else { - source_provider.source(args, &project, &contract_path, &compiler_version) + self.source_provider(args).source(args, context) }?; + let mut compiler_version = context.compiler_version.clone(); + compiler_version.build = match RE_BUILD_COMMIT.captures(compiler_version.build.as_str()) { + Some(cap) => BuildMetadata::new(cap.name("commit").unwrap().as_str())?, + _ => BuildMetadata::EMPTY, + }; + let zk_args = match zk_compiler_version { None => vec![], Some(zk) => { @@ -367,9 +333,10 @@ impl EtherscanVerificationProvider { ] } }; - let compiler_version = format!("v{}", ensure_solc_build_metadata(compiler_version).await?); - let constructor_args = self.constructor_args(args, &project)?; + let compiler_version = + format!("v{}", ensure_solc_build_metadata(compiler_version.clone()).await?); + let constructor_args = self.constructor_args(args, context).await?; let mut verify_args = VerifyContract::new(args.address, contract_name, source, compiler_version) .constructor_arguments(constructor_args) @@ -387,8 +354,8 @@ impl EtherscanVerificationProvider { if code_format == CodeFormat::SingleFile { verify_args = if let Some(optimizations) = args.num_of_optimizations { verify_args.optimized().runs(optimizations as u32) - } else if config.optimizer { - verify_args.optimized().runs(config.optimizer_runs.try_into()?) + } else if context.config.optimizer { + verify_args.optimized().runs(context.config.optimizer_runs.try_into()?) } else { verify_args.not_optimized() }; @@ -397,152 +364,43 @@ impl EtherscanVerificationProvider { Ok(verify_args) } - /// Get the target contract path. If it wasn't provided, attempt a lookup - /// in cache. Validate the path indeed exists on disk. - fn contract_path(&mut self, args: &VerifyArgs, project: &Project) -> Result { - let path = if let Some(path) = args.contract.path.as_ref() { - project.root().join(path) - } else { - let (path, _, _) = self.cache_entry(project, &args.contract).wrap_err( - "If cache is disabled, contract info must be provided in the format :", - )?; - path.to_owned() - }; - - // check that the provided contract is part of the source dir - if !path.exists() { - eyre::bail!("Contract {:?} does not exist.", path); - } - - Ok(path) - } - fn zk_compiler_version( &mut self, args: &VerifyArgs, - config: &Config, - project: &Project, + context: &VerificationContext, ) -> Result> { if !args.zksync { return Ok(None); } - //TODO: remove when foundry-compilers zksolc detection is fixed for 1.5.0 - let get_zksolc_compiler_version = |path: &std::path::Path| -> Result { - use std::process::*; - let mut cmd = Command::new(path); - cmd.arg("--version") - .stdin(Stdio::piped()) - .stderr(Stdio::piped()) - .stdout(Stdio::piped()); - debug!(?cmd, "getting ZkSolc version"); - let output = cmd.output().wrap_err("error retrieving --version for zksolc")?; - - if output.status.success() { - let stdout = String::from_utf8_lossy(&output.stdout); - let version = stdout - .lines() - .filter(|l| !l.trim().is_empty()) - .last() - .ok_or(eyre!("Version not found in zksolc output"))?; - Ok(Version::from_str( - version - .split_whitespace() - .find(|s| s.starts_with('v')) - .ok_or(eyre!("Unable to retrieve version from zksolc output"))? - .trim_start_matches('v'), - )?) - } else { - Err(eyre!("zkSolc error: {}", String::from_utf8_lossy(&output.stderr))) - .wrap_err("Error retrieving zksolc version with --version") - } - }; - - let zksolc = get_zksolc_compiler_version(project.zksync_zksolc.zksolc.as_ref())?; + let zksolc = context.project.zksync_zksolc.version()?; let mut is_zksync_solc = false; - let solc = if let Some(solc) = &config.zksync.solc_path { - let solc = Solc::new(solc); - let version = solc.version().wrap_err( - "unable to retrieve version of solc in use with zksolc via `solc_path`", - )?; + let solc = if let Some(solc) = &context.config.zksync.solc_path { + let solc = Solc::new(solc)?; + let version = solc.version; //TODO: determine if this solc is zksync or not Some(version) } else { //if there's no `solc_path` specified then we use the same - // as the project version, but the zksync forc + // as the project version, but the zksync fork is_zksync_solc = true; - Some(project.solc.version().wrap_err( - "unable to retrieve version of solc in use with zksolc via project `solc`", - )?) + Some(context.compiler_version.clone()) }; Ok(Some(ZkVersion { zksolc, solc, is_zksync_solc })) } - /// Parse the compiler version. - /// The priority desc: - /// 1. Through CLI arg `--compiler-version` - /// 2. `solc` defined in foundry.toml - /// 3. The version contract was last compiled with. - fn compiler_version( - &mut self, - args: &VerifyArgs, - config: &Config, - project: &Project, - ) -> Result { - if let Some(ref version) = args.compiler_version { - return Ok(version.trim_start_matches('v').parse()?) - } - - if let Some(ref solc) = config.solc { - match solc { - SolcReq::Version(version) => return Ok(version.to_owned()), - SolcReq::Local(solc) => { - if solc.is_file() { - return Ok(Solc::new(solc).version()?) - } - } - } - } - - let (_, entry, _) = self.cache_entry(project, &args.contract).wrap_err( - "If cache is disabled, compiler version must be either provided with `--compiler-version` option or set in foundry.toml" - )?; - let artifacts = entry.artifacts_versions().collect::>(); - - if artifacts.is_empty() { - eyre::bail!("No matching artifact found for {}", args.contract.name); - } - - // ensure we have a single version - let unique_versions = artifacts.iter().map(|a| a.0.to_string()).collect::>(); - if unique_versions.len() > 1 { - let versions = unique_versions.into_iter().collect::>(); - warn!("Ambiguous compiler versions found in cache: {}", versions.join(", ")); - eyre::bail!("Compiler version has to be set in `foundry.toml`. If the project was not deployed with foundry, specify the version through `--compiler-version` flag.") - } - - // we have a unique version - let mut version = artifacts[0].0.clone(); - version.build = match RE_BUILD_COMMIT.captures(version.build.as_str()) { - Some(cap) => BuildMetadata::new(cap.name("commit").unwrap().as_str())?, - _ => BuildMetadata::EMPTY, - }; - - Ok(version) - } - /// Return the optional encoded constructor arguments. If the path to /// constructor arguments was provided, read them and encode. Otherwise, /// return whatever was set in the [VerifyArgs] args. - fn constructor_args(&mut self, args: &VerifyArgs, project: &Project) -> Result> { + async fn constructor_args( + &mut self, + args: &VerifyArgs, + context: &VerificationContext, + ) -> Result> { if let Some(ref constructor_args_path) = args.constructor_args_path { - let (_, _, contract) = self.cache_entry(project, &args.contract).wrap_err( - "Cache must be enabled in order to use the `--constructor-args-path` option", - )?; - let abi = - contract.abi.as_ref().ok_or_else(|| eyre!("Can't find ABI in cached artifact."))?; + let abi = context.get_target_abi()?; let constructor = abi .constructor() .ok_or_else(|| eyre!("Can't retrieve constructor info from artifact ABI."))?; @@ -560,9 +418,73 @@ impl EtherscanVerificationProvider { let encoded_args = hex::encode(encoded_args); return Ok(Some(encoded_args[8..].into())) } + if args.guess_constructor_args { + return Ok(Some(self.guess_constructor_args(args, context).await?)) + } Ok(args.constructor_args.clone()) } + + /// Uses Etherscan API to fetch contract creation transaction. + /// If transaction is a create transaction or a invocation of default CREATE2 deployer, tries to + /// match provided creation code with local bytecode of the target contract. + /// If bytecode match, returns latest bytes of on-chain creation code as constructor arguments. + async fn guess_constructor_args( + &mut self, + args: &VerifyArgs, + context: &VerificationContext, + ) -> Result { + let provider = utils::get_provider(&context.config)?; + let client = self.client( + args.etherscan.chain.unwrap_or_default(), + args.verifier.verifier_url.as_deref(), + args.etherscan.key.as_deref(), + &context.config, + )?; + + //TODO: zk support + let creation_data = client.contract_creation_data(args.address).await?; + let transaction = provider + .get_transaction_by_hash(creation_data.transaction_hash) + .await? + .ok_or_eyre("Transaction not found")?; + let receipt = provider + .get_transaction_receipt(creation_data.transaction_hash) + .await? + .ok_or_eyre("Couldn't fetch transaction receipt from RPC")?; + + let maybe_creation_code = if receipt.contract_address == Some(args.address) { + &transaction.input + } else if transaction.to == Some(DEFAULT_CREATE2_DEPLOYER) { + &transaction.input[32..] + } else { + eyre::bail!("Fetching of constructor arguments is not supported for contracts created by contracts") + }; + + let output = context.project.compile_file(&context.target_path)?; + let artifact = output + .find(context.target_path.to_string_lossy(), &context.target_name) + .ok_or_eyre("Contract artifact wasn't found locally")?; + let bytecode = artifact + .get_bytecode_object() + .ok_or_eyre("Contract artifact does not contain bytecode")?; + + let bytecode = match bytecode.as_ref() { + BytecodeObject::Bytecode(bytes) => Ok(bytes), + BytecodeObject::Unlinked(_) => { + Err(eyre!("You have to provide correct libraries to use --guess-constructor-args")) + } + }?; + + if maybe_creation_code.starts_with(bytecode) { + let constructor_args = &maybe_creation_code[bytecode.len()..]; + let constructor_args = hex::encode(constructor_args); + shell::println(format!("Identified constructor arguments: {constructor_args}"))?; + Ok(constructor_args) + } else { + eyre::bail!("Local bytecode doesn't match on-chain bytecode") + } + } } /// Given any solc [Version] return a [Version] with build metadata @@ -686,8 +608,6 @@ mod tests { let contract_path = format!("{src_dir}/Counter.sol"); fs::write(root.join(&contract_path), "").unwrap(); - let mut etherscan = EtherscanVerificationProvider::default(); - // No compiler argument let args = VerifyArgs::parse_from([ "foundry-cli", @@ -696,44 +616,12 @@ mod tests { "--root", root_path, ]); - - let result = etherscan.preflight_check(args).await; + let result = args.resolve_context().await; assert!(result.is_err()); assert_eq!( result.unwrap_err().to_string(), "If cache is disabled, compiler version must be either provided with `--compiler-version` option or set in foundry.toml" ); - - // No contract path - let args = - VerifyArgs::parse_from(["foundry-cli", address, contract_name, "--root", root_path]); - - let result = etherscan.preflight_check(args).await; - assert!(result.is_err()); - assert_eq!( - result.unwrap_err().to_string(), - "If cache is disabled, contract info must be provided in the format :" - ); - - // Constructor args path - let args = VerifyArgs::parse_from([ - "foundry-cli", - address, - &format!("{contract_path}:{contract_name}"), - "--constructor-args-path", - ".", - "--compiler-version", - "0.8.15", - "--root", - root_path, - ]); - - let result = etherscan.preflight_check(args).await; - assert!(result.is_err()); - assert_eq!( - result.unwrap_err().to_string(), - "Cache must be enabled in order to use the `--constructor-args-path` option", - ); } forgetest_async!(respects_path_for_duplicate, |prj, cmd| { @@ -749,8 +637,9 @@ mod tests { "--root", &prj.root().to_string_lossy(), ]); + let context = args.resolve_context().await.unwrap(); let mut etherscan = EtherscanVerificationProvider::default(); - etherscan.preflight_check(args).await.unwrap(); + etherscan.preflight_check(args, context).await.unwrap(); }); } diff --git a/crates/verify/src/etherscan/standard_json.rs b/crates/verify/src/etherscan/standard_json.rs new file mode 100644 index 000000000..b93ecdf22 --- /dev/null +++ b/crates/verify/src/etherscan/standard_json.rs @@ -0,0 +1,76 @@ +use super::{EtherscanSourceProvider, VerifyArgs}; +use crate::provider::VerificationContext; +use eyre::{Context, Result}; +use foundry_block_explorers::verify::CodeFormat; +use foundry_compilers::artifacts::StandardJsonCompilerInput; + +#[derive(Debug)] +pub struct EtherscanStandardJsonSource; +impl EtherscanSourceProvider for EtherscanStandardJsonSource { + fn source( + &self, + _args: &VerifyArgs, + context: &VerificationContext, + ) -> Result<(String, String, CodeFormat)> { + let mut input: StandardJsonCompilerInput = context + .project + .standard_json_input(&context.target_path) + .wrap_err("Failed to get standard json input")? + .normalize_evm_version(&context.compiler_version); + + input.settings.libraries.libs = input + .settings + .libraries + .libs + .into_iter() + .map(|(f, libs)| { + (f.strip_prefix(context.project.root()).unwrap_or(&f).to_path_buf(), libs) + }) + .collect(); + + // remove all incompatible settings + input.settings.sanitize(&context.compiler_version); + + let source = + serde_json::to_string(&input).wrap_err("Failed to parse standard json input")?; + + trace!(target: "forge::verify", standard_json=source, "determined standard json input"); + + let name = format!( + "{}:{}", + context + .target_path + .strip_prefix(context.project.root()) + .unwrap_or(context.target_path.as_path()) + .display(), + context.target_name.clone() + ); + Ok((source, name, CodeFormat::StandardJsonInput)) + } + + fn zk_source( + &self, + _args: &VerifyArgs, + context: &VerificationContext, + ) -> Result<(String, String, CodeFormat)> { + let input = + foundry_zksync_compiler::standard_json_input(&context.project, &context.target_path) + .wrap_err("failed to get zksolc standard json")?; + + let source = + serde_json::to_string(&input).wrap_err("Failed to parse zksync standard json input")?; + + trace!(target: "forge::verify", standard_json=source, "determined zksync standard json input"); + + let name = format!( + "{}:{}", + context + .target_path + .strip_prefix(context.project.root()) + .unwrap_or(context.target_path.as_path()) + .display(), + context.target_name.clone() + ); + Ok((source, name, CodeFormat::StandardJsonInput)) + } +} diff --git a/crates/forge/bin/cmd/verify/mod.rs b/crates/verify/src/lib.rs similarity index 54% rename from crates/forge/bin/cmd/verify/mod.rs rename to crates/verify/src/lib.rs index 0f745c617..8022607b4 100644 --- a/crates/forge/bin/cmd/verify/mod.rs +++ b/crates/verify/src/lib.rs @@ -1,12 +1,28 @@ -use super::retry::RetryArgs; +//! # foundry-verify +//! +//! Smart contract verification. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +#[macro_use] +extern crate tracing; + use alloy_primitives::Address; +use alloy_provider::Provider; use clap::{Parser, ValueHint}; use eyre::Result; -use foundry_cli::{opts::EtherscanOpts, utils::LoadConfig}; -use foundry_compilers::{info::ContractInfo, EvmVersion}; -use foundry_config::{figment, impl_figment_convert, impl_figment_convert_cast, Config}; +use foundry_cli::{ + opts::{EtherscanOpts, RpcOpts}, + utils::{self, LoadConfig}, +}; +use foundry_common::{compile::ProjectCompiler, ContractsByArtifact}; +use foundry_compilers::{artifacts::EvmVersion, compilers::solc::Solc, info::ContractInfo}; +use foundry_config::{figment, impl_figment_convert, impl_figment_convert_cast, Config, SolcReq}; +use itertools::Itertools; use provider::VerificationProviderType; use reqwest::Url; +use revm_primitives::HashSet; use std::path::PathBuf; mod etherscan; @@ -15,23 +31,29 @@ use etherscan::EtherscanVerificationProvider; pub mod provider; use provider::VerificationProvider; +pub mod bytecode; +pub mod retry; mod sourcify; +pub use retry::RetryArgs; + +use crate::provider::VerificationContext; + /// Verification provider arguments #[derive(Clone, Debug, Parser)] pub struct VerifierArgs { /// The contract verification provider to use. - #[clap(long, help_heading = "Verifier options", default_value = "etherscan", value_enum)] + #[arg(long, help_heading = "Verifier options", default_value = "etherscan", value_enum)] pub verifier: VerificationProviderType, /// The verifier URL, if using a custom provider - #[clap(long, help_heading = "Verifier options", env = "VERIFIER_URL")] + #[arg(long, help_heading = "Verifier options", env = "VERIFIER_URL")] pub verifier_url: Option, } impl Default for VerifierArgs { fn default() -> Self { - VerifierArgs { verifier: VerificationProviderType::Etherscan, verifier_url: None } + Self { verifier: VerificationProviderType::Etherscan, verifier_url: None } } } @@ -42,10 +64,10 @@ pub struct VerifyArgs { pub address: Address, /// The contract identifier in the form `:`. - pub contract: ContractInfo, + pub contract: Option, /// The ABI-encoded constructor arguments. - #[clap( + #[arg( long, conflicts_with = "constructor_args_path", value_name = "ARGS", @@ -54,68 +76,75 @@ pub struct VerifyArgs { pub constructor_args: Option, /// The path to a file containing the constructor arguments. - #[clap(long, value_hint = ValueHint::FilePath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::FilePath, value_name = "PATH")] pub constructor_args_path: Option, + /// Try to extract constructor arguments from on-chain creation code. + #[arg(long)] + pub guess_constructor_args: bool, + /// The `solc` version to use to build the smart contract. - #[clap(long, value_name = "VERSION")] + #[arg(long, value_name = "VERSION")] pub compiler_version: Option, /// The number of optimization runs used to build the smart contract. - #[clap(long, visible_alias = "optimizer-runs", value_name = "NUM")] + #[arg(long, visible_alias = "optimizer-runs", value_name = "NUM")] pub num_of_optimizations: Option, /// Flatten the source code before verifying. - #[clap(long)] + #[arg(long)] pub flatten: bool, /// Do not compile the flattened smart contract before verifying (if --flatten is passed). - #[clap(short, long)] + #[arg(short, long)] pub force: bool, /// Do not check if the contract is already verified before verifying. - #[clap(long)] + #[arg(long)] pub skip_is_verified_check: bool, /// Wait for verification result after submission. - #[clap(long)] + #[arg(long)] pub watch: bool, /// Set pre-linked libraries. - #[clap(long, help_heading = "Linker options", env = "DAPP_LIBRARIES")] + #[arg(long, help_heading = "Linker options", env = "DAPP_LIBRARIES")] pub libraries: Vec, /// The project's root path. /// /// By default root of the Git repository, if in one, /// or the current working directory. - #[clap(long, value_hint = ValueHint::DirPath, value_name = "PATH")] + #[arg(long, value_hint = ValueHint::DirPath, value_name = "PATH")] pub root: Option, /// Prints the standard json compiler input. /// /// The standard json compiler input can be used to manually submit contract verification in /// the browser. - #[clap(long, conflicts_with = "flatten")] + #[arg(long, conflicts_with = "flatten")] pub show_standard_json_input: bool, /// Use the Yul intermediate representation compilation pipeline. - #[clap(long)] + #[arg(long)] pub via_ir: bool, /// The EVM version to use. /// /// Overrides the version specified in the config. - #[clap(long)] + #[arg(long)] pub evm_version: Option, - #[clap(flatten)] + #[command(flatten)] pub etherscan: EtherscanOpts, - #[clap(flatten)] + #[command(flatten)] + pub rpc: RpcOpts, + + #[command(flatten)] pub retry: RetryArgs, - #[clap(flatten)] + #[command(flatten)] pub verifier: VerifierArgs, /// Verify for zksync @@ -134,6 +163,8 @@ impl figment::Provider for VerifyArgs { &self, ) -> Result, figment::Error> { let mut dict = self.etherscan.dict(); + dict.extend(self.rpc.dict()); + if let Some(root) = self.root.as_ref() { dict.insert("root".to_string(), figment::value::Value::serialize(root)?); } @@ -159,20 +190,39 @@ impl VerifyArgs { /// Run the verify command to submit the contract's source code for verification on etherscan pub async fn run(mut self) -> Result<()> { let config = self.load_config_emit_warnings(); - let chain = config.chain.unwrap_or_default(); + + if self.guess_constructor_args && config.get_rpc_url().is_none() { + eyre::bail!( + "You have to provide a valid RPC URL to use --guess-constructor-args feature" + ) + } + + // If chain is not set, we try to get it from the RPC + // If RPC is not set, the default chain is used + let chain = match config.get_rpc_url() { + Some(_) => { + let provider = utils::get_provider(&config)?; + utils::get_chain(config.chain, provider).await? + } + None => config.chain.unwrap_or_default(), + }; + + let context = self.resolve_context().await?; + self.etherscan.chain = Some(chain); self.etherscan.key = config.get_etherscan_config_with_chain(Some(chain))?.map(|c| c.key); if self.show_standard_json_input { - let args = - EtherscanVerificationProvider::default().create_verify_request(&self, None).await?; + let args = EtherscanVerificationProvider::default() + .create_verify_request(&self, &context) + .await?; println!("{}", args.source); return Ok(()) } let verifier_url = self.verifier.verifier_url.clone(); println!("Start verifying contract `{}` deployed on {chain}", self.address); - self.verifier.verifier.client(&self.etherscan.key())?.verify(self).await.map_err(|err| { + self.verifier.verifier.client(&self.etherscan.key())?.verify(self, context).await.map_err(|err| { if let Some(verifier_url) = verifier_url { match Url::parse(&verifier_url) { Ok(url) => { @@ -198,6 +248,84 @@ impl VerifyArgs { pub fn verification_provider(&self) -> Result> { self.verifier.verifier.client(&self.etherscan.key()) } + + /// Resolves [VerificationContext] object either from entered contract name or by trying to + /// match bytecode located at given address. + pub async fn resolve_context(&self) -> Result { + let mut config = self.load_config_emit_warnings(); + config.libraries.extend(self.libraries.clone()); + + let project = config.project()?; + + if let Some(ref contract) = self.contract { + let contract_path = if let Some(ref path) = contract.path { + project.root().join(PathBuf::from(path)) + } else { + project.find_contract_path(&contract.name)? + }; + + let version = if let Some(ref version) = self.compiler_version { + version.trim_start_matches('v').parse()? + } else if let Some(ref solc) = config.solc { + match solc { + SolcReq::Version(version) => version.to_owned(), + SolcReq::Local(solc) => Solc::new(solc)?.version, + } + } else if let Some(entry) = project + .read_cache_file() + .ok() + .and_then(|mut cache| cache.files.remove(&contract_path)) + { + let unique_versions = entry + .artifacts + .get(&contract.name) + .map(|artifacts| artifacts.keys().collect::>()) + .unwrap_or_default(); + + if unique_versions.is_empty() { + eyre::bail!("No matching artifact found for {}", contract.name); + } else if unique_versions.len() > 1 { + warn!( + "Ambiguous compiler versions found in cache: {}", + unique_versions.iter().join(", ") + ); + eyre::bail!("Compiler version has to be set in `foundry.toml`. If the project was not deployed with foundry, specify the version through `--compiler-version` flag.") + } + + unique_versions.into_iter().next().unwrap().to_owned() + } else { + eyre::bail!("If cache is disabled, compiler version must be either provided with `--compiler-version` option or set in foundry.toml") + }; + + VerificationContext::new(contract_path, contract.name.clone(), version, config) + } else { + if config.get_rpc_url().is_none() { + eyre::bail!("You have to provide a contract name or a valid RPC URL") + } + let provider = utils::get_provider(&config)?; + let code = provider.get_code_at(self.address).await?; + + let output = ProjectCompiler::new().compile(&project)?; + let contracts = ContractsByArtifact::new( + output.artifact_ids().map(|(id, artifact)| (id, artifact.clone().into())), + ); + + //TODO: lookup for zksync + let Some((artifact_id, _)) = contracts.find_by_deployed_code_exact(&code) else { + eyre::bail!(format!( + "Bytecode at {} does not match any local contracts", + self.address + )) + }; + + VerificationContext::new( + artifact_id.source.clone(), + artifact_id.name.split('.').next().unwrap().to_owned(), + artifact_id.version.clone(), + config, + ) + } + } } /// Check verification status arguments @@ -210,13 +338,13 @@ pub struct VerifyCheckArgs { /// For Sourcify - Contract Address. id: String, - #[clap(flatten)] + #[command(flatten)] retry: RetryArgs, - #[clap(flatten)] + #[command(flatten)] etherscan: EtherscanOpts, - #[clap(flatten)] + #[command(flatten)] verifier: VerifierArgs, } diff --git a/crates/verify/src/provider.rs b/crates/verify/src/provider.rs new file mode 100644 index 000000000..12dfb23f6 --- /dev/null +++ b/crates/verify/src/provider.rs @@ -0,0 +1,175 @@ +use super::{ + etherscan::EtherscanVerificationProvider, sourcify::SourcifyVerificationProvider, VerifyArgs, + VerifyCheckArgs, +}; +use alloy_json_abi::JsonAbi; +use async_trait::async_trait; +use eyre::{OptionExt, Result}; +use foundry_common::compile::ProjectCompiler; +use foundry_compilers::{ + artifacts::{output_selection::OutputSelection, Metadata, Source}, + compilers::{multi::MultiCompilerParsedSource, solc::SolcCompiler, CompilerSettings}, + solc::Solc, + Graph, Project, +}; +use foundry_config::Config; +use semver::Version; +use std::{fmt, path::PathBuf, str::FromStr}; + +/// Container with data required for contract verification. +#[derive(Debug, Clone)] +pub struct VerificationContext { + pub config: Config, + pub project: Project, + pub target_path: PathBuf, + pub target_name: String, + pub compiler_version: Version, +} + +impl VerificationContext { + pub fn new( + target_path: PathBuf, + target_name: String, + compiler_version: Version, + config: Config, + ) -> Result { + let mut project = config.project()?; + project.no_artifacts = true; + + let solc = Solc::find_or_install(&compiler_version)?; + project.compiler.solc = SolcCompiler::Specific(solc); + + Ok(Self { config, project, target_name, target_path, compiler_version }) + } + + /// Compiles target contract requesting only ABI and returns it. + pub fn get_target_abi(&self) -> Result { + let mut project = self.project.clone(); + project.settings.update_output_selection(|selection| { + *selection = OutputSelection::common_output_selection(["abi".to_string()]) + }); + + let output = ProjectCompiler::new() + .quiet(true) + .files([self.target_path.clone()]) + .compile(&project)?; + + let artifact = output + .find(self.target_path.to_string_lossy(), &self.target_name) + .ok_or_eyre("failed to find target artifact when compiling for abi")?; + + artifact.abi.clone().ok_or_eyre("target artifact does not have an ABI") + } + + /// Compiles target file requesting only metadata and returns it. + pub fn get_target_metadata(&self) -> Result { + let mut project = self.project.clone(); + project.settings.update_output_selection(|selection| { + *selection = OutputSelection::common_output_selection(["metadata".to_string()]); + }); + + let output = ProjectCompiler::new() + .quiet(true) + .files([self.target_path.clone()]) + .compile(&project)?; + + let artifact = output + .find(self.target_path.to_string_lossy(), &self.target_name) + .ok_or_eyre("failed to find target artifact when compiling for metadata")?; + + artifact.metadata.clone().ok_or_eyre("target artifact does not have an ABI") + } + + /// Returns [Vec] containing imports of the target file. + pub fn get_target_imports(&self) -> Result> { + let mut sources = self.project.paths.read_input_files()?; + sources.insert(self.target_path.clone(), Source::read(&self.target_path)?); + let graph = + Graph::::resolve_sources(&self.project.paths, sources)?; + + Ok(graph.imports(&self.target_path).into_iter().cloned().collect()) + } +} + +/// An abstraction for various verification providers such as etherscan, sourcify, blockscout +#[async_trait] +pub trait VerificationProvider { + /// This should ensure the verify request can be prepared successfully. + /// + /// Caution: Implementers must ensure that this _never_ sends the actual verify request + /// `[VerificationProvider::verify]`, instead this is supposed to evaluate whether the given + /// [`VerifyArgs`] are valid to begin with. This should prevent situations where there's a + /// contract deployment that's executed before the verify request and the subsequent verify task + /// fails due to misconfiguration. + async fn preflight_check( + &mut self, + args: VerifyArgs, + context: VerificationContext, + ) -> Result<()>; + + /// Sends the actual verify request for the targeted contract. + async fn verify(&mut self, args: VerifyArgs, context: VerificationContext) -> Result<()>; + + /// Checks whether the contract is verified. + async fn check(&self, args: VerifyCheckArgs) -> Result<()>; +} + +impl FromStr for VerificationProviderType { + type Err = String; + + fn from_str(s: &str) -> Result { + match s { + "e" | "etherscan" => Ok(Self::Etherscan), + "s" | "sourcify" => Ok(Self::Sourcify), + "b" | "blockscout" => Ok(Self::Blockscout), + "o" | "oklink" => Ok(Self::Oklink), + _ => Err(format!("Unknown provider: {s}")), + } + } +} + +impl fmt::Display for VerificationProviderType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Etherscan => { + write!(f, "etherscan")?; + } + Self::Sourcify => { + write!(f, "sourcify")?; + } + Self::Blockscout => { + write!(f, "blockscout")?; + } + Self::Oklink => { + write!(f, "oklink")?; + } + }; + Ok(()) + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, clap::ValueEnum)] +pub enum VerificationProviderType { + #[default] + Etherscan, + Sourcify, + Blockscout, + Oklink, +} + +impl VerificationProviderType { + /// Returns the corresponding `VerificationProvider` for the key + pub fn client(&self, key: &Option) -> Result> { + match self { + Self::Etherscan => { + if key.as_ref().map_or(true, |key| key.is_empty()) { + eyre::bail!("ETHERSCAN_API_KEY must be set") + } + Ok(Box::::default()) + } + Self::Sourcify => Ok(Box::::default()), + Self::Blockscout => Ok(Box::::default()), + Self::Oklink => Ok(Box::::default()), + } + } +} diff --git a/crates/forge/bin/cmd/retry.rs b/crates/verify/src/retry.rs similarity index 89% rename from crates/forge/bin/cmd/retry.rs rename to crates/verify/src/retry.rs index 45305288e..528fd7497 100644 --- a/crates/forge/bin/cmd/retry.rs +++ b/crates/verify/src/retry.rs @@ -10,10 +10,10 @@ pub const RETRY_VERIFY_ON_CREATE: RetryArgs = RetryArgs { retries: 15, delay: 5 /// Retry arguments for contract verification. #[derive(Clone, Copy, Debug, Parser)] -#[clap(about = "Allows to use retry arguments for contract verification")] // override doc +#[command(about = "Allows to use retry arguments for contract verification")] // override doc pub struct RetryArgs { /// Number of attempts for retrying verification. - #[clap( + #[arg( long, value_parser = RangedU64ValueParser::::new().range(1..), default_value = "5", @@ -21,7 +21,7 @@ pub struct RetryArgs { pub retries: u32, /// Optional delay to apply inbetween verification attempts, in seconds. - #[clap( + #[arg( long, value_parser = RangedU64ValueParser::::new().range(0..=30), default_value = "5", @@ -37,7 +37,7 @@ impl Default for RetryArgs { impl From for Retry { fn from(r: RetryArgs) -> Self { - Retry::new(r.retries, Some(Duration::from_secs(r.delay as u64))) + Self::new(r.retries, Some(Duration::from_secs(r.delay as u64))) } } diff --git a/crates/forge/bin/cmd/verify/sourcify.rs b/crates/verify/src/sourcify.rs similarity index 70% rename from crates/forge/bin/cmd/verify/sourcify.rs rename to crates/verify/src/sourcify.rs index d30946f89..58cb2b4b9 100644 --- a/crates/forge/bin/cmd/verify/sourcify.rs +++ b/crates/verify/src/sourcify.rs @@ -1,13 +1,12 @@ use super::{provider::VerificationProvider, VerifyArgs, VerifyCheckArgs}; +use crate::provider::VerificationContext; use async_trait::async_trait; use eyre::Result; -use foundry_cli::utils::{get_cached_entry_by_name, LoadConfig}; use foundry_common::{fs, retry::Retry}; -use foundry_compilers::ConfigurableContractArtifact; use futures::FutureExt; use reqwest::Url; use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, path::PathBuf, str::FromStr}; +use std::{collections::HashMap, str::FromStr}; pub static SOURCIFY_URL: &str = "https://sourcify.dev/server/"; @@ -18,13 +17,17 @@ pub struct SourcifyVerificationProvider; #[async_trait] impl VerificationProvider for SourcifyVerificationProvider { - async fn preflight_check(&mut self, args: VerifyArgs) -> Result<()> { - let _ = self.prepare_request(&args)?; + async fn preflight_check( + &mut self, + args: VerifyArgs, + context: VerificationContext, + ) -> Result<()> { + let _ = self.prepare_request(&args, &context)?; Ok(()) } - async fn verify(&mut self, args: VerifyArgs) -> Result<()> { - let body = self.prepare_request(&args)?; + async fn verify(&mut self, args: VerifyArgs, context: VerificationContext) -> Result<()> { + let body = self.prepare_request(&args, &context)?; trace!("submitting verification request {:?}", body); @@ -36,7 +39,7 @@ impl VerificationProvider for SourcifyVerificationProvider { async { println!( "\nSubmitting verification for [{}] {:?}.", - args.contract.name, + context.target_name, args.address.to_string() ); let response = client @@ -99,54 +102,24 @@ impl VerificationProvider for SourcifyVerificationProvider { impl SourcifyVerificationProvider { /// Configures the API request to the sourcify API using the given [`VerifyArgs`]. - fn prepare_request(&self, args: &VerifyArgs) -> Result { - let mut config = args.try_load_config_emit_warnings()?; - config.libraries.extend(args.libraries.clone()); - - let project = config.project()?; - - if !config.cache { - eyre::bail!("Cache is required for sourcify verification.") - } - - let cache = project.read_cache_file()?; - let (path, entry) = get_cached_entry_by_name(&cache, &args.contract.name)?; + fn prepare_request( + &self, + args: &VerifyArgs, + context: &VerificationContext, + ) -> Result { + let metadata = context.get_target_metadata()?; + let imports = context.get_target_imports()?; - if entry.solc_config.settings.metadata.is_none() { - eyre::bail!( - r#"Contract {} was compiled without the solc `metadata` setting. -Sourcify requires contract metadata for verification. -metadata output can be enabled via `extra_output = ["metadata"]` in `foundry.toml`"#, - args.contract.name - ) - } + let mut files = HashMap::with_capacity(2 + imports.len()); - let mut files = HashMap::with_capacity(2 + entry.imports.len()); - - // the metadata is included in the contract's artifact file - let artifact_path = entry - .find_artifact_path(&args.contract.name) - .ok_or_else(|| eyre::eyre!("No artifact found for contract {}", args.contract.name))?; - - let artifact: ConfigurableContractArtifact = fs::read_json_file(artifact_path)?; - if let Some(metadata) = artifact.metadata { - let metadata = serde_json::to_string_pretty(&metadata)?; - files.insert("metadata.json".to_string(), metadata); - } else { - eyre::bail!( - r#"No metadata found in artifact `{}` for contract {}. -Sourcify requires contract metadata for verification. -metadata output can be enabled via `extra_output = ["metadata"]` in `foundry.toml`"#, - artifact_path.display(), - args.contract.name - ) - } + let metadata = serde_json::to_string_pretty(&metadata)?; + files.insert("metadata.json".to_string(), metadata); - let contract_path = args.contract.path.clone().map_or(path, PathBuf::from); + let contract_path = context.target_path.clone(); let filename = contract_path.file_name().unwrap().to_string_lossy().to_string(); files.insert(filename, fs::read_to_string(&contract_path)?); - for import in entry.imports { + for import in imports { let import_entry = format!("{}", import.display()); files.insert(import_entry, fs::read_to_string(&import)?); } diff --git a/crates/wallets/Cargo.toml b/crates/wallets/Cargo.toml index 1ef26972d..183a97854 100644 --- a/crates/wallets/Cargo.toml +++ b/crates/wallets/Cargo.toml @@ -9,34 +9,49 @@ license.workspace = true homepage.workspace = true repository.workspace = true -[dependencies] -alloy-primitives.workspace = true - -ethers-core.workspace = true -ethers-providers.workspace = true -ethers-signers = { workspace = true, features = ["aws", "ledger", "trezor"] } - -rusoto_core = { version = "0.48", default-features = false } -rusoto_kms = { version = "0.48", default-features = false } +[lints] +workspace = true +[dependencies] foundry-config.workspace = true -foundry-common.workspace = true -async-trait = "0.1" +alloy-primitives.workspace = true +alloy-signer = { workspace = true, features = ["eip712"] } +alloy-signer-local = { workspace = true, features = ["mnemonic", "keystore"] } +alloy-signer-ledger = { workspace = true, features = ["eip712"] } +alloy-signer-trezor.workspace = true +alloy-network.workspace = true +alloy-consensus.workspace = true +alloy-sol-types.workspace = true +alloy-dyn-abi.workspace = true + +# aws-kms +alloy-signer-aws = { workspace = true, features = ["eip712"], optional = true } +aws-config = { version = "1", optional = true } # default-features are necessary +aws-sdk-kms = { version = "1", default-features = false, optional = true } + +# gcp-kms +alloy-signer-gcp = { workspace = true, features = ["eip712"], optional = true } +gcloud-sdk = { version = "0.24", features = [ + "google-cloud-kms-v1", + "google-longrunning", +], optional = true } + +async-trait.workspace = true clap = { version = "4", features = ["derive", "env", "unicode", "wrap_help"] } derive_builder = "0.20.0" eyre.workspace = true hex = { workspace = true, features = ["serde"] } -itertools.workspace = true rpassword = "7" serde.workspace = true -thiserror = "1" +thiserror.workspace = true tracing.workspace = true [dev-dependencies] -tokio = { version = "1", features = ["macros"] } +tokio = { workspace = true, features = ["macros"] } [features] default = ["rustls"] -rustls = ["ethers-providers/rustls", "rusoto_core/rustls"] -openssl = ["ethers-providers/openssl"] +rustls = ["aws-sdk-kms?/rustls"] +aws-kms = ["dep:alloy-signer-aws", "dep:aws-config", "dep:aws-sdk-kms"] +gcp-kms = ["dep:alloy-signer-gcp", "dep:gcloud-sdk"] diff --git a/crates/wallets/src/error.rs b/crates/wallets/src/error.rs index 6588f5e22..4e299b055 100644 --- a/crates/wallets/src/error.rs +++ b/crates/wallets/src/error.rs @@ -1,6 +1,15 @@ -use ethers_signers::{AwsSignerError, LedgerError, TrezorError, WalletError}; +use alloy_signer::k256::ecdsa; +use alloy_signer_ledger::LedgerError; +use alloy_signer_local::LocalSignerError; +use alloy_signer_trezor::TrezorError; use hex::FromHexError; +#[cfg(feature = "aws-kms")] +use alloy_signer_aws::AwsSignerError; + +#[cfg(feature = "gcp-kms")] +use alloy_signer_gcp::GcpSignerError; + #[derive(Debug, thiserror::Error)] pub enum PrivateKeyError { #[error("Failed to create wallet from private key. Private key is invalid hex: {0}")] @@ -12,17 +21,33 @@ pub enum PrivateKeyError { #[derive(Debug, thiserror::Error)] pub enum WalletSignerError { #[error(transparent)] - Local(#[from] WalletError), + Local(#[from] LocalSignerError), #[error(transparent)] Ledger(#[from] LedgerError), #[error(transparent)] Trezor(#[from] TrezorError), #[error(transparent)] + #[cfg(feature = "aws-kms")] Aws(#[from] AwsSignerError), #[error(transparent)] + #[cfg(feature = "gcp-kms")] + Gcp(#[from] GcpSignerError), + #[error(transparent)] Io(#[from] std::io::Error), #[error(transparent)] InvalidHex(#[from] FromHexError), - #[error("{0} cannot sign raw hashes")] - CannotSignRawHash(&'static str), + #[error(transparent)] + Ecdsa(#[from] ecdsa::Error), + #[error("foundry was not built with support for {0} signer")] + UnsupportedSigner(&'static str), +} + +impl WalletSignerError { + pub fn aws_unsupported() -> Self { + Self::UnsupportedSigner("AWS KMS") + } + + pub fn gcp_unsupported() -> Self { + Self::UnsupportedSigner("Google Cloud KMS") + } } diff --git a/crates/wallets/src/lib.rs b/crates/wallets/src/lib.rs index 38ff5e7fa..e3be0971e 100644 --- a/crates/wallets/src/lib.rs +++ b/crates/wallets/src/lib.rs @@ -1,3 +1,10 @@ +//! # foundry-wallets +//! +//! Utilities for working with multiple signers. + +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + #[macro_use] extern crate tracing; diff --git a/crates/wallets/src/multi_wallet.rs b/crates/wallets/src/multi_wallet.rs index d3d7c9618..459074aaa 100644 --- a/crates/wallets/src/multi_wallet.rs +++ b/crates/wallets/src/multi_wallet.rs @@ -3,11 +3,10 @@ use crate::{ wallet_signer::{PendingSigner, WalletSigner}, }; use alloy_primitives::Address; +use alloy_signer::Signer; use clap::Parser; use derive_builder::Builder; -use ethers_signers::Signer; use eyre::Result; -use foundry_common::types::ToAlloy; use foundry_config::Config; use serde::Serialize; use std::{collections::HashMap, iter::repeat, path::PathBuf}; @@ -24,15 +23,14 @@ pub struct MultiWallet { impl MultiWallet { pub fn new(pending_signers: Vec, signers: Vec) -> Self { - let signers = - signers.into_iter().map(|signer| (signer.address().to_alloy(), signer)).collect(); + let signers = signers.into_iter().map(|signer| (signer.address(), signer)).collect(); Self { pending_signers, signers } } fn maybe_unlock_pending(&mut self) -> Result<()> { for pending in self.pending_signers.drain(..) { let signer = pending.unlock()?; - self.signers.insert(signer.address().to_alloy(), signer); + self.signers.insert(signer.address(), signer); } Ok(()) } @@ -48,7 +46,7 @@ impl MultiWallet { } pub fn add_signer(&mut self, signer: WalletSigner) { - self.signers.insert(signer.address().to_alloy(), signer); + self.signers.insert(signer.address(), signer); } } @@ -89,10 +87,10 @@ macro_rules! create_hw_wallets { /// 6. Private Keys (interactively via secure prompt) /// 7. AWS KMS #[derive(Builder, Clone, Debug, Default, Serialize, Parser)] -#[clap(next_help_heading = "Wallet options", about = None, long_about = None)] +#[command(next_help_heading = "Wallet options", about = None, long_about = None)] pub struct MultiWalletOpts { /// The sender accounts. - #[clap( + #[arg( long, short = 'a', help_heading = "Wallet options - raw", @@ -106,7 +104,7 @@ pub struct MultiWalletOpts { /// Open an interactive prompt to enter your private key. /// /// Takes a value for the number of keys to enter. - #[clap( + #[arg( long, short, help_heading = "Wallet options - raw", @@ -116,12 +114,12 @@ pub struct MultiWalletOpts { pub interactives: u32, /// Use the provided private keys. - #[clap(long, help_heading = "Wallet options - raw", value_name = "RAW_PRIVATE_KEYS")] + #[arg(long, help_heading = "Wallet options - raw", value_name = "RAW_PRIVATE_KEYS")] #[builder(default = "None")] pub private_keys: Option>, /// Use the provided private key. - #[clap( + #[arg( long, help_heading = "Wallet options - raw", conflicts_with = "private_keys", @@ -131,19 +129,19 @@ pub struct MultiWalletOpts { pub private_key: Option, /// Use the mnemonic phrases of mnemonic files at the specified paths. - #[clap(long, alias = "mnemonic-paths", help_heading = "Wallet options - raw")] + #[arg(long, alias = "mnemonic-paths", help_heading = "Wallet options - raw")] #[builder(default = "None")] pub mnemonics: Option>, /// Use a BIP39 passphrases for the mnemonic. - #[clap(long, help_heading = "Wallet options - raw", value_name = "PASSPHRASE")] + #[arg(long, help_heading = "Wallet options - raw", value_name = "PASSPHRASE")] #[builder(default = "None")] pub mnemonic_passphrases: Option>, /// The wallet derivation path. /// /// Works with both --mnemonic-path and hardware wallets. - #[clap( + #[arg( long = "mnemonic-derivation-paths", alias = "hd-paths", help_heading = "Wallet options - raw", @@ -155,7 +153,7 @@ pub struct MultiWalletOpts { /// Use the private key from the given mnemonic index. /// /// Can be used with --mnemonics, --ledger, --aws and --trezor. - #[clap( + #[arg( long, conflicts_with = "hd_paths", help_heading = "Wallet options - raw", @@ -165,7 +163,7 @@ pub struct MultiWalletOpts { pub mnemonic_indexes: Option>, /// Use the keystore in the given folder or file. - #[clap( + #[arg( long = "keystore", visible_alias = "keystores", help_heading = "Wallet options - keystore", @@ -176,7 +174,7 @@ pub struct MultiWalletOpts { pub keystore_paths: Option>, /// Use a keystore from the default keystores folder (~/.foundry/keystores) by its filename - #[clap( + #[arg( long = "account", visible_alias = "accounts", help_heading = "Wallet options - keystore", @@ -190,7 +188,7 @@ pub struct MultiWalletOpts { /// The keystore password. /// /// Used with --keystore. - #[clap( + #[arg( long = "password", help_heading = "Wallet options - keystore", requires = "keystore_paths", @@ -202,7 +200,7 @@ pub struct MultiWalletOpts { /// The keystore password file path. /// /// Used with --keystore. - #[clap( + #[arg( long = "password-file", help_heading = "Wallet options - keystore", requires = "keystore_paths", @@ -213,15 +211,15 @@ pub struct MultiWalletOpts { pub keystore_password_files: Option>, /// Use a Ledger hardware wallet. - #[clap(long, short, help_heading = "Wallet options - hardware wallet")] + #[arg(long, short, help_heading = "Wallet options - hardware wallet")] pub ledger: bool, /// Use a Trezor hardware wallet. - #[clap(long, short, help_heading = "Wallet options - hardware wallet")] + #[arg(long, short, help_heading = "Wallet options - hardware wallet")] pub trezor: bool, /// Use AWS Key Management Service. - #[clap(long, help_heading = "Wallet options - remote")] + #[arg(long, help_heading = "Wallet options - remote", hide = !cfg!(feature = "aws-kms"))] pub aws: bool, } @@ -377,6 +375,7 @@ impl MultiWalletOpts { } pub async fn aws_signers(&self) -> Result>> { + #[cfg(feature = "aws-kms")] if self.aws { let mut wallets = vec![]; let aws_keys = std::env::var("AWS_KMS_KEY_IDS") @@ -386,12 +385,13 @@ impl MultiWalletOpts { .collect::>(); for key in aws_keys { - let aws_signer = WalletSigner::from_aws(&key).await?; + let aws_signer = WalletSigner::from_aws(key).await?; wallets.push(aws_signer) } return Ok(Some(wallets)); } + Ok(None) } } @@ -399,7 +399,7 @@ impl MultiWalletOpts { #[cfg(test)] mod tests { use super::*; - use std::path::Path; + use std::{path::Path, str::FromStr}; #[test] fn parse_keystore_args() { @@ -439,7 +439,7 @@ mod tests { assert_eq!(unlocked.len(), 1); assert_eq!( unlocked[0].address(), - "ec554aeafe75601aaab43bd4621a22284db566c2".parse().unwrap() + Address::from_str("0xec554aeafe75601aaab43bd4621a22284db566c2").unwrap() ); } diff --git a/crates/wallets/src/raw_wallet.rs b/crates/wallets/src/raw_wallet.rs index ccb1d6388..3a5169cad 100644 --- a/crates/wallets/src/raw_wallet.rs +++ b/crates/wallets/src/raw_wallet.rs @@ -9,34 +9,34 @@ use serde::Serialize; /// 2. Private Key (interactively via secure prompt) /// 3. Mnemonic (via file path) #[derive(Clone, Debug, Default, Serialize, Parser)] -#[clap(next_help_heading = "Wallet options - raw", about = None, long_about = None)] +#[command(next_help_heading = "Wallet options - raw", about = None, long_about = None)] pub struct RawWalletOpts { /// Open an interactive prompt to enter your private key. - #[clap(long, short)] + #[arg(long, short)] pub interactive: bool, /// Use the provided private key. - #[clap(long, value_name = "RAW_PRIVATE_KEY")] + #[arg(long, value_name = "RAW_PRIVATE_KEY")] pub private_key: Option, /// Use the mnemonic phrase of mnemonic file at the specified path. - #[clap(long, alias = "mnemonic-path")] + #[arg(long, alias = "mnemonic-path")] pub mnemonic: Option, /// Use a BIP39 passphrase for the mnemonic. - #[clap(long, value_name = "PASSPHRASE")] + #[arg(long, value_name = "PASSPHRASE")] pub mnemonic_passphrase: Option, /// The wallet derivation path. /// /// Works with both --mnemonic-path and hardware wallets. - #[clap(long = "mnemonic-derivation-path", alias = "hd-path", value_name = "PATH")] + #[arg(long = "mnemonic-derivation-path", alias = "hd-path", value_name = "PATH")] pub hd_path: Option, /// Use the private key from the given mnemonic index. /// /// Used with --mnemonic-path. - #[clap(long, conflicts_with = "hd_path", default_value_t = 0, value_name = "INDEX")] + #[arg(long, conflicts_with = "hd_path", default_value_t = 0, value_name = "INDEX")] pub mnemonic_index: u32, } @@ -47,7 +47,7 @@ impl RawWalletOpts { return Ok(Some(PendingSigner::Interactive.unlock()?)); } if let Some(private_key) = &self.private_key { - return Ok(Some(utils::create_private_key_signer(private_key)?)) + return Ok(Some(utils::create_private_key_signer(private_key)?)); } if let Some(mnemonic) = &self.mnemonic { return Ok(Some(utils::create_mnemonic_signer( @@ -55,7 +55,7 @@ impl RawWalletOpts { self.mnemonic_passphrase.as_deref(), self.hd_path.as_deref(), self.mnemonic_index, - )?)) + )?)); } Ok(None) } diff --git a/crates/wallets/src/utils.rs b/crates/wallets/src/utils.rs index a10903313..da19b6d9e 100644 --- a/crates/wallets/src/utils.rs +++ b/crates/wallets/src/utils.rs @@ -1,39 +1,33 @@ use crate::{error::PrivateKeyError, PendingSigner, WalletSigner}; -use ethers_signers::{HDPath as LedgerHDPath, LocalWallet, TrezorHDPath, WalletError}; +use alloy_primitives::B256; +use alloy_signer_ledger::HDPath as LedgerHDPath; +use alloy_signer_local::PrivateKeySigner; +use alloy_signer_trezor::HDPath as TrezorHDPath; use eyre::{Context, Result}; use foundry_config::Config; +use hex::FromHex; use std::{ fs, path::{Path, PathBuf}, - str::FromStr, }; +fn ensure_pk_not_env(pk: &str) -> Result<()> { + if !pk.starts_with("0x") && std::env::var(pk).is_ok() { + return Err(PrivateKeyError::ExistsAsEnvVar(pk.to_string()).into()); + } + Ok(()) +} + /// Validates and sanitizes user inputs, returning configured [WalletSigner]. -pub fn create_private_key_signer(private_key: &str) -> Result { - let privk = private_key.trim().strip_prefix("0x").unwrap_or(private_key); - match LocalWallet::from_str(privk) { +pub fn create_private_key_signer(private_key_str: &str) -> Result { + let Ok(private_key) = B256::from_hex(private_key_str) else { + ensure_pk_not_env(private_key_str)?; + eyre::bail!("Failed to decode private key") + }; + match PrivateKeySigner::from_bytes(&private_key) { Ok(pk) => Ok(WalletSigner::Local(pk)), Err(err) => { - // helper closure to check if pk was meant to be an env var, this usually happens if - // `$` is missing - let ensure_not_env = |pk: &str| { - // check if pk was meant to be an env var - if !pk.starts_with("0x") && std::env::var(pk).is_ok() { - // SAFETY: at this point we know the user actually wanted to use an env var - // and most likely forgot the `$` anchor, so the - // `private_key` here is an unresolved env var - return Err(PrivateKeyError::ExistsAsEnvVar(pk.to_string())) - } - Ok(()) - }; - match err { - WalletError::HexError(err) => { - ensure_not_env(private_key)?; - return Err(PrivateKeyError::InvalidHex(err).into()); - } - WalletError::EcdsaError(_) => ensure_not_env(private_key)?, - _ => {} - }; + ensure_pk_not_env(private_key_str)?; eyre::bail!("Failed to create wallet from private key: {err}") } } @@ -147,7 +141,7 @@ pub fn create_keystore_signer( }?; if let Some(password) = password { - let wallet = LocalWallet::decrypt_keystore(path, password) + let wallet = PrivateKeySigner::decrypt_keystore(path, password) .wrap_err_with(|| format!("Failed to decrypt keystore {path:?}"))?; Ok((Some(WalletSigner::Local(wallet)), None)) } else { diff --git a/crates/wallets/src/wallet.rs b/crates/wallets/src/wallet.rs index 0cb06980d..ce049fe40 100644 --- a/crates/wallets/src/wallet.rs +++ b/crates/wallets/src/wallet.rs @@ -1,9 +1,8 @@ use crate::{raw_wallet::RawWalletOpts, utils, wallet_signer::WalletSigner}; use alloy_primitives::Address; +use alloy_signer::Signer; use clap::Parser; -use ethers_signers::Signer; use eyre::Result; -use foundry_common::types::ToAlloy; use serde::Serialize; /// The wallet options can either be: @@ -12,11 +11,12 @@ use serde::Serialize; /// 3. Trezor /// 4. Keystore (via file path) /// 5. AWS KMS +/// 6. Google Cloud KMS #[derive(Clone, Debug, Default, Serialize, Parser)] -#[clap(next_help_heading = "Wallet options", about = None, long_about = None)] +#[command(next_help_heading = "Wallet options", about = None, long_about = None)] pub struct WalletOpts { /// The sender account. - #[clap( + #[arg( long, short, value_name = "ADDRESS", @@ -25,11 +25,11 @@ pub struct WalletOpts { )] pub from: Option
, - #[clap(flatten)] + #[command(flatten)] pub raw: RawWalletOpts, /// Use the keystore in the given folder or file. - #[clap( + #[arg( long = "keystore", help_heading = "Wallet options - keystore", value_name = "PATH", @@ -38,7 +38,7 @@ pub struct WalletOpts { pub keystore_path: Option, /// Use a keystore from the default keystores folder (~/.foundry/keystores) by its filename - #[clap( + #[arg( long = "account", help_heading = "Wallet options - keystore", value_name = "ACCOUNT_NAME", @@ -50,7 +50,7 @@ pub struct WalletOpts { /// The keystore password. /// /// Used with --keystore. - #[clap( + #[arg( long = "password", help_heading = "Wallet options - keystore", requires = "keystore_path", @@ -61,7 +61,7 @@ pub struct WalletOpts { /// The keystore password file path. /// /// Used with --keystore. - #[clap( + #[arg( long = "password-file", help_heading = "Wallet options - keystore", requires = "keystore_path", @@ -71,16 +71,20 @@ pub struct WalletOpts { pub keystore_password_file: Option, /// Use a Ledger hardware wallet. - #[clap(long, short, help_heading = "Wallet options - hardware wallet")] + #[arg(long, short, help_heading = "Wallet options - hardware wallet")] pub ledger: bool, /// Use a Trezor hardware wallet. - #[clap(long, short, help_heading = "Wallet options - hardware wallet")] + #[arg(long, short, help_heading = "Wallet options - hardware wallet")] pub trezor: bool, /// Use AWS Key Management Service. - #[clap(long, help_heading = "Wallet options - AWS KMS")] + #[arg(long, help_heading = "Wallet options - remote", hide = !cfg!(feature = "aws-kms"))] pub aws: bool, + + /// Use Google Cloud Key Management Service. + #[arg(long, help_heading = "Wallet options - remote", hide = !cfg!(feature = "gcp-kms"))] + pub gcp: bool, } impl WalletOpts { @@ -95,7 +99,14 @@ impl WalletOpts { .await? } else if self.aws { let key_id = std::env::var("AWS_KMS_KEY_ID")?; - WalletSigner::from_aws(&key_id).await? + WalletSigner::from_aws(key_id).await? + } else if self.gcp { + let project_id = std::env::var("GCP_PROJECT_ID")?; + let location = std::env::var("GCP_LOCATION")?; + let keyring = std::env::var("GCP_KEYRING")?; + let key_name = std::env::var("GCP_KEY_NAME")?; + let key_version = std::env::var("GCP_KEY_VERSION")?.parse()?; + WalletSigner::from_gcp(project_id, location, keyring, key_name, key_version).await? } else if let Some(raw_wallet) = self.raw.signer()? { raw_wallet } else if let Some(path) = utils::maybe_get_keystore_path( @@ -120,7 +131,7 @@ impl WalletOpts { Error accessing local wallet. Did you set a private key, mnemonic or keystore? Run `cast send --help` or `forge create --help` and use the corresponding CLI flag to set your key via: ---private-key, --mnemonic-path, --aws, --interactive, --trezor or --ledger. +--private-key, --mnemonic-path, --aws, --gcp, --interactive, --trezor or --ledger. Alternatively, if you're using a local node with unlocked accounts, use the --unlocked flag and either set the `ETH_FROM` environment variable to the address of the unlocked account you want to use, or provide the --from flag with the address directly." @@ -139,7 +150,7 @@ of the unlocked account you want to use, or provide the --from flag with the add if let Some(from) = self.from { from } else if let Ok(signer) = self.signer().await { - signer.address().to_alloy() + signer.address() } else { Address::ZERO } @@ -176,7 +187,7 @@ mod tests { ]); let signer = wallet.signer().await.unwrap(); assert_eq!( - signer.address().to_alloy(), + signer.address(), Address::from_str("ec554aeafe75601aaab43bd4621a22284db566c2").unwrap() ); } @@ -200,6 +211,7 @@ mod tests { ledger: false, trezor: false, aws: false, + gcp: false, }; match wallet.signer().await { Ok(_) => { @@ -207,7 +219,7 @@ mod tests { } Err(x) => { assert!( - x.to_string().contains("Failed to create wallet"), + x.to_string().contains("Failed to decode private key"), "Error message is not user-friendly" ); } diff --git a/crates/wallets/src/wallet_signer.rs b/crates/wallets/src/wallet_signer.rs index d71fbe1af..f1f7bad88 100644 --- a/crates/wallets/src/wallet_signer.rs +++ b/crates/wallets/src/wallet_signer.rs @@ -1,60 +1,113 @@ use crate::error::WalletSignerError; -use alloy_primitives::B256; +use alloy_consensus::SignableTransaction; +use alloy_dyn_abi::TypedData; +use alloy_network::TxSigner; +use alloy_primitives::{Address, ChainId, B256}; +use alloy_signer::{Signature, Signer}; +use alloy_signer_ledger::{HDPath as LedgerHDPath, LedgerSigner}; +use alloy_signer_local::{coins_bip39::English, MnemonicBuilder, PrivateKeySigner}; +use alloy_signer_trezor::{HDPath as TrezorHDPath, TrezorSigner}; +use alloy_sol_types::{Eip712Domain, SolStruct}; use async_trait::async_trait; -use ethers_core::types::{ - transaction::{eip2718::TypedTransaction, eip712::Eip712}, - Signature, -}; -use ethers_signers::{ - coins_bip39::English, AwsSigner, HDPath as LedgerHDPath, Ledger, LocalWallet, MnemonicBuilder, - Signer, Trezor, TrezorHDPath, -}; -use rusoto_core::{ - credential::ChainProvider as AwsChainProvider, region::Region as AwsRegion, - request::HttpClient as AwsHttpClient, Client as AwsClient, -}; -use rusoto_kms::KmsClient; use std::path::PathBuf; +#[cfg(feature = "aws-kms")] +use {alloy_signer_aws::AwsSigner, aws_config::BehaviorVersion, aws_sdk_kms::Client as AwsClient}; + +#[cfg(feature = "gcp-kms")] +use { + alloy_signer_gcp::{GcpKeyRingRef, GcpSigner, GcpSignerError, KeySpecifier}, + gcloud_sdk::{ + google::cloud::kms::v1::key_management_service_client::KeyManagementServiceClient, + GoogleApi, + }, +}; + pub type Result = std::result::Result; /// Wrapper enum around different signers. #[derive(Debug)] pub enum WalletSigner { /// Wrapper around local wallet. e.g. private key, mnemonic - Local(LocalWallet), + Local(PrivateKeySigner), /// Wrapper around Ledger signer. - Ledger(Ledger), + Ledger(LedgerSigner), /// Wrapper around Trezor signer. - Trezor(Trezor), + Trezor(TrezorSigner), /// Wrapper around AWS KMS signer. + #[cfg(feature = "aws-kms")] Aws(AwsSigner), + /// Wrapper around Google Cloud KMS signer. + #[cfg(feature = "gcp-kms")] + Gcp(GcpSigner), } impl WalletSigner { pub async fn from_ledger_path(path: LedgerHDPath) -> Result { - let ledger = Ledger::new(path, 1).await?; + let ledger = LedgerSigner::new(path, None).await?; Ok(Self::Ledger(ledger)) } pub async fn from_trezor_path(path: TrezorHDPath) -> Result { - // cached to ~/.ethers-rs/trezor/cache/trezor.session - let trezor = Trezor::new(path, 1, None).await?; + let trezor = TrezorSigner::new(path, None).await?; Ok(Self::Trezor(trezor)) } - pub async fn from_aws(key_id: &str) -> Result { - let client = - AwsClient::new_with(AwsChainProvider::default(), AwsHttpClient::new().unwrap()); + pub async fn from_aws(key_id: String) -> Result { + #[cfg(feature = "aws-kms")] + { + let config = aws_config::load_defaults(BehaviorVersion::latest()).await; + let client = AwsClient::new(&config); - let kms = KmsClient::new_with_client(client, AwsRegion::default()); + Ok(Self::Aws(AwsSigner::new(client, key_id, None).await?)) + } - Ok(Self::Aws(AwsSigner::new(kms, key_id, 1).await?)) + #[cfg(not(feature = "aws-kms"))] + { + let _ = key_id; + Err(WalletSignerError::aws_unsupported()) + } } - pub fn from_private_key(private_key: impl AsRef<[u8]>) -> Result { - let wallet = LocalWallet::from_bytes(private_key.as_ref())?; - Ok(Self::Local(wallet)) + pub async fn from_gcp( + project_id: String, + location: String, + keyring: String, + key_name: String, + key_version: u64, + ) -> Result { + #[cfg(feature = "gcp-kms")] + { + let keyring = GcpKeyRingRef::new(&project_id, &location, &keyring); + let client = match GoogleApi::from_function( + KeyManagementServiceClient::new, + "https://cloudkms.googleapis.com", + None, + ) + .await + { + Ok(c) => c, + Err(e) => return Err(WalletSignerError::from(GcpSignerError::GoogleKmsError(e))), + }; + + let specifier = KeySpecifier::new(keyring, &key_name, key_version); + + Ok(Self::Gcp(GcpSigner::new(client, specifier, None).await?)) + } + + #[cfg(not(feature = "gcp-kms"))] + { + let _ = project_id; + let _ = location; + let _ = keyring; + let _ = key_name; + let _ = key_version; + Err(WalletSignerError::gcp_unsupported()) + } + } + + pub fn from_private_key(private_key: &B256) -> Result { + Ok(Self::Local(PrivateKeySigner::from_bytes(private_key)?)) } /// Returns a list of addresses available to use with current signer @@ -63,13 +116,13 @@ impl WalletSigner { /// - the result for Ledger signers includes addresses available for both LedgerLive and Legacy /// derivation paths /// - for Local and AWS signers the result contains a single address - pub async fn available_senders(&self, max: usize) -> Result> { + pub async fn available_senders(&self, max: usize) -> Result> { let mut senders = Vec::new(); match self { - WalletSigner::Local(local) => { + Self::Local(local) => { senders.push(local.address()); } - WalletSigner::Ledger(ledger) => { + Self::Ledger(ledger) => { for i in 0..max { if let Ok(address) = ledger.get_address_with_path(&LedgerHDPath::LedgerLive(i)).await @@ -85,7 +138,7 @@ impl WalletSigner { } } } - WalletSigner::Trezor(trezor) => { + Self::Trezor(trezor) => { for i in 0..max { if let Ok(address) = trezor.get_address_with_path(&TrezorHDPath::TrezorLive(i)).await @@ -94,8 +147,13 @@ impl WalletSigner { } } } - WalletSigner::Aws(aws) => { - senders.push(aws.address()); + #[cfg(feature = "aws-kms")] + Self::Aws(aws) => { + senders.push(alloy_signer::Signer::address(aws)); + } + #[cfg(feature = "gcp-kms")] + Self::Gcp(gcp) => { + senders.push(alloy_signer::Signer::address(gcp)); } } Ok(senders) @@ -129,85 +187,67 @@ macro_rules! delegate { Self::Local($inner) => $e, Self::Ledger($inner) => $e, Self::Trezor($inner) => $e, + #[cfg(feature = "aws-kms")] Self::Aws($inner) => $e, + #[cfg(feature = "gcp-kms")] + Self::Gcp($inner) => $e, } }; } #[async_trait] impl Signer for WalletSigner { - type Error = WalletSignerError; - - async fn sign_message>(&self, message: S) -> Result { - delegate!(self, inner => inner.sign_message(message).await.map_err(Into::into)) + /// Signs the given hash. + async fn sign_hash(&self, hash: &B256) -> alloy_signer::Result { + delegate!(self, inner => inner.sign_hash(hash)).await } - async fn sign_transaction(&self, message: &TypedTransaction) -> Result { - delegate!(self, inner => inner.sign_transaction(message).await.map_err(Into::into)) + async fn sign_message(&self, message: &[u8]) -> alloy_signer::Result { + delegate!(self, inner => inner.sign_message(message)).await } - async fn sign_typed_data(&self, payload: &T) -> Result { - delegate!(self, inner => inner.sign_typed_data(payload).await.map_err(Into::into)) + fn address(&self) -> Address { + delegate!(self, inner => alloy_signer::Signer::address(inner)) } - fn address(&self) -> ethers_core::types::Address { - delegate!(self, inner => inner.address()) - } - - fn chain_id(&self) -> u64 { + fn chain_id(&self) -> Option { delegate!(self, inner => inner.chain_id()) } - fn with_chain_id>(self, chain_id: T) -> Self { - match self { - Self::Local(inner) => Self::Local(inner.with_chain_id(chain_id)), - Self::Ledger(inner) => Self::Ledger(inner.with_chain_id(chain_id)), - Self::Trezor(inner) => Self::Trezor(inner.with_chain_id(chain_id)), - Self::Aws(inner) => Self::Aws(inner.with_chain_id(chain_id)), - } - } -} - -#[async_trait] -impl Signer for &WalletSigner { - type Error = WalletSignerError; - - async fn sign_message>(&self, message: S) -> Result { - (*self).sign_message(message).await - } - - async fn sign_transaction(&self, message: &TypedTransaction) -> Result { - (*self).sign_transaction(message).await + fn set_chain_id(&mut self, chain_id: Option) { + delegate!(self, inner => inner.set_chain_id(chain_id)) } - async fn sign_typed_data(&self, payload: &T) -> Result { - (*self).sign_typed_data(payload).await + async fn sign_typed_data( + &self, + payload: &T, + domain: &Eip712Domain, + ) -> alloy_signer::Result + where + Self: Sized, + { + delegate!(self, inner => inner.sign_typed_data(payload, domain)).await } - fn address(&self) -> ethers_core::types::Address { - (*self).address() + async fn sign_dynamic_typed_data( + &self, + payload: &TypedData, + ) -> alloy_signer::Result { + delegate!(self, inner => inner.sign_dynamic_typed_data(payload)).await } +} - fn chain_id(&self) -> u64 { - (*self).chain_id() +#[async_trait] +impl TxSigner for WalletSigner { + fn address(&self) -> Address { + delegate!(self, inner => alloy_signer::Signer::address(inner)) } - fn with_chain_id>(self, chain_id: T) -> Self { - let _ = chain_id; - self - } -} - -impl WalletSigner { - pub async fn sign_hash(&self, hash: &B256) -> Result { - match self { - // TODO: AWS can sign hashes but utilities aren't exposed in ethers-signers. - // TODO: Implement with alloy-signer. - Self::Aws(_aws) => Err(WalletSignerError::CannotSignRawHash("AWS")), - Self::Ledger(_) => Err(WalletSignerError::CannotSignRawHash("Ledger")), - Self::Local(wallet) => wallet.sign_hash(hash.0.into()).map_err(Into::into), - Self::Trezor(_) => Err(WalletSignerError::CannotSignRawHash("Trezor")), - } + async fn sign_transaction( + &self, + tx: &mut dyn SignableTransaction, + ) -> alloy_signer::Result { + delegate!(self, inner => inner.sign_transaction(tx)).await } } @@ -223,11 +263,11 @@ impl PendingSigner { match self { Self::Keystore(path) => { let password = rpassword::prompt_password("Enter keystore password:")?; - Ok(WalletSigner::Local(LocalWallet::decrypt_keystore(path, password)?)) + Ok(WalletSigner::Local(PrivateKeySigner::decrypt_keystore(path, password)?)) } Self::Interactive => { let private_key = rpassword::prompt_password("Enter private key:")?; - Ok(WalletSigner::from_private_key(hex::decode(private_key)?)?) + Ok(WalletSigner::from_private_key(&hex::FromHex::from_hex(private_key)?)?) } } } diff --git a/crates/zksync/compiler/Cargo.toml b/crates/zksync/compiler/Cargo.toml index 5cafe131a..7836d4ec8 100644 --- a/crates/zksync/compiler/Cargo.toml +++ b/crates/zksync/compiler/Cargo.toml @@ -12,11 +12,13 @@ repository.workspace = true exclude.workspace = true [dependencies] +foundry-config.workspace = true foundry-compilers = { workspace = true, features = ["svm-solc"] } alloy-primitives.workspace = true tracing.workspace = true serde_json.workspace = true serde.workspace = true +semver.workspace = true # zk zksync_types.workspace = true diff --git a/crates/zksync/compiler/src/lib.rs b/crates/zksync/compiler/src/lib.rs index d46df5b1d..44d4bcbb1 100644 --- a/crates/zksync/compiler/src/lib.rs +++ b/crates/zksync/compiler/src/lib.rs @@ -6,6 +6,139 @@ /// ZKSolc specific logic. mod zksolc; +use std::path::Path; + +use foundry_config::{Config, SkipBuildFilters, SolcReq}; pub use zksolc::*; pub mod libraries; + +use foundry_compilers::{ + artifacts::Severity, error::SolcError, solc::SolcCompiler, zksolc::ZkSolc, + zksync::config::ZkSolcConfig, Compiler, Project, ProjectBuilder, +}; + +/// Ensures that the configured version is installed if explicitly set +/// +/// If `zksolc` is [`SolcReq::Version`] then this will download and install the solc version if +/// it's missing, unless the `offline` flag is enabled, in which case an error is thrown. +/// +/// If `zksolc` is [`SolcReq::Local`] then this will ensure that the path exists. +pub fn ensure_zksolc(zksolc: Option<&SolcReq>, offline: bool) -> Result, SolcError> { + if let Some(ref zksolc) = zksolc { + let zksolc = match zksolc { + SolcReq::Version(version) => { + let mut zksolc = ZkSolc::find_installed_version(version)?; + if zksolc.is_none() { + if offline { + return Err(SolcError::msg(format!( + "can't install missing zksolc {version} in offline mode" + ))) + } + ZkSolc::blocking_install(version)?; + zksolc = ZkSolc::find_installed_version(version)?; + } + zksolc + } + SolcReq::Local(zksolc) => { + if !zksolc.is_file() { + return Err(SolcError::msg(format!( + "`zksolc` {} does not exist", + zksolc.display() + ))) + } + Some(ZkSolc::new(zksolc)) + } + }; + return Ok(zksolc) + } + + Ok(None) +} + +/// Create a new zkSync project +pub fn create_project( + config: &Config, + cached: bool, + no_artifacts: bool, +) -> Result, SolcError> { + let mut builder = ProjectBuilder::::default() + .artifacts(config.configured_artifacts_handler()) + .paths(config.project_paths()) + .settings(config.solc_settings()?) + .ignore_error_codes(config.ignored_error_codes.iter().copied().map(Into::into)) + .ignore_paths(config.ignored_file_paths.clone()) + .set_compiler_severity_filter(if config.deny_warnings { + Severity::Warning + } else { + Severity::Error + }) + .set_offline(config.offline) + .set_cached(cached) + .set_build_info(!no_artifacts && config.build_info) + .set_no_artifacts(no_artifacts); + + if !config.skip.is_empty() { + let filter = SkipBuildFilters::new(config.skip.clone(), config.root.0.clone()); + builder = builder.sparse_output(filter); + } + + let mut project = builder.build(config.solc_compiler()?)?; + + if config.force { + config.cleanup(&project)?; + } + + // Set up zksolc project values + // TODO: maybe some of these could be included + // when setting up the builder for the sake of consistency (requires dedicated + // builder methods) + project.zksync_zksolc_config = ZkSolcConfig { settings: config.zksync_zksolc_settings()? }; + + if let Some(zksolc) = ensure_zksolc(config.zksync.zksolc.as_ref(), config.offline)? { + project.zksync_zksolc = zksolc; + } else { + // TODO: we automatically install a zksolc version + // if none is found, but maybe we should mirror auto detect settings + // as done with solc + if !config.offline { + let default_version = semver::Version::new(1, 5, 0); + let mut zksolc = ZkSolc::find_installed_version(&default_version)?; + if zksolc.is_none() { + ZkSolc::blocking_install(&default_version)?; + zksolc = ZkSolc::find_installed_version(&default_version)?; + } + project.zksync_zksolc = + zksolc.unwrap_or_else(|| panic!("Could not install zksolc v{}", default_version)); + } + } + + Ok(project) +} + +/// Obtain a standard json input for zksolc +pub fn standard_json_input( + project: &Project, + target_path: impl AsRef, +) -> Result +where + C::Settings: Into, +{ + let mut input = project.standard_json_input(target_path)?; + tracing::debug!(?input.settings.remappings, "standard_json_input for zksync"); + + let mut settings = project.zksync_zksolc_config.settings.clone(); + settings.remappings = std::mem::take(&mut input.settings.remappings); + settings.libraries.libs = settings + .libraries + .libs + .into_iter() + .map(|(f, libs)| (f.strip_prefix(project.root()).unwrap_or(&f).to_path_buf(), libs)) + .collect(); + let settings = serde_json::to_value(settings).expect("able to serialize settings as json"); + + let mut serialized = serde_json::to_value(input).expect("able to serialize input as json"); + serialized["settings"] = settings; + + Ok(serialized) +} diff --git a/crates/zksync/compiler/src/zksolc/mod.rs b/crates/zksync/compiler/src/zksolc/mod.rs index bcfc103d8..41a71bdbf 100644 --- a/crates/zksync/compiler/src/zksolc/mod.rs +++ b/crates/zksync/compiler/src/zksolc/mod.rs @@ -70,7 +70,7 @@ impl DualCompiledContracts { .strip_prefix(&layout.artifacts) .unwrap_or_else(|_| { panic!( - "failed stripping artifact path '{:?}' from '{:?}'", + "failed stripping solc artifact path '{:?}' from '{:?}'", layout.artifacts, artifact_path ) }) @@ -108,7 +108,7 @@ impl DualCompiledContracts { .strip_prefix(&layout.zksync_artifacts) .unwrap_or_else(|_| { panic!( - "failed stripping artifact path '{:?}' from '{:?}'", + "failed stripping zksolc artifact path '{:?}' from '{:?}'", layout.zksync_artifacts, artifact_path ) }) @@ -117,6 +117,7 @@ impl DualCompiledContracts { let maybe_bytecode = &artifact.bytecode; let maybe_hash = &artifact.hash; let maybe_factory_deps = &artifact.factory_dependencies; + if let (Some(bytecode), Some(hash), Some(factory_deps_map)) = (maybe_bytecode, maybe_hash, maybe_factory_deps) { diff --git a/crates/zksync/core/Cargo.toml b/crates/zksync/core/Cargo.toml index d3b3eb28e..2b10142f3 100644 --- a/crates/zksync/core/Cargo.toml +++ b/crates/zksync/core/Cargo.toml @@ -16,7 +16,15 @@ foundry-common.workspace = true foundry-cheatcodes-common.workspace = true foundry-zksync-compiler.workspace = true alloy-primitives.workspace = true +alloy-signer.workspace = true +alloy-network.workspace = true alloy-sol-types.workspace = true +alloy-dyn-abi.workspace = true +alloy-serde.workspace = true +alloy-provider.workspace = true +alloy-transport.workspace = true +alloy-rpc-types.workspace = true +alloy-consensus.workspace = true hex.workspace = true itertools.workspace = true revm = { workspace = true, default-features = false, features = [ @@ -30,6 +38,7 @@ revm = { workspace = true, default-features = false, features = [ "optimism", ] } tracing.workspace = true +serde_json.workspace = true # zk multivm.workspace = true @@ -45,4 +54,4 @@ ansi_term = "0.12.1" once_cell = "1" eyre = "0.6" url = "2" -lazy_static = "1.4.0" \ No newline at end of file +lazy_static = "1.5.0" \ No newline at end of file diff --git a/crates/zksync/core/src/cheatcodes.rs b/crates/zksync/core/src/cheatcodes.rs index 4647be977..dc50d2cb7 100644 --- a/crates/zksync/core/src/cheatcodes.rs +++ b/crates/zksync/core/src/cheatcodes.rs @@ -2,8 +2,8 @@ use std::fmt::Debug; use alloy_primitives::{Bytes, B256}; use revm::{ - primitives::{Address, Bytecode, Env, U256 as rU256}, - Database, JournaledState, + primitives::{Address, Bytecode, U256 as rU256}, + Database, InnerEvmContext, }; use tracing::info; use zksync_types::{ @@ -21,66 +21,47 @@ use crate::{ }; /// Sets `block.timestamp`. -pub fn warp<'a, DB>( - timestamp: rU256, - env: &'a mut Env, - db: &'a mut DB, - journaled_state: &'a mut JournaledState, -) where +pub fn warp(timestamp: rU256, ecx: &mut InnerEvmContext) +where DB: Database, ::Error: Debug, { info!(?timestamp, "cheatcode warp"); let system_account = SYSTEM_CONTEXT_ADDRESS.to_address(); - journaled_state.load_account(system_account, db).expect("account could not be loaded"); + ecx.load_account(system_account).expect("account could not be loaded"); let block_info_key = CURRENT_VIRTUAL_BLOCK_INFO_POSITION.to_ru256(); - let (block_info, _) = - journaled_state.sload(system_account, block_info_key, db).unwrap_or_default(); + let (block_info, _) = ecx.sload(system_account, block_info_key).unwrap_or_default(); let (block_number, _block_timestamp) = unpack_block_info(block_info.to_u256()); let new_block_info = pack_block_info(block_number, timestamp.as_limbs()[0]).to_ru256(); - journaled_state.touch(&system_account); - journaled_state - .sstore(system_account, block_info_key, new_block_info, db) - .expect("failed storing value"); - env.block.timestamp = timestamp; + ecx.touch(&system_account); + ecx.sstore(system_account, block_info_key, new_block_info).expect("failed storing value"); + ecx.env.block.timestamp = timestamp; } /// Sets `block.number`. -pub fn roll<'a, DB>( - number: rU256, - env: &'a mut Env, - db: &'a mut DB, - journaled_state: &'a mut JournaledState, -) where +pub fn roll(number: rU256, ecx: &mut InnerEvmContext) +where DB: Database, ::Error: Debug, { info!(?number, "cheatcode roll"); let system_account = SYSTEM_CONTEXT_ADDRESS.to_address(); - journaled_state.load_account(system_account, db).expect("account could not be loaded"); + ecx.load_account(system_account).expect("account could not be loaded"); let block_info_key = CURRENT_VIRTUAL_BLOCK_INFO_POSITION.to_ru256(); - let (block_info, _) = - journaled_state.sload(system_account, block_info_key, db).unwrap_or_default(); + let (block_info, _) = ecx.sload(system_account, block_info_key).unwrap_or_default(); let (_block_number, block_timestamp) = unpack_block_info(block_info.to_u256()); let new_block_info = pack_block_info(number.as_limbs()[0], block_timestamp).to_ru256(); - journaled_state.touch(&system_account); - journaled_state - .sstore(system_account, block_info_key, new_block_info, db) - .expect("failed storing value"); - env.block.number = number; + ecx.touch(&system_account); + ecx.sstore(system_account, block_info_key, new_block_info).expect("failed storing value"); + ecx.env.block.number = number; } /// Sets balance for a specific address. -pub fn deal<'a, DB>( - address: Address, - balance: rU256, - db: &'a mut DB, - journaled_state: &'a mut JournaledState, -) -> rU256 +pub fn deal(address: Address, balance: rU256, ecx: &mut InnerEvmContext) -> rU256 where DB: Database, ::Error: Debug, @@ -88,23 +69,19 @@ where info!(?address, ?balance, "cheatcode deal"); let balance_addr = L2_BASE_TOKEN_ADDRESS.to_address(); - journaled_state.load_account(balance_addr, db).expect("account could not be loaded"); + ecx.load_account(balance_addr).expect("account could not be loaded"); let zk_address = address.to_h160(); let balance_key = storage_key_for_eth_balance(&zk_address).key().to_ru256(); - let (old_balance, _) = journaled_state.sload(balance_addr, balance_key, db).unwrap_or_default(); - journaled_state.touch(&balance_addr); - journaled_state.sstore(balance_addr, balance_key, balance, db).expect("failed storing value"); + let (old_balance, _) = ecx.sload(balance_addr, balance_key).unwrap_or_default(); + ecx.touch(&balance_addr); + ecx.sstore(balance_addr, balance_key, balance).expect("failed storing value"); old_balance } /// Sets nonce for a specific address. -pub fn set_nonce<'a, DB>( - address: Address, - nonce: rU256, - db: &'a mut DB, - journaled_state: &'a mut JournaledState, -) where +pub fn set_nonce(address: Address, nonce: rU256, ecx: &mut InnerEvmContext) +where DB: Database, ::Error: Debug, { @@ -113,21 +90,15 @@ pub fn set_nonce<'a, DB>( let (tx_nonce, _deploy_nonce) = decompose_full_nonce(nonce.to_u256()); let nonce_addr = NONCE_HOLDER_ADDRESS.to_address(); - journaled_state.load_account(nonce_addr, db).expect("account could not be loaded"); + ecx.load_account(nonce_addr).expect("account could not be loaded"); let zk_address = address.to_h160(); let nonce_key = get_nonce_key(&zk_address).key().to_ru256(); - journaled_state.touch(&nonce_addr); - journaled_state - .sstore(nonce_addr, nonce_key, tx_nonce.to_ru256(), db) - .expect("failed storing value"); + ecx.touch(&nonce_addr); + ecx.sstore(nonce_addr, nonce_key, tx_nonce.to_ru256()).expect("failed storing value"); } /// Gets nonce for a specific address. -pub fn get_nonce<'a, DB>( - address: Address, - db: &'a mut DB, - journaled_state: &'a mut JournaledState, -) -> rU256 +pub fn get_nonce(address: Address, ecx: &mut InnerEvmContext) -> rU256 where DB: Database, ::Error: Debug, @@ -135,22 +106,18 @@ where info!(?address, "cheatcode getNonce"); let nonce_addr = NONCE_HOLDER_ADDRESS.to_address(); - journaled_state.load_account(nonce_addr, db).expect("account could not be loaded"); + ecx.load_account(nonce_addr).expect("account could not be loaded"); let zk_address = address.to_h160(); let nonce_key = get_nonce_key(&zk_address).key().to_ru256(); - let (full_nonce, _) = journaled_state.sload(nonce_addr, nonce_key, db).unwrap_or_default(); + let (full_nonce, _) = ecx.sload(nonce_addr, nonce_key).unwrap_or_default(); let (tx_nonce, _deploy_nonce) = decompose_full_nonce(full_nonce.to_u256()); tx_nonce.to_ru256() } /// Sets code for a specific address. -pub fn etch<'a, DB>( - address: Address, - bytecode: &[u8], - db: &'a mut DB, - journaled_state: &'a mut JournaledState, -) where +pub fn etch(address: Address, bytecode: &[u8], ecx: &mut InnerEvmContext) +where DB: Database, ::Error: Debug, { @@ -168,35 +135,28 @@ pub fn etch<'a, DB>( let account_code_addr = ACCOUNT_CODE_STORAGE_ADDRESS.to_address(); let known_codes_addr = KNOWN_CODES_STORAGE_ADDRESS.to_address(); - journaled_state.load_account(account_code_addr, db).expect("account could not be loaded"); - journaled_state.touch(&account_code_addr); - journaled_state.load_account(known_codes_addr, db).expect("account could not be loaded"); - journaled_state.touch(&known_codes_addr); + ecx.load_account(account_code_addr).expect("account could not be loaded"); + ecx.touch(&account_code_addr); + ecx.load_account(known_codes_addr).expect("account could not be loaded"); + ecx.touch(&known_codes_addr); let zk_address = address.to_h160(); - journaled_state - .sstore(account_code_addr, zk_address.to_h256().to_ru256(), bytecode_hash, db) - .expect("failed storing value"); - journaled_state - .sstore(known_codes_addr, bytecode_hash, rU256::ZERO, db) + ecx.sstore(account_code_addr, zk_address.to_h256().to_ru256(), bytecode_hash) .expect("failed storing value"); + ecx.sstore(known_codes_addr, bytecode_hash, rU256::ZERO).expect("failed storing value"); - journaled_state.load_account(address, db).expect("account could not be loaded"); - journaled_state.touch(&address); - let account = journaled_state.state.get_mut(&address).expect("failed loading account"); + ecx.load_account(address).expect("account could not be loaded"); + ecx.touch(&address); + let account = ecx.journaled_state.state.get_mut(&address).expect("failed loading account"); account.info.code_hash = B256::from(bytecode_hash.to_be_bytes()); account.info.code = Some(bytecode.clone()); } /// Sets code for a mocked account. If not done, the mocked call will revert. /// The call has no effect if the mocked account already has a bytecode entry. -pub fn set_mocked_account<'a, DB>( - address: Address, - db: &'a mut DB, - journaled_state: &'a mut JournaledState, - caller: Address, -) where +pub fn set_mocked_account(address: Address, ecx: &mut InnerEvmContext, caller: Address) +where DB: Database, ::Error: Debug, { @@ -207,11 +167,9 @@ pub fn set_mocked_account<'a, DB>( let account_code_addr = zksync_types::ACCOUNT_CODE_STORAGE_ADDRESS.to_address(); let known_code_addr = zksync_types::KNOWN_CODES_STORAGE_ADDRESS.to_address(); { - journaled_state - .load_account(account_code_addr, db) + ecx.load_account(account_code_addr) .expect("account 'ACCOUNT_CODE_STORAGE_ADDRESS' could not be loaded"); - journaled_state - .load_account(known_code_addr, db) + ecx.load_account(known_code_addr) .expect("account 'KNOWN_CODES_STORAGE_ADDRESS' could not be loaded"); } @@ -219,30 +177,23 @@ pub fn set_mocked_account<'a, DB>( // update account code storage for empty code let account_key = address.to_h256().to_ru256(); - let has_code = journaled_state - .sload(account_code_addr, account_key, db) - .map(|(v, _)| !v.is_zero()) - .unwrap_or_default(); + let has_code = + ecx.sload(account_code_addr, account_key).map(|(v, _)| !v.is_zero()).unwrap_or_default(); if has_code { return; } // update known code storage for empty code - journaled_state.touch(&account_code_addr); - journaled_state - .sstore(account_code_addr, account_key, empty_code_hash.to_ru256(), db) + ecx.touch(&account_code_addr); + ecx.sstore(account_code_addr, account_key, empty_code_hash.to_ru256()) .expect("failed storing value"); let hash_key = empty_code_hash.to_ru256(); - let has_hash = journaled_state - .sload(known_code_addr, hash_key, db) - .map(|(v, _)| !v.is_zero()) - .unwrap_or_default(); + let has_hash = + ecx.sload(known_code_addr, hash_key).map(|(v, _)| !v.is_zero()).unwrap_or_default(); if !has_hash { - journaled_state.touch(&known_code_addr); - journaled_state - .sstore(known_code_addr, hash_key, rU256::from(1u32), db) - .expect("failed storing value"); + ecx.touch(&known_code_addr); + ecx.sstore(known_code_addr, hash_key, rU256::from(1u32)).expect("failed storing value"); } } @@ -255,11 +206,6 @@ mod tests { #[test] #[should_panic(expected = "bytecode length must be divisible by 32")] fn test_etch_panics_when_bytecode_not_aligned_on_32_bytes() { - etch( - Address::ZERO, - &[0], - &mut EmptyDB::default(), - &mut JournaledState::new(revm::primitives::SpecId::BYZANTIUM, vec![]), - ); + etch(Address::ZERO, &[0], &mut InnerEvmContext::new(EmptyDB::default())); } } diff --git a/crates/zksync/core/src/convert.rs b/crates/zksync/core/src/convert.rs index 0702b332f..c6f84a6f9 100644 --- a/crates/zksync/core/src/convert.rs +++ b/crates/zksync/core/src/convert.rs @@ -5,6 +5,12 @@ use revm::primitives::{Address, B256}; use zksync_basic_types::{H160, H256, U256}; use zksync_utils::{address_to_h256, h256_to_u256, u256_to_h256}; +use alloy_primitives::{Bytes as AlloyBytes, Signature as AlloySignature}; +use zksync_web3_rs::types::{Bytes as ZkBytes, Signature as ZkSignature}; + +mod eip712; +pub use eip712::*; + /// Conversions from [U256] pub trait ConvertU256 { /// Convert to [rU256] @@ -130,6 +136,67 @@ impl ConvertAddress for Address { } } +/// Conversions to/from [`ZkSignature`] & [`AlloySignature`] +pub trait ConvertSignature { + /// Cast to [`ZkSignature`] + fn to_ethers(self) -> ZkSignature; + /// Cast to [`AlloySignature`] + fn to_alloy(self) -> AlloySignature; +} + +impl ConvertSignature for ZkSignature { + fn to_ethers(self) -> ZkSignature { + self + } + + fn to_alloy(self) -> AlloySignature { + AlloySignature::from_rs_and_parity(self.r.to_ru256(), self.s.to_ru256(), self.v).unwrap() + } +} + +impl ConvertSignature for AlloySignature { + fn to_ethers(self) -> ZkSignature { + let v = self.v(); + ZkSignature { + r: self.r().to_u256(), + s: self.s().to_u256(), + v: v.y_parity_byte_non_eip155().unwrap_or(v.y_parity_byte()) as u64, + } + } + + fn to_alloy(self) -> AlloySignature { + self + } +} + +/// Convert to/from [`AlloyBytes`] & [`ZkBytes`] +pub trait ConvertBytes { + /// Convert to [`AlloyBytes`] + fn to_alloy(self) -> AlloyBytes; + /// Convert to [`ZkBytes`] + fn to_ethers(self) -> ZkBytes; +} + +impl ConvertBytes for AlloyBytes { + fn to_alloy(self) -> AlloyBytes { + self + } + + fn to_ethers(self) -> ZkBytes { + ZkBytes(self.0) + } +} + +impl ConvertBytes for ZkBytes { + fn to_alloy(self) -> AlloyBytes { + AlloyBytes(self.0) + } + + fn to_ethers(self) -> ZkBytes { + self + } +} + #[cfg(test)] mod test { use std::str::FromStr; diff --git a/crates/zksync/core/src/convert/eip712.rs b/crates/zksync/core/src/convert/eip712.rs new file mode 100644 index 000000000..f4cc9ae5f --- /dev/null +++ b/crates/zksync/core/src/convert/eip712.rs @@ -0,0 +1,194 @@ +use alloy_consensus::{SignableTransaction, Transaction}; +use alloy_dyn_abi::TypedData; +use alloy_primitives::{bytes::BufMut, Signature, B256}; +/// Conversion between ethers and alloy for EIP712 items +use alloy_sol_types::Eip712Domain as AlloyEip712Domain; +use zksync_web3_rs::{ + eip712::Eip712Transaction, + types::transaction::eip712::{ + encode_type, EIP712Domain as EthersEip712Domain, Eip712 as EthersEip712, Eip712DomainType, + Types, + }, +}; + +use super::{ConvertAddress, ConvertH160, ConvertRU256, ConvertU256}; + +/// Convert between Eip712Domain types +pub trait ConvertEIP712Domain { + /// Cast to ethers-rs's Eip712Domain + fn to_ethers(self) -> EthersEip712Domain; + + /// Cast to alloy-rs's Eip712Domain + fn to_alloy(self) -> AlloyEip712Domain; +} + +impl ConvertEIP712Domain for AlloyEip712Domain { + fn to_ethers(self) -> EthersEip712Domain { + EthersEip712Domain { + name: self.name.map(Into::into), + version: self.version.map(Into::into), + chain_id: self.chain_id.map(ConvertRU256::to_u256), + verifying_contract: self.verifying_contract.map(ConvertAddress::to_h160), + salt: self.salt.map(Into::into), + } + } + + fn to_alloy(self) -> AlloyEip712Domain { + self + } +} + +impl ConvertEIP712Domain for EthersEip712Domain { + fn to_ethers(self) -> EthersEip712Domain { + self + } + + fn to_alloy(self) -> AlloyEip712Domain { + AlloyEip712Domain::new( + self.name.map(Into::into), + self.version.map(Into::into), + self.chain_id.map(ConvertU256::to_ru256), + self.verifying_contract.map(ConvertH160::to_address), + self.salt.map(Into::into), + ) + } +} + +/// Wrapper around [`Eip712Transaction`] implementing [`SignableTransaction`] +pub struct Eip712SignableTransaction(Eip712Transaction); + +impl Transaction for Eip712SignableTransaction { + fn chain_id(&self) -> Option { + Some(self.0.chain_id.as_u64()) + } + + fn nonce(&self) -> u64 { + self.0.nonce.as_u64() + } + + fn gas_limit(&self) -> u128 { + self.0.gas_limit.as_u128() + } + + fn gas_price(&self) -> Option { + None + } + + fn to(&self) -> alloy_primitives::TxKind { + alloy_primitives::TxKind::Call(self.0.to.to_address()) + } + + fn value(&self) -> alloy_primitives::U256 { + self.0.value.to_ru256() + } + + fn input(&self) -> &[u8] { + self.0.data.as_ref() + } +} + +impl SignableTransaction for Eip712SignableTransaction { + fn set_chain_id(&mut self, chain_id: alloy_primitives::ChainId) { + self.0.chain_id = chain_id.into(); + } + + fn encode_for_signing(&self, out: &mut dyn BufMut) { + out.put_u8(0x19); + out.put_u8(0x01); + + let domain_separator = self.0.domain_separator().expect("able to get domain separator"); + out.put_slice(&domain_separator); + + let struct_hash = self.0.struct_hash().expect("able to get struct hash"); + out.put_slice(&struct_hash); + } + + fn payload_len_for_signature(&self) -> usize { + 2 + 32 + 32 + } + + fn into_signed(self, signature: Signature) -> alloy_consensus::Signed + where + Self: Sized, + { + let hash = self.0.encode_eip712().map(B256::from).expect("able to encode EIP712 hash"); + alloy_consensus::Signed::new_unchecked(self, signature, hash) + } +} + +/// Convert to [`SignableTransaction`] +pub trait ToSignable { + /// Type to convert to + type Signable: SignableTransaction; + + /// Perform conversion + fn to_signable_tx(self) -> Self::Signable; +} + +impl ToSignable for Eip712Transaction { + type Signable = Eip712SignableTransaction; + + fn to_signable_tx(self) -> Self::Signable { + Eip712SignableTransaction(self) + } +} + +/// Convert to [`TypedData`] +pub trait ToTypedData { + /// Convert item to [`TypedData`] + fn to_typed_data(self) -> TypedData; +} + +impl ToTypedData for Eip712Transaction { + fn to_typed_data(self) -> TypedData { + use alloy_dyn_abi::*; + + let types = eip712_transaction_types(); + let primary_type = types.first_key_value().unwrap().0.clone(); + + let domain = EthersEip712::domain(&self).expect("Eip712Transaction has domain").to_alloy(); + + let message = serde_json::to_value(&self).expect("able to serialize as json"); + + let encode_type = encode_type(&primary_type, &types).expect("able to encodeType"); + + let resolver = { + let mut resolver = Resolver::default(); + resolver.ingest_string(&encode_type).expect("able to ingest encodeType"); + resolver + }; + + TypedData { domain, resolver, primary_type, message } + } +} + +//zksync_web3_rs::eip712::transaction +fn eip712_transaction_types() -> Types { + let mut types = Types::new(); + + types.insert( + "Transaction".to_owned(), + vec![ + Eip712DomainType { name: "txType".to_owned(), r#type: "uint256".to_owned() }, + Eip712DomainType { name: "from".to_owned(), r#type: "uint256".to_owned() }, + Eip712DomainType { name: "to".to_owned(), r#type: "uint256".to_owned() }, + Eip712DomainType { name: "gasLimit".to_owned(), r#type: "uint256".to_owned() }, + Eip712DomainType { + name: "gasPerPubdataByteLimit".to_owned(), + r#type: "uint256".to_owned(), + }, + Eip712DomainType { name: "maxFeePerGas".to_owned(), r#type: "uint256".to_owned() }, + Eip712DomainType { + name: "maxPriorityFeePerGas".to_owned(), + r#type: "uint256".to_owned(), + }, + Eip712DomainType { name: "paymaster".to_owned(), r#type: "uint256".to_owned() }, + Eip712DomainType { name: "nonce".to_owned(), r#type: "uint256".to_owned() }, + Eip712DomainType { name: "value".to_owned(), r#type: "uint256".to_owned() }, + Eip712DomainType { name: "data".to_owned(), r#type: "bytes".to_owned() }, + Eip712DomainType { name: "factoryDeps".to_owned(), r#type: "bytes32[]".to_owned() }, + Eip712DomainType { name: "paymasterInput".to_owned(), r#type: "bytes".to_owned() }, + ], + ); + types +} diff --git a/crates/zksync/core/src/lib.rs b/crates/zksync/core/src/lib.rs index 990bb448f..8d132bf56 100644 --- a/crates/zksync/core/src/lib.rs +++ b/crates/zksync/core/src/lib.rs @@ -18,8 +18,17 @@ pub mod vm; /// ZKSync Era State implementation. pub mod state; +use alloy_network::{AnyNetwork, TxSigner}; use alloy_primitives::{Address, Bytes, U256 as rU256}; -use convert::{ConvertAddress, ConvertH160, ConvertH256, ConvertRU256, ConvertU256}; +use alloy_provider::Provider; +use alloy_rpc_types::TransactionRequest; +use alloy_serde::WithOtherFields; +use alloy_signer::Signature; +use alloy_transport::Transport; +use convert::{ + ConvertAddress, ConvertBytes, ConvertH160, ConvertH256, ConvertRU256, ConvertSignature, + ToSignable, +}; use eyre::{eyre, OptionExt}; pub use utils::{fix_l2_gas_limit, fix_l2_gas_price}; pub use vm::{balance, encode_create_params, nonce}; @@ -32,9 +41,6 @@ pub use zksync_types::{ pub use zksync_utils::bytecode::hash_bytecode; use zksync_web3_rs::{ eip712::{Eip712Meta, Eip712Transaction, Eip712TransactionRequest}, - providers::Middleware, - signers::Signer, - types::transaction::eip2718::TypedTransaction, zks_provider::types::Fee, zks_utils::EIP712_TX_TYPE, }; @@ -59,35 +65,47 @@ pub struct ZkTransactionMetadata { pub factory_deps: Vec>, } +impl ZkTransactionMetadata { + /// Create a new [`ZkTransactionMetadata`] with the given factory deps + pub fn new(factory_deps: Vec>) -> Self { + Self { factory_deps } + } +} + /// Creates a new signed EIP-712 transaction with the provided factory deps. -pub async fn new_eip712_transaction( - legacy_or_1559: TypedTransaction, +pub async fn new_eip712_transaction< + P: Provider, + S: TxSigner + Sync, + T: Transport + Clone, +>( + tx: WithOtherFields, factory_deps: Vec>, - provider: M, + provider: P, signer: S, ) -> Result { - let from = legacy_or_1559.from().cloned().ok_or_eyre("`from` cannot be empty")?; - let to = legacy_or_1559 - .to() - .and_then(|to| to.as_address()) - .cloned() + let from = tx.from.ok_or_eyre("`from` cannot be empty")?; + let to = tx + .to + .and_then(|to| match to { + alloy_primitives::TxKind::Create => None, + alloy_primitives::TxKind::Call(to) => Some(to), + }) .ok_or_eyre("`to` cannot be empty")?; - let chain_id = legacy_or_1559.chain_id().ok_or_eyre("`chain_id` cannot be empty")?; - let nonce = legacy_or_1559.nonce().ok_or_eyre("`nonce` cannot be empty")?; - let gas_price = legacy_or_1559.gas_price().ok_or_eyre("`gas_price` cannot be empty")?; - let max_cost = legacy_or_1559.max_cost().ok_or_eyre("`max_cost` cannot be empty")?; - let data = legacy_or_1559.data().cloned().ok_or_eyre("`data` cannot be empty")?; + let chain_id = tx.chain_id.ok_or_eyre("`chain_id` cannot be empty")?; + let nonce = tx.nonce.ok_or_eyre("`nonce` cannot be empty")?; + let gas_price = tx.gas_price.ok_or_eyre("`gas_price` cannot be empty")?; + + let data = tx.input.clone().into_input().unwrap_or_default(); let custom_data = Eip712Meta::new().factory_deps(factory_deps); let mut deploy_request = Eip712TransactionRequest::new() .r#type(EIP712_TX_TYPE) - .from(from) - .to(to) - .chain_id(chain_id.as_u64()) + .from(from.to_h160()) + .to(to.to_h160()) + .chain_id(chain_id) .nonce(nonce) .gas_price(gas_price) - .max_fee_per_gas(max_cost) - .data(data) + .data(data.to_ethers()) .custom_data(custom_data); let gas_price = provider @@ -95,8 +113,7 @@ pub async fn new_eip712_transaction( .await .map_err(|err| eyre!("failed retrieving gas_price {:?}", err))?; let fee: Fee = provider - .provider() - .request("zks_estimateFee", [deploy_request.clone()]) + .raw_request("zks_estimateFee".into(), [deploy_request.clone()]) .await .map_err(|err| eyre!("failed estimating fee {:?}", err))?; deploy_request = deploy_request @@ -110,9 +127,11 @@ pub async fn new_eip712_transaction( .try_into() .map_err(|err| eyre!("failed converting deploy request to eip-712 tx {:?}", err))?; - let signature = signer.sign_typed_data(&signable).await.expect("Failed to sign typed data"); + let mut signable = signable.to_signable_tx(); + let signature = + signer.sign_transaction(&mut signable).await.expect("Failed to sign typed data"); let encoded_rlp = deploy_request - .rlp_signed(signature) + .rlp_signed(signature.to_ethers()) .map_err(|err| eyre!("failed encoding deployment request {:?}", err))?; let tx = [&[EIP712_TX_TYPE], encoded_rlp.to_vec().as_slice()].concat().into(); @@ -123,48 +142,53 @@ pub async fn new_eip712_transaction( /// Estimated gas from a ZK network. pub struct EstimatedGas { /// Estimated gas price. - pub price: rU256, + pub price: u128, /// Estimated gas limit. - pub limit: rU256, + pub limit: u128, } /// Estimates the gas parameters for the provided transaction. -pub async fn estimate_gas( - legacy_or_1559: &TypedTransaction, +pub async fn estimate_gas, T: Transport + Clone>( + tx: &WithOtherFields, factory_deps: Vec>, - provider: M, + provider: P, ) -> Result { - let to = legacy_or_1559 - .to() - .and_then(|to| to.as_address()) - .cloned() + let to = tx + .to + .and_then(|to| match to { + alloy_primitives::TxKind::Create => None, + alloy_primitives::TxKind::Call(to) => Some(to), + }) .ok_or_eyre("`to` cannot be empty")?; - let chain_id = legacy_or_1559.chain_id().ok_or_eyre("`chain_id` cannot be empty")?; - let nonce = legacy_or_1559.nonce().ok_or_eyre("`nonce` cannot be empty")?; - let gas_price = legacy_or_1559.gas_price().ok_or_eyre("`gas_price` cannot be empty")?; - let data = legacy_or_1559.data().cloned().ok_or_eyre("`data` cannot be empty")?; + let chain_id = tx.chain_id.ok_or_eyre("`chain_id` cannot be empty")?; + let nonce = tx.nonce.ok_or_eyre("`nonce` cannot be empty")?; + let gas_price = if let Some(gas_price) = tx.gas_price { + gas_price + } else { + provider.get_gas_price().await? + }; + let data = tx.input.clone().into_input().unwrap_or_default(); let custom_data = Eip712Meta::new().factory_deps(factory_deps); let mut deploy_request = Eip712TransactionRequest::new() .r#type(EIP712_TX_TYPE) - .to(to) - .chain_id(chain_id.as_u64()) + .to(to.to_h160()) + .chain_id(chain_id) .nonce(nonce) .gas_price(gas_price) - .data(data) + .data(data.to_ethers()) .custom_data(custom_data); - if let Some(from) = legacy_or_1559.from() { - deploy_request = deploy_request.from(*from) + if let Some(from) = tx.from { + deploy_request = deploy_request.from(from.to_h160()) } let gas_price = provider.get_gas_price().await.unwrap(); let fee: Fee = provider - .provider() - .request("zks_estimateFee", [deploy_request.clone()]) + .raw_request("zks_estimateFee".into(), [deploy_request.clone()]) .await .map_err(|err| eyre!("failed rpc call for estimating fee: {:?}", err))?; - Ok(EstimatedGas { price: gas_price.to_ru256(), limit: fee.gas_limit.to_ru256() }) + Ok(EstimatedGas { price: gas_price, limit: fee.gas_limit.low_u128() }) } /// Returns true if the provided address is a reserved zkSync system address diff --git a/crates/zksync/core/src/vm/db.rs b/crates/zksync/core/src/vm/db.rs index 3f0784a80..a8da56688 100644 --- a/crates/zksync/core/src/vm/db.rs +++ b/crates/zksync/core/src/vm/db.rs @@ -8,7 +8,7 @@ use std::{collections::HashMap, fmt::Debug}; use alloy_primitives::{Address, U256 as rU256}; use foundry_cheatcodes_common::record::RecordAccess; -use revm::{primitives::Account, Database, JournaledState}; +use revm::{primitives::Account, Database, EvmContext}; use zksync_basic_types::{L2ChainId, H160, H256, U256}; use zksync_state::ReadStorage; use zksync_types::{ @@ -24,15 +24,19 @@ use crate::convert::{ConvertAddress, ConvertH160, ConvertH256, ConvertRU256, Con /// Default chain id pub(crate) const DEFAULT_CHAIN_ID: u32 = 31337; -pub struct ZKVMData<'a, DB> { - pub db: &'a mut DB, - pub journaled_state: &'a mut JournaledState, +pub struct ZKVMData<'a, DB: Database> { + // pub db: &'a mut DB, + // pub journaled_state: &'a mut JournaledState, + ecx: &'a mut EvmContext, pub factory_deps: HashMap>, pub override_keys: HashMap, pub accesses: Option<&'a mut RecordAccess>, } -impl<'a, DB> Debug for ZKVMData<'a, DB> { +impl<'a, DB> Debug for ZKVMData<'a, DB> +where + DB: Database, +{ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("ZKVMData") .field("db", &"db") @@ -49,41 +53,31 @@ where ::Error: Debug, { /// Create a new instance of [ZKEVMData]. - pub fn new(db: &'a mut DB, journaled_state: &'a mut JournaledState) -> Self { + pub fn new(ecx: &'a mut EvmContext) -> Self { // load all deployed contract bytecodes from the JournaledState as factory deps - let mut factory_deps = - journaled_state - .state - .values() - .flat_map(|account| { - if account.info.is_empty_code_hash() { - None - } else { - account.info.code.as_ref().map(|code| { - (H256::from(account.info.code_hash.0), code.bytecode.to_vec()) - }) - } - }) - .collect::>(); + let mut factory_deps = ecx + .journaled_state + .state + .values() + .flat_map(|account| { + if account.info.is_empty_code_hash() { + None + } else { + account.info.code.as_ref().map(|code| { + (H256::from(account.info.code_hash.0), code.bytecode().to_vec()) + }) + } + }) + .collect::>(); let empty_code = vec![0u8; 32]; let empty_code_hash = hash_bytecode(&empty_code); factory_deps.insert(empty_code_hash, empty_code); - Self { - db, - journaled_state, - factory_deps, - override_keys: Default::default(), - accesses: None, - } + Self { ecx, factory_deps, override_keys: Default::default(), accesses: None } } /// Create a new instance of [ZKEVMData] with system contracts. - pub fn new_with_system_contracts( - db: &'a mut DB, - journaled_state: &'a mut JournaledState, - chain_id: L2ChainId, - ) -> Self { + pub fn new_with_system_contracts(ecx: &'a mut EvmContext, chain_id: L2ChainId) -> Self { let contracts = era_test_node::system_contracts::get_deployed_contracts( &era_test_node::system_contracts::Options::BuiltInWithoutSecurity, ); @@ -106,7 +100,7 @@ where .into_iter() .map(|contract| (hash_bytecode(&contract.bytecode), contract.bytecode)) .collect::>(); - factory_deps.extend(journaled_state.state.values().flat_map(|account| { + factory_deps.extend(ecx.journaled_state.state.values().flat_map(|account| { if account.info.is_empty_code_hash() { None } else { @@ -114,14 +108,14 @@ where .info .code .as_ref() - .map(|code| (H256::from(account.info.code_hash.0), code.bytecode.to_vec())) + .map(|code| (H256::from(account.info.code_hash.0), code.bytecode().to_vec())) } })); let empty_code = vec![0u8; 32]; let empty_code_hash = hash_bytecode(&empty_code); factory_deps.insert(empty_code_hash, empty_code); - Self { db, journaled_state, factory_deps, override_keys, accesses: None } + Self { ecx, factory_deps, override_keys, accesses: None } } /// Extends the currently known factory deps with the provided input @@ -172,25 +166,21 @@ where /// Load an account into the journaled state. pub fn load_account(&mut self, address: Address) -> &mut Account { - let (account, _) = self - .journaled_state - .load_account(address, self.db) - .expect("account could not be loaded"); + let (account, _) = self.ecx.load_account(address).expect("account could not be loaded"); account } /// Load an storage slot into the journaled state. /// The account must be already loaded else this function panics. pub fn sload(&mut self, address: Address, key: rU256) -> rU256 { - let (value, _) = self.journaled_state.sload(address, key, self.db).unwrap_or_default(); + let (value, _) = self.ecx.sload(address, key).unwrap_or_default(); value } fn read_db(&mut self, address: H160, idx: U256) -> H256 { let addr = address.to_address(); - self.journaled_state.load_account(addr, self.db).expect("failed loading account"); - let (value, _) = - self.journaled_state.sload(addr, idx.to_ru256(), self.db).expect("failed sload"); + self.ecx.load_account(addr).expect("failed loading account"); + let (value, _) = self.ecx.sload(addr, idx.to_ru256()).expect("failed sload"); value.to_h256() } } @@ -214,17 +204,22 @@ where fn load_factory_dep(&mut self, hash: H256) -> Option> { self.factory_deps.get(&hash).cloned().or_else(|| { let hash_b256 = hash.to_b256(); - self.journaled_state + self.ecx + .journaled_state .state .values() .find_map(|account| { if account.info.code_hash == hash_b256 { - return Some(account.info.code.clone().map(|code| code.bytecode.to_vec())) + return Some(account.info.code.clone().map(|code| code.bytecode().to_vec())) } None }) .unwrap_or_else(|| { - self.db.code_by_hash(hash_b256).ok().map(|bytecode| bytecode.bytecode.to_vec()) + self.ecx + .db + .code_by_hash(hash_b256) + .ok() + .map(|bytecode| bytecode.bytecode().to_vec()) }) }) } diff --git a/crates/zksync/core/src/vm/env.rs b/crates/zksync/core/src/vm/env.rs index 394dac7a9..0eabbb1b4 100644 --- a/crates/zksync/core/src/vm/env.rs +++ b/crates/zksync/core/src/vm/env.rs @@ -36,8 +36,7 @@ pub(crate) fn create_l1_batch_env( max_virtual_blocks_to_create: 1, } }; - let (mut batch_number, mut batch_timestamp) = - if let Some(batch) = load_last_l1_batch(storage) { batch } else { (0, 0) }; + let (mut batch_number, mut batch_timestamp) = load_last_l1_batch(storage).unwrap_or_default(); batch_number += 1; diff --git a/crates/zksync/core/src/vm/inspect.rs b/crates/zksync/core/src/vm/inspect.rs index 45c90a1bc..1d5603c6a 100644 --- a/crates/zksync/core/src/vm/inspect.rs +++ b/crates/zksync/core/src/vm/inspect.rs @@ -17,12 +17,13 @@ use multivm::{ }; use once_cell::sync::OnceCell; use revm::{ + db::states::StorageSlot, primitives::{ - Address, Bytecode, Bytes, EVMResultGeneric, Env, Eval, ExecutionResult as rExecutionResult, - Halt as rHalt, HashMap as rHashMap, Log as rLog, OutOfGasError, Output, StorageSlot, B256, + Address, Bytecode, Bytes, EVMResultGeneric, ExecutionResult as rExecutionResult, + HaltReason, HashMap as rHashMap, Log as rLog, OutOfGasError, Output, SuccessReason, B256, U256 as rU256, }, - Database, JournaledState, + Database, EvmContext, }; use tracing::{debug, error, info, trace, warn}; use zksync_basic_types::{ethabi, L2ChainId, Nonce, H160, H256, U256}; @@ -67,17 +68,15 @@ pub type ZKVMResult = EVMResultGeneric; /// immediately. /// All logs will be collected as they happen, and returned with the final result. //TODO: should we make this transparent in `inspect` directly? -pub fn inspect_as_batch<'a, DB, E>( +pub fn inspect_as_batch( tx: L2Tx, - env: &'a mut Env, - db: &'a mut DB, - journaled_state: &'a mut JournaledState, + ecx: &mut EvmContext, ccx: &mut CheatcodeTracerContext, call_ctx: CallContext, ) -> ZKVMResult where DB: Database + Send, - ::Error: Debug, + ::Error: Send + Debug, { let txns = split_tx_by_factory_deps(tx); let total_txns = txns.len(); @@ -93,8 +92,8 @@ where //deducted gas used so far tx.common_data.fee.gas_limit -= gas_used; - info!("executing batched tx ({}/{})", idx, total_txns); - let mut result = inspect(tx, env, db, journaled_state, ccx, call_ctx.clone())?; + info!("executing batched tx ({}/{})", idx + 1, total_txns); + let mut result = inspect(tx, ecx, ccx, call_ctx.clone())?; match (&mut aggregated_result, result.execution_result) { (_, exec @ rExecutionResult::Revert { .. } | exec @ rExecutionResult::Halt { .. }) => { @@ -135,22 +134,20 @@ where /// Processes a [`L2Tx`] with EraVM and returns the final execution result and logs. /// /// State changes will be reflected in the given `Env`, `DB`, `JournaledState`. -pub fn inspect<'a, DB, E>( +pub fn inspect( mut tx: L2Tx, - env: &'a mut Env, - db: &'a mut DB, - journaled_state: &'a mut JournaledState, + ecx: &mut EvmContext, ccx: &mut CheatcodeTracerContext, call_ctx: CallContext, ) -> ZKVMResult where DB: Database + Send, - ::Error: Debug, + ::Error: Send + Debug, { - let chain_id = if env.cfg.chain_id <= u32::MAX as u64 { - L2ChainId::from(env.cfg.chain_id as u32) + let chain_id = if ecx.env.cfg.chain_id <= u32::MAX as u64 { + L2ChainId::from(ecx.env.cfg.chain_id as u32) } else { - warn!(provided = ?env.cfg.chain_id, using = DEFAULT_CHAIN_ID, "using default chain id as provided chain_id does not fit into u32"); + warn!(provided = ?ecx.env.cfg.chain_id, using = DEFAULT_CHAIN_ID, "using default chain id as provided chain_id does not fit into u32"); L2ChainId::from(DEFAULT_CHAIN_ID) }; @@ -160,7 +157,8 @@ where .map(|factory_deps| (*factory_deps).clone()) .unwrap_or_default(); - let mut era_db = ZKVMData::new_with_system_contracts(db, journaled_state, chain_id) + let env_tx_gas_limit = ecx.env.tx.gas_limit; + let mut era_db = ZKVMData::new_with_system_contracts(ecx, chain_id) .with_extra_factory_deps(persisted_factory_deps) .with_storage_accesses(ccx.accesses.take()); @@ -191,10 +189,12 @@ where .events .clone() .into_iter() - .map(|event| revm::primitives::Log { - address: event.address.to_address(), - topics: event.indexed_topics.iter().cloned().map(|t| B256::from(t.0)).collect(), - data: event.value.into(), + .map(|event| { + revm::primitives::Log::new_unchecked( + event.address.to_address(), + event.indexed_topics.iter().cloned().map(|t| B256::from(t.0)).collect(), + event.value.into(), + ) }) .collect_vec(); @@ -221,7 +221,7 @@ where ZKVMExecutionResult { logs: logs.clone(), execution_result: rExecutionResult::Success { - reason: Eval::Return, + reason: SuccessReason::Return, gas_used: tx_result.statistics.gas_used, gas_refunded: tx_result.refunds.gas_refunded, logs, @@ -239,7 +239,7 @@ where ZKVMExecutionResult { logs, execution_result: rExecutionResult::Revert { - gas_used: env.tx.gas_limit - tx_result.refunds.gas_refunded, + gas_used: env_tx_gas_limit - tx_result.refunds.gas_refunded, output: Bytes::from(output), }, } @@ -247,15 +247,15 @@ where ExecutionResult::Halt { reason } => { error!("tx execution halted: {}", reason); let mapped_reason = match reason { - Halt::NotEnoughGasProvided => rHalt::OutOfGas(OutOfGasError::BasicOutOfGas), - _ => rHalt::PrecompileError, + Halt::NotEnoughGasProvided => HaltReason::OutOfGas(OutOfGasError::Basic), + _ => HaltReason::PrecompileError, }; ZKVMExecutionResult { logs, execution_result: rExecutionResult::Halt { reason: mapped_reason, - gas_used: env.tx.gas_limit - tx_result.refunds.gas_refunded, + gas_used: env_tx_gas_limit - tx_result.refunds.gas_refunded, }, } } @@ -309,20 +309,18 @@ where } for (address, storage) in storage { - journaled_state.load_account(address, db).expect("account could not be loaded"); - journaled_state.touch(&address); + ecx.load_account(address).expect("account could not be loaded"); + ecx.touch(&address); for (key, value) in storage { - journaled_state - .sstore(address, key, value.present_value, db) - .expect("failed writing to slot"); + ecx.sstore(address, key, value.present_value).expect("failed writing to slot"); } } for (address, (code_hash, code)) in codes { - journaled_state.load_account(address, db).expect("account could not be loaded"); - journaled_state.touch(&address); - let account = journaled_state.state.get_mut(&address).expect("account is loaded"); + ecx.load_account(address).expect("account could not be loaded"); + ecx.touch(&address); + let account = ecx.journaled_state.state.get_mut(&address).expect("account is loaded"); account.info.code_hash = code_hash; account.info.code = Some(code); @@ -424,13 +422,11 @@ fn inspect_inner( .iter() .map(|b| bytecode_to_factory_dep(b.original.clone())) .collect(); - let modified_keys = if is_static { Default::default() } else { storage.borrow().modified_storage_keys().clone() }; - (tx_result, bytecodes, modified_keys) } @@ -533,7 +529,7 @@ pub fn batch_factory_dependencies(mut factory_deps: Vec>) -> Vec(); - tracing::info!(count=factory_deps_count, total=factory_deps_total_size, sizes=?factory_deps_sizes, max=*MAX_FACTORY_DEPENDENCIES_SIZE_BYTES, "optimizing factory_deps"); + tracing::debug!(count=factory_deps_count, total=factory_deps_total_size, sizes=?factory_deps_sizes, max=*MAX_FACTORY_DEPENDENCIES_SIZE_BYTES, "optimizing factory_deps"); let mut batches = vec![]; let mut current_batch = vec![]; @@ -563,7 +559,7 @@ pub fn batch_factory_dependencies(mut factory_deps: Vec>) -> Vec()).collect_vec(); let batch_total_size = batch_cumulative_sizes.iter().sum::(); - tracing::debug!(count=batch_count, total=batch_total_size, sizes=?batch_cumulative_sizes, batched_sizes=?batch_individual_sizes, "optimized factory_deps into batches"); + tracing::info!(count=batch_count, total=batch_total_size, sizes=?batch_cumulative_sizes, batched_sizes=?batch_individual_sizes, "optimized factory_deps into batches"); batches } diff --git a/crates/zksync/core/src/vm/runner.rs b/crates/zksync/core/src/vm/runner.rs index 124b86403..17da2ae5d 100644 --- a/crates/zksync/core/src/vm/runner.rs +++ b/crates/zksync/core/src/vm/runner.rs @@ -1,11 +1,9 @@ use foundry_zksync_compiler::DualCompiledContract; +use itertools::Itertools; use revm::{ - interpreter::{CallInputs, CallScheme, CreateInputs}, - precompile::Precompiles, - primitives::{ - Address, CreateScheme, Env, ResultAndState, SpecId, TransactTo, B256, U256 as rU256, - }, - Database, JournaledState, + interpreter::{CallInputs, CallScheme, CallValue, CreateInputs}, + primitives::{Address, CreateScheme, Env, ResultAndState, TransactTo, B256, U256 as rU256}, + Database, EvmContext, }; use tracing::{debug, error, info}; use zksync_basic_types::H256; @@ -35,27 +33,19 @@ pub fn transact<'a, DB>( ) -> eyre::Result where DB: Database + Send, - ::Error: Debug, + ::Error: Send + Debug, { - debug!(calldata = ?env.tx.data, fdeps = factory_deps.as_ref().map(|v| v.len()).unwrap_or_default(), "zk transact"); - let mut journaled_state = JournaledState::new( - env.cfg.spec_id, - Precompiles::new(to_precompile_id(env.cfg.spec_id)) - .addresses() - .into_iter() - .copied() - .collect(), - ); + info!(calldata = ?env.tx.data, fdeps = factory_deps.as_ref().map(|deps| deps.iter().map(|dep| dep.len()).join(",")).unwrap_or_default(), "zk transact"); + let mut ecx = EvmContext::new_with_env(db, Box::new(env.clone())); let caller = env.tx.caller; - let nonce = ZKVMData::new(db, &mut journaled_state).get_tx_nonce(caller); + let nonce = ZKVMData::new(&mut ecx).get_tx_nonce(caller); let (transact_to, is_create) = match env.tx.transact_to { TransactTo::Call(to) => (to.to_h160(), false), - TransactTo::Create(CreateScheme::Create) | - TransactTo::Create(CreateScheme::Create2 { .. }) => (CONTRACT_DEPLOYER_ADDRESS, true), + TransactTo::Create => (CONTRACT_DEPLOYER_ADDRESS, true), }; - let (gas_limit, max_fee_per_gas) = gas_params(env, db, &mut journaled_state, caller); + let (gas_limit, max_fee_per_gas) = gas_params(&mut ecx, caller); debug!(?gas_limit, ?max_fee_per_gas, "tx gas parameters"); let tx = L2Tx::new( transact_to, @@ -80,91 +70,70 @@ where delegate_as: None, block_number: env.block.number, block_timestamp: env.block.timestamp, + block_hashes: get_historical_block_hashes(&mut ecx), block_basefee: min(max_fee_per_gas.to_ru256(), env.block.basefee), is_create, is_static: false, }; - let mut cheatcode_tracer_context = - CheatcodeTracerContext { persisted_factory_deps, ..Default::default() }; + let mut ccx = CheatcodeTracerContext { persisted_factory_deps, ..Default::default() }; - match inspect::<_, DB::Error>( - tx, - env, - db, - &mut journaled_state, - &mut cheatcode_tracer_context, - call_ctx, - ) { + match inspect::<_, DB::Error>(tx, &mut ecx, &mut ccx, call_ctx) { Ok(ZKVMExecutionResult { execution_result: result, .. }) => { - Ok(ResultAndState { result, state: journaled_state.finalize().0 }) + Ok(ResultAndState { result, state: ecx.journaled_state.finalize().0 }) } Err(err) => eyre::bail!("zk backend: failed while inspecting: {err:?}"), } } /// Retrieves L2 ETH balance for a given address. -pub fn balance<'a, DB>( - address: Address, - db: &'a mut DB, - journaled_state: &'a mut JournaledState, -) -> rU256 +pub fn balance(address: Address, ecx: &mut EvmContext) -> rU256 where DB: Database, ::Error: Debug, { - let balance = ZKVMData::new(db, journaled_state).get_balance(address); + let balance = ZKVMData::new(ecx).get_balance(address); balance.to_ru256() } /// Retrieves bytecode hash stored at a given address. #[allow(dead_code)] -pub fn code_hash<'a, DB>( - address: Address, - db: &'a mut DB, - journaled_state: &'a mut JournaledState, -) -> B256 +pub fn code_hash(address: Address, ecx: &mut EvmContext) -> B256 where DB: Database, ::Error: Debug, { - B256::from(ZKVMData::new(db, journaled_state).get_code_hash(address).0) + B256::from(ZKVMData::new(ecx).get_code_hash(address).0) } /// Retrieves nonce for a given address. -pub fn nonce<'a, DB>( - address: Address, - db: &'a mut DB, - journaled_state: &'a mut JournaledState, -) -> u32 +pub fn nonce(address: Address, ecx: &mut EvmContext) -> u32 where DB: Database, ::Error: Debug, { - ZKVMData::new(db, journaled_state).get_tx_nonce(address).0 + ZKVMData::new(ecx).get_tx_nonce(address).0 } /// Executes a CREATE opcode on the ZK-VM. -pub fn create<'a, DB, E>( +pub fn create( call: &CreateInputs, contract: &DualCompiledContract, factory_deps: Vec>, - env: &'a mut Env, - db: &'a mut DB, - journaled_state: &'a mut JournaledState, + ecx: &mut EvmContext, mut ccx: CheatcodeTracerContext, ) -> ZKVMResult where DB: Database + Send, - ::Error: Debug, + ::Error: Send + Debug, { info!(?call, "create tx {}", hex::encode(&call.init_code)); let constructor_input = call.init_code[contract.evm_bytecode.len()..].to_vec(); - let caller = env.tx.caller; + let caller = ecx.env.tx.caller; let calldata = encode_create_params(&call.scheme, contract.zk_bytecode_hash, constructor_input); - let nonce = ZKVMData::new(db, journaled_state).get_tx_nonce(caller); + let nonce = ZKVMData::new(ecx).get_tx_nonce(caller); - let (gas_limit, max_fee_per_gas) = gas_params(env, db, journaled_state, caller); + let (gas_limit, max_fee_per_gas) = gas_params(ecx, caller); info!(?gas_limit, ?max_fee_per_gas, "tx gas parameters"); let tx = L2Tx::new( @@ -174,7 +143,7 @@ where Fee { gas_limit, max_fee_per_gas, - max_priority_fee_per_gas: env.tx.gas_priority_fee.unwrap_or_default().to_u256(), + max_priority_fee_per_gas: ecx.env.tx.gas_priority_fee.unwrap_or_default().to_u256(), gas_per_pubdata_limit: U256::from(20000), }, caller.to_h160(), @@ -184,50 +153,52 @@ where ); let call_ctx = CallContext { - tx_caller: env.tx.caller, + tx_caller: ecx.env.tx.caller, msg_sender: call.caller, contract: CONTRACT_DEPLOYER_ADDRESS.to_address(), delegate_as: None, - block_number: env.block.number, - block_timestamp: env.block.timestamp, - block_basefee: min(max_fee_per_gas.to_ru256(), env.block.basefee), + block_number: ecx.env.block.number, + block_timestamp: ecx.env.block.timestamp, + block_basefee: min(max_fee_per_gas.to_ru256(), ecx.env.block.basefee), + block_hashes: get_historical_block_hashes(ecx), is_create: true, is_static: false, }; - inspect_as_batch(tx, env, db, journaled_state, &mut ccx, call_ctx) + inspect_as_batch(tx, ecx, &mut ccx, call_ctx) } /// Executes a CALL opcode on the ZK-VM. -pub fn call<'a, DB, E>( +pub fn call( call: &CallInputs, - env: &'a mut Env, - db: &'a mut DB, - journaled_state: &'a mut JournaledState, + ecx: &mut EvmContext, mut ccx: CheatcodeTracerContext, ) -> ZKVMResult where DB: Database + Send, - ::Error: Debug, + ::Error: Send + Debug, { info!(?call, "call tx {}", hex::encode(&call.input)); - let caller = env.tx.caller; - let nonce: zksync_types::Nonce = ZKVMData::new(db, journaled_state).get_tx_nonce(caller); + let caller = ecx.env.tx.caller; + let nonce: zksync_types::Nonce = ZKVMData::new(ecx).get_tx_nonce(caller); - let (gas_limit, max_fee_per_gas) = gas_params(env, db, journaled_state, caller); + let (gas_limit, max_fee_per_gas) = gas_params(ecx, caller); info!(?gas_limit, ?max_fee_per_gas, "tx gas parameters"); let tx = L2Tx::new( - call.contract.to_h160(), + call.bytecode_address.to_h160(), call.input.to_vec(), nonce, Fee { gas_limit, max_fee_per_gas, - max_priority_fee_per_gas: env.tx.gas_priority_fee.unwrap_or_default().to_u256(), + max_priority_fee_per_gas: ecx.env.tx.gas_priority_fee.unwrap_or_default().to_u256(), gas_per_pubdata_limit: U256::from(20000), }, caller.to_h160(), - call.transfer.value.to_u256(), + match call.value { + CallValue::Transfer(value) => value.to_u256(), + _ => U256::zero(), + }, None, PaymasterParams::default(), ); @@ -237,41 +208,37 @@ where // CallCode => { address: contract.address, caller: contract.address } // DelegateCall => { address: contract.address, caller: contract.caller } let call_ctx = CallContext { - tx_caller: env.tx.caller, - msg_sender: call.context.caller, - contract: call.contract, - delegate_as: match call.context.scheme { - CallScheme::DelegateCall => Some(call.context.address), + tx_caller: ecx.env.tx.caller, + msg_sender: call.caller, + contract: call.bytecode_address, + delegate_as: match call.scheme { + CallScheme::DelegateCall => Some(call.target_address), _ => None, }, - block_number: env.block.number, - block_timestamp: env.block.timestamp, - block_basefee: min(max_fee_per_gas.to_ru256(), env.block.basefee), + block_number: ecx.env.block.number, + block_timestamp: ecx.env.block.timestamp, + block_hashes: get_historical_block_hashes(ecx), + block_basefee: min(max_fee_per_gas.to_ru256(), ecx.env.block.basefee), is_create: false, is_static: call.is_static, }; - inspect(tx, env, db, journaled_state, &mut ccx, call_ctx) + inspect(tx, ecx, &mut ccx, call_ctx) } /// Assign gas parameters that satisfy zkSync's fee model. -fn gas_params<'a, DB>( - env: &'a mut Env, - db: &'a mut DB, - journaled_state: &'a mut JournaledState, - caller: Address, -) -> (U256, U256) +fn gas_params(ecx: &mut EvmContext, caller: Address) -> (U256, U256) where DB: Database + Send, ::Error: Debug, { - let value = env.tx.value.to_u256(); - let balance = ZKVMData::new(db, journaled_state).get_balance(caller); + let value = ecx.env.tx.value.to_u256(); + let balance = ZKVMData::new(ecx).get_balance(caller); if balance.is_zero() { error!("balance is 0 for {caller:?}, transaction will fail"); } - let max_fee_per_gas = fix_l2_gas_price(env.tx.gas_price.to_u256()); - let gas_limit = fix_l2_gas_limit(env.tx.gas_limit.into(), max_fee_per_gas, value, balance); + let max_fee_per_gas = fix_l2_gas_price(ecx.env.tx.gas_price.to_u256()); + let gas_limit = fix_l2_gas_limit(ecx.env.tx.gas_limit.into(), max_fee_per_gas, value, balance); (gas_limit, max_fee_per_gas) } @@ -307,28 +274,21 @@ pub fn encode_create_params( signature.iter().copied().chain(params).collect() } -fn to_precompile_id(spec_id: SpecId) -> revm::precompile::SpecId { - match spec_id { - SpecId::FRONTIER | - SpecId::FRONTIER_THAWING | - SpecId::HOMESTEAD | - SpecId::DAO_FORK | - SpecId::TANGERINE | - SpecId::SPURIOUS_DRAGON => revm::precompile::SpecId::HOMESTEAD, - SpecId::BYZANTIUM | SpecId::CONSTANTINOPLE | SpecId::PETERSBURG => { - revm::precompile::SpecId::BYZANTIUM +/// Get last 256 block hashes mapped to block numbers. This excludes the current block. +fn get_historical_block_hashes(ecx: &mut EvmContext) -> HashMap { + let mut block_hashes = HashMap::default(); + for i in 1..=256u32 { + let (block_number, overflow) = ecx.env.block.number.overflowing_sub(rU256::from(i)); + if overflow { + break + } + match ecx.block_hash(block_number) { + Ok(block_hash) => { + block_hashes.insert(block_number, block_hash); + } + Err(_) => break, } - SpecId::ISTANBUL | SpecId::MUIR_GLACIER => revm::precompile::SpecId::ISTANBUL, - SpecId::BERLIN | - SpecId::LONDON | - SpecId::ARROW_GLACIER | - SpecId::GRAY_GLACIER | - SpecId::MERGE | - SpecId::SHANGHAI | - SpecId::CANCUN | - SpecId::BEDROCK | - SpecId::REGOLITH | - SpecId::CANYON | - SpecId::LATEST => revm::precompile::SpecId::BERLIN, } + + block_hashes } diff --git a/crates/zksync/core/src/vm/tracer.rs b/crates/zksync/core/src/vm/tracer.rs index 8dbf12280..2004c2898 100644 --- a/crates/zksync/core/src/vm/tracer.rs +++ b/crates/zksync/core/src/vm/tracer.rs @@ -109,6 +109,9 @@ pub struct CallContext { pub is_create: bool, /// Whether the current call is a static call. pub is_static: bool, + /// L1 block hashes to return when `BLOCKHASH` opcode is encountered. This ensures consistency + /// when returning environment data in L2. + pub block_hashes: HashMap>, } /// A tracer to allow for foundry-specific functionality. @@ -302,8 +305,14 @@ impl DynTracer> for CheatcodeTracer if current.code_address == SYSTEM_CONTEXT_ADDRESS && calldata.starts_with(&SELECTOR_BLOCK_HASH) { - self.farcall_handler.set_immediate_return(rU256::ZERO.to_be_bytes_vec()); - return + let block_number = U256::from(&calldata[4..36]); + let block_hash = self + .call_context + .block_hashes + .get(&block_number.to_ru256()) + .unwrap_or_default(); + self.farcall_handler.set_immediate_return(block_hash.to_vec()); + return; } } diff --git a/deny.toml b/deny.toml index e3ae679d0..5589f4c5d 100644 --- a/deny.toml +++ b/deny.toml @@ -1,7 +1,3 @@ -# Temporarily exclude rusoto and ethers-providers from bans since we've yet to transition to the -# Rust AWS SDK. -exclude = ["rusoto_core", "rusoto_kms", "rusoto_credential", "ethers-providers", "tungstenite", "shlex"] - # This section is considered when running `cargo deny check advisories` # More documentation for the advisories section can be found here: # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html @@ -10,7 +6,11 @@ vulnerability = "deny" unmaintained = "warn" yanked = "warn" notice = "warn" -ignore = [] +ignore = [ + # https://github.com/watchexec/watchexec/issues/852 + "RUSTSEC-2024-0350", + "RUSTSEC-2024-0351", +] # This section is considered when running `cargo deny check bans`. # More documentation about the 'bans' section can be found here: @@ -22,7 +22,6 @@ multiple-versions = "warn" wildcards = "allow" highlight = "all" # List of crates to deny -deny = [{ name = "openssl" }] # Certain crates/versions that will be skipped when doing duplicate detection. skip = [] # Similarly to `skip` allows you to skip certain crates during duplicate @@ -32,23 +31,29 @@ skip = [] skip-tree = [] [licenses] -unlicensed = "deny" +version = 2 +confidence-threshold = 0.8 + # List of explicitly allowed licenses # See https://spdx.org/licenses/ for list of possible licenses # [possible values: any SPDX 3.7 short identifier (+ optional exception)]. allow = [ "MIT", "Apache-2.0", - "Apache-2.0 WITH LLVM-exception", - "BSD-2-Clause", - "BSD-3-Clause", "ISC", + "Unlicense", + "MPL-2.0", "Unicode-DFS-2016", + "CC0-1.0", + "BSD-2-Clause", + "BSD-3-Clause", + "Zlib", "OpenSSL", - "Unlicense", - "WTFPL", + "Apache-2.0 WITH LLVM-exception", "BSL-1.0", "0BSD", + "WTFPL", + "Unicode-3.0", ] # Allow 1 or more licenses on a per-crate basis, so that particular licenses @@ -57,16 +62,13 @@ exceptions = [ # CC0 is a permissive license but somewhat unclear status for source code # so we prefer to not have dependencies using it # https://tldrlegal.com/license/creative-commons-cc0-1.0-universal - { allow = ["CC0-1.0"], name = "secp256k1" }, - { allow = ["CC0-1.0"], name = "secp256k1-sys" }, { allow = ["CC0-1.0"], name = "tiny-keccak" }, { allow = ["CC0-1.0"], name = "to_method" }, - { allow = ["CC0-1.0"], name = "more-asserts" }, { allow = ["CC0-1.0"], name = "trezor-client" }, { allow = ["CC0-1.0"], name = "notify" }, - { allow = ["CC0-1.0"], name = "constant_time_eq" }, { allow = ["CC0-1.0"], name = "dunce" }, { allow = ["CC0-1.0"], name = "aurora-engine-modexp" }, + { allow = ["CC0-1.0"], name = "constant_time_eq" }, ] #copyleft = "deny" @@ -76,6 +78,7 @@ name = "unicode-ident" version = "*" expression = "(MIT OR Apache-2.0) AND Unicode-DFS-2016" license-files = [{ path = "LICENSE-UNICODE", hash = 0x3fb01745 }] + [[licenses.clarify]] name = "ring" version = "*" @@ -91,4 +94,16 @@ license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] unknown-registry = "warn" # Lint level for what to happen when a crate from a git repository that is not # in the allow list is encountered -unknown-git = "allow" +unknown-git = "deny" +allow-git = [ + "https://github.com/alloy-rs/alloy", + "https://github.com/paradigmxyz/revm-inspectors", + "https://github.com/bluealloy/revm", + "https://github.com/lambdaclass/zksync-web3-rs", + "https://github.com/Moonsong-Labs/compilers", + "https://github.com/Moonsong-Labs/block-explorers", + "https://github.com/RustCrypto/hashes", +] + +[sources.allow-org] +github = ["matter-labs"] diff --git a/docs/dev/debugging.md b/docs/dev/debugging.md index 9431d7c33..df8664440 100644 --- a/docs/dev/debugging.md +++ b/docs/dev/debugging.md @@ -20,4 +20,5 @@ Filters are explained in detail in the [`env_logger` crate docs](https://docs.rs ### Compiler input and output -You can get the compiler input JSON and output JSON from `ethers-solc` by passing the `--build-info` flag. This will create two files: one for the input and one for the output. +You can get the compiler input JSON and output JSON by passing the `--build-info` flag. +This will create two files: one for the input and one for the output. diff --git a/flake.lock b/flake.lock index a03305813..9ad80af8b 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1694529238, - "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", "type": "github" }, "original": { @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1700538105, - "narHash": "sha256-uZhOCmwv8VupEmPZm3erbr9XXmyg7K67Ul3+Rx2XMe0=", + "lastModified": 1711655175, + "narHash": "sha256-1xiaYhC3ul4y+i3eicYxeERk8ZkrNjLkrFSb/UW36Zw=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "51a01a7e5515b469886c120e38db325c96694c2f", + "rev": "64c81edb4b97a51c5bbc54c191763ac71a6517ee", "type": "github" }, "original": { @@ -52,11 +52,11 @@ ] }, "locked": { - "lastModified": 1700705722, - "narHash": "sha256-cFfTFToYTeRQtdNqo53+E+G5RxPiTbWusGq+MpZSpbA=", + "lastModified": 1711678273, + "narHash": "sha256-7lIB0hMRnfzx/9oSIwTnwXmVnbvVGRoadOCW+1HI5zY=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "67998ae1cabcf683cb115c5ab01ae4ff067e3d60", + "rev": "42a168449605950935f15ea546f6f770e5f7f629", "type": "github" }, "original": { @@ -75,11 +75,11 @@ ] }, "locked": { - "lastModified": 1700417764, - "narHash": "sha256-ssdwqKWkYUd/Nr6P9veR4D/PrtlwGJkPoUQoEgVJVpo=", + "lastModified": 1711538161, + "narHash": "sha256-rETVdEIQ2PyEcNgzXXFSiYAYl0koCeGDIWp9XYBTxoQ=", "owner": "hellwolf", "repo": "solc.nix", - "rev": "80d2e38e98e589872b0dc3770f838c4be847305e", + "rev": "a995838545a7383a0b37776e969743b1346d5479", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 5abae8e64..46ddb920c 100644 --- a/flake.nix +++ b/flake.nix @@ -27,9 +27,9 @@ overlays = [ rust-overlay.overlays.default solc.overlay ]; }; lib = pkgs.lib; - toolchain = pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default.override { + toolchain = pkgs.rust-bin.stable.latest.default.override { extensions = [ "rustfmt" "clippy" "rust-src" ]; - }); + }; in { devShells.default = pkgs.mkShell { diff --git a/foundryup-zksync/foundryup-zksync b/foundryup-zksync/foundryup-zksync index 4c5c83f53..b24afa6f2 100755 --- a/foundryup-zksync/foundryup-zksync +++ b/foundryup-zksync/foundryup-zksync @@ -6,9 +6,11 @@ FOUNDRY_DIR=${FOUNDRY_DIR:-"$BASE_DIR/.foundry"} FOUNDRY_BIN_DIR="$FOUNDRY_DIR/bin" FOUNDRY_MAN_DIR="$FOUNDRY_DIR/share/man/man1" +FOUNDRYUP_JOBS="" + BINS=(forge cast) -export RUSTFLAGS="-C target-cpu=native" +export RUSTFLAGS="${RUSTFLAGS:--C target-cpu=native}" main() { need_cmd git @@ -24,6 +26,7 @@ main() { -p|--path) shift; FOUNDRYUP_LOCAL_REPO=$1;; -P|--pr) shift; FOUNDRYUP_PR=$1;; -C|--commit) shift; FOUNDRYUP_COMMIT=$1;; + -j|--jobs) shift; FOUNDRYUP_JOBS=$1;; --arch) shift; FOUNDRYUP_ARCH=$1;; --platform) shift; FOUNDRYUP_PLATFORM=$1;; -h|--help) @@ -37,6 +40,12 @@ main() { esac; shift done + CARGO_BUILD_ARGS=(--release) + + if [ -n "$FOUNDRYUP_JOBS" ]; then + CARGO_BUILD_ARGS+=(--jobs "$FOUNDRYUP_JOBS") + fi + # Print the banner after successfully parsing args banner @@ -60,7 +69,7 @@ main() { # Enter local repo and build say "installing from $FOUNDRYUP_LOCAL_REPO" cd "$FOUNDRYUP_LOCAL_REPO" - ensure cargo build --bin forge --bin cast --release # need 4 speed + ensure cargo build --bins "${CARGO_BUILD_ARGS[@]}" for bin in "${BINS[@]}"; do # Remove prior installations if they exist @@ -197,7 +206,7 @@ EOF fi # Build the repo and install the binaries locally to the .foundry bin directory. - ensure cargo build --bin forge --bin cast --release + ensure cargo build --bins "${CARGO_BUILD_ARGS[@]}" for bin in "${BINS[@]}"; do for try_path in target/release/$bin target/release/$bin.exe; do if [ -f "$try_path" ]; then @@ -224,17 +233,20 @@ The installer for Foundry-zksync. Update or revert to a specific Foundry-zksync version with ease. +By default, the latest nightly version is installed from built binaries. + USAGE: foundryup-zksync OPTIONS: -h, --help Print help information - -v, --version Install a specific version - -b, --branch Install a specific branch - -P, --pr Install a specific Pull Request - -C, --commit Install a specific commit - -r, --repo Install from a remote GitHub repo (uses default branch if no other options are set) - -p, --path Install a local repository + -v, --version Install a specific version from built binaries + -b, --branch Build and install a specific branch + -P, --pr Build and install a specific Pull Request + -C, --commit Build and install a specific commit + -r, --repo Build and install from a remote GitHub repo (uses default branch if no other options are set) + -p, --path Build and install a local repository + -j, --jobs Number of CPUs to use for building Foundry (default: all CPUs) --arch Install a specific architecture (supports amd64 and arm64) --platform Install a specific platform (supports win32, linux, and darwin) EOF @@ -292,14 +304,14 @@ download() { fi } -# Banner Function for Foundry +# Banner Function for Foundry banner() { printf ' .xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx - + ╔═╗ ╔═╗ ╦ ╦ ╔╗╔ ╔╦╗ ╦═╗ ╦ ╦ Portable and modular toolkit - ╠╣ ║ ║ ║ ║ ║║║ ║║ ╠╦╝ ╚╦╝ for Ethereum Application Development + ╠╣ ║ ║ ║ ║ ║║║ ║║ ╠╦╝ ╚╦╝ for Ethereum Application Development ╚ ╚═╝ ╚═╝ ╝╚╝ ═╩╝ ╩╚═ ╩ written in Rust. .xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx.xOx diff --git a/foundryup-zksync/install b/foundryup-zksync/install index 8ae7c6528..5966b26f8 100755 --- a/foundryup-zksync/install +++ b/foundryup-zksync/install @@ -44,7 +44,13 @@ esac # Only add foundryup-zksync if it isn't already in PATH. if [[ ":$PATH:" != *":${FOUNDRY_BIN_DIR}:"* ]]; then - echo >> "$PROFILE" && echo "export PATH=\"\$PATH:$FOUNDRY_BIN_DIR\"" >> "$PROFILE" + # Add the foundryup directory to the path and ensure the old PATH variables remain. + # If the shell is fish, echo fish_add_path instead of export. + if [[ "$PREF_SHELL" == "fish" ]]; then + echo >> "$PROFILE" && echo "fish_add_path -a $FOUNDRY_BIN_DIR" >> "$PROFILE" + else + echo >> "$PROFILE" && echo "export PATH=\"\$PATH:$FOUNDRY_BIN_DIR\"" >> "$PROFILE" + fi fi # Warn MacOS users that they may need to manually install libusb via Homebrew: diff --git a/rust-toolchain b/rust-toolchain index 6ef646989..4c3d4becb 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2024-02-06" +channel = "nightly-2024-04-28" components = [ "rustfmt", "clippy" ] \ No newline at end of file diff --git a/testdata/cancun/cheats/BlobBaseFee.t.sol b/testdata/cancun/cheats/BlobBaseFee.t.sol new file mode 100644 index 000000000..54fbc8f7f --- /dev/null +++ b/testdata/cancun/cheats/BlobBaseFee.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.25; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract BlobBaseFeeTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function test_blob_base_fee() public { + vm.blobBaseFee(6969); + assertEq(vm.getBlobBaseFee(), 6969); + } +} diff --git a/testdata/cancun/cheats/Blobhashes.t.sol b/testdata/cancun/cheats/Blobhashes.t.sol new file mode 100644 index 000000000..4a589b45a --- /dev/null +++ b/testdata/cancun/cheats/Blobhashes.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.25; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract BlobhashesTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testSetAndGetBlobhashes() public { + bytes32[] memory blobhashes = new bytes32[](2); + blobhashes[0] = bytes32(0x0000000000000000000000000000000000000000000000000000000000000001); + blobhashes[1] = bytes32(0x0000000000000000000000000000000000000000000000000000000000000002); + vm.blobhashes(blobhashes); + + bytes32[] memory gotBlobhashes = vm.getBlobhashes(); + assertEq(gotBlobhashes[0], blobhashes[0]); + assertEq(gotBlobhashes[1], blobhashes[1]); + } +} diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 39f44f574..edf40f227 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -26,6 +26,17 @@ interface Vm { Extcodehash, Extcodecopy } + enum ForgeContext { + TestGroup, + Test, + Coverage, + Snapshot, + ScriptGroup, + ScriptDryRun, + ScriptBroadcast, + ScriptResume, + Unknown + } struct Log { bytes32[] topics; @@ -111,6 +122,14 @@ interface Vm { bool reverted; } + struct Gas { + uint64 gasLimit; + uint64 gasTotalUsed; + uint64 gasMemoryUsed; + int64 gasRefunded; + uint64 gasRemaining; + } + function _expectCheatcodeRevert() external; function _expectCheatcodeRevert(bytes4 revertData) external; function _expectCheatcodeRevert(bytes calldata revertData) external; @@ -267,6 +286,8 @@ interface Vm { function assertTrue(bool condition) external pure; function assertTrue(bool condition, string calldata error) external pure; function assume(bool condition) external pure; + function blobBaseFee(uint256 newBlobBaseFee) external; + function blobhashes(bytes32[] calldata hashes) external; function breakpoint(string calldata char) external; function breakpoint(string calldata char, bool value) external; function broadcast() external; @@ -312,6 +333,7 @@ interface Vm { returns (uint256 privateKey); function difficulty(uint256 newDifficulty) external; function dumpState(string calldata pathToStateJson) external; + function ensNamehash(string calldata name) external pure returns (bytes32); function envAddress(string calldata name) external view returns (address value); function envAddress(string calldata name, string calldata delim) external view returns (address[] memory value); function envBool(string calldata name) external view returns (bool value); @@ -320,6 +342,7 @@ interface Vm { function envBytes32(string calldata name, string calldata delim) external view returns (bytes32[] memory value); function envBytes(string calldata name) external view returns (bytes memory value); function envBytes(string calldata name, string calldata delim) external view returns (bytes[] memory value); + function envExists(string calldata name) external view returns (bool result); function envInt(string calldata name) external view returns (int256 value); function envInt(string calldata name, string calldata delim) external view returns (int256[] memory value); function envOr(string calldata name, bool defaultValue) external view returns (bool value); @@ -388,6 +411,8 @@ interface Vm { function fee(uint256 newBasefee) external; function ffi(string[] calldata commandInput) external returns (bytes memory result); function fsMetadata(string calldata path) external view returns (FsMetadata memory metadata); + function getBlobBaseFee() external view returns (uint256 blobBaseFee); + function getBlobhashes() external view returns (bytes32[] memory hashes); function getBlockNumber() external view returns (uint256 height); function getBlockTimestamp() external view returns (uint256 timestamp); function getCode(string calldata artifactPath) external view returns (bytes memory creationBytecode); @@ -401,11 +426,16 @@ interface Vm { function getNonce(address account) external view returns (uint64 nonce); function getNonce(Wallet calldata wallet) external returns (uint64 nonce); function getRecordedLogs() external returns (Log[] memory logs); + function indexOf(string calldata input, string calldata key) external pure returns (uint256); + function isContext(ForgeContext context) external view returns (bool result); function isDir(string calldata path) external returns (bool result); function isFile(string calldata path) external returns (bool result); function isPersistent(address account) external view returns (bool persistent); function keyExists(string calldata json, string calldata key) external view returns (bool); + function keyExistsJson(string calldata json, string calldata key) external view returns (bool); + function keyExistsToml(string calldata toml, string calldata key) external view returns (bool); function label(address account, string calldata newLabel) external; + function lastCallGas() external view returns (Gas memory gas); function load(address target, bytes32 slot) external view returns (bytes32 data); function loadAllocs(string calldata pathToAllocsJson) external; function makePersistent(address account) external; @@ -445,12 +475,44 @@ interface Vm { function parseJsonUintArray(string calldata json, string calldata key) external pure returns (uint256[] memory); function parseJson(string calldata json) external pure returns (bytes memory abiEncodedData); function parseJson(string calldata json, string calldata key) external pure returns (bytes memory abiEncodedData); + function parseTomlAddress(string calldata toml, string calldata key) external pure returns (address); + function parseTomlAddressArray(string calldata toml, string calldata key) + external + pure + returns (address[] memory); + function parseTomlBool(string calldata toml, string calldata key) external pure returns (bool); + function parseTomlBoolArray(string calldata toml, string calldata key) external pure returns (bool[] memory); + function parseTomlBytes(string calldata toml, string calldata key) external pure returns (bytes memory); + function parseTomlBytes32(string calldata toml, string calldata key) external pure returns (bytes32); + function parseTomlBytes32Array(string calldata toml, string calldata key) + external + pure + returns (bytes32[] memory); + function parseTomlBytesArray(string calldata toml, string calldata key) external pure returns (bytes[] memory); + function parseTomlInt(string calldata toml, string calldata key) external pure returns (int256); + function parseTomlIntArray(string calldata toml, string calldata key) external pure returns (int256[] memory); + function parseTomlKeys(string calldata toml, string calldata key) external pure returns (string[] memory keys); + function parseTomlString(string calldata toml, string calldata key) external pure returns (string memory); + function parseTomlStringArray(string calldata toml, string calldata key) external pure returns (string[] memory); + function parseTomlUint(string calldata toml, string calldata key) external pure returns (uint256); + function parseTomlUintArray(string calldata toml, string calldata key) external pure returns (uint256[] memory); + function parseToml(string calldata toml) external pure returns (bytes memory abiEncodedData); + function parseToml(string calldata toml, string calldata key) external pure returns (bytes memory abiEncodedData); function parseUint(string calldata stringifiedValue) external pure returns (uint256 parsedValue); function pauseGasMetering() external; function prank(address msgSender) external; function prank(address msgSender, address txOrigin) external; function prevrandao(bytes32 newPrevrandao) external; + function prevrandao(uint256 newPrevrandao) external; function projectRoot() external view returns (string memory path); + function prompt(string calldata promptText) external returns (string memory input); + function promptAddress(string calldata promptText) external returns (address); + function promptSecret(string calldata promptText) external returns (string memory input); + function promptSecretUint(string calldata promptText) external returns (uint256); + function promptUint(string calldata promptText) external returns (uint256); + function randomAddress() external returns (address); + function randomUint() external returns (uint256); + function randomUint(uint256 min, uint256 max) external returns (uint256); function readCallers() external returns (CallerMode callerMode, address msgSender, address txOrigin); function readDir(string calldata path) external view returns (DirEntry[] memory entries); function readDir(string calldata path, uint64 maxDepth) external view returns (DirEntry[] memory entries); @@ -524,6 +586,9 @@ interface Vm { function serializeString(string calldata objectKey, string calldata valueKey, string[] calldata values) external returns (string memory json); + function serializeUintToHex(string calldata objectKey, string calldata valueKey, uint256 value) + external + returns (string memory json); function serializeUint(string calldata objectKey, string calldata valueKey, uint256 value) external returns (string memory json); @@ -535,6 +600,8 @@ interface Vm { function setNonceUnsafe(address account, uint64 newNonce) external; function signP256(uint256 privateKey, bytes32 digest) external pure returns (bytes32 r, bytes32 s); function sign(uint256 privateKey, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); + function sign(bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); + function sign(address signer, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); function sign(Wallet calldata wallet, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s); function skip(bool skipTest) external; function sleep(uint256 duration) external; @@ -577,6 +644,8 @@ interface Vm { function writeJson(string calldata json, string calldata path) external; function writeJson(string calldata json, string calldata path, string calldata valueKey) external; function writeLine(string calldata path, string calldata data) external; + function writeToml(string calldata json, string calldata path) external; + function writeToml(string calldata json, string calldata path, string calldata valueKey) external; function zkRegisterContract( string calldata name, bytes32 evmBytecodeHash, diff --git a/testdata/cheats/Addr.t.sol b/testdata/default/cheats/Addr.t.sol similarity index 95% rename from testdata/cheats/Addr.t.sol rename to testdata/default/cheats/Addr.t.sol index 39f14f6aa..432c52e69 100644 --- a/testdata/cheats/Addr.t.sol +++ b/testdata/default/cheats/Addr.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract AddrTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/Assert.t.sol b/testdata/default/cheats/Assert.t.sol similarity index 99% rename from testdata/cheats/Assert.t.sol rename to testdata/default/cheats/Assert.t.sol index 10015a850..b33af6292 100644 --- a/testdata/cheats/Assert.t.sol +++ b/testdata/default/cheats/Assert.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract AssertionsTest is DSTest { string constant errorMessage = "User provided message"; @@ -823,5 +823,7 @@ contract AssertionsTest is DSTest { bytes("assertion failed: 1 !~= 0 (max delta: 0.0000000000000000%, real delta: undefined)") ); vm.assertApproxEqRel(uint256(1), uint256(0), uint256(0)); + + vm.assertApproxEqRel(uint256(0), uint256(0), uint256(0)); } } diff --git a/testdata/cheats/Assume.t.sol b/testdata/default/cheats/Assume.t.sol similarity index 92% rename from testdata/cheats/Assume.t.sol rename to testdata/default/cheats/Assume.t.sol index 7520cfd6d..de11d6644 100644 --- a/testdata/cheats/Assume.t.sol +++ b/testdata/default/cheats/Assume.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract AssumeTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/Bank.t.sol b/testdata/default/cheats/Bank.t.sol similarity index 95% rename from testdata/cheats/Bank.t.sol rename to testdata/default/cheats/Bank.t.sol index 31feed498..a02fe1667 100644 --- a/testdata/cheats/Bank.t.sol +++ b/testdata/default/cheats/Bank.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract CoinbaseTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/Base64.t.sol b/testdata/default/cheats/Base64.t.sol similarity index 96% rename from testdata/cheats/Base64.t.sol rename to testdata/default/cheats/Base64.t.sol index 88b792cb2..0d2249395 100644 --- a/testdata/cheats/Base64.t.sol +++ b/testdata/default/cheats/Base64.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; import "../logs/console.sol"; contract Base64Test is DSTest { diff --git a/testdata/cheats/Broadcast.t.sol b/testdata/default/cheats/Broadcast.t.sol similarity index 91% rename from testdata/cheats/Broadcast.t.sol rename to testdata/default/cheats/Broadcast.t.sol index d542c28c0..6a099dc6e 100644 --- a/testdata/cheats/Broadcast.t.sol +++ b/testdata/default/cheats/Broadcast.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; library F { function t2() public pure returns (uint256) { @@ -528,3 +528,45 @@ contract ScriptAdditionalContracts is DSTest { new Parent(); } } + +contract SignatureTester { + address public immutable owner; + + constructor() { + owner = msg.sender; + } + + function verifySignature(bytes32 digest, uint8 v, bytes32 r, bytes32 s) public view returns (bool) { + require(ecrecover(digest, v, r, s) == owner, "Invalid signature"); + } +} + +contract ScriptSign is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + bytes32 digest = keccak256("something"); + + function run() external { + vm.startBroadcast(); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(digest); + + vm._expectCheatcodeRevert( + bytes(string.concat("signer with address ", vm.toString(address(this)), " is not available")) + ); + vm.sign(address(this), digest); + + SignatureTester tester = new SignatureTester(); + (, address caller,) = vm.readCallers(); + assertEq(tester.owner(), caller); + tester.verifySignature(digest, v, r, s); + } + + function run(address sender) external { + vm._expectCheatcodeRevert(bytes("could not determine signer")); + vm.sign(digest); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(sender, digest); + address actual = ecrecover(digest, v, r, s); + + assertEq(actual, sender); + } +} diff --git a/testdata/cheats/ChainId.t.sol b/testdata/default/cheats/ChainId.t.sol similarity index 93% rename from testdata/cheats/ChainId.t.sol rename to testdata/default/cheats/ChainId.t.sol index af5312241..aa8fa0a13 100644 --- a/testdata/cheats/ChainId.t.sol +++ b/testdata/default/cheats/ChainId.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract DealTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/Cool.t.sol b/testdata/default/cheats/Cool.t.sol similarity index 95% rename from testdata/cheats/Cool.t.sol rename to testdata/default/cheats/Cool.t.sol index d721a442d..82212f1b1 100644 --- a/testdata/cheats/Cool.t.sol +++ b/testdata/default/cheats/Cool.t.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity 0.8.18; -import "../lib/ds-test/src/test.sol"; -import "./Vm.sol"; +import "lib/ds-test/src/test.sol"; +import "cheats/Vm.sol"; contract CoolTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/Deal.t.sol b/testdata/default/cheats/Deal.t.sol similarity index 96% rename from testdata/cheats/Deal.t.sol rename to testdata/default/cheats/Deal.t.sol index 2729ac73e..ac4776435 100644 --- a/testdata/cheats/Deal.t.sol +++ b/testdata/default/cheats/Deal.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract DealTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/Derive.t.sol b/testdata/default/cheats/Derive.t.sol similarity index 96% rename from testdata/cheats/Derive.t.sol rename to testdata/default/cheats/Derive.t.sol index e2107e80c..fb1443333 100644 --- a/testdata/cheats/Derive.t.sol +++ b/testdata/default/cheats/Derive.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract DeriveTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/default/cheats/EnsNamehash.t.sol b/testdata/default/cheats/EnsNamehash.t.sol new file mode 100644 index 000000000..2d66beea4 --- /dev/null +++ b/testdata/default/cheats/EnsNamehash.t.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract EnsNamehashTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testEnsNamehash() public { + assertEq(vm.ensNamehash(""), 0x0000000000000000000000000000000000000000000000000000000000000000); + assertEq(vm.ensNamehash("eth"), 0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae); + assertEq(vm.ensNamehash("foo.eth"), 0xde9b09fd7c5f901e23a3f19fecc54828e9c848539801e86591bd9801b019f84f); + } +} diff --git a/testdata/cheats/Env.t.sol b/testdata/default/cheats/Env.t.sol similarity index 99% rename from testdata/cheats/Env.t.sol rename to testdata/default/cheats/Env.t.sol index ae6c89ec9..e325df2fa 100644 --- a/testdata/cheats/Env.t.sol +++ b/testdata/default/cheats/Env.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract EnvTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -13,6 +13,14 @@ contract EnvTest is DSTest { vm.setEnv(key, val); } + function testEnvExists() public { + string memory key = "_foundryCheatcodeEnvExistsTestKey"; + string memory val = "_foundryCheatcodeEnvExistsTestVal"; + vm.setEnv(key, val); + require(vm.envExists(key), "envExists failed"); + require(!vm.envExists("nonexistent"), "envExists failed"); + } + uint256 constant numEnvBoolTests = 2; function testEnvBool() public { diff --git a/testdata/cheats/Etch.t.sol b/testdata/default/cheats/Etch.t.sol similarity index 96% rename from testdata/cheats/Etch.t.sol rename to testdata/default/cheats/Etch.t.sol index f93a002b2..6e58fc13b 100644 --- a/testdata/cheats/Etch.t.sol +++ b/testdata/default/cheats/Etch.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract EtchTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/ExpectCall.t.sol b/testdata/default/cheats/ExpectCall.t.sol similarity index 96% rename from testdata/cheats/ExpectCall.t.sol rename to testdata/default/cheats/ExpectCall.t.sol index 3cc9e6c57..7d757101a 100644 --- a/testdata/cheats/ExpectCall.t.sol +++ b/testdata/default/cheats/ExpectCall.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Contract { function numberA() public pure returns (uint256) { @@ -50,6 +50,16 @@ contract NestedContract { } } +contract SimpleCall { + function call() public {} +} + +contract ProxyWithDelegateCall { + function delegateCall(SimpleCall simpleCall) public { + address(simpleCall).delegatecall(abi.encodeWithSignature("call()")); + } +} + contract ExpectCallTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -249,6 +259,14 @@ contract ExpectCallTest is DSTest { vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector)); this.exposed_callTargetNTimes(target, 5, 5, 1); } + + /// Ensure expectCall works for Proxy DelegateCalls. Ref: + function testExpectCallForProxyDelegateCall() public { + ProxyWithDelegateCall proxyWithDelegateCall = new ProxyWithDelegateCall(); + SimpleCall simpleCall = new SimpleCall(); + vm.expectCall(address(simpleCall), abi.encodeWithSignature("call()")); + proxyWithDelegateCall.delegateCall(simpleCall); + } } contract ExpectCallCountTest is DSTest { diff --git a/testdata/cheats/ExpectEmit.t.sol b/testdata/default/cheats/ExpectEmit.t.sol similarity index 99% rename from testdata/cheats/ExpectEmit.t.sol rename to testdata/default/cheats/ExpectEmit.t.sol index b232ab36b..cad184355 100644 --- a/testdata/cheats/ExpectEmit.t.sol +++ b/testdata/default/cheats/ExpectEmit.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Emitter { uint256 public thing; diff --git a/testdata/cheats/ExpectRevert.t.sol b/testdata/default/cheats/ExpectRevert.t.sol similarity index 99% rename from testdata/cheats/ExpectRevert.t.sol rename to testdata/default/cheats/ExpectRevert.t.sol index 6006f4506..0cc6cac59 100644 --- a/testdata/cheats/ExpectRevert.t.sol +++ b/testdata/default/cheats/ExpectRevert.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Reverter { error CustomError(); diff --git a/testdata/cheats/Fee.t.sol b/testdata/default/cheats/Fee.t.sol similarity index 94% rename from testdata/cheats/Fee.t.sol rename to testdata/default/cheats/Fee.t.sol index 3d6ea72a8..ad93fed6a 100644 --- a/testdata/cheats/Fee.t.sol +++ b/testdata/default/cheats/Fee.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract FeeTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/Ffi.t.sol b/testdata/default/cheats/Ffi.t.sol similarity index 97% rename from testdata/cheats/Ffi.t.sol rename to testdata/default/cheats/Ffi.t.sol index 2aa2175e2..897783d7e 100644 --- a/testdata/cheats/Ffi.t.sol +++ b/testdata/default/cheats/Ffi.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract FfiTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/Fork.t.sol b/testdata/default/cheats/Fork.t.sol similarity index 88% rename from testdata/cheats/Fork.t.sol rename to testdata/default/cheats/Fork.t.sol index 0b64b9eb1..cb6a4e3ff 100644 --- a/testdata/cheats/Fork.t.sol +++ b/testdata/default/cheats/Fork.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; interface IWETH { function deposit() external payable; @@ -111,4 +111,15 @@ contract ForkTest is DSTest { uint256 expected = block.chainid; assertEq(newChainId, expected); } + + // ensures forks change chain ids automatically + function testCanAutoUpdateChainId() public { + vm.createSelectFork("https://polygon-pokt.nodies.app"); // Polygon mainnet RPC URL + assertEq(block.chainid, 137); + } + + // ensures forks storage is cached at block + function testStorageCaching() public { + vm.createSelectFork("https://eth-mainnet.alchemyapi.io/v2/Lc7oIGYeL_QvInzI0Wiu_pOZZDEKBrdf", 19800000); + } } diff --git a/testdata/cheats/Fork2.t.sol b/testdata/default/cheats/Fork2.t.sol similarity index 99% rename from testdata/cheats/Fork2.t.sol rename to testdata/default/cheats/Fork2.t.sol index b3c1008b7..4b4053334 100644 --- a/testdata/cheats/Fork2.t.sol +++ b/testdata/default/cheats/Fork2.t.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; import "../logs/console.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; struct MyStruct { uint256 value; diff --git a/testdata/cheats/Fs.t.sol b/testdata/default/cheats/Fs.t.sol similarity index 99% rename from testdata/cheats/Fs.t.sol rename to testdata/default/cheats/Fs.t.sol index 13093ede6..c48adefec 100644 --- a/testdata/cheats/Fs.t.sol +++ b/testdata/default/cheats/Fs.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract FsTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/GasMetering.t.sol b/testdata/default/cheats/GasMetering.t.sol similarity index 98% rename from testdata/cheats/GasMetering.t.sol rename to testdata/default/cheats/GasMetering.t.sol index e5616634f..54d0a7422 100644 --- a/testdata/cheats/GasMetering.t.sol +++ b/testdata/default/cheats/GasMetering.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract B { function a() public returns (uint256) { diff --git a/testdata/cheats/GetBlockTimestamp.t.sol b/testdata/default/cheats/GetBlockTimestamp.t.sol similarity index 96% rename from testdata/cheats/GetBlockTimestamp.t.sol rename to testdata/default/cheats/GetBlockTimestamp.t.sol index 144d6b56e..383bfa8b0 100644 --- a/testdata/cheats/GetBlockTimestamp.t.sol +++ b/testdata/default/cheats/GetBlockTimestamp.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract GetBlockTimestampTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/GetCode.t.sol b/testdata/default/cheats/GetCode.t.sol similarity index 80% rename from testdata/cheats/GetCode.t.sol rename to testdata/default/cheats/GetCode.t.sol index db8841f60..73980d7b2 100644 --- a/testdata/cheats/GetCode.t.sol +++ b/testdata/default/cheats/GetCode.t.sol @@ -2,7 +2,11 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; + +contract TestContract {} + +contract TestContractGetCode {} contract GetCodeTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -70,4 +74,22 @@ contract GetCodeTest is DSTest { function testFailGetUnlinked() public { vm.getCode("UnlinkedContract.sol"); } + + function testWithVersion() public { + bytes memory code = vm.getCode("cheats/GetCode.t.sol:TestContract:0.8.18"); + assertEq(type(TestContract).creationCode, code); + + vm._expectCheatcodeRevert("No matching artifact found"); + vm.getCode("cheats/GetCode.t.sol:TestContract:0.8.19"); + } + + function testByName() public { + bytes memory code = vm.getCode("TestContractGetCode"); + assertEq(type(TestContractGetCode).creationCode, code); + } + + function testByNameAndVersion() public { + bytes memory code = vm.getCode("TestContractGetCode:0.8.18"); + assertEq(type(TestContractGetCode).creationCode, code); + } } diff --git a/testdata/cheats/GetDeployedCode.t.sol b/testdata/default/cheats/GetDeployedCode.t.sol similarity index 87% rename from testdata/cheats/GetDeployedCode.t.sol rename to testdata/default/cheats/GetDeployedCode.t.sol index 71020af18..8d95b243c 100644 --- a/testdata/cheats/GetDeployedCode.t.sol +++ b/testdata/default/cheats/GetDeployedCode.t.sol @@ -2,7 +2,9 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; + +contract TestContract {} contract GetDeployedCodeTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -36,6 +38,16 @@ contract GetDeployedCodeTest is DSTest { emit Payload(address(this), address(0), "hello"); over.emitPayload(address(0), "hello"); } + + function testWithVersion() public { + TestContract test = new TestContract(); + bytes memory code = vm.getDeployedCode("cheats/GetDeployedCode.t.sol:TestContract:0.8.18"); + + assertEq(address(test).code, code); + + vm._expectCheatcodeRevert("No matching artifact found"); + vm.getDeployedCode("cheats/GetDeployedCode.t.sol:TestContract:0.8.19"); + } } interface Override { diff --git a/testdata/cheats/GetLabel.t.sol b/testdata/default/cheats/GetLabel.t.sol similarity index 94% rename from testdata/cheats/GetLabel.t.sol rename to testdata/default/cheats/GetLabel.t.sol index 784a18cea..dcbe0812c 100644 --- a/testdata/cheats/GetLabel.t.sol +++ b/testdata/default/cheats/GetLabel.t.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.0; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract GetLabelTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/GetNonce.t.sol b/testdata/default/cheats/GetNonce.t.sol similarity index 94% rename from testdata/cheats/GetNonce.t.sol rename to testdata/default/cheats/GetNonce.t.sol index 8d3a19646..7eb53f205 100644 --- a/testdata/cheats/GetNonce.t.sol +++ b/testdata/default/cheats/GetNonce.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Foo {} diff --git a/testdata/cheats/Json.t.sol b/testdata/default/cheats/Json.t.sol similarity index 80% rename from testdata/cheats/Json.t.sol rename to testdata/default/cheats/Json.t.sol index a43a7be5a..ca53b1801 100644 --- a/testdata/cheats/Json.t.sol +++ b/testdata/default/cheats/Json.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; import "../logs/console.sol"; contract ParseJsonTest is DSTest { @@ -14,39 +14,25 @@ contract ParseJsonTest is DSTest { json = vm.readFile(path); } - function test_uintArray() public { - bytes memory data = vm.parseJson(json, ".uintArray"); - uint256[] memory decodedData = abi.decode(data, (uint256[])); - assertEq(42, decodedData[0]); - assertEq(43, decodedData[1]); - } - - function test_str() public { - bytes memory data = vm.parseJson(json, ".str"); + function test_basicString() public { + bytes memory data = vm.parseJson(json, ".basicString"); string memory decodedData = abi.decode(data, (string)); assertEq("hai", decodedData); } - function test_strArray() public { - bytes memory data = vm.parseJson(json, ".strArray"); + function test_null() public { + bytes memory data = vm.parseJson(json, ".null"); + bytes memory decodedData = abi.decode(data, (bytes)); + assertEq(new bytes(0), decodedData); + } + + function test_stringArray() public { + bytes memory data = vm.parseJson(json, ".stringArray"); string[] memory decodedData = abi.decode(data, (string[])); assertEq("hai", decodedData[0]); assertEq("there", decodedData[1]); } - function test_bool() public { - bytes memory data = vm.parseJson(json, ".bool"); - bool decodedData = abi.decode(data, (bool)); - assertTrue(decodedData); - } - - function test_boolArray() public { - bytes memory data = vm.parseJson(json, ".boolArray"); - bool[] memory decodedData = abi.decode(data, (bool[])); - assertTrue(decodedData[0]); - assertTrue(!decodedData[1]); - } - function test_address() public { bytes memory data = vm.parseJson(json, ".address"); address decodedData = abi.decode(data, (address)); @@ -65,18 +51,31 @@ contract ParseJsonTest is DSTest { assertEq("0000000000000000000000000000000000001337", data); } - struct Nested { - uint256 number; - string str; + function test_bool() public { + bytes memory data = vm.parseJson(json, ".boolTrue"); + bool decodedData = abi.decode(data, (bool)); + assertTrue(decodedData); + + data = vm.parseJson(json, ".boolFalse"); + decodedData = abi.decode(data, (bool)); + assertTrue(!decodedData); } - function test_nestedObject() public { - bytes memory data = vm.parseJson(json, ".nestedObject"); - Nested memory nested = abi.decode(data, (Nested)); - assertEq(nested.number, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - assertEq(nested.str, "NEST"); + function test_boolArray() public { + bytes memory data = vm.parseJson(json, ".boolArray"); + bool[] memory decodedData = abi.decode(data, (bool[])); + assertTrue(decodedData[0]); + assertTrue(!decodedData[1]); + } + + function test_uintArray() public { + bytes memory data = vm.parseJson(json, ".uintArray"); + uint256[] memory decodedData = abi.decode(data, (uint256[])); + assertEq(42, decodedData[0]); + assertEq(43, decodedData[1]); } + // Object keys are sorted alphabetically, regardless of input. struct Whole { string str; string[] strArray; @@ -85,7 +84,7 @@ contract ParseJsonTest is DSTest { function test_wholeObject() public { // we need to make the path relative to the crate that's running tests for it (forge crate) - string memory path = "fixtures/Json/wholeJson.json"; + string memory path = "fixtures/Json/whole_json.json"; console.log(path); json = vm.readFile(path); bytes memory data = vm.parseJson(json); @@ -99,35 +98,68 @@ contract ParseJsonTest is DSTest { function test_coercionRevert() public { vm._expectCheatcodeRevert("values at \".nestedObject\" must not be JSON objects"); - uint256 number = vm.parseJsonUint(json, ".nestedObject"); + vm.parseJsonUint(json, ".nestedObject"); } function test_coercionUint() public { - uint256 number = vm.parseJsonUint(json, ".hexUint"); + uint256 number = vm.parseJsonUint(json, ".uintHex"); assertEq(number, 1231232); - number = vm.parseJsonUint(json, ".stringUint"); + number = vm.parseJsonUint(json, ".uintString"); assertEq(number, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - number = vm.parseJsonUint(json, ".numberUint"); + number = vm.parseJsonUint(json, ".uintNumber"); assertEq(number, 115792089237316195423570985008687907853269984665640564039457584007913129639935); - uint256[] memory numbers = vm.parseJsonUintArray(json, ".arrayUint"); + uint256[] memory numbers = vm.parseJsonUintArray(json, ".uintArray"); + assertEq(numbers[0], 42); + assertEq(numbers[1], 43); + numbers = vm.parseJsonUintArray(json, ".uintStringArray"); assertEq(numbers[0], 1231232); assertEq(numbers[1], 1231232); assertEq(numbers[2], 1231232); } function test_coercionInt() public { - int256 number = vm.parseJsonInt(json, ".hexInt"); + int256 number = vm.parseJsonInt(json, ".intNumber"); assertEq(number, -12); - number = vm.parseJsonInt(json, ".stringInt"); + number = vm.parseJsonInt(json, ".intString"); + assertEq(number, -12); + number = vm.parseJsonInt(json, ".intHex"); assertEq(number, -12); } function test_coercionBool() public { - bool boolean = vm.parseJsonBool(json, ".booleanString"); + bool boolean = vm.parseJsonBool(json, ".boolTrue"); + assertTrue(boolean); + bool boolFalse = vm.parseJsonBool(json, ".boolFalse"); + assertTrue(!boolFalse); + boolean = vm.parseJsonBool(json, ".boolString"); assertEq(boolean, true); - bool[] memory booleans = vm.parseJsonBoolArray(json, ".booleanArray"); - assert(booleans[0]); - assert(!booleans[1]); + bool[] memory booleans = vm.parseJsonBoolArray(json, ".boolArray"); + assertTrue(booleans[0]); + assertTrue(!booleans[1]); + booleans = vm.parseJsonBoolArray(json, ".boolStringArray"); + assertTrue(booleans[0]); + assertTrue(!booleans[1]); + } + + function test_coercionBytes() public { + bytes memory bytes_ = vm.parseJsonBytes(json, ".bytesString"); + assertEq(bytes_, hex"01"); + + bytes[] memory bytesArray = vm.parseJsonBytesArray(json, ".bytesStringArray"); + assertEq(bytesArray[0], hex"01"); + assertEq(bytesArray[1], hex"02"); + } + + struct Nested { + uint256 number; + string str; + } + + function test_nestedObject() public { + bytes memory data = vm.parseJson(json, ".nestedObject"); + Nested memory nested = abi.decode(data, (Nested)); + assertEq(nested.number, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + assertEq(nested.str, "NEST"); } function test_advancedJsonPath() public { @@ -138,7 +170,7 @@ contract ParseJsonTest is DSTest { } function test_canonicalizePath() public { - bytes memory data = vm.parseJson(json, "$.str"); + bytes memory data = vm.parseJson(json, "$.basicString"); string memory decodedData = abi.decode(data, (string)); assertEq("hai", decodedData); } @@ -192,6 +224,7 @@ contract WriteJsonTest is DSTest { vm.serializeBool(json1, "boolean", true); vm.serializeInt(json2, "key2", -234); vm.serializeUint(json2, "deploy", uint256(254)); + vm.serializeUintToHex(json2, "hexUint", uint256(255)); string memory data = vm.serializeBool(json2, "boolean", true); vm.serializeString(json2, "json1", data); emit log(data); @@ -286,17 +319,25 @@ contract WriteJsonTest is DSTest { assertEq(decodedData.a, 123); } - function test_checkKeyExists() public { + function test_checkKeyExistsJson() public { string memory path = "fixtures/Json/write_complex_test.json"; string memory json = vm.readFile(path); - bool exists = vm.keyExists(json, ".a"); + bool exists = vm.keyExistsJson(json, ".a"); + assertTrue(exists); + + // TODO: issue deprecation warning + exists = vm.keyExists(json, ".a"); assertTrue(exists); } - function test_checkKeyDoesNotExist() public { + function test_checkKeyDoesNotExistJson() public { string memory path = "fixtures/Json/write_complex_test.json"; string memory json = vm.readFile(path); - bool exists = vm.keyExists(json, ".d"); + bool exists = vm.keyExistsJson(json, ".d"); + assertTrue(!exists); + + // TODO: issue deprecation warning + exists = vm.keyExists(json, ".d"); assertTrue(!exists); } diff --git a/testdata/cheats/Label.t.sol b/testdata/default/cheats/Label.t.sol similarity index 91% rename from testdata/cheats/Label.t.sol rename to testdata/default/cheats/Label.t.sol index b8e29d195..d554f637d 100644 --- a/testdata/cheats/Label.t.sol +++ b/testdata/default/cheats/Label.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract LabelTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/default/cheats/LastCallGas.t.sol b/testdata/default/cheats/LastCallGas.t.sol new file mode 100644 index 000000000..0f5b65e35 --- /dev/null +++ b/testdata/default/cheats/LastCallGas.t.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract Target { + uint256 public slot0; + + function expandMemory(uint256 n) public pure returns (uint256) { + uint256[] memory arr = new uint256[](n); + + for (uint256 i = 0; i < n; i++) { + arr[i] = i; + } + + return arr.length; + } + + function setValue(uint256 value) public { + slot0 = value; + } + + function resetValue() public { + slot0 = 0; + } + + fallback() external {} +} + +abstract contract LastCallGasFixture is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + Target public target; + + struct Gas { + uint64 gasTotalUsed; + uint64 gasMemoryUsed; + int64 gasRefunded; + } + + function testRevertNoCachedLastCallGas() public { + vm.expectRevert(); + vm.lastCallGas(); + } + + function _setup() internal { + // Cannot be set in `setUp` due to `testRevertNoCachedLastCallGas` + // relying on no calls being made before `lastCallGas` is called. + target = new Target(); + } + + function _performCall() internal returns (bool success) { + (success,) = address(target).call(""); + } + + function _performRefund() internal { + target.setValue(1); + target.resetValue(); + } + + function _assertGas(Vm.Gas memory lhs, Gas memory rhs) internal { + assertGt(lhs.gasLimit, 0); + assertGt(lhs.gasRemaining, 0); + assertEq(lhs.gasTotalUsed, rhs.gasTotalUsed); + assertEq(lhs.gasMemoryUsed, rhs.gasMemoryUsed); + assertEq(lhs.gasRefunded, rhs.gasRefunded); + } +} + +contract LastCallGasIsolatedTest is LastCallGasFixture { + function testRecordLastCallGas() public { + _setup(); + _performCall(); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21064, gasMemoryUsed: 0, gasRefunded: 0})); + + _performCall(); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21064, gasMemoryUsed: 0, gasRefunded: 0})); + + _performCall(); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21064, gasMemoryUsed: 0, gasRefunded: 0})); + } + + function testRecordGasRefund() public { + _setup(); + _performRefund(); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21380, gasMemoryUsed: 0, gasRefunded: 4800})); + } +} + +// Without isolation mode enabled the gas usage will be incorrect. +contract LastCallGasDefaultTest is LastCallGasFixture { + function testRecordLastCallGas() public { + _setup(); + _performCall(); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 64, gasMemoryUsed: 0, gasRefunded: 0})); + + _performCall(); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 64, gasMemoryUsed: 0, gasRefunded: 0})); + + _performCall(); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 64, gasMemoryUsed: 0, gasRefunded: 0})); + } + + function testRecordGasRefund() public { + _setup(); + _performRefund(); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 216, gasMemoryUsed: 0, gasRefunded: 19900})); + } +} diff --git a/testdata/cheats/Load.t.sol b/testdata/default/cheats/Load.t.sol similarity index 97% rename from testdata/cheats/Load.t.sol rename to testdata/default/cheats/Load.t.sol index fa5680d71..37a2c80b2 100644 --- a/testdata/cheats/Load.t.sol +++ b/testdata/default/cheats/Load.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Storage { uint256 slot0 = 10; diff --git a/testdata/cheats/Mapping.t.sol b/testdata/default/cheats/Mapping.t.sol similarity index 99% rename from testdata/cheats/Mapping.t.sol rename to testdata/default/cheats/Mapping.t.sol index 4dec4156b..6cd141fa8 100644 --- a/testdata/cheats/Mapping.t.sol +++ b/testdata/default/cheats/Mapping.t.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.0; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract RecordMapping { int256 length; diff --git a/testdata/cheats/MemSafety.t.sol b/testdata/default/cheats/MemSafety.t.sol similarity index 95% rename from testdata/cheats/MemSafety.t.sol rename to testdata/default/cheats/MemSafety.t.sol index 48205233b..096d8ac47 100644 --- a/testdata/cheats/MemSafety.t.sol +++ b/testdata/default/cheats/MemSafety.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract MemSafetyTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -411,6 +411,22 @@ contract MemSafetyTest is DSTest { uint256 b = a + 1; } + /// @dev Tests that expanding memory outside of the range given to `expectSafeMemory` + /// will cause the test to fail while using the `MLOAD` opcode. + function testExpectSafeMemory_MLOAD_REVERT() public { + vm.expectSafeMemory(0x80, 0x100); + + vm.expectRevert(); + + // This should revert. Ugly hack to make sure the mload isn't optimized + // out. + uint256 a; + assembly { + a := mload(0x100) + } + uint256 b = a + 1; + } + /// @dev Tests that expanding memory outside of the range given to `expectSafeMemory` /// will cause the test to fail while using the `MLOAD` opcode. function testFailExpectSafeMemory_MLOAD() public { @@ -486,6 +502,17 @@ contract MemSafetyTest is DSTest { } } + /// @dev Tests that expanding memory outside of the range given to `expectSafeMemory` + /// will cause the test to fail while using the `LOG0` opcode. + function testExpectSafeMemory_LOG0_REVERT() public { + vm.expectSafeMemory(0x80, 0x100); + vm.expectRevert(); + // This should revert. + assembly { + log0(0x100, 0x20) + } + } + //////////////////////////////////////////////////////////////// // CREATE/CREATE2 (Read Expansion) // //////////////////////////////////////////////////////////////// @@ -711,6 +738,24 @@ contract MemSafetyTest is DSTest { vm.stopExpectSafeMemory(); } + /// @dev Tests that the `stopExpectSafeMemory` cheatcode can still be called if the free memory pointer was + /// updated to the exclusive upper boundary during execution. + function testStopExpectSafeMemory_freeMemUpdate() public { + uint64 initPtr; + assembly { + initPtr := mload(0x40) + } + + vm.expectSafeMemory(initPtr, initPtr + 0x20); + assembly { + // write outside of allowed range, this should revert + mstore(initPtr, 0x01) + mstore(0x40, add(initPtr, 0x20)) + } + + vm.stopExpectSafeMemory(); + } + //////////////////////////////////////////////////////////////// // HELPERS // //////////////////////////////////////////////////////////////// diff --git a/testdata/cheats/MockCall.t.sol b/testdata/default/cheats/MockCall.t.sol similarity index 90% rename from testdata/cheats/MockCall.t.sol rename to testdata/default/cheats/MockCall.t.sol index fa7d9f314..df7ee89c7 100644 --- a/testdata/cheats/MockCall.t.sol +++ b/testdata/default/cheats/MockCall.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Mock { uint256 state = 0; @@ -42,6 +42,20 @@ contract NestedMock { } } +contract NestedMockDelegateCall { + Mock private inner; + + constructor(Mock _inner) { + inner = _inner; + } + + function sum() public returns (uint256) { + (, bytes memory dataA) = address(inner).delegatecall(abi.encodeWithSelector(Mock.numberA.selector)); + (, bytes memory dataB) = address(inner).delegatecall(abi.encodeWithSelector(Mock.numberB.selector)); + return abi.decode(dataA, (uint256)) + abi.decode(dataB, (uint256)); + } +} + contract MockCallTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -72,6 +86,18 @@ contract MockCallTest is DSTest { assertEq(target.sum(), 10); } + // Ref: https://github.com/foundry-rs/foundry/issues/8066 + function testMockNestedDelegate() public { + Mock inner = new Mock(); + NestedMockDelegateCall target = new NestedMockDelegateCall(inner); + + assertEq(target.sum(), 3); + + vm.mockCall(address(inner), abi.encodeWithSelector(inner.numberB.selector), abi.encode(9)); + + assertEq(target.sum(), 10); + } + function testMockSelector() public { Mock target = new Mock(); assertEq(target.add(5, 5), 10); diff --git a/testdata/cheats/Parse.t.sol b/testdata/default/cheats/Parse.t.sol similarity index 99% rename from testdata/cheats/Parse.t.sol rename to testdata/default/cheats/Parse.t.sol index a39d32d08..71d49af6f 100644 --- a/testdata/cheats/Parse.t.sol +++ b/testdata/default/cheats/Parse.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract ParseTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/Prank.t.sol b/testdata/default/cheats/Prank.t.sol similarity index 99% rename from testdata/cheats/Prank.t.sol rename to testdata/default/cheats/Prank.t.sol index 0e23ed7b8..f7dd9b714 100644 --- a/testdata/cheats/Prank.t.sol +++ b/testdata/default/cheats/Prank.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Victim { function assertCallerAndOrigin( diff --git a/testdata/cheats/Prevrandao.t.sol b/testdata/default/cheats/Prevrandao.t.sol similarity index 85% rename from testdata/cheats/Prevrandao.t.sol rename to testdata/default/cheats/Prevrandao.t.sol index 20bab12c4..7011ce3be 100644 --- a/testdata/cheats/Prevrandao.t.sol +++ b/testdata/default/cheats/Prevrandao.t.sol @@ -2,21 +2,21 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract PrevrandaoTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); function testPrevrandao() public { assertEq(block.prevrandao, 0); - vm.prevrandao(bytes32(uint256(10))); + vm.prevrandao(uint256(10)); assertEq(block.prevrandao, 10, "prevrandao cheatcode failed"); } function testPrevrandaoFuzzed(uint256 newPrevrandao) public { vm.assume(newPrevrandao != block.prevrandao); assertEq(block.prevrandao, 0); - vm.prevrandao(bytes32(newPrevrandao)); + vm.prevrandao(newPrevrandao); assertEq(block.prevrandao, newPrevrandao); } @@ -25,7 +25,7 @@ contract PrevrandaoTest is DSTest { uint256 oldPrevrandao = block.prevrandao; uint256 snapshot = vm.snapshot(); - vm.prevrandao(bytes32(newPrevrandao)); + vm.prevrandao(newPrevrandao); assertEq(block.prevrandao, newPrevrandao); assert(vm.revertTo(snapshot)); diff --git a/testdata/cheats/ProjectRoot.t.sol b/testdata/default/cheats/ProjectRoot.t.sol similarity index 97% rename from testdata/cheats/ProjectRoot.t.sol rename to testdata/default/cheats/ProjectRoot.t.sol index 1edfb0e07..31e68e105 100644 --- a/testdata/cheats/ProjectRoot.t.sol +++ b/testdata/default/cheats/ProjectRoot.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract ProjectRootTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/default/cheats/Prompt.t.sol b/testdata/default/cheats/Prompt.t.sol new file mode 100644 index 000000000..33f83fea8 --- /dev/null +++ b/testdata/default/cheats/Prompt.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; +import "../logs/console.sol"; + +contract PromptTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testPrompt_revertNotATerminal() public { + // should revert in CI and testing environments either with timout or because no terminal is available + vm._expectCheatcodeRevert(); + vm.prompt("test"); + + vm._expectCheatcodeRevert(); + vm.promptSecret("test"); + + vm._expectCheatcodeRevert(); + uint256 test = vm.promptSecretUint("test"); + } + + function testPrompt_Address() public { + vm._expectCheatcodeRevert(); + address test = vm.promptAddress("test"); + } + + function testPrompt_Uint() public { + vm._expectCheatcodeRevert(); + uint256 test = vm.promptUint("test"); + } +} diff --git a/testdata/default/cheats/RandomUint.t.sol b/testdata/default/cheats/RandomUint.t.sol new file mode 100644 index 000000000..5c5b1024a --- /dev/null +++ b/testdata/default/cheats/RandomUint.t.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract RandomUint is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testRandomUint() public { + uint256 rand = vm.randomUint(); + + assertTrue(rand > 0); + } +} diff --git a/testdata/cheats/ReadCallers.t.sol b/testdata/default/cheats/ReadCallers.t.sol similarity index 99% rename from testdata/cheats/ReadCallers.t.sol rename to testdata/default/cheats/ReadCallers.t.sol index 82210b6c4..e0da8ed0d 100644 --- a/testdata/cheats/ReadCallers.t.sol +++ b/testdata/default/cheats/ReadCallers.t.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.0; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Target { function consumeNewCaller() external {} diff --git a/testdata/cheats/Record.t.sol b/testdata/default/cheats/Record.t.sol similarity index 98% rename from testdata/cheats/Record.t.sol rename to testdata/default/cheats/Record.t.sol index 6fdfa627d..152a5ccb5 100644 --- a/testdata/cheats/Record.t.sol +++ b/testdata/default/cheats/Record.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract RecordAccess { function record() public returns (NestedRecordAccess) { diff --git a/testdata/cheats/RecordAccountAccesses.t.sol b/testdata/default/cheats/RecordAccountAccesses.t.sol similarity index 99% rename from testdata/cheats/RecordAccountAccesses.t.sol rename to testdata/default/cheats/RecordAccountAccesses.t.sol index a86361a75..a0aa2cb53 100644 --- a/testdata/cheats/RecordAccountAccesses.t.sol +++ b/testdata/default/cheats/RecordAccountAccesses.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; /// @notice Helper contract with a construction that makes a call to itself then /// optionally reverts if zero-length data is passed @@ -943,7 +943,7 @@ contract RecordAccountAccessesTest is DSTest { data: abi.encodeCall( Create2or.create2, (bytes32(0), abi.encodePacked(type(ConstructorStorer).creationCode, abi.encode(true))) - ), + ), reverted: false, storageAccesses: new Vm.StorageAccess[](0), depth: 1 diff --git a/testdata/cheats/RecordLogs.t.sol b/testdata/default/cheats/RecordLogs.t.sol similarity index 99% rename from testdata/cheats/RecordLogs.t.sol rename to testdata/default/cheats/RecordLogs.t.sol index 25fbfaeba..728acdb9b 100644 --- a/testdata/cheats/RecordLogs.t.sol +++ b/testdata/default/cheats/RecordLogs.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Emitter { event LogAnonymous(bytes data) anonymous; diff --git a/testdata/cheats/Remember.t.sol b/testdata/default/cheats/Remember.t.sol similarity index 96% rename from testdata/cheats/Remember.t.sol rename to testdata/default/cheats/Remember.t.sol index 5592081ea..b5487c369 100644 --- a/testdata/cheats/Remember.t.sol +++ b/testdata/default/cheats/Remember.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract RememberTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/ResetNonce.t.sol b/testdata/default/cheats/ResetNonce.t.sol similarity index 97% rename from testdata/cheats/ResetNonce.t.sol rename to testdata/default/cheats/ResetNonce.t.sol index 914577bdc..901433609 100644 --- a/testdata/cheats/ResetNonce.t.sol +++ b/testdata/default/cheats/ResetNonce.t.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.0; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Foo { function f() external view returns (uint256) { diff --git a/testdata/cheats/Roll.t.sol b/testdata/default/cheats/Roll.t.sol similarity index 97% rename from testdata/cheats/Roll.t.sol rename to testdata/default/cheats/Roll.t.sol index 50011fe87..820cd9887 100644 --- a/testdata/cheats/Roll.t.sol +++ b/testdata/default/cheats/Roll.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract RollTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/RpcUrls.t.sol b/testdata/default/cheats/RpcUrls.t.sol similarity index 87% rename from testdata/cheats/RpcUrls.t.sol rename to testdata/default/cheats/RpcUrls.t.sol index 282c2addd..4e3ceba58 100644 --- a/testdata/cheats/RpcUrls.t.sol +++ b/testdata/default/cheats/RpcUrls.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract RpcUrlTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -33,12 +33,15 @@ contract RpcUrlTest is DSTest { assertEq(url, envUrl); string[2][] memory allUrls = vm.rpcUrls(); - assertEq(allUrls.length, 2); + assertEq(allUrls.length, 3); string[2] memory val = allUrls[0]; assertEq(val[0], "rpcAlias"); string[2] memory env = allUrls[1]; - assertEq(env[0], "rpcEnvAlias"); + assertEq(env[0], "rpcAliasSepolia"); + + string[2] memory env2 = allUrls[2]; + assertEq(env2[0], "rpcEnvAlias"); } } diff --git a/testdata/cheats/SetNonce.t.sol b/testdata/default/cheats/SetNonce.t.sol similarity index 96% rename from testdata/cheats/SetNonce.t.sol rename to testdata/default/cheats/SetNonce.t.sol index 9285ca7e6..7f2e419b9 100644 --- a/testdata/cheats/SetNonce.t.sol +++ b/testdata/default/cheats/SetNonce.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Foo { function f() external view returns (uint256) { diff --git a/testdata/cheats/SetNonceUnsafe.t.sol b/testdata/default/cheats/SetNonceUnsafe.t.sol similarity index 97% rename from testdata/cheats/SetNonceUnsafe.t.sol rename to testdata/default/cheats/SetNonceUnsafe.t.sol index 1209a2814..723f66ae2 100644 --- a/testdata/cheats/SetNonceUnsafe.t.sol +++ b/testdata/default/cheats/SetNonceUnsafe.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Foo { function f() external view returns (uint256) { diff --git a/testdata/cheats/Setup.t.sol b/testdata/default/cheats/Setup.t.sol similarity index 93% rename from testdata/cheats/Setup.t.sol rename to testdata/default/cheats/Setup.t.sol index 986b232d8..d694fb2c1 100644 --- a/testdata/cheats/Setup.t.sol +++ b/testdata/default/cheats/Setup.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Victim { function assertSender(address sender) external { @@ -21,7 +21,7 @@ contract VmSetupTest is DSTest { vm.chainId(99); vm.roll(100); vm.fee(1000); - vm.prevrandao(bytes32(uint256(10000))); + vm.prevrandao(uint256(10000)); vm.startPrank(address(1337)); } diff --git a/testdata/cheats/Sign.t.sol b/testdata/default/cheats/Sign.t.sol similarity index 96% rename from testdata/cheats/Sign.t.sol rename to testdata/default/cheats/Sign.t.sol index 587d80e5f..e46439b58 100644 --- a/testdata/cheats/Sign.t.sol +++ b/testdata/default/cheats/Sign.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract SignTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/SignP256.t.sol b/testdata/default/cheats/SignP256.t.sol similarity index 96% rename from testdata/cheats/SignP256.t.sol rename to testdata/default/cheats/SignP256.t.sol index ee0363e97..f1b62fe78 100644 --- a/testdata/cheats/SignP256.t.sol +++ b/testdata/default/cheats/SignP256.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract SignTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/Skip.t.sol b/testdata/default/cheats/Skip.t.sol similarity index 96% rename from testdata/cheats/Skip.t.sol rename to testdata/default/cheats/Skip.t.sol index b5f8a019b..fb2deadb4 100644 --- a/testdata/cheats/Skip.t.sol +++ b/testdata/default/cheats/Skip.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract SkipTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/Sleep.t.sol b/testdata/default/cheats/Sleep.t.sol similarity index 98% rename from testdata/cheats/Sleep.t.sol rename to testdata/default/cheats/Sleep.t.sol index 37be632cc..448d34668 100644 --- a/testdata/cheats/Sleep.t.sol +++ b/testdata/default/cheats/Sleep.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract SleepTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/Snapshots.t.sol b/testdata/default/cheats/Snapshots.t.sol similarity index 97% rename from testdata/cheats/Snapshots.t.sol rename to testdata/default/cheats/Snapshots.t.sol index baf82e2e5..bb7b4e0e6 100644 --- a/testdata/cheats/Snapshots.t.sol +++ b/testdata/default/cheats/Snapshots.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; struct Storage { uint256 slot0; @@ -93,7 +93,7 @@ contract SnapshotTest is DSTest { vm.roll(99); assertEq(block.number, 99); - vm.prevrandao(bytes32(uint256(123))); + vm.prevrandao(uint256(123)); assertEq(block.prevrandao, 123); assert(vm.revertTo(snapshot)); diff --git a/testdata/cheats/Store.t.sol b/testdata/default/cheats/Store.t.sol similarity index 98% rename from testdata/cheats/Store.t.sol rename to testdata/default/cheats/Store.t.sol index 08803b92f..059952fca 100644 --- a/testdata/cheats/Store.t.sol +++ b/testdata/default/cheats/Store.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Storage { uint256 public slot0 = 10; diff --git a/testdata/cheats/StringUtils.t.sol b/testdata/default/cheats/StringUtils.t.sol similarity index 73% rename from testdata/cheats/StringUtils.t.sol rename to testdata/default/cheats/StringUtils.t.sol index 4fe8bba01..136164a41 100644 --- a/testdata/cheats/StringUtils.t.sol +++ b/testdata/default/cheats/StringUtils.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract StringManipulationTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -39,4 +39,16 @@ contract StringManipulationTest is DSTest { assertEq("World", splitResult[1]); assertEq("Reth", splitResult[2]); } + + function testIndexOf() public { + string memory input = "Hello, World!"; + string memory key1 = "Hello,"; + string memory key2 = "World!"; + string memory key3 = ""; + string memory key4 = "foundry"; + assertEq(vm.indexOf(input, key1), 0); + assertEq(vm.indexOf(input, key2), 7); + assertEq(vm.indexOf(input, key3), 0); + assertEq(vm.indexOf(input, key4), type(uint256).max); + } } diff --git a/testdata/cheats/ToString.t.sol b/testdata/default/cheats/ToString.t.sol similarity index 98% rename from testdata/cheats/ToString.t.sol rename to testdata/default/cheats/ToString.t.sol index c26fdce4c..835c85242 100644 --- a/testdata/cheats/ToString.t.sol +++ b/testdata/default/cheats/ToString.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract ToStringTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/default/cheats/Toml.t.sol b/testdata/default/cheats/Toml.t.sol new file mode 100644 index 000000000..40667743f --- /dev/null +++ b/testdata/default/cheats/Toml.t.sol @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; +import "../logs/console.sol"; + +contract ParseTomlTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + string toml; + + function setUp() public { + string memory path = "fixtures/Toml/test.toml"; + toml = vm.readFile(path); + } + + function test_basicString() public { + bytes memory data = vm.parseToml(toml, ".basicString"); + string memory decodedData = abi.decode(data, (string)); + assertEq("hai", decodedData); + } + + function test_nullString() public { + bytes memory data = vm.parseToml(toml, ".nullString"); + string memory decodedData = abi.decode(data, (string)); + assertEq("", decodedData); + } + + function test_stringMultiline() public { + bytes memory data = vm.parseToml(toml, ".multilineString"); + string memory decodedData = abi.decode(data, (string)); + assertEq("hai\nthere\n", decodedData); + } + + function test_stringArray() public { + bytes memory data = vm.parseToml(toml, ".stringArray"); + string[] memory decodedData = abi.decode(data, (string[])); + assertEq("hai", decodedData[0]); + assertEq("there", decodedData[1]); + } + + function test_address() public { + bytes memory data = vm.parseToml(toml, ".address"); + address decodedData = abi.decode(data, (address)); + assertEq(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, decodedData); + } + + function test_addressArray() public { + bytes memory data = vm.parseToml(toml, ".addressArray"); + address[] memory decodedData = abi.decode(data, (address[])); + assertEq(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, decodedData[0]); + assertEq(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D, decodedData[1]); + } + + function test_H160ButNotaddress() public { + string memory data = abi.decode(vm.parseToml(toml, ".H160NotAddress"), (string)); + assertEq("0000000000000000000000000000000000001337", data); + } + + function test_bool() public { + bytes memory data = vm.parseToml(toml, ".boolTrue"); + bool decodedData = abi.decode(data, (bool)); + assertTrue(decodedData); + + data = vm.parseToml(toml, ".boolFalse"); + decodedData = abi.decode(data, (bool)); + assertTrue(!decodedData); + } + + function test_boolArray() public { + bytes memory data = vm.parseToml(toml, ".boolArray"); + bool[] memory decodedData = abi.decode(data, (bool[])); + assertTrue(decodedData[0]); + assertTrue(!decodedData[1]); + } + + function test_dateTime() public { + bytes memory data = vm.parseToml(toml, ".datetime"); + string memory decodedData = abi.decode(data, (string)); + assertEq(decodedData, "2021-08-10T14:48:00Z"); + } + + function test_dateTimeArray() public { + bytes memory data = vm.parseToml(toml, ".datetimeArray"); + string[] memory decodedData = abi.decode(data, (string[])); + assertEq(decodedData[0], "2021-08-10T14:48:00Z"); + assertEq(decodedData[1], "2021-08-10T14:48:00Z"); + } + + function test_uintArray() public { + bytes memory data = vm.parseToml(toml, ".uintArray"); + uint256[] memory decodedData = abi.decode(data, (uint256[])); + assertEq(42, decodedData[0]); + assertEq(43, decodedData[1]); + } + + // Object keys are sorted alphabetically, regardless of input. + struct Whole { + string str; + string[] strArray; + uint256[] uintArray; + } + + function test_wholeToml() public { + // we need to make the path relative to the crate that's running tests for it (forge crate) + string memory path = "fixtures/Toml/whole_toml.toml"; + console.log(path); + toml = vm.readFile(path); + bytes memory data = vm.parseToml(toml); + Whole memory whole = abi.decode(data, (Whole)); + assertEq(whole.str, "hai"); + assertEq(whole.strArray[0], "hai"); + assertEq(whole.strArray[1], "there"); + assertEq(whole.uintArray[0], 42); + assertEq(whole.uintArray[1], 43); + } + + function test_coercionRevert() public { + vm._expectCheatcodeRevert("values at \".nestedObject\" must not be JSON objects"); + vm.parseTomlUint(toml, ".nestedObject"); + } + + function test_coercionUint() public { + uint256 number = vm.parseTomlUint(toml, ".uintNumber"); + assertEq(number, 9223372036854775807); // TOML is limited to 64-bit integers + number = vm.parseTomlUint(toml, ".uintString"); + assertEq(number, 115792089237316195423570985008687907853269984665640564039457584007913129639935); + number = vm.parseTomlUint(toml, ".uintHex"); + assertEq(number, 1231232); + uint256[] memory numbers = vm.parseTomlUintArray(toml, ".uintArray"); + assertEq(numbers[0], 42); + assertEq(numbers[1], 43); + numbers = vm.parseTomlUintArray(toml, ".uintStringArray"); + assertEq(numbers[0], 1231232); + assertEq(numbers[1], 1231232); + assertEq(numbers[2], 1231232); + } + + function test_coercionInt() public { + int256 number = vm.parseTomlInt(toml, ".intNumber"); + assertEq(number, -12); + number = vm.parseTomlInt(toml, ".intString"); + assertEq(number, -12); + number = vm.parseTomlInt(toml, ".intHex"); + assertEq(number, -12); + } + + function test_coercionBool() public { + bool boolean = vm.parseTomlBool(toml, ".boolTrue"); + assertTrue(boolean); + bool boolFalse = vm.parseTomlBool(toml, ".boolFalse"); + assertTrue(!boolFalse); + boolean = vm.parseTomlBool(toml, ".boolString"); + assertEq(boolean, true); + bool[] memory booleans = vm.parseTomlBoolArray(toml, ".boolArray"); + assertTrue(booleans[0]); + assertTrue(!booleans[1]); + booleans = vm.parseTomlBoolArray(toml, ".boolStringArray"); + assertTrue(booleans[0]); + assertTrue(!booleans[1]); + } + + function test_coercionBytes() public { + bytes memory bytes_ = vm.parseTomlBytes(toml, ".bytesString"); + assertEq(bytes_, hex"01"); + + bytes[] memory bytesArray = vm.parseTomlBytesArray(toml, ".bytesStringArray"); + assertEq(bytesArray[0], hex"01"); + assertEq(bytesArray[1], hex"02"); + } + + struct Nested { + uint256 number; + string str; + } + + function test_nestedObject() public { + bytes memory data = vm.parseToml(toml, ".nestedObject"); + Nested memory nested = abi.decode(data, (Nested)); + assertEq(nested.number, 9223372036854775807); // TOML is limited to 64-bit integers + assertEq(nested.str, "NEST"); + } + + function test_advancedJsonPath() public { + bytes memory data = vm.parseToml(toml, ".advancedJsonPath[*].id"); + uint256[] memory numbers = abi.decode(data, (uint256[])); + assertEq(numbers[0], 1); + assertEq(numbers[1], 2); + } + + function test_canonicalizePath() public { + bytes memory data = vm.parseToml(toml, "$.basicString"); + string memory decodedData = abi.decode(data, (string)); + assertEq("hai", decodedData); + } + + function test_nonExistentKey() public { + bytes memory data = vm.parseToml(toml, ".thisKeyDoesNotExist"); + assertEq(0, data.length); + } + + function test_parseTomlKeys() public { + string memory tomlString = + "some_key_to_value = \"some_value\"\n some_key_to_array = [1, 2, 3]\n [some_key_to_object]\n key1 = \"value1\"\n key2 = 2"; + + string[] memory keys = vm.parseTomlKeys(tomlString, "$"); + string[] memory expected = new string[](3); + expected[0] = "some_key_to_value"; + expected[1] = "some_key_to_array"; + expected[2] = "some_key_to_object"; + assertEq(abi.encode(keys), abi.encode(expected)); + + keys = vm.parseTomlKeys(tomlString, ".some_key_to_object"); + expected = new string[](2); + expected[0] = "key1"; + expected[1] = "key2"; + assertEq(abi.encode(keys), abi.encode(expected)); + + vm._expectCheatcodeRevert("JSON value at \".some_key_to_array\" is not an object"); + vm.parseTomlKeys(tomlString, ".some_key_to_array"); + + vm._expectCheatcodeRevert("JSON value at \".some_key_to_value\" is not an object"); + vm.parseTomlKeys(tomlString, ".some_key_to_value"); + + vm._expectCheatcodeRevert("key \".*\" must return exactly one JSON object"); + vm.parseTomlKeys(tomlString, ".*"); + } +} + +contract WriteTomlTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + string json1; + string json2; + + function setUp() public { + json1 = "example"; + json2 = "example2"; + } + + struct simpleJson { + uint256 a; + string b; + } + + struct notSimpleJson { + uint256 a; + string b; + simpleJson c; + } + + function test_serializeNotSimpleToml() public { + string memory json3 = "json3"; + string memory path = "fixtures/Toml/write_complex_test.toml"; + vm.serializeUint(json3, "a", uint256(123)); + string memory semiFinal = vm.serializeString(json3, "b", "test"); + string memory finalJson = vm.serializeString(json3, "c", semiFinal); + console.log(finalJson); + vm.writeToml(finalJson, path); + string memory toml = vm.readFile(path); + bytes memory data = vm.parseToml(toml); + notSimpleJson memory decodedData = abi.decode(data, (notSimpleJson)); + } + + function test_retrieveEntireToml() public { + string memory path = "fixtures/Toml/write_complex_test.toml"; + string memory toml = vm.readFile(path); + bytes memory data = vm.parseToml(toml, "."); + notSimpleJson memory decodedData = abi.decode(data, (notSimpleJson)); + console.log(decodedData.a); + assertEq(decodedData.a, 123); + } + + function test_checkKeyExists() public { + string memory path = "fixtures/Toml/write_complex_test.toml"; + string memory toml = vm.readFile(path); + bool exists = vm.keyExistsToml(toml, ".a"); + assertTrue(exists); + } + + function test_checkKeyDoesNotExist() public { + string memory path = "fixtures/Toml/write_complex_test.toml"; + string memory toml = vm.readFile(path); + bool exists = vm.keyExistsToml(toml, ".d"); + assertTrue(!exists); + } + + function test_writeToml() public { + string memory json3 = "json3"; + string memory path = "fixtures/Toml/write_test.toml"; + vm.serializeUint(json3, "a", uint256(123)); + string memory finalJson = vm.serializeString(json3, "b", "test"); + vm.writeToml(finalJson, path); + + string memory toml = vm.readFile(path); + bytes memory data = vm.parseToml(toml); + simpleJson memory decodedData = abi.decode(data, (simpleJson)); + assertEq(decodedData.a, 123); + assertEq(decodedData.b, "test"); + + // write json3 to key b + vm.writeToml(finalJson, path, ".b"); + // read again + toml = vm.readFile(path); + data = vm.parseToml(toml, ".b"); + decodedData = abi.decode(data, (simpleJson)); + assertEq(decodedData.a, 123); + assertEq(decodedData.b, "test"); + + // replace a single value to key b + address ex = address(0xBEEF); + vm.writeToml(vm.toString(ex), path, ".b"); + toml = vm.readFile(path); + data = vm.parseToml(toml, ".b"); + address decodedAddress = abi.decode(data, (address)); + assertEq(decodedAddress, ex); + } +} diff --git a/testdata/cheats/Travel.t.sol b/testdata/default/cheats/Travel.t.sol similarity index 95% rename from testdata/cheats/Travel.t.sol rename to testdata/default/cheats/Travel.t.sol index 297017852..733559b29 100644 --- a/testdata/cheats/Travel.t.sol +++ b/testdata/default/cheats/Travel.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract ChainIdTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/TryFfi.sol b/testdata/default/cheats/TryFfi.sol similarity index 97% rename from testdata/cheats/TryFfi.sol rename to testdata/default/cheats/TryFfi.sol index 745b65ce0..58d93a48b 100644 --- a/testdata/cheats/TryFfi.sol +++ b/testdata/default/cheats/TryFfi.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract TryFfiTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/UnixTime.t.sol b/testdata/default/cheats/UnixTime.t.sol similarity index 80% rename from testdata/cheats/UnixTime.t.sol rename to testdata/default/cheats/UnixTime.t.sol index 66cbcc395..786c1ef59 100644 --- a/testdata/cheats/UnixTime.t.sol +++ b/testdata/default/cheats/UnixTime.t.sol @@ -2,13 +2,13 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract UnixTimeTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); // This is really wide because CI sucks. - uint256 constant errMargin = 300; + uint256 constant errMargin = 500; function testUnixTimeAgainstDate() public { string[] memory inputs = new string[](2); @@ -33,7 +33,6 @@ contract UnixTimeTest is DSTest { uint256 end = vm.unixTime(); uint256 interval = end - start; - assertGe(interval, sleepTime - errMargin, ".unixTime() is inaccurate"); - assertLe(interval, sleepTime + errMargin, ".unixTime() is inaccurate"); + vm.assertApproxEqAbs(interval, sleepTime, errMargin, ".unixTime() is inaccurate"); } } diff --git a/testdata/cheats/Wallet.t.sol b/testdata/default/cheats/Wallet.t.sol similarity index 99% rename from testdata/cheats/Wallet.t.sol rename to testdata/default/cheats/Wallet.t.sol index a8ce5cc02..8ecb707ae 100644 --- a/testdata/cheats/Wallet.t.sol +++ b/testdata/default/cheats/Wallet.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract Foo {} diff --git a/testdata/cheats/Warp.t.sol b/testdata/default/cheats/Warp.t.sol similarity index 96% rename from testdata/cheats/Warp.t.sol rename to testdata/default/cheats/Warp.t.sol index 0400099f8..01ebc8e89 100644 --- a/testdata/cheats/Warp.t.sol +++ b/testdata/default/cheats/Warp.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract WarpTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/dumpState.t.sol b/testdata/default/cheats/dumpState.t.sol similarity index 99% rename from testdata/cheats/dumpState.t.sol rename to testdata/default/cheats/dumpState.t.sol index 387865a1b..74ebd3071 100644 --- a/testdata/cheats/dumpState.t.sol +++ b/testdata/default/cheats/dumpState.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract SimpleContract { constructor() { diff --git a/testdata/cheats/getBlockNumber.t.sol b/testdata/default/cheats/getBlockNumber.t.sol similarity index 97% rename from testdata/cheats/getBlockNumber.t.sol rename to testdata/default/cheats/getBlockNumber.t.sol index 774091747..c874e5e2f 100644 --- a/testdata/cheats/getBlockNumber.t.sol +++ b/testdata/default/cheats/getBlockNumber.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract GetBlockNumberTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/cheats/loadAllocs.t.sol b/testdata/default/cheats/loadAllocs.t.sol similarity index 99% rename from testdata/cheats/loadAllocs.t.sol rename to testdata/default/cheats/loadAllocs.t.sol index f219d025e..358608860 100644 --- a/testdata/cheats/loadAllocs.t.sol +++ b/testdata/default/cheats/loadAllocs.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "./Vm.sol"; +import "cheats/Vm.sol"; contract LoadAllocsTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/core/Abstract.t.sol b/testdata/default/core/Abstract.t.sol similarity index 100% rename from testdata/core/Abstract.t.sol rename to testdata/default/core/Abstract.t.sol diff --git a/testdata/default/core/BadSigAfterInvariant.t.sol b/testdata/default/core/BadSigAfterInvariant.t.sol new file mode 100644 index 000000000..6d303b04b --- /dev/null +++ b/testdata/default/core/BadSigAfterInvariant.t.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; + +contract BadSigAfterInvariant is DSTest { + function afterinvariant() public {} + + function testShouldPassWithWarning() public { + assert(true); + } +} diff --git a/testdata/core/ContractEnvironment.t.sol b/testdata/default/core/ContractEnvironment.t.sol similarity index 100% rename from testdata/core/ContractEnvironment.t.sol rename to testdata/default/core/ContractEnvironment.t.sol diff --git a/testdata/core/DSStyle.t.sol b/testdata/default/core/DSStyle.t.sol similarity index 100% rename from testdata/core/DSStyle.t.sol rename to testdata/default/core/DSStyle.t.sol diff --git a/testdata/core/FailingSetup.t.sol b/testdata/default/core/FailingSetup.t.sol similarity index 100% rename from testdata/core/FailingSetup.t.sol rename to testdata/default/core/FailingSetup.t.sol diff --git a/testdata/core/FailingTestAfterFailedSetup.t.sol b/testdata/default/core/FailingTestAfterFailedSetup.t.sol similarity index 100% rename from testdata/core/FailingTestAfterFailedSetup.t.sol rename to testdata/default/core/FailingTestAfterFailedSetup.t.sol diff --git a/testdata/default/core/MultipleAfterInvariant.t.sol b/testdata/default/core/MultipleAfterInvariant.t.sol new file mode 100644 index 000000000..446e76cbb --- /dev/null +++ b/testdata/default/core/MultipleAfterInvariant.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; + +contract MultipleAfterInvariant is DSTest { + function afterInvariant() public {} + + function afterinvariant() public {} + + function testFailShouldBeMarkedAsFailedBecauseOfAfterInvariant() public { + assert(true); + } +} diff --git a/testdata/core/MultipleSetup.t.sol b/testdata/default/core/MultipleSetup.t.sol similarity index 100% rename from testdata/core/MultipleSetup.t.sol rename to testdata/default/core/MultipleSetup.t.sol diff --git a/testdata/core/PaymentFailure.t.sol b/testdata/default/core/PaymentFailure.t.sol similarity index 93% rename from testdata/core/PaymentFailure.t.sol rename to testdata/default/core/PaymentFailure.t.sol index 21558cf9c..d4751b2d5 100644 --- a/testdata/core/PaymentFailure.t.sol +++ b/testdata/default/core/PaymentFailure.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract Payable { function pay() public payable {} diff --git a/testdata/core/Reverting.t.sol b/testdata/default/core/Reverting.t.sol similarity index 100% rename from testdata/core/Reverting.t.sol rename to testdata/default/core/Reverting.t.sol diff --git a/testdata/core/SetupConsistency.t.sol b/testdata/default/core/SetupConsistency.t.sol similarity index 100% rename from testdata/core/SetupConsistency.t.sol rename to testdata/default/core/SetupConsistency.t.sol diff --git a/testdata/fork/DssExecLib.sol b/testdata/default/fork/DssExecLib.sol similarity index 100% rename from testdata/fork/DssExecLib.sol rename to testdata/default/fork/DssExecLib.sol diff --git a/testdata/fork/ForkSame_1.t.sol b/testdata/default/fork/ForkSame_1.t.sol similarity index 95% rename from testdata/fork/ForkSame_1.t.sol rename to testdata/default/fork/ForkSame_1.t.sol index 01c89e6e2..bff9678f6 100644 --- a/testdata/fork/ForkSame_1.t.sol +++ b/testdata/default/fork/ForkSame_1.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract ForkTest is DSTest { address constant WETH_TOKEN_ADDR = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; diff --git a/testdata/fork/ForkSame_2.t.sol b/testdata/default/fork/ForkSame_2.t.sol similarity index 95% rename from testdata/fork/ForkSame_2.t.sol rename to testdata/default/fork/ForkSame_2.t.sol index 01c89e6e2..bff9678f6 100644 --- a/testdata/fork/ForkSame_2.t.sol +++ b/testdata/default/fork/ForkSame_2.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract ForkTest is DSTest { address constant WETH_TOKEN_ADDR = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; diff --git a/testdata/fork/LaunchFork.t.sol b/testdata/default/fork/LaunchFork.t.sol similarity index 100% rename from testdata/fork/LaunchFork.t.sol rename to testdata/default/fork/LaunchFork.t.sol diff --git a/testdata/fork/Transact.t.sol b/testdata/default/fork/Transact.t.sol similarity index 99% rename from testdata/fork/Transact.t.sol rename to testdata/default/fork/Transact.t.sol index 79c53fa3f..ec803906d 100644 --- a/testdata/fork/Transact.t.sol +++ b/testdata/default/fork/Transact.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; import "../logs/console.sol"; interface IERC20 { diff --git a/testdata/fs/Default.t.sol b/testdata/default/fs/Default.t.sol similarity index 97% rename from testdata/fs/Default.t.sol rename to testdata/default/fs/Default.t.sol index 7ef8c5bd2..5e776e696 100644 --- a/testdata/fs/Default.t.sol +++ b/testdata/default/fs/Default.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract DefaultAccessTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/fs/Disabled.t.sol b/testdata/default/fs/Disabled.t.sol similarity index 97% rename from testdata/fs/Disabled.t.sol rename to testdata/default/fs/Disabled.t.sol index 4c818d914..4efe9affc 100644 --- a/testdata/fs/Disabled.t.sol +++ b/testdata/default/fs/Disabled.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract DisabledTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/fuzz/Fuzz.t.sol b/testdata/default/fuzz/Fuzz.t.sol similarity index 100% rename from testdata/fuzz/Fuzz.t.sol rename to testdata/default/fuzz/Fuzz.t.sol diff --git a/testdata/fuzz/FuzzCollection.t.sol b/testdata/default/fuzz/FuzzCollection.t.sol similarity index 100% rename from testdata/fuzz/FuzzCollection.t.sol rename to testdata/default/fuzz/FuzzCollection.t.sol diff --git a/testdata/default/fuzz/FuzzFailurePersist.t.sol b/testdata/default/fuzz/FuzzFailurePersist.t.sol new file mode 100644 index 000000000..0823f29fb --- /dev/null +++ b/testdata/default/fuzz/FuzzFailurePersist.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +struct TestTuple { + address user; + uint256 amount; +} + +contract FuzzFailurePersistTest is DSTest { + Vm vm = Vm(HEVM_ADDRESS); + + function test_persist_fuzzed_failure( + uint256 x, + int256 y, + address addr, + bool cond, + string calldata test, + TestTuple calldata tuple, + address[] calldata addresses + ) public { + // dummy assume to trigger runs + vm.assume(x > 1 && x < 1111111111111111111111111111); + vm.assume(y > 1 && y < 1111111111111111111111111111); + require(false); + } +} diff --git a/testdata/fuzz/FuzzInt.t.sol b/testdata/default/fuzz/FuzzInt.t.sol similarity index 67% rename from testdata/fuzz/FuzzInt.t.sol rename to testdata/default/fuzz/FuzzInt.t.sol index aac0825db..071727f6d 100644 --- a/testdata/fuzz/FuzzInt.t.sol +++ b/testdata/default/fuzz/FuzzInt.t.sol @@ -3,7 +3,8 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -// See https://github.com/foundry-rs/foundry/pull/735 for context +// https://github.com/foundry-rs/foundry/pull/735 behavior changed with https://github.com/foundry-rs/foundry/issues/3521 +// random values (instead edge cases) are generated if no fixtures defined contract FuzzNumbersTest is DSTest { function testPositive(int256) public { assertTrue(true); @@ -14,31 +15,31 @@ contract FuzzNumbersTest is DSTest { } function testNegative0(int256 val) public { - assertTrue(val != 0); + assertTrue(val == 0); } function testNegative1(int256 val) public { - assertTrue(val != -1); + assertTrue(val == -1); } function testNegative2(int128 val) public { - assertTrue(val != 1); + assertTrue(val == 1); } function testNegativeMax0(int256 val) public { - assertTrue(val != type(int256).max); + assertTrue(val == type(int256).max); } function testNegativeMax1(int256 val) public { - assertTrue(val != type(int256).max - 2); + assertTrue(val == type(int256).max - 2); } function testNegativeMin0(int256 val) public { - assertTrue(val != type(int256).min); + assertTrue(val == type(int256).min); } function testNegativeMin1(int256 val) public { - assertTrue(val != type(int256).min + 2); + assertTrue(val == type(int256).min + 2); } function testEquality(int256 x, int256 y) public { diff --git a/testdata/fuzz/FuzzPositive.t.sol b/testdata/default/fuzz/FuzzPositive.t.sol similarity index 100% rename from testdata/fuzz/FuzzPositive.t.sol rename to testdata/default/fuzz/FuzzPositive.t.sol diff --git a/testdata/default/fuzz/FuzzScrapeBytecode.t.sol b/testdata/default/fuzz/FuzzScrapeBytecode.t.sol new file mode 100644 index 000000000..ffded67f0 --- /dev/null +++ b/testdata/default/fuzz/FuzzScrapeBytecode.t.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; + +// https://github.com/foundry-rs/foundry/issues/1168 +contract FuzzerDict { + // Immutables should get added to the dictionary. + address public immutable immutableOwner; + // Regular storage variables should also get added to the dictionary. + address public storageOwner; + + constructor(address _immutableOwner, address _storageOwner) { + immutableOwner = _immutableOwner; + storageOwner = _storageOwner; + } +} + +contract FuzzerDictTest is DSTest { + FuzzerDict fuzzerDict; + + function setUp() public { + fuzzerDict = new FuzzerDict(address(100), address(200)); + } + + // Fuzzer should try `fuzzerDict.immutableOwner()` as input, causing this to fail + function testImmutableOwner(address who) public { + assertTrue(who != fuzzerDict.immutableOwner()); + } + + // Fuzzer should try `fuzzerDict.storageOwner()` as input, causing this to fail + function testStorageOwner(address who) public { + assertTrue(who != fuzzerDict.storageOwner()); + } +} diff --git a/testdata/fuzz/FuzzUint.t.sol b/testdata/default/fuzz/FuzzUint.t.sol similarity index 69% rename from testdata/fuzz/FuzzUint.t.sol rename to testdata/default/fuzz/FuzzUint.t.sol index 5ae90a57b..923c2980f 100644 --- a/testdata/fuzz/FuzzUint.t.sol +++ b/testdata/default/fuzz/FuzzUint.t.sol @@ -3,7 +3,8 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -// See https://github.com/foundry-rs/foundry/pull/735 for context +// https://github.com/foundry-rs/foundry/pull/735 behavior changed with https://github.com/foundry-rs/foundry/issues/3521 +// random values (instead edge cases) are generated if no fixtures defined contract FuzzNumbersTest is DSTest { function testPositive(uint256) public { assertTrue(true); @@ -14,19 +15,19 @@ contract FuzzNumbersTest is DSTest { } function testNegative0(uint256 val) public { - assertTrue(val != 0); + assertTrue(val == 0); } function testNegative2(uint256 val) public { - assertTrue(val != 2); + assertTrue(val == 2); } function testNegative2Max(uint256 val) public { - assertTrue(val != type(uint256).max - 2); + assertTrue(val == type(uint256).max - 2); } function testNegativeMax(uint256 val) public { - assertTrue(val != type(uint256).max); + assertTrue(val == type(uint256).max); } function testEquality(uint256 x, uint256 y) public { diff --git a/testdata/default/fuzz/invariant/common/InvariantAfterInvariant.t.sol b/testdata/default/fuzz/invariant/common/InvariantAfterInvariant.t.sol new file mode 100644 index 000000000..3030b43e0 --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantAfterInvariant.t.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "ds-test/test.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +contract AfterInvariantHandler { + uint256 public count; + + function inc() external { + count += 1; + } +} + +contract InvariantAfterInvariantTest is DSTest { + AfterInvariantHandler handler; + + function setUp() public { + handler = new AfterInvariantHandler(); + } + + function targetSelectors() public returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = handler.inc.selector; + targets[0] = FuzzSelector(address(handler), selectors); + return targets; + } + + function afterInvariant() public { + require(handler.count() < 10, "afterInvariant failure"); + } + + /// forge-config: default.invariant.runs = 1 + /// forge-config: default.invariant.depth = 11 + function invariant_after_invariant_failure() public view { + require(handler.count() < 20, "invariant after invariant failure"); + } + + /// forge-config: default.invariant.runs = 1 + /// forge-config: default.invariant.depth = 11 + function invariant_failure() public view { + require(handler.count() < 9, "invariant failure"); + } + + /// forge-config: default.invariant.runs = 1 + /// forge-config: default.invariant.depth = 5 + function invariant_success() public view { + require(handler.count() < 11, "invariant should not fail"); + } +} diff --git a/testdata/default/fuzz/invariant/common/InvariantAssume.t.sol b/testdata/default/fuzz/invariant/common/InvariantAssume.t.sol new file mode 100644 index 000000000..9808a870f --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantAssume.t.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.0; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract Handler is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function doSomething(uint256 param) public { + vm.assume(param == 0); + } +} + +contract InvariantAssume is DSTest { + Handler handler; + + function setUp() public { + handler = new Handler(); + } + + function invariant_dummy() public {} +} diff --git a/testdata/default/fuzz/invariant/common/InvariantCalldataDictionary.t.sol b/testdata/default/fuzz/invariant/common/InvariantCalldataDictionary.t.sol new file mode 100644 index 000000000..5387b020d --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantCalldataDictionary.t.sol @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +// https://github.com/foundry-rs/foundry/issues/5868 +contract Owned { + address public owner; + address private ownerCandidate; + + constructor() { + owner = msg.sender; + } + + modifier onlyOwner() { + require(msg.sender == owner); + _; + } + + modifier onlyOwnerCandidate() { + require(msg.sender == ownerCandidate); + _; + } + + function transferOwnership(address candidate) external onlyOwner { + ownerCandidate = candidate; + } + + function acceptOwnership() external onlyOwnerCandidate { + owner = ownerCandidate; + } +} + +contract Handler is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + Owned owned; + + constructor(Owned _owned) { + owned = _owned; + } + + function transferOwnership(address sender, address candidate) external { + vm.assume(sender != address(0)); + vm.prank(sender); + owned.transferOwnership(candidate); + } + + function acceptOwnership(address sender) external { + vm.assume(sender != address(0)); + vm.prank(sender); + owned.acceptOwnership(); + } +} + +contract InvariantCalldataDictionary is DSTest { + address owner; + Owned owned; + Handler handler; + address[] actors; + + function setUp() public { + owner = address(this); + owned = new Owned(); + handler = new Handler(owned); + actors.push(owner); + actors.push(address(777)); + } + + function targetSelectors() public returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](2); + selectors[0] = handler.transferOwnership.selector; + selectors[1] = handler.acceptOwnership.selector; + targets[0] = FuzzSelector(address(handler), selectors); + return targets; + } + + function fixtureSender() external returns (address[] memory) { + return actors; + } + + function fixtureCandidate() external returns (address[] memory) { + return actors; + } + + function invariant_owner_never_changes() public { + assertEq(owned.owner(), owner); + } +} diff --git a/testdata/default/fuzz/invariant/common/InvariantCustomError.t.sol b/testdata/default/fuzz/invariant/common/InvariantCustomError.t.sol new file mode 100644 index 000000000..737cf5ba9 --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantCustomError.t.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.0; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract ContractWithCustomError { + error InvariantCustomError(uint256, string); + + function revertWithInvariantCustomError() external { + revert InvariantCustomError(111, "custom"); + } +} + +contract Handler is DSTest { + ContractWithCustomError target; + + constructor() { + target = new ContractWithCustomError(); + } + + function revertTarget() external { + target.revertWithInvariantCustomError(); + } +} + +contract InvariantCustomError is DSTest { + Handler handler; + + function setUp() external { + handler = new Handler(); + } + + function invariant_decode_error() public {} +} diff --git a/testdata/default/fuzz/invariant/common/InvariantExcludedSenders.t.sol b/testdata/default/fuzz/invariant/common/InvariantExcludedSenders.t.sol new file mode 100644 index 000000000..8fe0bed2c --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantExcludedSenders.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "ds-test/test.sol"; + +contract InvariantSenders { + function checkSender() external { + require(msg.sender != 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D, "sender cannot be cheatcode address"); + require(msg.sender != 0x000000000000000000636F6e736F6c652e6c6f67, "sender cannot be console address"); + require(msg.sender != 0x4e59b44847b379578588920cA78FbF26c0B4956C, "sender cannot be CREATE2 deployer"); + } +} + +contract InvariantExcludedSendersTest is DSTest { + InvariantSenders target; + + function setUp() public { + target = new InvariantSenders(); + } + + function invariant_check_sender() public view {} +} diff --git a/testdata/default/fuzz/invariant/common/InvariantFixtures.t.sol b/testdata/default/fuzz/invariant/common/InvariantFixtures.t.sol new file mode 100644 index 000000000..b3f1e17cb --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantFixtures.t.sol @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.0; + +import "ds-test/test.sol"; + +contract Target { + bool ownerFound; + bool amountFound; + bool magicFound; + bool keyFound; + bool backupFound; + bool extraStringFound; + + function fuzzWithFixtures( + address owner_, + uint256 _amount, + int32 magic, + bytes32 key, + bytes memory backup, + string memory extra + ) external { + if (owner_ == address(0x6B175474E89094C44Da98b954EedeAC495271d0F)) { + ownerFound = true; + } + if (_amount == 1122334455) amountFound = true; + if (magic == -777) magicFound = true; + if (key == "abcd1234") keyFound = true; + if (keccak256(backup) == keccak256("qwerty1234")) backupFound = true; + if (keccak256(abi.encodePacked(extra)) == keccak256(abi.encodePacked("112233aabbccdd"))) { + extraStringFound = true; + } + } + + function isCompromised() public view returns (bool) { + return ownerFound && amountFound && magicFound && keyFound && backupFound && extraStringFound; + } +} + +/// Try to compromise target contract by finding all accepted values using fixtures. +contract InvariantFixtures is DSTest { + Target target; + address[] public fixture_owner_ = [address(0x6B175474E89094C44Da98b954EedeAC495271d0F)]; + uint256[] public fixture_amount = [1, 2, 1122334455]; + + function setUp() public { + target = new Target(); + } + + function fixtureMagic() external returns (int32[2] memory) { + int32[2] memory magic; + magic[0] = -777; + magic[1] = 777; + return magic; + } + + function fixtureKey() external pure returns (bytes32[] memory) { + bytes32[] memory keyFixture = new bytes32[](1); + keyFixture[0] = "abcd1234"; + return keyFixture; + } + + function fixtureBackup() external pure returns (bytes[] memory) { + bytes[] memory backupFixture = new bytes[](1); + backupFixture[0] = "qwerty1234"; + return backupFixture; + } + + function fixtureExtra() external pure returns (string[] memory) { + string[] memory extraFixture = new string[](1); + extraFixture[0] = "112233aabbccdd"; + return extraFixture; + } + + function invariant_target_not_compromised() public { + assertEq(target.isCompromised(), false); + } +} diff --git a/testdata/fuzz/invariant/common/InvariantHandlerFailure.t.sol b/testdata/default/fuzz/invariant/common/InvariantHandlerFailure.t.sol similarity index 100% rename from testdata/fuzz/invariant/common/InvariantHandlerFailure.t.sol rename to testdata/default/fuzz/invariant/common/InvariantHandlerFailure.t.sol diff --git a/testdata/fuzz/invariant/common/InvariantInnerContract.t.sol b/testdata/default/fuzz/invariant/common/InvariantInnerContract.t.sol similarity index 100% rename from testdata/fuzz/invariant/common/InvariantInnerContract.t.sol rename to testdata/default/fuzz/invariant/common/InvariantInnerContract.t.sol diff --git a/testdata/default/fuzz/invariant/common/InvariantPreserveState.t.sol b/testdata/default/fuzz/invariant/common/InvariantPreserveState.t.sol new file mode 100644 index 000000000..546980136 --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantPreserveState.t.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +// https://github.com/foundry-rs/foundry/issues/7219 + +contract Handler is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function thisFunctionReverts() external { + if (block.number < 10) {} else { + revert(); + } + } + + function advanceTime(uint256 blocks) external { + blocks = blocks % 10; + vm.roll(block.number + blocks); + vm.warp(block.timestamp + blocks * 12); + } +} + +contract InvariantPreserveState is DSTest { + Handler handler; + + function setUp() public { + handler = new Handler(); + } + + function targetSelectors() public returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](2); + selectors[0] = handler.thisFunctionReverts.selector; + selectors[1] = handler.advanceTime.selector; + targets[0] = FuzzSelector(address(handler), selectors); + return targets; + } + + function invariant_preserve_state() public { + assertTrue(true); + } +} diff --git a/testdata/fuzz/invariant/common/InvariantReentrancy.t.sol b/testdata/default/fuzz/invariant/common/InvariantReentrancy.t.sol similarity index 64% rename from testdata/fuzz/invariant/common/InvariantReentrancy.t.sol rename to testdata/default/fuzz/invariant/common/InvariantReentrancy.t.sol index 086d5f99a..06b4b21d7 100644 --- a/testdata/fuzz/invariant/common/InvariantReentrancy.t.sol +++ b/testdata/default/fuzz/invariant/common/InvariantReentrancy.t.sol @@ -5,7 +5,9 @@ import "ds-test/test.sol"; contract Malicious { function world() public { - // Does not matter, since it will get overridden. + // add code so contract is accounted as valid sender + // see https://github.com/foundry-rs/foundry/issues/4245 + payable(msg.sender).transfer(1); } } @@ -39,6 +41,14 @@ contract InvariantReentrancy is DSTest { vuln = new Vulnerable(address(mal)); } + // do not include `mal` in identified contracts + // see https://github.com/foundry-rs/foundry/issues/4245 + function targetContracts() public view returns (address[] memory) { + address[] memory targets = new address[](1); + targets[0] = address(vuln); + return targets; + } + function invariantNotStolen() public { require(vuln.stolen() == false, "stolen"); } diff --git a/testdata/default/fuzz/invariant/common/InvariantRollFork.t.sol b/testdata/default/fuzz/invariant/common/InvariantRollFork.t.sol new file mode 100644 index 000000000..d74509cd4 --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantRollFork.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +interface IERC20 { + function totalSupply() external view returns (uint256 supply); +} + +contract RollForkHandler is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + uint256 public totalSupply; + + function work() external { + vm.rollFork(block.number + 1); + totalSupply = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F).totalSupply(); + } +} + +contract InvariantRollForkBlockTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + RollForkHandler forkHandler; + + function setUp() public { + vm.createSelectFork("rpcAlias", 19812632); + forkHandler = new RollForkHandler(); + } + + /// forge-config: default.invariant.runs = 2 + /// forge-config: default.invariant.depth = 4 + function invariant_fork_handler_block() public { + require(block.number < 19812634, "too many blocks mined"); + } +} + +contract InvariantRollForkStateTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + RollForkHandler forkHandler; + + function setUp() public { + vm.createSelectFork("rpcAlias", 19812632); + forkHandler = new RollForkHandler(); + } + + /// forge-config: default.invariant.runs = 1 + function invariant_fork_handler_state() public { + require(forkHandler.totalSupply() < 3254378807384273078310283461, "wrong supply"); + } +} diff --git a/testdata/default/fuzz/invariant/common/InvariantScrapeValues.t.sol b/testdata/default/fuzz/invariant/common/InvariantScrapeValues.t.sol new file mode 100644 index 000000000..40824a260 --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantScrapeValues.t.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract FindFromReturnValue { + bool public found = false; + + function seed() public returns (int256) { + int256 mystery = 13337; + return (1337 + mystery); + } + + function find(int256 i) public { + int256 mystery = 13337; + if (i == 1337 + mystery) { + found = true; + } + } +} + +contract FindFromReturnValueTest is DSTest { + FindFromReturnValue target; + + function setUp() public { + target = new FindFromReturnValue(); + } + + /// forge-config: default.invariant.runs = 50 + /// forge-config: default.invariant.depth = 300 + /// forge-config: default.invariant.fail-on-revert = true + function invariant_value_not_found() public view { + require(!target.found(), "value from return found"); + } +} + +contract FindFromLogValue { + event FindFromLog(int256 indexed mystery, bytes32 rand); + + bool public found = false; + + function seed() public { + int256 mystery = 13337; + emit FindFromLog(1337 + mystery, keccak256(abi.encodePacked("mystery"))); + } + + function find(int256 i) public { + int256 mystery = 13337; + if (i == 1337 + mystery) { + found = true; + } + } +} + +contract FindFromLogValueTest is DSTest { + FindFromLogValue target; + + function setUp() public { + target = new FindFromLogValue(); + } + + /// forge-config: default.invariant.runs = 50 + /// forge-config: default.invariant.depth = 300 + /// forge-config: default.invariant.fail-on-revert = true + function invariant_value_not_found() public view { + require(!target.found(), "value from logs found"); + } +} diff --git a/testdata/default/fuzz/invariant/common/InvariantSelectorsWeight.t.sol b/testdata/default/fuzz/invariant/common/InvariantSelectorsWeight.t.sol new file mode 100644 index 000000000..918fd7b01 --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantSelectorsWeight.t.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "ds-test/test.sol"; + +contract HandlerWithOneSelector { + uint256 public hit1; + + function selector1() external { + hit1 += 1; + } +} + +contract HandlerWithFiveSelectors { + uint256 public hit2; + uint256 public hit3; + uint256 public hit4; + uint256 public hit5; + uint256 public hit6; + + function selector2() external { + hit2 += 1; + } + + function selector3() external { + hit3 += 1; + } + + function selector4() external { + hit4 += 1; + } + + function selector5() external { + hit5 += 1; + } + + function selector6() external { + hit6 += 1; + } +} + +contract HandlerWithFourSelectors { + uint256 public hit7; + uint256 public hit8; + uint256 public hit9; + uint256 public hit10; + + function selector7() external { + hit7 += 1; + } + + function selector8() external { + hit8 += 1; + } + + function selector9() external { + hit9 += 1; + } + + function selector10() external { + hit10 += 1; + } +} + +contract InvariantSelectorsWeightTest is DSTest { + HandlerWithOneSelector handlerOne; + HandlerWithFiveSelectors handlerTwo; + HandlerWithFourSelectors handlerThree; + + function setUp() public { + handlerOne = new HandlerWithOneSelector(); + handlerTwo = new HandlerWithFiveSelectors(); + handlerThree = new HandlerWithFourSelectors(); + } + + function afterInvariant() public { + // selector hits before and after https://github.com/foundry-rs/foundry/issues/2986 + // hit1: 11 | hit2: 4 | hit3: 0 | hit4: 0 | hit5: 4 | hit6: 1 | hit7: 2 | hit8: 2 | hit9: 2 | hit10: 4 + // hit1: 2 | hit2: 5 | hit3: 4 | hit4: 5 | hit5: 3 | hit6: 1 | hit7: 4 | hit8: 1 | hit9: 1 | hit10: 4 + + uint256 hit1 = handlerOne.hit1(); + uint256 hit2 = handlerTwo.hit2(); + uint256 hit3 = handlerTwo.hit3(); + uint256 hit4 = handlerTwo.hit4(); + uint256 hit5 = handlerTwo.hit5(); + uint256 hit6 = handlerTwo.hit6(); + uint256 hit7 = handlerThree.hit7(); + uint256 hit8 = handlerThree.hit8(); + uint256 hit9 = handlerThree.hit9(); + uint256 hit10 = handlerThree.hit10(); + + require( + hit1 > 0 && hit2 > 0 && hit3 > 0 && hit4 > 0 && hit5 > 0 && hit6 > 0 && hit7 > 0 && hit8 > 0 && hit9 > 0 + && hit10 > 0 + ); + } + + /// forge-config: default.invariant.runs = 1 + /// forge-config: default.invariant.depth = 30 + function invariant_selectors_weight() public view {} +} diff --git a/testdata/default/fuzz/invariant/common/InvariantShrinkBigSequence.t.sol b/testdata/default/fuzz/invariant/common/InvariantShrinkBigSequence.t.sol new file mode 100644 index 000000000..5699d9c45 --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantShrinkBigSequence.t.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract ShrinkBigSequence { + uint256 cond; + + function work(uint256 x) public { + if (x % 2 != 0 && x < 9000) { + cond++; + } + } + + function checkCond() public view { + require(cond < 77, "condition met"); + } +} + +contract ShrinkBigSequenceTest is DSTest { + ShrinkBigSequence target; + + function setUp() public { + target = new ShrinkBigSequence(); + } + + function invariant_shrink_big_sequence() public view { + target.checkCond(); + } +} diff --git a/testdata/default/fuzz/invariant/common/InvariantShrinkFailOnRevert.t.sol b/testdata/default/fuzz/invariant/common/InvariantShrinkFailOnRevert.t.sol new file mode 100644 index 000000000..d971367b6 --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantShrinkFailOnRevert.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract ShrinkFailOnRevert { + uint256 cond; + + function work(uint256 x) public { + if (x % 2 != 0 && x < 9000) { + cond++; + } + require(cond < 10, "condition met"); + } +} + +contract ShrinkFailOnRevertTest is DSTest { + ShrinkFailOnRevert target; + + function setUp() public { + target = new ShrinkFailOnRevert(); + } + + function invariant_shrink_fail_on_revert() public view {} +} diff --git a/testdata/default/fuzz/invariant/common/InvariantShrinkWithAssert.t.sol b/testdata/default/fuzz/invariant/common/InvariantShrinkWithAssert.t.sol new file mode 100644 index 000000000..d5dcfe674 --- /dev/null +++ b/testdata/default/fuzz/invariant/common/InvariantShrinkWithAssert.t.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } + + function decrement() public { + number--; + } + + function double() public { + number *= 2; + } + + function half() public { + number /= 2; + } + + function triple() public { + number *= 3; + } + + function third() public { + number /= 3; + } + + function quadruple() public { + number *= 4; + } + + function quarter() public { + number /= 4; + } +} + +contract Handler is DSTest { + Counter public counter; + + constructor(Counter _counter) { + counter = _counter; + counter.setNumber(0); + } + + function increment() public { + counter.increment(); + } + + function setNumber(uint256 x) public { + counter.setNumber(x); + } +} + +contract InvariantShrinkWithAssert is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + Counter public counter; + Handler handler; + + function setUp() public { + counter = new Counter(); + handler = new Handler(counter); + } + + function targetSelectors() public returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](2); + selectors[0] = handler.increment.selector; + selectors[1] = handler.setNumber.selector; + targets[0] = FuzzSelector(address(handler), selectors); + return targets; + } + + function invariant_with_assert() public { + assertTrue(counter.number() != 3, "wrong counter"); + } +} + +contract InvariantShrinkWithRequire is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + Counter public counter; + Handler handler; + + function setUp() public { + counter = new Counter(); + handler = new Handler(counter); + } + + function targetSelectors() public returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](2); + selectors[0] = handler.increment.selector; + selectors[1] = handler.setNumber.selector; + targets[0] = FuzzSelector(address(handler), selectors); + return targets; + } + + function invariant_with_require() public { + require(counter.number() != 3, "wrong counter"); + } +} diff --git a/testdata/fuzz/invariant/common/InvariantTest1.t.sol b/testdata/default/fuzz/invariant/common/InvariantTest1.t.sol similarity index 100% rename from testdata/fuzz/invariant/common/InvariantTest1.t.sol rename to testdata/default/fuzz/invariant/common/InvariantTest1.t.sol diff --git a/testdata/fuzz/invariant/storage/InvariantStorageTest.t.sol b/testdata/default/fuzz/invariant/storage/InvariantStorageTest.t.sol similarity index 100% rename from testdata/fuzz/invariant/storage/InvariantStorageTest.t.sol rename to testdata/default/fuzz/invariant/storage/InvariantStorageTest.t.sol diff --git a/testdata/fuzz/invariant/target/ExcludeContracts.t.sol b/testdata/default/fuzz/invariant/target/ExcludeContracts.t.sol similarity index 100% rename from testdata/fuzz/invariant/target/ExcludeContracts.t.sol rename to testdata/default/fuzz/invariant/target/ExcludeContracts.t.sol diff --git a/testdata/default/fuzz/invariant/target/ExcludeSelectors.t.sol b/testdata/default/fuzz/invariant/target/ExcludeSelectors.t.sol new file mode 100644 index 000000000..526da0c67 --- /dev/null +++ b/testdata/default/fuzz/invariant/target/ExcludeSelectors.t.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; + +struct FuzzSelector { + address addr; + bytes4[] selectors; +} + +contract Hello { + bool public world = false; + + function change() public { + world = true; + } + + function real_change() public { + world = false; + } +} + +contract ExcludeSelectors is DSTest { + Hello hello; + + function setUp() public { + hello = new Hello(); + } + + function excludeSelectors() public returns (FuzzSelector[] memory) { + FuzzSelector[] memory targets = new FuzzSelector[](1); + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = Hello.change.selector; + targets[0] = FuzzSelector(address(hello), selectors); + return targets; + } + + function invariantFalseWorld() public { + require(hello.world() == false, "true world"); + } +} diff --git a/testdata/fuzz/invariant/target/ExcludeSenders.t.sol b/testdata/default/fuzz/invariant/target/ExcludeSenders.t.sol similarity index 100% rename from testdata/fuzz/invariant/target/ExcludeSenders.t.sol rename to testdata/default/fuzz/invariant/target/ExcludeSenders.t.sol diff --git a/testdata/default/fuzz/invariant/target/FuzzedTargetContracts.t.sol b/testdata/default/fuzz/invariant/target/FuzzedTargetContracts.t.sol new file mode 100644 index 000000000..7988d5c8a --- /dev/null +++ b/testdata/default/fuzz/invariant/target/FuzzedTargetContracts.t.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; + +interface Vm { + function etch(address target, bytes calldata newRuntimeBytecode) external; +} + +// https://github.com/foundry-rs/foundry/issues/5625 +// https://github.com/foundry-rs/foundry/issues/6166 +// `Target.wrongSelector` is not called when handler added as `targetContract` +// `Target.wrongSelector` is called (and test fails) when no `targetContract` set +contract Target { + uint256 count; + + function wrongSelector() external { + revert("wrong target selector called"); + } + + function goodSelector() external { + count++; + } +} + +contract Handler is DSTest { + function increment() public { + Target(0x6B175474E89094C44Da98b954EedeAC495271d0F).goodSelector(); + } +} + +contract ExplicitTargetContract is DSTest { + Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + Handler handler; + + function setUp() public { + Target target = new Target(); + bytes memory targetCode = address(target).code; + vm.etch(address(0x6B175474E89094C44Da98b954EedeAC495271d0F), targetCode); + + handler = new Handler(); + } + + function targetContracts() public returns (address[] memory) { + address[] memory addrs = new address[](1); + addrs[0] = address(handler); + return addrs; + } + + function invariant_explicit_target() public {} +} + +contract DynamicTargetContract is DSTest { + Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); + Handler handler; + + function setUp() public { + Target target = new Target(); + bytes memory targetCode = address(target).code; + vm.etch(address(0x6B175474E89094C44Da98b954EedeAC495271d0F), targetCode); + + handler = new Handler(); + } + + function invariant_dynamic_targets() public {} +} diff --git a/testdata/fuzz/invariant/target/TargetContracts.t.sol b/testdata/default/fuzz/invariant/target/TargetContracts.t.sol similarity index 100% rename from testdata/fuzz/invariant/target/TargetContracts.t.sol rename to testdata/default/fuzz/invariant/target/TargetContracts.t.sol diff --git a/testdata/fuzz/invariant/target/TargetInterfaces.t.sol b/testdata/default/fuzz/invariant/target/TargetInterfaces.t.sol similarity index 100% rename from testdata/fuzz/invariant/target/TargetInterfaces.t.sol rename to testdata/default/fuzz/invariant/target/TargetInterfaces.t.sol diff --git a/testdata/fuzz/invariant/target/TargetSelectors.t.sol b/testdata/default/fuzz/invariant/target/TargetSelectors.t.sol similarity index 100% rename from testdata/fuzz/invariant/target/TargetSelectors.t.sol rename to testdata/default/fuzz/invariant/target/TargetSelectors.t.sol diff --git a/testdata/fuzz/invariant/target/TargetSenders.t.sol b/testdata/default/fuzz/invariant/target/TargetSenders.t.sol similarity index 100% rename from testdata/fuzz/invariant/target/TargetSenders.t.sol rename to testdata/default/fuzz/invariant/target/TargetSenders.t.sol diff --git a/testdata/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol b/testdata/default/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol similarity index 91% rename from testdata/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol rename to testdata/default/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol index ce81a51cf..bf457ab17 100644 --- a/testdata/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol +++ b/testdata/default/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol @@ -35,7 +35,7 @@ contract ExcludeArtifacts is DSTest { function excludeArtifacts() public returns (string[] memory) { string[] memory abis = new string[](1); - abis[0] = "fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol:Excluded"; + abis[0] = "default/fuzz/invariant/targetAbi/ExcludeArtifacts.t.sol:Excluded"; return abis; } diff --git a/testdata/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol b/testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol similarity index 65% rename from testdata/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol rename to testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol index e82e7b1a3..2957c57de 100644 --- a/testdata/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol +++ b/testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol @@ -3,8 +3,8 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -struct FuzzAbiSelector { - string contract_abi; +struct FuzzArtifactSelector { + string artifact; bytes4[] selectors; } @@ -27,11 +27,12 @@ contract TargetArtifactSelectors is DSTest { hello = new Hi(); } - function targetArtifactSelectors() public returns (FuzzAbiSelector[] memory) { - FuzzAbiSelector[] memory targets = new FuzzAbiSelector[](1); + function targetArtifactSelectors() public returns (FuzzArtifactSelector[] memory) { + FuzzArtifactSelector[] memory targets = new FuzzArtifactSelector[](1); bytes4[] memory selectors = new bytes4[](1); selectors[0] = Hi.no_change.selector; - targets[0] = FuzzAbiSelector("fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol:Hi", selectors); + targets[0] = + FuzzArtifactSelector("default/fuzz/invariant/targetAbi/TargetArtifactSelectors.t.sol:Hi", selectors); return targets; } diff --git a/testdata/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol b/testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol similarity index 71% rename from testdata/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol rename to testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol index 5592aa849..c12cae74f 100644 --- a/testdata/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol +++ b/testdata/default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol @@ -3,8 +3,8 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -struct FuzzAbiSelector { - string contract_abi; +struct FuzzArtifactSelector { + string artifact; bytes4[] selectors; } @@ -46,16 +46,20 @@ contract TargetArtifactSelectors2 is DSTest { parent = new Parent(); } - function targetArtifactSelectors() public returns (FuzzAbiSelector[] memory) { - FuzzAbiSelector[] memory targets = new FuzzAbiSelector[](2); + function targetArtifactSelectors() public returns (FuzzArtifactSelector[] memory) { + FuzzArtifactSelector[] memory targets = new FuzzArtifactSelector[](2); bytes4[] memory selectors_child = new bytes4[](1); selectors_child[0] = Child.change_parent.selector; - targets[0] = FuzzAbiSelector("fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol:Child", selectors_child); + targets[0] = FuzzArtifactSelector( + "default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol:Child", selectors_child + ); bytes4[] memory selectors_parent = new bytes4[](1); selectors_parent[0] = Parent.create.selector; - targets[1] = FuzzAbiSelector("fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol:Parent", selectors_parent); + targets[1] = FuzzArtifactSelector( + "default/fuzz/invariant/targetAbi/TargetArtifactSelectors2.t.sol:Parent", selectors_parent + ); return targets; } diff --git a/testdata/fuzz/invariant/targetAbi/TargetArtifacts.t.sol b/testdata/default/fuzz/invariant/targetAbi/TargetArtifacts.t.sol similarity index 91% rename from testdata/fuzz/invariant/targetAbi/TargetArtifacts.t.sol rename to testdata/default/fuzz/invariant/targetAbi/TargetArtifacts.t.sol index d3eb2cf9d..ea86ab135 100644 --- a/testdata/fuzz/invariant/targetAbi/TargetArtifacts.t.sol +++ b/testdata/default/fuzz/invariant/targetAbi/TargetArtifacts.t.sol @@ -30,7 +30,7 @@ contract TargetArtifacts is DSTest { function targetArtifacts() public returns (string[] memory) { string[] memory abis = new string[](1); - abis[0] = "fuzz/invariant/targetAbi/TargetArtifacts.t.sol:Targeted"; + abis[0] = "default/fuzz/invariant/targetAbi/TargetArtifacts.t.sol:Targeted"; return abis; } diff --git a/testdata/inline/FuzzInlineConf.t.sol b/testdata/default/inline/FuzzInlineConf.t.sol similarity index 100% rename from testdata/inline/FuzzInlineConf.t.sol rename to testdata/default/inline/FuzzInlineConf.t.sol diff --git a/testdata/inline/InvariantInlineConf.t.sol b/testdata/default/inline/InvariantInlineConf.t.sol similarity index 100% rename from testdata/inline/InvariantInlineConf.t.sol rename to testdata/default/inline/InvariantInlineConf.t.sol diff --git a/testdata/linking/cycle/Cycle.t.sol b/testdata/default/linking/cycle/Cycle.t.sol similarity index 100% rename from testdata/linking/cycle/Cycle.t.sol rename to testdata/default/linking/cycle/Cycle.t.sol diff --git a/testdata/linking/duplicate/Duplicate.t.sol b/testdata/default/linking/duplicate/Duplicate.t.sol similarity index 100% rename from testdata/linking/duplicate/Duplicate.t.sol rename to testdata/default/linking/duplicate/Duplicate.t.sol diff --git a/testdata/linking/nested/Nested.t.sol b/testdata/default/linking/nested/Nested.t.sol similarity index 100% rename from testdata/linking/nested/Nested.t.sol rename to testdata/default/linking/nested/Nested.t.sol diff --git a/testdata/linking/simple/Simple.t.sol b/testdata/default/linking/simple/Simple.t.sol similarity index 100% rename from testdata/linking/simple/Simple.t.sol rename to testdata/default/linking/simple/Simple.t.sol diff --git a/testdata/logs/DebugLogs.t.sol b/testdata/default/logs/DebugLogs.t.sol similarity index 100% rename from testdata/logs/DebugLogs.t.sol rename to testdata/default/logs/DebugLogs.t.sol diff --git a/testdata/logs/HardhatLogs.t.sol b/testdata/default/logs/HardhatLogs.t.sol similarity index 100% rename from testdata/logs/HardhatLogs.t.sol rename to testdata/default/logs/HardhatLogs.t.sol diff --git a/testdata/logs/console.sol b/testdata/default/logs/console.sol similarity index 100% rename from testdata/logs/console.sol rename to testdata/default/logs/console.sol diff --git a/testdata/repros/Issue2623.t.sol b/testdata/default/repros/Issue2623.t.sol similarity index 95% rename from testdata/repros/Issue2623.t.sol rename to testdata/default/repros/Issue2623.t.sol index cf91d10a5..8534aeeaf 100644 --- a/testdata/repros/Issue2623.t.sol +++ b/testdata/default/repros/Issue2623.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/2623 contract Issue2623Test is DSTest { diff --git a/testdata/repros/Issue2629.t.sol b/testdata/default/repros/Issue2629.t.sol similarity index 96% rename from testdata/repros/Issue2629.t.sol rename to testdata/default/repros/Issue2629.t.sol index 296ebfc32..a1f430858 100644 --- a/testdata/repros/Issue2629.t.sol +++ b/testdata/default/repros/Issue2629.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/2629 contract Issue2629Test is DSTest { diff --git a/testdata/repros/Issue2723.t.sol b/testdata/default/repros/Issue2723.t.sol similarity index 95% rename from testdata/repros/Issue2723.t.sol rename to testdata/default/repros/Issue2723.t.sol index 6ecd7df8d..c260f9467 100644 --- a/testdata/repros/Issue2723.t.sol +++ b/testdata/default/repros/Issue2723.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/2723 contract Issue2723Test is DSTest { diff --git a/testdata/default/repros/Issue2851.t.sol b/testdata/default/repros/Issue2851.t.sol new file mode 100644 index 000000000..f90a5f7c5 --- /dev/null +++ b/testdata/default/repros/Issue2851.t.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.1; + +import "ds-test/test.sol"; + +contract Backdoor { + uint256 public number = 1; + + function backdoor(uint256 newNumber) public payable { + uint256 x = newNumber - 1; + if (x == 6912213124124531) { + number = 0; + } + } +} + +// https://github.com/foundry-rs/foundry/issues/2851 +contract Issue2851Test is DSTest { + Backdoor back; + + function setUp() public { + back = new Backdoor(); + } + + function invariantNotZero() public { + assertEq(back.number(), 1); + } +} diff --git a/testdata/repros/Issue2898.t.sol b/testdata/default/repros/Issue2898.t.sol similarity index 96% rename from testdata/repros/Issue2898.t.sol rename to testdata/default/repros/Issue2898.t.sol index 6f6eb5e35..23de35bcd 100644 --- a/testdata/repros/Issue2898.t.sol +++ b/testdata/default/repros/Issue2898.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; import "../logs/console.sol"; // https://github.com/foundry-rs/foundry/issues/2898 diff --git a/testdata/repros/Issue2956.t.sol b/testdata/default/repros/Issue2956.t.sol similarity index 82% rename from testdata/repros/Issue2956.t.sol rename to testdata/default/repros/Issue2956.t.sol index 8e9841e2b..9d9e5f9ac 100644 --- a/testdata/repros/Issue2956.t.sol +++ b/testdata/default/repros/Issue2956.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/2956 contract Issue2956Test is DSTest { @@ -11,7 +11,7 @@ contract Issue2956Test is DSTest { uint256 fork2; function setUp() public { - fork1 = vm.createFork("https://goerli.infura.io/v3/b9794ad1ddf84dfb8c34d6bb5dca2001", 7475589); + fork1 = vm.createFork("rpcAliasSepolia", 5565573); fork2 = vm.createFork("https://api.avax-test.network/ext/bc/C/rpc", 12880747); } @@ -28,7 +28,7 @@ contract Issue2956Test is DSTest { new Counter(); vm.selectFork(fork1); - assertEq(vm.getNonce(user), 3); + assertEq(vm.getNonce(user), 1); vm.prank(user); new Counter(); } diff --git a/testdata/repros/Issue2984.t.sol b/testdata/default/repros/Issue2984.t.sol similarity index 96% rename from testdata/repros/Issue2984.t.sol rename to testdata/default/repros/Issue2984.t.sol index 223866926..8e55d5dae 100644 --- a/testdata/repros/Issue2984.t.sol +++ b/testdata/default/repros/Issue2984.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/2984 contract Issue2984Test is DSTest { diff --git a/testdata/repros/Issue3055.t.sol b/testdata/default/repros/Issue3055.t.sol similarity index 97% rename from testdata/repros/Issue3055.t.sol rename to testdata/default/repros/Issue3055.t.sol index 0a94e4d2a..cacf5282f 100644 --- a/testdata/repros/Issue3055.t.sol +++ b/testdata/default/repros/Issue3055.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3055 contract Issue3055Test is DSTest { diff --git a/testdata/repros/Issue3077.t.sol b/testdata/default/repros/Issue3077.t.sol similarity index 97% rename from testdata/repros/Issue3077.t.sol rename to testdata/default/repros/Issue3077.t.sol index 36cfb071c..cc76b57b6 100644 --- a/testdata/repros/Issue3077.t.sol +++ b/testdata/default/repros/Issue3077.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3077 abstract contract ZeroState is DSTest { diff --git a/testdata/repros/Issue3110.t.sol b/testdata/default/repros/Issue3110.t.sol similarity index 97% rename from testdata/repros/Issue3110.t.sol rename to testdata/default/repros/Issue3110.t.sol index 259a467d3..7a2622427 100644 --- a/testdata/repros/Issue3110.t.sol +++ b/testdata/default/repros/Issue3110.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3110 abstract contract ZeroState is DSTest { diff --git a/testdata/repros/Issue3119.t.sol b/testdata/default/repros/Issue3119.t.sol similarity index 96% rename from testdata/repros/Issue3119.t.sol rename to testdata/default/repros/Issue3119.t.sol index 80f539660..5c94b4c5f 100644 --- a/testdata/repros/Issue3119.t.sol +++ b/testdata/default/repros/Issue3119.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3119 contract Issue3119Test is DSTest { diff --git a/testdata/repros/Issue3189.t.sol b/testdata/default/repros/Issue3189.t.sol similarity index 96% rename from testdata/repros/Issue3189.t.sol rename to testdata/default/repros/Issue3189.t.sol index 771b8f514..27ea0ac51 100644 --- a/testdata/repros/Issue3189.t.sol +++ b/testdata/default/repros/Issue3189.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3189 contract MyContract { diff --git a/testdata/repros/Issue3190.t.sol b/testdata/default/repros/Issue3190.t.sol similarity index 94% rename from testdata/repros/Issue3190.t.sol rename to testdata/default/repros/Issue3190.t.sol index b5d5c70e9..4a9add5f5 100644 --- a/testdata/repros/Issue3190.t.sol +++ b/testdata/default/repros/Issue3190.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; import "../logs/console.sol"; // https://github.com/foundry-rs/foundry/issues/3190 diff --git a/testdata/repros/Issue3192.t.sol b/testdata/default/repros/Issue3192.t.sol similarity index 95% rename from testdata/repros/Issue3192.t.sol rename to testdata/default/repros/Issue3192.t.sol index 36841fd08..0deb22f49 100644 --- a/testdata/repros/Issue3192.t.sol +++ b/testdata/default/repros/Issue3192.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3192 contract Issue3192Test is DSTest { diff --git a/testdata/repros/Issue3220.t.sol b/testdata/default/repros/Issue3220.t.sol similarity index 97% rename from testdata/repros/Issue3220.t.sol rename to testdata/default/repros/Issue3220.t.sol index acf75352d..b88d997c1 100644 --- a/testdata/repros/Issue3220.t.sol +++ b/testdata/default/repros/Issue3220.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3220 contract Issue3220Test is DSTest { diff --git a/testdata/repros/Issue3221.t.sol b/testdata/default/repros/Issue3221.t.sol similarity index 82% rename from testdata/repros/Issue3221.t.sol rename to testdata/default/repros/Issue3221.t.sol index cc6f8039e..4a9dd7be4 100644 --- a/testdata/repros/Issue3221.t.sol +++ b/testdata/default/repros/Issue3221.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3221 contract Issue3221Test is DSTest { @@ -11,7 +11,7 @@ contract Issue3221Test is DSTest { uint256 fork2; function setUp() public { - fork1 = vm.createFork("https://goerli.infura.io/v3/b1d3925804e74152b316ca7da97060d3", 7475589); + fork1 = vm.createFork("rpcAliasSepolia", 5565573); fork2 = vm.createFork("https://api.avax-test.network/ext/bc/C/rpc", 12880747); } @@ -27,7 +27,7 @@ contract Issue3221Test is DSTest { new Counter(); vm.selectFork(fork1); - assertEq(vm.getNonce(user), 3); + assertEq(vm.getNonce(user), 1); vm.prank(user); new Counter(); } diff --git a/testdata/repros/Issue3223.t.sol b/testdata/default/repros/Issue3223.t.sol similarity index 76% rename from testdata/repros/Issue3223.t.sol rename to testdata/default/repros/Issue3223.t.sol index 14d46838e..d4c5da751 100644 --- a/testdata/repros/Issue3223.t.sol +++ b/testdata/default/repros/Issue3223.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3223 contract Issue3223Test is DSTest { @@ -11,7 +11,7 @@ contract Issue3223Test is DSTest { uint256 fork2; function setUp() public { - fork1 = vm.createFork("https://goerli.infura.io/v3/b9794ad1ddf84dfb8c34d6bb5dca2001", 7475589); + fork1 = vm.createFork("rpcAliasSepolia", 2362365); fork2 = vm.createFork("https://api.avax-test.network/ext/bc/C/rpc", 12880747); } @@ -25,9 +25,7 @@ contract Issue3223Test is DSTest { new Counter(); vm.selectFork(fork1); - assertEq(vm.getNonce(user), 3); - vm.prank(user); - new Counter(); + assertEq(vm.getNonce(user), 1); } } diff --git a/testdata/repros/Issue3347.t.sol b/testdata/default/repros/Issue3347.t.sol similarity index 91% rename from testdata/repros/Issue3347.t.sol rename to testdata/default/repros/Issue3347.t.sol index 66657ea62..ed9be5f36 100644 --- a/testdata/repros/Issue3347.t.sol +++ b/testdata/default/repros/Issue3347.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3347 contract Issue3347Test is DSTest { diff --git a/testdata/repros/Issue3437.t.sol b/testdata/default/repros/Issue3437.t.sol similarity index 93% rename from testdata/repros/Issue3437.t.sol rename to testdata/default/repros/Issue3437.t.sol index acd02ada7..69f56ca82 100644 --- a/testdata/repros/Issue3437.t.sol +++ b/testdata/default/repros/Issue3437.t.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3437 contract Issue3347Test is DSTest { diff --git a/testdata/repros/Issue3596.t.sol b/testdata/default/repros/Issue3596.t.sol similarity index 96% rename from testdata/repros/Issue3596.t.sol rename to testdata/default/repros/Issue3596.t.sol index 9a942d342..04ee470d7 100644 --- a/testdata/repros/Issue3596.t.sol +++ b/testdata/default/repros/Issue3596.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3596 contract Issue3596Test is DSTest { diff --git a/testdata/repros/Issue3653.t.sol b/testdata/default/repros/Issue3653.t.sol similarity index 95% rename from testdata/repros/Issue3653.t.sol rename to testdata/default/repros/Issue3653.t.sol index 5022af678..b86f84c3e 100644 --- a/testdata/repros/Issue3653.t.sol +++ b/testdata/default/repros/Issue3653.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3653 contract Issue3653Test is DSTest { diff --git a/testdata/repros/Issue3661.t.sol b/testdata/default/repros/Issue3661.t.sol similarity index 100% rename from testdata/repros/Issue3661.t.sol rename to testdata/default/repros/Issue3661.t.sol diff --git a/testdata/repros/Issue3674.t.sol b/testdata/default/repros/Issue3674.t.sol similarity index 77% rename from testdata/repros/Issue3674.t.sol rename to testdata/default/repros/Issue3674.t.sol index 57216e805..813f73b5d 100644 --- a/testdata/repros/Issue3674.t.sol +++ b/testdata/default/repros/Issue3674.t.sol @@ -2,14 +2,14 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3674 contract Issue3674Test is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); function testNonceCreateSelect() public { - vm.createSelectFork("https://goerli.infura.io/v3/b9794ad1ddf84dfb8c34d6bb5dca2001"); + vm.createSelectFork("rpcAliasSepolia"); vm.createSelectFork("https://api.avax-test.network/ext/bc/C/rpc"); assert(vm.getNonce(msg.sender) > 0x17); diff --git a/testdata/repros/Issue3685.t.sol b/testdata/default/repros/Issue3685.t.sol similarity index 97% rename from testdata/repros/Issue3685.t.sol rename to testdata/default/repros/Issue3685.t.sol index 748367f65..7e8f886d8 100644 --- a/testdata/repros/Issue3685.t.sol +++ b/testdata/default/repros/Issue3685.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; import "../logs/console.sol"; // https://github.com/foundry-rs/foundry/issues/3685 diff --git a/testdata/repros/Issue3703.t.sol b/testdata/default/repros/Issue3703.t.sol similarity index 98% rename from testdata/repros/Issue3703.t.sol rename to testdata/default/repros/Issue3703.t.sol index c941fe223..06ce6bcbe 100644 --- a/testdata/repros/Issue3703.t.sol +++ b/testdata/default/repros/Issue3703.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3703 contract Issue3703Test is DSTest { diff --git a/testdata/repros/Issue3708.t.sol b/testdata/default/repros/Issue3708.t.sol similarity index 97% rename from testdata/repros/Issue3708.t.sol rename to testdata/default/repros/Issue3708.t.sol index 7cf57cd0e..f5bdf48bf 100644 --- a/testdata/repros/Issue3708.t.sol +++ b/testdata/default/repros/Issue3708.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3708 contract Issue3708Test is DSTest { diff --git a/testdata/repros/Issue3723.t.sol b/testdata/default/repros/Issue3723.t.sol similarity index 93% rename from testdata/repros/Issue3723.t.sol rename to testdata/default/repros/Issue3723.t.sol index 0f5d6694b..9ea3fe733 100644 --- a/testdata/repros/Issue3723.t.sol +++ b/testdata/default/repros/Issue3723.t.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3723 contract Issue3723Test is DSTest { diff --git a/testdata/repros/Issue3753.t.sol b/testdata/default/repros/Issue3753.t.sol similarity index 94% rename from testdata/repros/Issue3753.t.sol rename to testdata/default/repros/Issue3753.t.sol index 1edbb42b8..7af774baf 100644 --- a/testdata/repros/Issue3753.t.sol +++ b/testdata/default/repros/Issue3753.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/3753 contract Issue3753Test is DSTest { diff --git a/testdata/repros/Issue3792.t.sol b/testdata/default/repros/Issue3792.t.sol similarity index 96% rename from testdata/repros/Issue3792.t.sol rename to testdata/default/repros/Issue3792.t.sol index e01671f37..723329f93 100644 --- a/testdata/repros/Issue3792.t.sol +++ b/testdata/default/repros/Issue3792.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract Config { address public test = 0xcBa28b38103307Ec8dA98377ffF9816C164f9AFa; diff --git a/testdata/default/repros/Issue4402.t.sol b/testdata/default/repros/Issue4402.t.sol new file mode 100644 index 000000000..3bf0f33fb --- /dev/null +++ b/testdata/default/repros/Issue4402.t.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +// https://github.com/foundry-rs/foundry/issues/4402 +contract Issue4402Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testReadNonEmptyArray() public { + string memory path = "fixtures/Json/Issue4402.json"; + string memory json = vm.readFile(path); + address[] memory tokens = vm.parseJsonAddressArray(json, ".tokens"); + assertEq(tokens.length, 1); + + path = "fixtures/Toml/Issue4402.toml"; + string memory toml = vm.readFile(path); + tokens = vm.parseTomlAddressArray(toml, ".tokens"); + assertEq(tokens.length, 1); + } + + function testReadEmptyArray() public { + string memory path = "fixtures/Json/Issue4402.json"; + string memory json = vm.readFile(path); + + // Every one of these used to causes panic + address[] memory emptyAddressArray = vm.parseJsonAddressArray(json, ".empty"); + bool[] memory emptyBoolArray = vm.parseJsonBoolArray(json, ".empty"); + bytes[] memory emptyBytesArray = vm.parseJsonBytesArray(json, ".empty"); + bytes32[] memory emptyBytes32Array = vm.parseJsonBytes32Array(json, ".empty"); + string[] memory emptyStringArray = vm.parseJsonStringArray(json, ".empty"); + int256[] memory emptyIntArray = vm.parseJsonIntArray(json, ".empty"); + uint256[] memory emptyUintArray = vm.parseJsonUintArray(json, ".empty"); + + assertEq(emptyAddressArray.length, 0); + assertEq(emptyBoolArray.length, 0); + assertEq(emptyBytesArray.length, 0); + assertEq(emptyBytes32Array.length, 0); + assertEq(emptyStringArray.length, 0); + assertEq(emptyIntArray.length, 0); + assertEq(emptyUintArray.length, 0); + + path = "fixtures/Toml/Issue4402.toml"; + string memory toml = vm.readFile(path); + + // Every one of these used to causes panic + emptyAddressArray = vm.parseTomlAddressArray(toml, ".empty"); + emptyBoolArray = vm.parseTomlBoolArray(toml, ".empty"); + emptyBytesArray = vm.parseTomlBytesArray(toml, ".empty"); + emptyBytes32Array = vm.parseTomlBytes32Array(toml, ".empty"); + emptyStringArray = vm.parseTomlStringArray(toml, ".empty"); + emptyIntArray = vm.parseTomlIntArray(toml, ".empty"); + emptyUintArray = vm.parseTomlUintArray(toml, ".empty"); + + assertEq(emptyAddressArray.length, 0); + assertEq(emptyBoolArray.length, 0); + assertEq(emptyBytesArray.length, 0); + assertEq(emptyBytes32Array.length, 0); + assertEq(emptyStringArray.length, 0); + assertEq(emptyIntArray.length, 0); + assertEq(emptyUintArray.length, 0); + } +} diff --git a/testdata/repros/Issue4586.t.sol b/testdata/default/repros/Issue4586.t.sol similarity index 97% rename from testdata/repros/Issue4586.t.sol rename to testdata/default/repros/Issue4586.t.sol index 6eb615c02..a41ba7a04 100644 --- a/testdata/repros/Issue4586.t.sol +++ b/testdata/default/repros/Issue4586.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/4586 contract Issue4586Test is DSTest { diff --git a/testdata/repros/Issue4630.t.sol b/testdata/default/repros/Issue4630.t.sol similarity index 96% rename from testdata/repros/Issue4630.t.sol rename to testdata/default/repros/Issue4630.t.sol index 3979d5072..4b9fe9c9b 100644 --- a/testdata/repros/Issue4630.t.sol +++ b/testdata/default/repros/Issue4630.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/4630 contract Issue4630Test is DSTest { diff --git a/testdata/repros/Issue4640.t.sol b/testdata/default/repros/Issue4640.t.sol similarity index 94% rename from testdata/repros/Issue4640.t.sol rename to testdata/default/repros/Issue4640.t.sol index b16f4d071..a875d000d 100644 --- a/testdata/repros/Issue4640.t.sol +++ b/testdata/default/repros/Issue4640.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/4640 contract Issue4640Test is DSTest { diff --git a/testdata/repros/Issue4832.t.sol b/testdata/default/repros/Issue4832.t.sol similarity index 92% rename from testdata/repros/Issue4832.t.sol rename to testdata/default/repros/Issue4832.t.sol index 72f846873..192d805c1 100644 --- a/testdata/repros/Issue4832.t.sol +++ b/testdata/default/repros/Issue4832.t.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/4832 contract Issue4832Test is DSTest { diff --git a/testdata/repros/Issue5038.t.sol b/testdata/default/repros/Issue5038.t.sol similarity index 99% rename from testdata/repros/Issue5038.t.sol rename to testdata/default/repros/Issue5038.t.sol index bee48f0b7..834f82783 100644 --- a/testdata/repros/Issue5038.t.sol +++ b/testdata/default/repros/Issue5038.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; struct Value { uint256 value; diff --git a/testdata/repros/Issue5529.t.sol b/testdata/default/repros/Issue5529.t.sol similarity index 97% rename from testdata/repros/Issue5529.t.sol rename to testdata/default/repros/Issue5529.t.sol index 35e714001..14ec7cfdb 100644 --- a/testdata/repros/Issue5529.t.sol +++ b/testdata/default/repros/Issue5529.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.13; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract Counter { uint256 public number; diff --git a/testdata/default/repros/Issue5739.t.sol b/testdata/default/repros/Issue5739.t.sol new file mode 100644 index 000000000..2864c0cbf --- /dev/null +++ b/testdata/default/repros/Issue5739.t.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +interface IERC20 { + function totalSupply() external view returns (uint256 supply); +} + +// https://github.com/foundry-rs/foundry/issues/5739 +contract Issue5739Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + IERC20 dai; + + function setUp() public { + vm.createSelectFork("rpcAlias", 19000000); + dai = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); + } + + function testRollForkStateUpdated() public { + // dai not persistent so state should be updated between rolls + assertEq(dai.totalSupply(), 3723031040751006502480211083); + vm.rollFork(19925849); + assertEq(dai.totalSupply(), 3320242279303699674318705475); + } + + function testRollForkStatePersisted() public { + // make dai persistent so state is preserved between rolls + vm.makePersistent(address(dai)); + assertEq(dai.totalSupply(), 3723031040751006502480211083); + vm.rollFork(19925849); + assertEq(dai.totalSupply(), 3723031040751006502480211083); + } +} diff --git a/testdata/repros/Issue5808.t.sol b/testdata/default/repros/Issue5808.t.sol similarity index 95% rename from testdata/repros/Issue5808.t.sol rename to testdata/default/repros/Issue5808.t.sol index 1914264bf..66ea82b30 100644 --- a/testdata/repros/Issue5808.t.sol +++ b/testdata/default/repros/Issue5808.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/5808 contract Issue5808Test is DSTest { diff --git a/testdata/repros/Issue5929.t.sol b/testdata/default/repros/Issue5929.t.sol similarity index 95% rename from testdata/repros/Issue5929.t.sol rename to testdata/default/repros/Issue5929.t.sol index 53ca10ae8..f1009f03b 100644 --- a/testdata/repros/Issue5929.t.sol +++ b/testdata/default/repros/Issue5929.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/5929 contract Issue5929Test is DSTest { diff --git a/testdata/repros/Issue5935.t.sol b/testdata/default/repros/Issue5935.t.sol similarity index 97% rename from testdata/repros/Issue5935.t.sol rename to testdata/default/repros/Issue5935.t.sol index 8d6f8687b..95b6f8fd5 100644 --- a/testdata/repros/Issue5935.t.sol +++ b/testdata/default/repros/Issue5935.t.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.0 <0.9.0; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract SimpleStorage { uint256 public value; diff --git a/testdata/repros/Issue5948.t.sol b/testdata/default/repros/Issue5948.t.sol similarity index 97% rename from testdata/repros/Issue5948.t.sol rename to testdata/default/repros/Issue5948.t.sol index b496caf66..992099fb1 100644 --- a/testdata/repros/Issue5948.t.sol +++ b/testdata/default/repros/Issue5948.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/5948 contract Issue5948Test is DSTest { diff --git a/testdata/repros/Issue6006.t.sol b/testdata/default/repros/Issue6006.t.sol similarity index 97% rename from testdata/repros/Issue6006.t.sol rename to testdata/default/repros/Issue6006.t.sol index 63e2cd5c6..dac37cd24 100644 --- a/testdata/repros/Issue6006.t.sol +++ b/testdata/default/repros/Issue6006.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/6006 contract Issue6066Test is DSTest { diff --git a/testdata/repros/Issue6032.t.sol b/testdata/default/repros/Issue6032.t.sol similarity index 98% rename from testdata/repros/Issue6032.t.sol rename to testdata/default/repros/Issue6032.t.sol index c9f82f209..fc230c47e 100644 --- a/testdata/repros/Issue6032.t.sol +++ b/testdata/default/repros/Issue6032.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/6032 contract Issue6032Test is DSTest { diff --git a/testdata/repros/Issue6070.t.sol b/testdata/default/repros/Issue6070.t.sol similarity index 95% rename from testdata/repros/Issue6070.t.sol rename to testdata/default/repros/Issue6070.t.sol index db330f4b1..e699f5ca9 100644 --- a/testdata/repros/Issue6070.t.sol +++ b/testdata/default/repros/Issue6070.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/6070 contract Issue6066Test is DSTest { diff --git a/testdata/repros/Issue6115.t.sol b/testdata/default/repros/Issue6115.t.sol similarity index 100% rename from testdata/repros/Issue6115.t.sol rename to testdata/default/repros/Issue6115.t.sol diff --git a/testdata/repros/Issue6170.t.sol b/testdata/default/repros/Issue6170.t.sol similarity index 95% rename from testdata/repros/Issue6170.t.sol rename to testdata/default/repros/Issue6170.t.sol index 43f2067d6..543ca3142 100644 --- a/testdata/repros/Issue6170.t.sol +++ b/testdata/default/repros/Issue6170.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract Emitter { event Values(uint256 indexed a, uint256 indexed b); diff --git a/testdata/repros/Issue6180.t.sol b/testdata/default/repros/Issue6180.t.sol similarity index 95% rename from testdata/repros/Issue6180.t.sol rename to testdata/default/repros/Issue6180.t.sol index 7ff068434..591c60bdf 100644 --- a/testdata/repros/Issue6180.t.sol +++ b/testdata/default/repros/Issue6180.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/6180 contract Issue6180Test is DSTest { diff --git a/testdata/repros/Issue6293.t.sol b/testdata/default/repros/Issue6293.t.sol similarity index 93% rename from testdata/repros/Issue6293.t.sol rename to testdata/default/repros/Issue6293.t.sol index c90f56806..303e8fbbe 100644 --- a/testdata/repros/Issue6293.t.sol +++ b/testdata/default/repros/Issue6293.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/6293 contract Issue6293Test is DSTest { diff --git a/testdata/repros/Issue6355.t.sol b/testdata/default/repros/Issue6355.t.sol similarity index 96% rename from testdata/repros/Issue6355.t.sol rename to testdata/default/repros/Issue6355.t.sol index 7271011c6..d7830152a 100644 --- a/testdata/repros/Issue6355.t.sol +++ b/testdata/default/repros/Issue6355.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/6355 contract Issue6355Test is DSTest { diff --git a/testdata/repros/Issue6437.t.sol b/testdata/default/repros/Issue6437.t.sol similarity index 97% rename from testdata/repros/Issue6437.t.sol rename to testdata/default/repros/Issue6437.t.sol index 529c96d2e..c18af2dfd 100644 --- a/testdata/repros/Issue6437.t.sol +++ b/testdata/default/repros/Issue6437.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/6437 contract Issue6437Test is DSTest { diff --git a/testdata/repros/Issue6501.t.sol b/testdata/default/repros/Issue6501.t.sol similarity index 100% rename from testdata/repros/Issue6501.t.sol rename to testdata/default/repros/Issue6501.t.sol diff --git a/testdata/repros/Issue6538.t.sol b/testdata/default/repros/Issue6538.t.sol similarity index 95% rename from testdata/repros/Issue6538.t.sol rename to testdata/default/repros/Issue6538.t.sol index d174449c7..d83bbc850 100644 --- a/testdata/repros/Issue6538.t.sol +++ b/testdata/default/repros/Issue6538.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/6538 contract Issue6538Test is DSTest { diff --git a/testdata/repros/Issue6554.t.sol b/testdata/default/repros/Issue6554.t.sol similarity index 61% rename from testdata/repros/Issue6554.t.sol rename to testdata/default/repros/Issue6554.t.sol index be7af3d9d..c13ebc4a7 100644 --- a/testdata/repros/Issue6554.t.sol +++ b/testdata/default/repros/Issue6554.t.sol @@ -2,15 +2,15 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/6554 contract Issue6554Test is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); function testPermissions() public { - vm.writeFile("./out/Issue6554.t.sol/cachedFile.txt", "cached data"); - string memory content = vm.readFile("./out/Issue6554.t.sol/cachedFile.txt"); + vm.writeFile("./out/default/Issue6554.t.sol/cachedFile.txt", "cached data"); + string memory content = vm.readFile("./out/default/Issue6554.t.sol/cachedFile.txt"); assertEq(content, "cached data"); } } diff --git a/testdata/repros/Issue6616.t.sol b/testdata/default/repros/Issue6616.t.sol similarity index 96% rename from testdata/repros/Issue6616.t.sol rename to testdata/default/repros/Issue6616.t.sol index 6c9991f0e..24fa00e21 100644 --- a/testdata/repros/Issue6616.t.sol +++ b/testdata/default/repros/Issue6616.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/6616 contract Issue6616Test is DSTest { diff --git a/testdata/repros/Issue6634.t.sol b/testdata/default/repros/Issue6634.t.sol similarity index 99% rename from testdata/repros/Issue6634.t.sol rename to testdata/default/repros/Issue6634.t.sol index 3b1acb9c1..22294f6df 100644 --- a/testdata/repros/Issue6634.t.sol +++ b/testdata/default/repros/Issue6634.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; import "../logs/console.sol"; contract Box { diff --git a/testdata/repros/Issue6759.t.sol b/testdata/default/repros/Issue6759.t.sol similarity index 95% rename from testdata/repros/Issue6759.t.sol rename to testdata/default/repros/Issue6759.t.sol index 45a2f42b0..a8039035e 100644 --- a/testdata/repros/Issue6759.t.sol +++ b/testdata/default/repros/Issue6759.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/6759 contract Issue6759Test is DSTest { diff --git a/testdata/repros/Issue6966.t.sol b/testdata/default/repros/Issue6966.t.sol similarity index 100% rename from testdata/repros/Issue6966.t.sol rename to testdata/default/repros/Issue6966.t.sol diff --git a/testdata/default/repros/Issue7481.t.sol b/testdata/default/repros/Issue7481.t.sol new file mode 100644 index 000000000..eb568dc94 --- /dev/null +++ b/testdata/default/repros/Issue7481.t.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +// https://github.com/foundry-rs/foundry/issues/7481 +// This test ensures that we don't panic +contract Issue7481Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testFailTransact() public { + vm.createSelectFork("rpcAlias", 19514903); + + // Transfer some funds to sender of tx being transacted to ensure that it appears in journaled state + payable(address(0x5C60cD7a3D50877Bfebd484750FBeb245D936dAD)).call{value: 1}(""); + vm.transact(0xccfd66fc409a633a99b5b75b0e9a2040fcf562d03d9bee3fefc1a5c0eb49c999); + + // Revert the current call to ensure that revm can revert state journal + revert("HERE"); + } +} diff --git a/testdata/default/repros/Issue8004.t.sol b/testdata/default/repros/Issue8004.t.sol new file mode 100644 index 000000000..3e077e2a2 --- /dev/null +++ b/testdata/default/repros/Issue8004.t.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract NonPersistentHelper is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + uint256 public curState; + + function createSelectFork() external { + vm.createSelectFork("rpcAlias"); + curState += 1; + } + + function createSelectForkAtBlock() external { + vm.createSelectFork("rpcAlias", 19000000); + curState += 1; + } + + function createSelectForkAtTx() external { + vm.createSelectFork( + "rpcAlias", vm.parseBytes32("0xb5c978f15d01fcc9b4d78967e8189e35ecc21ff4e78315ea5d616f3467003c84") + ); + curState += 1; + } + + function selectFork(uint256 forkId) external { + vm.selectFork(forkId); + curState += 1; + } + + function rollForkAtBlock() external { + vm.rollFork(19000000); + curState += 1; + } + + function rollForkIdAtBlock(uint256 forkId) external { + vm.rollFork(forkId, 19000000); + curState += 1; + } +} + +// https://github.com/foundry-rs/foundry/issues/8004 +contract Issue8004CreateSelectForkTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + NonPersistentHelper helper; + + function setUp() public { + helper = new NonPersistentHelper(); + } + + function testNonPersistentHelperCreateFork() external { + helper.createSelectFork(); + assertEq(helper.curState(), 1); + } + + function testNonPersistentHelperCreateForkAtBlock() external { + helper.createSelectForkAtBlock(); + assertEq(helper.curState(), 1); + } + + function testNonPersistentHelperCreateForkAtTx() external { + helper.createSelectForkAtBlock(); + assertEq(helper.curState(), 1); + } + + function testNonPersistentHelperSelectFork() external { + uint256 forkId = vm.createFork("rpcAlias"); + helper.selectFork(forkId); + assertEq(helper.curState(), 1); + } +} + +contract Issue8004RollForkTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + NonPersistentHelper helper; + uint256 forkId; + + function setUp() public { + forkId = vm.createSelectFork("rpcAlias"); + helper = new NonPersistentHelper(); + } + + function testNonPersistentHelperRollForkAtBlock() external { + helper.rollForkAtBlock(); + assertEq(helper.curState(), 1); + } + + function testNonPersistentHelperRollForkIdAtBlock() external { + helper.rollForkIdAtBlock(forkId); + assertEq(helper.curState(), 1); + } +} diff --git a/testdata/default/repros/Issue8006.t.sol b/testdata/default/repros/Issue8006.t.sol new file mode 100644 index 000000000..1a45ddf97 --- /dev/null +++ b/testdata/default/repros/Issue8006.t.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +interface IERC20 { + function totalSupply() external view returns (uint256 supply); +} + +contract Mock { + function totalSupply() external view returns (uint256 supply) { + return 1; + } +} + +// https://github.com/foundry-rs/foundry/issues/8006 +contract Issue8006Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + IERC20 dai; + bytes32 transaction = 0x67cbad73764049e228495a3f90144aab4a37cb4b5fd697dffc234aa5ed811ace; + + function setUp() public { + vm.createSelectFork("rpcAlias", 16261704); + dai = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); + } + + function testRollForkEtchNotCalled() public { + // dai not persistent so should not call mock code + vm.etch(address(dai), address(new Mock()).code); + assertEq(dai.totalSupply(), 1); + vm.rollFork(transaction); + assertEq(dai.totalSupply(), 5155217627191887307044676292); + } + + function testRollForkEtchCalled() public { + // make dai persistent so mock code is preserved + vm.etch(address(dai), address(new Mock()).code); + vm.makePersistent(address(dai)); + assertEq(dai.totalSupply(), 1); + vm.rollFork(transaction); + assertEq(dai.totalSupply(), 1); + } +} diff --git a/testdata/default/script/broadcast/deploy.sol/31337/run-latest.json b/testdata/default/script/broadcast/deploy.sol/31337/run-latest.json new file mode 100644 index 000000000..1a8bccb39 --- /dev/null +++ b/testdata/default/script/broadcast/deploy.sol/31337/run-latest.json @@ -0,0 +1 @@ +{"transactions":[{"hash":null,"type":"CREATE","contractName":null,"contractAddress":"0x731a10897d267e19b34503ad902d0a29173ba4b1","function":null,"arguments":null,"transaction":{"type":"0x02","from":"0x00a329c0648769a73afac7f9381e08fb43dbea72","gas":"0x54653","value":"0x0","data":"0x608060405234801561001057600080fd5b506103d9806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063d5dcf1271461003b578063f8194e4814610050575b600080fd5b61004e6100493660046100e9565b600155565b005b61006361005e366004610118565b610079565b60405161007091906101f9565b60405180910390f35b6060600061008783826102b5565b5060008260405160200161009b9190610375565b60405160208183030381529060405290507fefdeaaf566f7751d16a12c7fa8909eb74120f42cba334d07dd5246c48f1fba81816040516100db91906101f9565b60405180910390a192915050565b6000602082840312156100fb57600080fd5b5035919050565b634e487b7160e01b600052604160045260246000fd5b60006020828403121561012a57600080fd5b813567ffffffffffffffff8082111561014257600080fd5b818401915084601f83011261015657600080fd5b81358181111561016857610168610102565b604051601f8201601f19908116603f0116810190838211818310171561019057610190610102565b816040528281528760208487010111156101a957600080fd5b826020860160208301376000928101602001929092525095945050505050565b60005b838110156101e45781810151838201526020016101cc565b838111156101f3576000848401525b50505050565b60208152600082518060208401526102188160408501602087016101c9565b601f01601f19169190910160400192915050565b600181811c9082168061024057607f821691505b60208210810361026057634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156102b057600081815260208120601f850160051c8101602086101561028d5750805b601f850160051c820191505b818110156102ac57828155600101610299565b5050505b505050565b815167ffffffffffffffff8111156102cf576102cf610102565b6102e3816102dd845461022c565b84610266565b602080601f83116001811461031857600084156103005750858301515b600019600386901b1c1916600185901b1785556102ac565b600085815260208120601f198616915b8281101561034757888601518255948401946001909101908401610328565b50858210156103655787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b6502432b63637960d51b8152600082516103968160068501602087016101c9565b919091016006019291505056fea2646970667358221220a380cb042b6ca762a5a0f97e497c4cffa21c45dc21e2dab4107e5415921a704a64736f6c634300080f0033","nonce":"0x0","accessList":[]}},{"hash":null,"type":"CALL","contractName":null,"contractAddress":"0x731a10897d267e19b34503ad902d0a29173ba4b1","function":null,"arguments":null,"transaction":{"type":"0x02","from":"0x00a329c0648769a73afac7f9381e08fb43dbea72","to":"0x731a10897d267e19b34503ad902d0a29173ba4b1","gas":"0xef15","value":"0x0","data":"0xf8194e48000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000046a6f686e00000000000000000000000000000000000000000000000000000000","nonce":"0x1","accessList":[]}},{"hash":null,"type":"CALL","contractName":null,"contractAddress":"0x731a10897d267e19b34503ad902d0a29173ba4b1","function":null,"arguments":null,"transaction":{"type":"0x02","from":"0x00a329c0648769a73afac7f9381e08fb43dbea72","to":"0x731a10897d267e19b34503ad902d0a29173ba4b1","gas":"0xdcde","value":"0x0","data":"0xd5dcf127000000000000000000000000000000000000000000000000000000000000007b","nonce":"0x2","accessList":[]}}],"receipts":[],"libraries":[],"pending":[],"path":"broadcast/deploy.sol/31337/run-latest.json","returns":{},"timestamp":1658913881} \ No newline at end of file diff --git a/testdata/script/deploy.sol b/testdata/default/script/deploy.sol similarity index 89% rename from testdata/script/deploy.sol rename to testdata/default/script/deploy.sol index f05afe487..013e009d3 100644 --- a/testdata/script/deploy.sol +++ b/testdata/default/script/deploy.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 pragma solidity 0.8.18; -import {DSTest} from "../lib/ds-test/src/test.sol"; -import {Vm} from "../cheats/Vm.sol"; +import {DSTest} from "lib/ds-test/src/test.sol"; +import {Vm} from "cheats/Vm.sol"; contract Greeter { string name; diff --git a/testdata/spec/ShanghaiCompat.t.sol b/testdata/default/spec/ShanghaiCompat.t.sol similarity index 96% rename from testdata/spec/ShanghaiCompat.t.sol rename to testdata/default/spec/ShanghaiCompat.t.sol index f490441ef..02856a88f 100644 --- a/testdata/spec/ShanghaiCompat.t.sol +++ b/testdata/default/spec/ShanghaiCompat.t.sol @@ -2,7 +2,7 @@ pragma solidity 0.8.20; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract ShanghaiCompat is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); diff --git a/testdata/trace/ConflictingSignatures.t.sol b/testdata/default/trace/ConflictingSignatures.t.sol similarity index 97% rename from testdata/trace/ConflictingSignatures.t.sol rename to testdata/default/trace/ConflictingSignatures.t.sol index 896390212..67dfd5d3a 100644 --- a/testdata/trace/ConflictingSignatures.t.sol +++ b/testdata/default/trace/ConflictingSignatures.t.sol @@ -1,7 +1,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract ReturnsNothing { function func() public pure {} diff --git a/testdata/trace/Trace.t.sol b/testdata/default/trace/Trace.t.sol similarity index 98% rename from testdata/trace/Trace.t.sol rename to testdata/default/trace/Trace.t.sol index 2eefba7ba..d513e8637 100644 --- a/testdata/trace/Trace.t.sol +++ b/testdata/default/trace/Trace.t.sol @@ -1,7 +1,7 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; -import "../cheats/Vm.sol"; +import "cheats/Vm.sol"; contract RecursiveCall { TraceTest factory; diff --git a/testdata/etherscan/0x044b75f554b886A065b9567891e45c79542d7357/creation_data.json b/testdata/etherscan/0x044b75f554b886A065b9567891e45c79542d7357/creation_data.json new file mode 100644 index 000000000..899a44c15 --- /dev/null +++ b/testdata/etherscan/0x044b75f554b886A065b9567891e45c79542d7357/creation_data.json @@ -0,0 +1 @@ +{"contractAddress":"0x044b75f554b886a065b9567891e45c79542d7357","contractCreator":"0xf87bc5535602077d340806d71f805ea9907a843d","txHash":"0x9a89d2f5528bf07661e92f3f78a3311396f11f15da19e3ec4d880be1ad1a4bec"} \ No newline at end of file diff --git a/testdata/etherscan/0x044b75f554b886A065b9567891e45c79542d7357/metadata.json b/testdata/etherscan/0x044b75f554b886A065b9567891e45c79542d7357/metadata.json new file mode 100644 index 000000000..54aec7d6a --- /dev/null +++ b/testdata/etherscan/0x044b75f554b886A065b9567891e45c79542d7357/metadata.json @@ -0,0 +1 @@ +[{"SourceCode":{"language":"Solidity","sources":{"contracts/InputStream.sol":{"content":"// SPDX-License-Identifier: UNLICENSED\n\npragma solidity 0.8.10;\n\nlibrary InputStream {\n function createStream(bytes memory data) internal pure returns (uint256 stream) {\n assembly {\n stream := mload(0x40)\n mstore(0x40, add(stream, 64))\n mstore(stream, data)\n let length := mload(data)\n mstore(add(stream, 32), add(data, length))\n }\n }\n\n function isNotEmpty(uint256 stream) internal pure returns (bool) {\n uint256 pos;\n uint256 finish;\n assembly {\n pos := mload(stream)\n finish := mload(add(stream, 32))\n }\n return pos < finish;\n }\n\n function readUint8(uint256 stream) internal pure returns (uint8 res) {\n assembly {\n let pos := mload(stream)\n pos := add(pos, 1)\n res := mload(pos)\n mstore(stream, pos)\n }\n }\n\n function readUint16(uint256 stream) internal pure returns (uint16 res) {\n assembly {\n let pos := mload(stream)\n pos := add(pos, 2)\n res := mload(pos)\n mstore(stream, pos)\n }\n }\n\n function readUint32(uint256 stream) internal pure returns (uint32 res) {\n assembly {\n let pos := mload(stream)\n pos := add(pos, 4)\n res := mload(pos)\n mstore(stream, pos)\n }\n }\n\n function readUint(uint256 stream) internal pure returns (uint256 res) {\n assembly {\n let pos := mload(stream)\n pos := add(pos, 32)\n res := mload(pos)\n mstore(stream, pos)\n }\n }\n\n function readAddress(uint256 stream) internal pure returns (address res) {\n assembly {\n let pos := mload(stream)\n pos := add(pos, 20)\n res := mload(pos)\n mstore(stream, pos)\n }\n }\n\n function readBytes(uint256 stream) internal pure returns (bytes memory res) {\n assembly {\n let pos := mload(stream)\n res := add(pos, 32)\n let length := mload(res)\n mstore(stream, add(res, length))\n }\n }\n}\n"},"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../IERC20.sol\";\nimport \"../extensions/draft-IERC20Permit.sol\";\nimport \"../../../utils/Address.sol\";\n\n/**\n * @title SafeERC20\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\n * contract returns false). Tokens that return no value (and instead revert or\n * throw on failure) are also supported, non-reverting calls are assumed to be\n * successful.\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\n */\nlibrary SafeERC20 {\n using Address for address;\n\n function safeTransfer(\n IERC20 token,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\n }\n\n function safeTransferFrom(\n IERC20 token,\n address from,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\n }\n\n /**\n * @dev Deprecated. This function has issues similar to the ones found in\n * {IERC20-approve}, and its usage is discouraged.\n *\n * Whenever possible, use {safeIncreaseAllowance} and\n * {safeDecreaseAllowance} instead.\n */\n function safeApprove(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n // safeApprove should only be called when setting an initial allowance,\n // or when resetting it to zero. To increase and decrease it, use\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\n require(\n (value == 0) || (token.allowance(address(this), spender) == 0),\n \"SafeERC20: approve from non-zero to non-zero allowance\"\n );\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\n }\n\n function safeIncreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n uint256 newAllowance = token.allowance(address(this), spender) + value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n\n function safeDecreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n unchecked {\n uint256 oldAllowance = token.allowance(address(this), spender);\n require(oldAllowance >= value, \"SafeERC20: decreased allowance below zero\");\n uint256 newAllowance = oldAllowance - value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n }\n\n function safePermit(\n IERC20Permit token,\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal {\n uint256 nonceBefore = token.nonces(owner);\n token.permit(owner, spender, value, deadline, v, r, s);\n uint256 nonceAfter = token.nonces(owner);\n require(nonceAfter == nonceBefore + 1, \"SafeERC20: permit did not succeed\");\n }\n\n /**\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n * on the return value: the return value is optional (but if data is returned, it must not be false).\n * @param token The token targeted by the call.\n * @param data The call data (encoded using abi.encode or one of its variants).\n */\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\n // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that\n // the target address contains contract code and also asserts for success in the low-level call.\n\n bytes memory returndata = address(token).functionCall(data, \"SafeERC20: low-level call failed\");\n if (returndata.length > 0) {\n // Return data is optional\n require(abi.decode(returndata, (bool)), \"SafeERC20: ERC20 operation did not succeed\");\n }\n }\n}\n"},"interfaces/IBentoBoxMinimal.sol":{"content":"// SPDX-License-Identifier: UNLICENSED\n\npragma solidity >=0.8.0;\n\nstruct Rebase {\n uint128 elastic;\n uint128 base;\n}\n\nstruct StrategyData {\n uint64 strategyStartDate;\n uint64 targetPercentage;\n uint128 balance; // the balance of the strategy that BentoBox thinks is in there\n}\n\n/// @notice A rebasing library\nlibrary RebaseLibrary {\n /// @notice Calculates the base value in relationship to `elastic` and `total`.\n function toBase(Rebase memory total, uint256 elastic) internal pure returns (uint256 base) {\n if (total.elastic == 0) {\n base = elastic;\n } else {\n base = (elastic * total.base) / total.elastic;\n }\n }\n\n /// @notice Calculates the elastic value in relationship to `base` and `total`.\n function toElastic(Rebase memory total, uint256 base) internal pure returns (uint256 elastic) {\n if (total.base == 0) {\n elastic = base;\n } else {\n elastic = (base * total.elastic) / total.base;\n }\n }\n}\n\n/// @notice Minimal BentoBox vault interface.\n/// @dev `token` is aliased as `address` from `IERC20` for simplicity.\ninterface IBentoBoxMinimal {\n /// @notice Balance per ERC-20 token per account in shares.\n function balanceOf(address, address) external view returns (uint256);\n\n /// @dev Helper function to represent an `amount` of `token` in shares.\n /// @param token The ERC-20 token.\n /// @param amount The `token` amount.\n /// @param roundUp If the result `share` should be rounded up.\n /// @return share The token amount represented in shares.\n function toShare(\n address token,\n uint256 amount,\n bool roundUp\n ) external view returns (uint256 share);\n\n /// @dev Helper function to represent shares back into the `token` amount.\n /// @param token The ERC-20 token.\n /// @param share The amount of shares.\n /// @param roundUp If the result should be rounded up.\n /// @return amount The share amount back into native representation.\n function toAmount(\n address token,\n uint256 share,\n bool roundUp\n ) external view returns (uint256 amount);\n\n /// @notice Registers this contract so that users can approve it for BentoBox.\n function registerProtocol() external;\n\n /// @notice Deposit an amount of `token` represented in either `amount` or `share`.\n /// @param token The ERC-20 token to deposit.\n /// @param from which account to pull the tokens.\n /// @param to which account to push the tokens.\n /// @param amount Token amount in native representation to deposit.\n /// @param share Token amount represented in shares to deposit. Takes precedence over `amount`.\n /// @return amountOut The amount deposited.\n /// @return shareOut The deposited amount represented in shares.\n function deposit(\n address token,\n address from,\n address to,\n uint256 amount,\n uint256 share\n ) external payable returns (uint256 amountOut, uint256 shareOut);\n\n /// @notice Withdraws an amount of `token` from a user account.\n /// @param token_ The ERC-20 token to withdraw.\n /// @param from which user to pull the tokens.\n /// @param to which user to push the tokens.\n /// @param amount of tokens. Either one of `amount` or `share` needs to be supplied.\n /// @param share Like above, but `share` takes precedence over `amount`.\n function withdraw(\n address token_,\n address from,\n address to,\n uint256 amount,\n uint256 share\n ) external returns (uint256 amountOut, uint256 shareOut);\n\n /// @notice Transfer shares from a user account to another one.\n /// @param token The ERC-20 token to transfer.\n /// @param from which user to pull the tokens.\n /// @param to which user to push the tokens.\n /// @param share The amount of `token` in shares.\n function transfer(\n address token,\n address from,\n address to,\n uint256 share\n ) external;\n\n /// @dev Reads the Rebase `totals`from storage for a given token\n function totals(address token) external view returns (Rebase memory total);\n\n function strategyData(address token) external view returns (StrategyData memory total);\n\n /// @dev Approves users' BentoBox assets to a \"master\" contract.\n function setMasterContractApproval(\n address user,\n address masterContract,\n bool approved,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external;\n\n function harvest(\n address token,\n bool balance,\n uint256 maxChangeAmount\n ) external;\n}\n"},"@openzeppelin/contracts/utils/Address.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)\n\npragma solidity ^0.8.1;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n * ====\n *\n * [IMPORTANT]\n * ====\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\n *\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\n * constructor.\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize/address.code.length, which returns 0\n // for contracts in construction, since the code is only stored at the end\n // of the constructor execution.\n\n return account.code.length > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling\n * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.\n *\n * _Available since v4.8._\n */\n function verifyCallResultFromTarget(\n address target,\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n if (success) {\n if (returndata.length == 0) {\n // only check isContract if the call was successful and the return data is empty\n // otherwise we already know that it was a contract\n require(isContract(target), \"Address: call to non-contract\");\n }\n return returndata;\n } else {\n _revert(returndata, errorMessage);\n }\n }\n\n /**\n * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason or using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n _revert(returndata, errorMessage);\n }\n }\n\n function _revert(bytes memory returndata, string memory errorMessage) private pure {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n /// @solidity memory-safe-assembly\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n}\n"},"interfaces/IPool.sol":{"content":"// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity >=0.5.0;\npragma experimental ABIEncoderV2;\n\n/// @notice Trident pool interface.\ninterface IPool {\n /// @notice Executes a swap from one token to another.\n /// @dev The input tokens must've already been sent to the pool.\n /// @param data ABI-encoded params that the pool requires.\n /// @return finalAmountOut The amount of output tokens that were sent to the user.\n function swap(bytes calldata data) external returns (uint256 finalAmountOut);\n\n /// @notice Executes a swap from one token to another with a callback.\n /// @dev This function allows borrowing the output tokens and sending the input tokens in the callback.\n /// @param data ABI-encoded params that the pool requires.\n /// @return finalAmountOut The amount of output tokens that were sent to the user.\n function flashSwap(bytes calldata data) external returns (uint256 finalAmountOut);\n\n /// @notice Mints liquidity tokens.\n /// @param data ABI-encoded params that the pool requires.\n /// @return liquidity The amount of liquidity tokens that were minted for the user.\n function mint(bytes calldata data) external returns (uint256 liquidity);\n\n /// @notice Burns liquidity tokens.\n /// @dev The input LP tokens must've already been sent to the pool.\n /// @param data ABI-encoded params that the pool requires.\n /// @return withdrawnAmounts The amount of various output tokens that were sent to the user.\n function burn(bytes calldata data) external returns (TokenAmount[] memory withdrawnAmounts);\n\n /// @notice Burns liquidity tokens for a single output token.\n /// @dev The input LP tokens must've already been sent to the pool.\n /// @param data ABI-encoded params that the pool requires.\n /// @return amountOut The amount of output tokens that were sent to the user.\n function burnSingle(bytes calldata data) external returns (uint256 amountOut);\n\n /// @return A unique identifier for the pool type.\n function poolIdentifier() external pure returns (bytes32);\n\n /// @return An array of tokens supported by the pool.\n function getAssets() external view returns (address[] memory);\n\n /// @notice Simulates a trade and returns the expected output.\n /// @dev The pool does not need to include a trade simulator directly in itself - it can use a library.\n /// @param data ABI-encoded params that the pool requires.\n /// @return finalAmountOut The amount of output tokens that will be sent to the user if the trade is executed.\n function getAmountOut(bytes calldata data) external view returns (uint256 finalAmountOut);\n\n /// @notice Simulates a trade and returns the expected output.\n /// @dev The pool does not need to include a trade simulator directly in itself - it can use a library.\n /// @param data ABI-encoded params that the pool requires.\n /// @return finalAmountIn The amount of input tokens that are required from the user if the trade is executed.\n function getAmountIn(bytes calldata data) external view returns (uint256 finalAmountIn);\n\n /// @dev This event must be emitted on all swaps.\n event Swap(address indexed recipient, address indexed tokenIn, address indexed tokenOut, uint256 amountIn, uint256 amountOut);\n\n /// @dev This struct frames output tokens for burns.\n struct TokenAmount {\n address token;\n uint256 amount;\n }\n}\n"},"@openzeppelin/contracts/token/ERC20/IERC20.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `from` to `to` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address from,\n address to,\n uint256 amount\n ) external returns (bool);\n}\n"},"contracts/RouteProcessor2.sol":{"content":"// SPDX-License-Identifier: UNLICENSED\n\npragma solidity 0.8.10;\n\nimport '../interfaces/IUniswapV2Pair.sol';\nimport '../interfaces/IUniswapV3Pool.sol';\nimport '../interfaces/ITridentCLPool.sol';\nimport '../interfaces/IBentoBoxMinimal.sol';\nimport '../interfaces/IPool.sol';\nimport '../interfaces/IWETH.sol';\nimport './InputStream.sol';\nimport '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';\n\naddress constant NATIVE_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;\naddress constant IMPOSSIBLE_POOL_ADDRESS = 0x0000000000000000000000000000000000000001;\n\n/// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK)\nuint160 constant MIN_SQRT_RATIO = 4295128739;\n/// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK)\nuint160 constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342;\n\n/// @title A route processor for the Sushi Aggregator\n/// @author Ilya Lyalin\ncontract RouteProcessor2 {\n using SafeERC20 for IERC20;\n using InputStream for uint256;\n\n IBentoBoxMinimal public immutable bentoBox;\n address private lastCalledPool;\n\n uint private unlocked = 1;\n modifier lock() {\n require(unlocked == 1, 'RouteProcessor is locked');\n unlocked = 2;\n _;\n unlocked = 1;\n }\n\n constructor(address _bentoBox) {\n bentoBox = IBentoBoxMinimal(_bentoBox);\n lastCalledPool = IMPOSSIBLE_POOL_ADDRESS;\n }\n\n /// @notice For native unwrapping\n receive() external payable {}\n\n /// @notice Processes the route generated off-chain. Has a lock\n /// @param tokenIn Address of the input token\n /// @param amountIn Amount of the input token\n /// @param tokenOut Address of the output token\n /// @param amountOutMin Minimum amount of the output token\n /// @return amountOut Actual amount of the output token\n function processRoute(\n address tokenIn,\n uint256 amountIn,\n address tokenOut,\n uint256 amountOutMin,\n address to,\n bytes memory route\n ) external payable lock returns (uint256 amountOut) {\n return processRouteInternal(tokenIn, amountIn, tokenOut, amountOutMin, to, route);\n }\n\n /// @notice Transfers some value to and then processes the route\n /// @param transferValueTo Address where the value should be transferred\n /// @param amountValueTransfer How much value to transfer\n /// @param tokenIn Address of the input token\n /// @param amountIn Amount of the input token\n /// @param tokenOut Address of the output token\n /// @param amountOutMin Minimum amount of the output token\n /// @return amountOut Actual amount of the output token\n function transferValueAndprocessRoute(\n address payable transferValueTo,\n uint256 amountValueTransfer,\n address tokenIn,\n uint256 amountIn,\n address tokenOut,\n uint256 amountOutMin,\n address to,\n bytes memory route\n ) external payable lock returns (uint256 amountOut) {\n (bool success, bytes memory returnBytes) = transferValueTo.call{value: amountValueTransfer}('');\n require(success, string(abi.encodePacked(returnBytes)));\n return processRouteInternal(tokenIn, amountIn, tokenOut, amountOutMin, to, route);\n }\n\n /// @notice Processes the route generated off-chain\n /// @param tokenIn Address of the input token\n /// @param amountIn Amount of the input token\n /// @param tokenOut Address of the output token\n /// @param amountOutMin Minimum amount of the output token\n /// @return amountOut Actual amount of the output token\n function processRouteInternal(\n address tokenIn,\n uint256 amountIn,\n address tokenOut,\n uint256 amountOutMin,\n address to,\n bytes memory route\n ) private returns (uint256 amountOut) {\n uint256 balanceInInitial = tokenIn == NATIVE_ADDRESS ? address(this).balance : IERC20(tokenIn).balanceOf(msg.sender);\n uint256 balanceOutInitial = tokenOut == NATIVE_ADDRESS ? address(to).balance : IERC20(tokenOut).balanceOf(to);\n\n uint256 stream = InputStream.createStream(route);\n while (stream.isNotEmpty()) {\n uint8 commandCode = stream.readUint8();\n if (commandCode == 1) processMyERC20(stream);\n else if (commandCode == 2) processUserERC20(stream, amountIn);\n else if (commandCode == 3) processNative(stream);\n else if (commandCode == 4) processOnePool(stream);\n else if (commandCode == 5) processInsideBento(stream);\n else revert('RouteProcessor: Unknown command code');\n }\n\n uint256 balanceInFinal = tokenIn == NATIVE_ADDRESS ? address(this).balance : IERC20(tokenIn).balanceOf(msg.sender);\n require(balanceInFinal + amountIn >= balanceInInitial, 'RouteProcessor: Minimal imput balance violation');\n\n uint256 balanceOutFinal = tokenOut == NATIVE_ADDRESS ? address(to).balance : IERC20(tokenOut).balanceOf(to);\n require(balanceOutFinal >= balanceOutInitial + amountOutMin, 'RouteProcessor: Minimal ouput balance violation');\n\n amountOut = balanceOutFinal - balanceOutInitial;\n }\n\n /// @notice Processes native coin: call swap for all pools that swap from native coin\n /// @param stream Streamed process program\n function processNative(uint256 stream) private {\n uint256 amountTotal = address(this).balance;\n distributeAndSwap(stream, address(this), NATIVE_ADDRESS, amountTotal);\n }\n\n /// @notice Processes ERC20 token from this contract balance:\n /// @notice Call swap for all pools that swap from this token\n /// @param stream Streamed process program\n function processMyERC20(uint256 stream) private {\n address token = stream.readAddress();\n uint256 amountTotal = IERC20(token).balanceOf(address(this));\n unchecked {\n if (amountTotal > 0) amountTotal -= 1; // slot undrain protection\n }\n distributeAndSwap(stream, address(this), token, amountTotal);\n }\n \n /// @notice Processes ERC20 token from msg.sender balance:\n /// @notice Call swap for all pools that swap from this token\n /// @param stream Streamed process program\n /// @param amountTotal Amount of tokens to take from msg.sender\n function processUserERC20(uint256 stream, uint256 amountTotal) private {\n address token = stream.readAddress();\n distributeAndSwap(stream, msg.sender, token, amountTotal);\n }\n\n /// @notice Distributes amountTotal to several pools according to their shares and calls swap for each pool\n /// @param stream Streamed process program\n /// @param from Where to take liquidity for swap\n /// @param tokenIn Input token\n /// @param amountTotal Total amount of tokenIn for swaps \n function distributeAndSwap(uint256 stream, address from, address tokenIn, uint256 amountTotal) private {\n uint8 num = stream.readUint8();\n unchecked {\n for (uint256 i = 0; i < num; ++i) {\n uint16 share = stream.readUint16();\n uint256 amount = (amountTotal * share) / 65535;\n amountTotal -= amount;\n swap(stream, from, tokenIn, amount);\n }\n }\n }\n\n /// @notice Processes ERC20 token for cases when the token has only one output pool\n /// @notice In this case liquidity is already at pool balance. This is an optimization\n /// @notice Call swap for all pools that swap from this token\n /// @param stream Streamed process program\n function processOnePool(uint256 stream) private {\n address token = stream.readAddress();\n swap(stream, address(this), token, 0);\n }\n\n /// @notice Processes Bento tokens \n /// @notice Call swap for all pools that swap from this token\n /// @param stream Streamed process program\n function processInsideBento(uint256 stream) private {\n address token = stream.readAddress();\n uint8 num = stream.readUint8();\n\n uint256 amountTotal = bentoBox.balanceOf(token, address(this));\n unchecked {\n if (amountTotal > 0) amountTotal -= 1; // slot undrain protection\n for (uint256 i = 0; i < num; ++i) {\n uint16 share = stream.readUint16();\n uint256 amount = (amountTotal * share) / 65535;\n amountTotal -= amount;\n swap(stream, address(this), token, amount);\n }\n }\n }\n\n /// @notice Makes swap\n /// @param stream Streamed process program\n /// @param from Where to take liquidity for swap\n /// @param tokenIn Input token\n /// @param amountIn Amount of tokenIn to take for swap\n function swap(uint256 stream, address from, address tokenIn, uint256 amountIn) private {\n uint8 poolType = stream.readUint8();\n if (poolType == 0) swapUniV2(stream, from, tokenIn, amountIn);\n else if (poolType == 1) swapUniV3(stream, from, tokenIn, amountIn);\n else if (poolType == 2) wrapNative(stream, from, tokenIn, amountIn);\n else if (poolType == 3) bentoBridge(stream, from, tokenIn, amountIn);\n else if (poolType == 4) swapTrident(stream, from, tokenIn, amountIn);\n else if (poolType == 5) swapTridentCL(stream, from, tokenIn, amountIn);\n else revert('RouteProcessor: Unknown pool type');\n }\n\n /// @notice Wraps/unwraps native token\n /// @param stream [direction & fake, recipient, wrapToken?]\n /// @param from Where to take liquidity for swap\n /// @param tokenIn Input token\n /// @param amountIn Amount of tokenIn to take for swap\n function wrapNative(uint256 stream, address from, address tokenIn, uint256 amountIn) private {\n uint8 directionAndFake = stream.readUint8();\n address to = stream.readAddress();\n\n if (directionAndFake & 1 == 1) { // wrap native\n address wrapToken = stream.readAddress();\n if (directionAndFake & 2 == 0) IWETH(wrapToken).deposit{value: amountIn}();\n if (to != address(this)) IERC20(wrapToken).safeTransfer(to, amountIn);\n } else { // unwrap native\n if (directionAndFake & 2 == 0) {\n if (from != address(this)) IERC20(tokenIn).safeTransferFrom(from, address(this), amountIn);\n IWETH(tokenIn).withdraw(amountIn);\n }\n payable(to).transfer(address(this).balance);\n }\n }\n\n /// @notice Bridge/unbridge tokens to/from Bento\n /// @param stream [direction, recipient]\n /// @param from Where to take liquidity for swap\n /// @param tokenIn Input token\n /// @param amountIn Amount of tokenIn to take for swap\n function bentoBridge(uint256 stream, address from, address tokenIn, uint256 amountIn) private {\n uint8 direction = stream.readUint8();\n address to = stream.readAddress();\n\n if (direction > 0) { // outside to Bento\n // deposit to arbitrary recipient is possible only from address(bentoBox)\n if (amountIn != 0) {\n if (from == address(this)) IERC20(tokenIn).safeTransfer(address(bentoBox), amountIn);\n else IERC20(tokenIn).safeTransferFrom(from, address(bentoBox), amountIn);\n } else {\n // tokens already are at address(bentoBox)\n amountIn = IERC20(tokenIn).balanceOf(address(bentoBox)) +\n bentoBox.strategyData(tokenIn).balance -\n bentoBox.totals(tokenIn).elastic;\n }\n bentoBox.deposit(tokenIn, address(bentoBox), to, amountIn, 0);\n } else { // Bento to outside\n if (amountIn > 0) {\n bentoBox.transfer(tokenIn, from, address(this), amountIn);\n } else amountIn = bentoBox.balanceOf(tokenIn, address(this));\n bentoBox.withdraw(tokenIn, address(this), to, 0, amountIn);\n }\n }\n\n /// @notice UniswapV2 pool swap\n /// @param stream [pool, direction, recipient]\n /// @param from Where to take liquidity for swap\n /// @param tokenIn Input token\n /// @param amountIn Amount of tokenIn to take for swap\n function swapUniV2(uint256 stream, address from, address tokenIn, uint256 amountIn) private {\n address pool = stream.readAddress();\n uint8 direction = stream.readUint8();\n address to = stream.readAddress();\n\n (uint256 r0, uint256 r1, ) = IUniswapV2Pair(pool).getReserves();\n require(r0 > 0 && r1 > 0, 'Wrong pool reserves');\n (uint256 reserveIn, uint256 reserveOut) = direction == 1 ? (r0, r1) : (r1, r0);\n\n if (amountIn != 0) {\n if (from == address(this)) IERC20(tokenIn).safeTransfer(pool, amountIn);\n else IERC20(tokenIn).safeTransferFrom(from, pool, amountIn);\n } else amountIn = IERC20(tokenIn).balanceOf(pool) - reserveIn; // tokens already were transferred\n\n uint256 amountInWithFee = amountIn * 997;\n uint256 amountOut = (amountInWithFee * reserveOut) / (reserveIn * 1000 + amountInWithFee);\n (uint256 amount0Out, uint256 amount1Out) = direction == 1 ? (uint256(0), amountOut) : (amountOut, uint256(0));\n IUniswapV2Pair(pool).swap(amount0Out, amount1Out, to, new bytes(0));\n }\n\n /// @notice Trident pool swap\n /// @param stream [pool, swapData]\n /// @param from Where to take liquidity for swap\n /// @param tokenIn Input token\n /// @param amountIn Amount of tokenIn to take for swap\n function swapTrident(uint256 stream, address from, address tokenIn, uint256 amountIn) private {\n address pool = stream.readAddress();\n bytes memory swapData = stream.readBytes();\n\n if (amountIn != 0) {\n bentoBox.transfer(tokenIn, from, pool, amountIn);\n }\n \n IPool(pool).swap(swapData);\n }\n\n /// @notice UniswapV3 pool swap\n /// @param stream [pool, direction, recipient]\n /// @param from Where to take liquidity for swap\n /// @param tokenIn Input token\n /// @param amountIn Amount of tokenIn to take for swap\n function swapUniV3(uint256 stream, address from, address tokenIn, uint256 amountIn) private {\n address pool = stream.readAddress();\n bool zeroForOne = stream.readUint8() > 0;\n address recipient = stream.readAddress();\n\n lastCalledPool = pool;\n IUniswapV3Pool(pool).swap(\n recipient,\n zeroForOne,\n int256(amountIn),\n zeroForOne ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1,\n abi.encode(tokenIn, from)\n );\n require(lastCalledPool == IMPOSSIBLE_POOL_ADDRESS, 'RouteProcessor.swapUniV3: unexpected'); // Just to be sure\n }\n\n /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap.\n /// @dev In the implementation you must pay the pool tokens owed for the swap.\n /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.\n /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.\n /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by\n /// the end of the swap. If positive, the callback must send that amount of token0 to the pool.\n /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by\n /// the end of the swap. If positive, the callback must send that amount of token1 to the pool.\n /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call\n function uniswapV3SwapCallback(\n int256 amount0Delta,\n int256 amount1Delta,\n bytes calldata data\n ) external {\n require(msg.sender == lastCalledPool, 'RouteProcessor.uniswapV3SwapCallback: call from unknown source');\n lastCalledPool = IMPOSSIBLE_POOL_ADDRESS;\n (address tokenIn, address from) = abi.decode(data, (address, address));\n int256 amount = amount0Delta > 0 ? amount0Delta : amount1Delta;\n require(amount > 0, 'RouteProcessor.uniswapV3SwapCallback: not positive amount');\n\n if (from == address(this)) IERC20(tokenIn).safeTransfer(msg.sender, uint256(amount));\n else IERC20(tokenIn).safeTransferFrom(from, msg.sender, uint256(amount));\n }\n\n /// @notice TridentCL pool swap\n /// @param stream [pool, direction, recipient]\n /// @param from Where to take liquidity for swap\n /// @param tokenIn Input token\n /// @param amountIn Amount of tokenIn to take for swap\n function swapTridentCL(uint256 stream, address from, address tokenIn, uint256 amountIn) private {\n address pool = stream.readAddress();\n bool zeroForOne = stream.readUint8() > 0;\n address recipient = stream.readAddress();\n\n lastCalledPool = pool;\n ITridentCLPool(pool).swap(\n recipient,\n zeroForOne,\n int256(amountIn),\n zeroForOne ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1,\n false,\n abi.encode(tokenIn, from)\n );\n require(lastCalledPool == IMPOSSIBLE_POOL_ADDRESS, 'RouteProcessor.swapTridentCL: unexpected'); // Just to be sure\n }\n\n /// @notice Called to `msg.sender` after executing a swap via ITridentCLPool#swap.\n /// @dev In the implementation you must pay the pool tokens owed for the swap.\n /// The caller of this method must be checked to be a TridentCLPool deployed by the canonical TridentCLFactory.\n /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.\n /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by\n /// the end of the swap. If positive, the callback must send that amount of token0 to the pool.\n /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by\n /// the end of the swap. If positive, the callback must send that amount of token1 to the pool.\n /// @param data Any data passed through by the caller via the ITridentCLPoolActions#swap call\n function tridentCLSwapCallback(\n int256 amount0Delta,\n int256 amount1Delta,\n bytes calldata data\n ) external {\n require(msg.sender == lastCalledPool, 'RouteProcessor.TridentCLSwapCallback: call from unknown source');\n lastCalledPool = IMPOSSIBLE_POOL_ADDRESS;\n (address tokenIn, address from) = abi.decode(data, (address, address));\n int256 amount = amount0Delta > 0 ? amount0Delta : amount1Delta;\n require(amount > 0, 'RouteProcessor.TridentCLSwapCallback: not positive amount');\n\n if (from == address(this)) IERC20(tokenIn).safeTransfer(msg.sender, uint256(amount));\n else IERC20(tokenIn).safeTransferFrom(from, msg.sender, uint256(amount));\n }\n}\n"},"interfaces/ITridentCLPool.sol":{"content":"// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity 0.8.10;\n\ninterface ITridentCLPool {\n function token0() external returns (address);\n function token1() external returns (address);\n\n function swap(\n address recipient,\n bool zeroForOne,\n int256 amountSpecified,\n uint160 sqrtPriceLimitX96,\n bool unwrapBento,\n bytes calldata data\n ) external returns (int256 amount0, int256 amount1);\n}\n"},"@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in\n * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].\n *\n * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by\n * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't\n * need to send a transaction, and thus is not required to hold Ether at all.\n */\ninterface IERC20Permit {\n /**\n * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,\n * given ``owner``'s signed approval.\n *\n * IMPORTANT: The same issues {IERC20-approve} has related to transaction\n * ordering also apply here.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `deadline` must be a timestamp in the future.\n * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`\n * over the EIP712-formatted function arguments.\n * - the signature must use ``owner``'s current nonce (see {nonces}).\n *\n * For more information on the signature format, see the\n * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP\n * section].\n */\n function permit(\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external;\n\n /**\n * @dev Returns the current nonce for `owner`. This value must be\n * included whenever a signature is generated for {permit}.\n *\n * Every successful call to {permit} increases ``owner``'s nonce by one. This\n * prevents a signature from being used multiple times.\n */\n function nonces(address owner) external view returns (uint256);\n\n /**\n * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.\n */\n // solhint-disable-next-line func-name-mixedcase\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n}\n"},"interfaces/IUniswapV2Pair.sol":{"content":"// SPDX-License-Identifier: GPL-3.0\n\npragma solidity >=0.5.0;\n\ninterface IUniswapV2Pair {\n event Approval(address indexed owner, address indexed spender, uint value);\n event Transfer(address indexed from, address indexed to, uint value);\n\n function name() external pure returns (string memory);\n function symbol() external pure returns (string memory);\n function decimals() external pure returns (uint8);\n function totalSupply() external view returns (uint);\n function balanceOf(address owner) external view returns (uint);\n function allowance(address owner, address spender) external view returns (uint);\n\n function approve(address spender, uint value) external returns (bool);\n function transfer(address to, uint value) external returns (bool);\n function transferFrom(address from, address to, uint value) external returns (bool);\n\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n function PERMIT_TYPEHASH() external pure returns (bytes32);\n function nonces(address owner) external view returns (uint);\n\n function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;\n\n event Mint(address indexed sender, uint amount0, uint amount1);\n event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);\n event Swap(\n address indexed sender,\n uint amount0In,\n uint amount1In,\n uint amount0Out,\n uint amount1Out,\n address indexed to\n );\n event Sync(uint112 reserve0, uint112 reserve1);\n\n function MINIMUM_LIQUIDITY() external pure returns (uint);\n function factory() external view returns (address);\n function token0() external view returns (address);\n function token1() external view returns (address);\n function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);\n function price0CumulativeLast() external view returns (uint);\n function price1CumulativeLast() external view returns (uint);\n function kLast() external view returns (uint);\n\n function mint(address to) external returns (uint liquidity);\n function burn(address to) external returns (uint amount0, uint amount1);\n function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;\n function skim(address to) external;\n function sync() external;\n\n function initialize(address, address) external;\n}"},"interfaces/IUniswapV3Pool.sol":{"content":"// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity 0.8.10;\n\ninterface IUniswapV3Pool {\n function token0() external returns (address);\n function token1() external returns (address);\n\n function swap(\n address recipient,\n bool zeroForOne,\n int256 amountSpecified,\n uint160 sqrtPriceLimitX96,\n bytes calldata data\n ) external returns (int256 amount0, int256 amount1);\n}\n"},"interfaces/IWETH.sol":{"content":"// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity 0.8.10;\n\ninterface IWETH {\n function deposit() external payable;\n\n function transfer(address to, uint256 value) external returns (bool);\n\n function withdraw(uint256) external;\n}\n"}},"settings":{"optimizer":{"enabled":true,"runs":10000000},"outputSelection":{"*":{"*":["evm.bytecode","evm.deployedBytecode","devdoc","userdoc","metadata","abi"]}},"metadata":{"useLiteralContent":true},"libraries":{}}},"ABI":"[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_bentoBox\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"bentoBox\",\"outputs\":[{\"internalType\":\"contract IBentoBoxMinimal\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"tokenIn\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amountIn\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"tokenOut\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amountOutMin\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"route\",\"type\":\"bytes\"}],\"name\":\"processRoute\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amountOut\",\"type\":\"uint256\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address payable\",\"name\":\"transferValueTo\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amountValueTransfer\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"tokenIn\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amountIn\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"tokenOut\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amountOutMin\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"route\",\"type\":\"bytes\"}],\"name\":\"transferValueAndprocessRoute\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"amountOut\",\"type\":\"uint256\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int256\",\"name\":\"amount0Delta\",\"type\":\"int256\"},{\"internalType\":\"int256\",\"name\":\"amount1Delta\",\"type\":\"int256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"tridentCLSwapCallback\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"int256\",\"name\":\"amount0Delta\",\"type\":\"int256\"},{\"internalType\":\"int256\",\"name\":\"amount1Delta\",\"type\":\"int256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"uniswapV3SwapCallback\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]","ContractName":"RouteProcessor2","CompilerVersion":"v0.8.10+commit.fc410830","OptimizationUsed":1,"Runs":10000000,"ConstructorArguments":"0x000000000000000000000000f5bce5077908a1b7370b9ae04adc565ebd643966","EVMVersion":"Default","Library":"","LicenseType":"","Proxy":0,"SwarmSource":""}] \ No newline at end of file diff --git a/testdata/etherscan/0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193/creation_data.json b/testdata/etherscan/0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193/creation_data.json new file mode 100644 index 000000000..e3433ef20 --- /dev/null +++ b/testdata/etherscan/0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193/creation_data.json @@ -0,0 +1 @@ +{"contractAddress":"0x35fb958109b70799a8f9bc2a8b1ee4cc62034193","contractCreator":"0x3e32324277e96b69750bc6f7c4ba27e122413e07","txHash":"0x41e3517f8262b55e1eb1707ba0760b603a70e89ea4a86eff56072fcc80c3d0a1"} \ No newline at end of file diff --git a/testdata/etherscan/0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193/metadata.json b/testdata/etherscan/0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193/metadata.json new file mode 100644 index 000000000..bd48a2efb --- /dev/null +++ b/testdata/etherscan/0x35Fb958109b70799a8f9Bc2a8b1Ee4cC62034193/metadata.json @@ -0,0 +1 @@ +[{"SourceCode":"/**\r\n *Submitted for verification at Etherscan.io on 2022-02-19\r\n*/\r\n\r\n/**\r\n *Submitted for verification at Etherscan.io on 2022-02-18\r\n*/\r\n\r\n/**\r\n *Submitted for verification at Etherscan.io on 2022-02-14\r\n*/\r\n\r\n/**\r\n *Submitted for verification at Etherscan.io on 2022-02-10\r\n*/\r\n\r\n/**\r\n *Submitted for verification at Etherscan.io on 2022-02-09\r\n*/\r\n\r\n/**\r\n *Submitted for verification at Etherscan.io on 2022-02-08\r\n*/\r\n\r\n/**\r\n *Submitted for verification at Etherscan.io on 2022-02-05\r\n*/\r\n\r\n/**\r\n *Submitted for verification at Etherscan.io on 2022-01-22\r\n*/\r\n\r\n// SPDX-License-Identifier: UNLICENSED\r\n/*\r\nmade by cty0312\r\n2022.01.22\r\n**/\r\n\r\npragma solidity >=0.7.0 <0.9.0;\r\n\r\nlibrary StringsUpgradeable {\r\n bytes16 private constant _HEX_SYMBOLS = \"0123456789abcdef\";\r\n\r\n /**\r\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\r\n */\r\n function toString(uint256 value) internal pure returns (string memory) {\r\n // Inspired by OraclizeAPI's implementation - MIT licence\r\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\r\n\r\n if (value == 0) {\r\n return \"0\";\r\n }\r\n uint256 temp = value;\r\n uint256 digits;\r\n while (temp != 0) {\r\n digits++;\r\n temp /= 10;\r\n }\r\n bytes memory buffer = new bytes(digits);\r\n while (value != 0) {\r\n digits -= 1;\r\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\r\n value /= 10;\r\n }\r\n return string(buffer);\r\n }\r\n\r\n /**\r\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\r\n */\r\n function toHexString(uint256 value) internal pure returns (string memory) {\r\n if (value == 0) {\r\n return \"0x00\";\r\n }\r\n uint256 temp = value;\r\n uint256 length = 0;\r\n while (temp != 0) {\r\n length++;\r\n temp >>= 8;\r\n }\r\n return toHexString(value, length);\r\n }\r\n\r\n /**\r\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\r\n */\r\n function toHexString(uint256 value, uint256 length)\r\n internal\r\n pure\r\n returns (string memory)\r\n {\r\n bytes memory buffer = new bytes(2 * length + 2);\r\n buffer[0] = \"0\";\r\n buffer[1] = \"x\";\r\n for (uint256 i = 2 * length + 1; i > 1; --i) {\r\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\r\n value >>= 4;\r\n }\r\n require(value == 0, \"Strings: hex length insufficient\");\r\n return string(buffer);\r\n }\r\n}\r\n\r\nlibrary AddressUpgradeable {\r\n /**\r\n * @dev Returns true if `account` is a contract.\r\n *\r\n * [IMPORTANT]\r\n * ====\r\n * It is unsafe to assume that an address for which this function returns\r\n * false is an externally-owned account (EOA) and not a contract.\r\n *\r\n * Among others, `isContract` will return false for the following\r\n * types of addresses:\r\n *\r\n * - an externally-owned account\r\n * - a contract in construction\r\n * - an address where a contract will be created\r\n * - an address where a contract lived, but was destroyed\r\n * ====\r\n */\r\n function isContract(address account) internal view returns (bool) {\r\n // This method relies on extcodesize, which returns 0 for contracts in\r\n // construction, since the code is only stored at the end of the\r\n // constructor execution.\r\n\r\n uint256 size;\r\n assembly {\r\n size := extcodesize(account)\r\n }\r\n return size > 0;\r\n }\r\n\r\n /**\r\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\r\n * `recipient`, forwarding all available gas and reverting on errors.\r\n *\r\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\r\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\r\n * imposed by `transfer`, making them unable to receive funds via\r\n * `transfer`. {sendValue} removes this limitation.\r\n *\r\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\r\n *\r\n * IMPORTANT: because control is transferred to `recipient`, care must be\r\n * taken to not create reentrancy vulnerabilities. Consider using\r\n * {ReentrancyGuard} or the\r\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\r\n */\r\n function sendValue(address payable recipient, uint256 amount) internal {\r\n require(\r\n address(this).balance >= amount,\r\n \"Address: insufficient balance\"\r\n );\r\n\r\n (bool success, ) = recipient.call{value: amount}(\"\");\r\n require(\r\n success,\r\n \"Address: unable to send value, recipient may have reverted\"\r\n );\r\n }\r\n\r\n /**\r\n * @dev Performs a Solidity function call using a low level `call`. A\r\n * plain `call` is an unsafe replacement for a function call: use this\r\n * function instead.\r\n *\r\n * If `target` reverts with a revert reason, it is bubbled up by this\r\n * function (like regular Solidity function calls).\r\n *\r\n * Returns the raw returned data. To convert to the expected return value,\r\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\r\n *\r\n * Requirements:\r\n *\r\n * - `target` must be a contract.\r\n * - calling `target` with `data` must not revert.\r\n *\r\n * _Available since v3.1._\r\n */\r\n function functionCall(address target, bytes memory data)\r\n internal\r\n returns (bytes memory)\r\n {\r\n return functionCall(target, data, \"Address: low-level call failed\");\r\n }\r\n\r\n /**\r\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\r\n * `errorMessage` as a fallback revert reason when `target` reverts.\r\n *\r\n * _Available since v3.1._\r\n */\r\n function functionCall(\r\n address target,\r\n bytes memory data,\r\n string memory errorMessage\r\n ) internal returns (bytes memory) {\r\n return functionCallWithValue(target, data, 0, errorMessage);\r\n }\r\n\r\n /**\r\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\r\n * but also transferring `value` wei to `target`.\r\n *\r\n * Requirements:\r\n *\r\n * - the calling contract must have an ETH balance of at least `value`.\r\n * - the called Solidity function must be `payable`.\r\n *\r\n * _Available since v3.1._\r\n */\r\n function functionCallWithValue(\r\n address target,\r\n bytes memory data,\r\n uint256 value\r\n ) internal returns (bytes memory) {\r\n return\r\n functionCallWithValue(\r\n target,\r\n data,\r\n value,\r\n \"Address: low-level call with value failed\"\r\n );\r\n }\r\n\r\n /**\r\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\r\n * with `errorMessage` as a fallback revert reason when `target` reverts.\r\n *\r\n * _Available since v3.1._\r\n */\r\n function functionCallWithValue(\r\n address target,\r\n bytes memory data,\r\n uint256 value,\r\n string memory errorMessage\r\n ) internal returns (bytes memory) {\r\n require(\r\n address(this).balance >= value,\r\n \"Address: insufficient balance for call\"\r\n );\r\n require(isContract(target), \"Address: call to non-contract\");\r\n\r\n (bool success, bytes memory returndata) = target.call{value: value}(\r\n data\r\n );\r\n return verifyCallResult(success, returndata, errorMessage);\r\n }\r\n\r\n /**\r\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\r\n * but performing a static call.\r\n *\r\n * _Available since v3.3._\r\n */\r\n function functionStaticCall(address target, bytes memory data)\r\n internal\r\n view\r\n returns (bytes memory)\r\n {\r\n return\r\n functionStaticCall(\r\n target,\r\n data,\r\n \"Address: low-level static call failed\"\r\n );\r\n }\r\n\r\n /**\r\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\r\n * but performing a static call.\r\n *\r\n * _Available since v3.3._\r\n */\r\n function functionStaticCall(\r\n address target,\r\n bytes memory data,\r\n string memory errorMessage\r\n ) internal view returns (bytes memory) {\r\n require(isContract(target), \"Address: static call to non-contract\");\r\n\r\n (bool success, bytes memory returndata) = target.staticcall(data);\r\n return verifyCallResult(success, returndata, errorMessage);\r\n }\r\n\r\n /**\r\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\r\n * revert reason using the provided one.\r\n *\r\n * _Available since v4.3._\r\n */\r\n function verifyCallResult(\r\n bool success,\r\n bytes memory returndata,\r\n string memory errorMessage\r\n ) internal pure returns (bytes memory) {\r\n if (success) {\r\n return returndata;\r\n } else {\r\n // Look for revert reason and bubble it up if present\r\n if (returndata.length > 0) {\r\n // The easiest way to bubble the revert reason is using memory via assembly\r\n\r\n assembly {\r\n let returndata_size := mload(returndata)\r\n revert(add(32, returndata), returndata_size)\r\n }\r\n } else {\r\n revert(errorMessage);\r\n }\r\n }\r\n }\r\n}\r\n\r\nlibrary SafeMathUpgradeable {\r\n /**\r\n * @dev Returns the addition of two unsigned integers, reverting on\r\n * overflow.\r\n *\r\n * Counterpart to Solidity's `+` operator.\r\n *\r\n * Requirements:\r\n *\r\n * - Addition cannot overflow.\r\n */\r\n function add(uint256 a, uint256 b) internal pure returns (uint256) {\r\n uint256 c = a + b;\r\n require(c >= a, \"SafeMath: addition overflow\");\r\n\r\n return c;\r\n }\r\n\r\n /**\r\n * @dev Returns the subtraction of two unsigned integers, reverting on\r\n * overflow (when the result is negative).\r\n *\r\n * Counterpart to Solidity's `-` operator.\r\n *\r\n * Requirements:\r\n *\r\n * - Subtraction cannot overflow.\r\n */\r\n function sub(uint256 a, uint256 b) internal pure returns (uint256) {\r\n return sub(a, b, \"SafeMath: subtraction overflow\");\r\n }\r\n\r\n /**\r\n * @dev Returns the subtraction of two unsigned integers, reverting with custom message on\r\n * overflow (when the result is negative).\r\n *\r\n * Counterpart to Solidity's `-` operator.\r\n *\r\n * Requirements:\r\n *\r\n * - Subtraction cannot overflow.\r\n */\r\n function sub(\r\n uint256 a,\r\n uint256 b,\r\n string memory errorMessage\r\n ) internal pure returns (uint256) {\r\n require(b <= a, errorMessage);\r\n uint256 c = a - b;\r\n\r\n return c;\r\n }\r\n\r\n /**\r\n * @dev Returns the multiplication of two unsigned integers, reverting on\r\n * overflow.\r\n *\r\n * Counterpart to Solidity's `*` operator.\r\n *\r\n * Requirements:\r\n *\r\n * - Multiplication cannot overflow.\r\n */\r\n function mul(uint256 a, uint256 b) internal pure returns (uint256) {\r\n // Gas optimization: this is cheaper than requiring 'a' not being zero, but the\r\n // benefit is lost if 'b' is also tested.\r\n // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522\r\n if (a == 0) {\r\n return 0;\r\n }\r\n\r\n uint256 c = a * b;\r\n require(c / a == b, \"SafeMath: multiplication overflow\");\r\n\r\n return c;\r\n }\r\n\r\n /**\r\n * @dev Returns the integer division of two unsigned integers. Reverts on\r\n * division by zero. The result is rounded towards zero.\r\n *\r\n * Counterpart to Solidity's `/` operator. Note: this function uses a\r\n * `revert` opcode (which leaves remaining gas untouched) while Solidity\r\n * uses an invalid opcode to revert (consuming all remaining gas).\r\n *\r\n * Requirements:\r\n *\r\n * - The divisor cannot be zero.\r\n */\r\n function div(uint256 a, uint256 b) internal pure returns (uint256) {\r\n return div(a, b, \"SafeMath: division by zero\");\r\n }\r\n\r\n /**\r\n * @dev Returns the integer division of two unsigned integers. Reverts with custom message on\r\n * division by zero. The result is rounded towards zero.\r\n *\r\n * Counterpart to Solidity's `/` operator. Note: this function uses a\r\n * `revert` opcode (which leaves remaining gas untouched) while Solidity\r\n * uses an invalid opcode to revert (consuming all remaining gas).\r\n *\r\n * Requirements:\r\n *\r\n * - The divisor cannot be zero.\r\n */\r\n function div(\r\n uint256 a,\r\n uint256 b,\r\n string memory errorMessage\r\n ) internal pure returns (uint256) {\r\n require(b > 0, errorMessage);\r\n uint256 c = a / b;\r\n // assert(a == b * c + a % b); // There is no case in which this doesn't hold\r\n\r\n return c;\r\n }\r\n\r\n /**\r\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\r\n * Reverts when dividing by zero.\r\n *\r\n * Counterpart to Solidity's `%` operator. This function uses a `revert`\r\n * opcode (which leaves remaining gas untouched) while Solidity uses an\r\n * invalid opcode to revert (consuming all remaining gas).\r\n *\r\n * Requirements:\r\n *\r\n * - The divisor cannot be zero.\r\n */\r\n function mod(uint256 a, uint256 b) internal pure returns (uint256) {\r\n return mod(a, b, \"SafeMath: modulo by zero\");\r\n }\r\n\r\n /**\r\n * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),\r\n * Reverts with custom message when dividing by zero.\r\n *\r\n * Counterpart to Solidity's `%` operator. This function uses a `revert`\r\n * opcode (which leaves remaining gas untouched) while Solidity uses an\r\n * invalid opcode to revert (consuming all remaining gas).\r\n *\r\n * Requirements:\r\n *\r\n * - The divisor cannot be zero.\r\n */\r\n function mod(\r\n uint256 a,\r\n uint256 b,\r\n string memory errorMessage\r\n ) internal pure returns (uint256) {\r\n require(b != 0, errorMessage);\r\n return a % b;\r\n }\r\n}\r\n\r\nabstract contract Initializable {\r\n /**\r\n * @dev Indicates that the contract has been initialized.\r\n */\r\n bool private _initialized;\r\n\r\n /**\r\n * @dev Indicates that the contract is in the process of being initialized.\r\n */\r\n bool private _initializing;\r\n\r\n /**\r\n * @dev Modifier to protect an initializer function from being invoked twice.\r\n */\r\n modifier initializer() {\r\n // If the contract is initializing we ignore whether _initialized is set in order to support multiple\r\n // inheritance patterns, but we only do this in the context of a constructor, because in other contexts the\r\n // contract may have been reentered.\r\n require(\r\n _initializing ? _isConstructor() : !_initialized,\r\n \"Initializable: contract is already initialized\"\r\n );\r\n\r\n bool isTopLevelCall = !_initializing;\r\n if (isTopLevelCall) {\r\n _initializing = true;\r\n _initialized = true;\r\n }\r\n\r\n _;\r\n\r\n if (isTopLevelCall) {\r\n _initializing = false;\r\n }\r\n }\r\n\r\n /**\r\n * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the\r\n * {initializer} modifier, directly or indirectly.\r\n */\r\n modifier onlyInitializing() {\r\n require(_initializing, \"Initializable: contract is not initializing\");\r\n _;\r\n }\r\n\r\n function _isConstructor() private view returns (bool) {\r\n return !AddressUpgradeable.isContract(address(this));\r\n }\r\n}\r\n\r\nabstract contract ContextUpgradeable is Initializable {\r\n function __Context_init() internal onlyInitializing {\r\n __Context_init_unchained();\r\n }\r\n\r\n function __Context_init_unchained() internal onlyInitializing {}\r\n\r\n function _msgSender() internal view virtual returns (address) {\r\n return msg.sender;\r\n }\r\n\r\n function _msgData() internal view virtual returns (bytes calldata) {\r\n return msg.data;\r\n }\r\n\r\n uint256[50] private __gap;\r\n}\r\n\r\nabstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {\r\n address private _owner;\r\n\r\n event OwnershipTransferred(\r\n address indexed previousOwner,\r\n address indexed newOwner\r\n );\r\n\r\n /**\r\n * @dev Initializes the contract setting the deployer as the initial owner.\r\n */\r\n function __Ownable_init() internal onlyInitializing {\r\n __Context_init_unchained();\r\n __Ownable_init_unchained();\r\n }\r\n\r\n function __Ownable_init_unchained() internal onlyInitializing {\r\n _transferOwnership(_msgSender());\r\n }\r\n\r\n /**\r\n * @dev Returns the address of the current owner.\r\n */\r\n function owner() public view virtual returns (address) {\r\n return _owner;\r\n }\r\n\r\n /**\r\n * @dev Throws if called by any account other than the owner.\r\n */\r\n modifier onlyOwner() {\r\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\r\n _;\r\n }\r\n\r\n /**\r\n * @dev Leaves the contract without owner. It will not be possible to call\r\n * `onlyOwner` functions anymore. Can only be called by the current owner.\r\n *\r\n * NOTE: Renouncing ownership will leave the contract without an owner,\r\n * thereby removing any functionality that is only available to the owner.\r\n */\r\n function renounceOwnership() public virtual onlyOwner {\r\n _transferOwnership(address(0));\r\n }\r\n\r\n /**\r\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\r\n * Can only be called by the current owner.\r\n */\r\n function transferOwnership(address newOwner) public virtual onlyOwner {\r\n require(\r\n newOwner != address(0),\r\n \"Ownable: new owner is the zero address\"\r\n );\r\n _transferOwnership(newOwner);\r\n }\r\n\r\n /**\r\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\r\n * Internal function without access restriction.\r\n */\r\n function _transferOwnership(address newOwner) internal virtual {\r\n address oldOwner = _owner;\r\n _owner = newOwner;\r\n emit OwnershipTransferred(oldOwner, newOwner);\r\n }\r\n\r\n uint256[49] private __gap;\r\n}\r\n\r\ninterface IERC165Upgradeable {\r\n /**\r\n * @dev Returns true if this contract implements the interface defined by\r\n * `interfaceId`. See the corresponding\r\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\r\n * to learn more about how these ids are created.\r\n *\r\n * This function call must use less than 30 000 gas.\r\n */\r\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\r\n}\r\n\r\ninterface IERC721Upgradeable is IERC165Upgradeable {\r\n /**\r\n * @dev Emitted when `tokenId` token is transferred from `from` to `to`.\r\n */\r\n event Transfer(\r\n address indexed from,\r\n address indexed to,\r\n uint256 indexed tokenId\r\n );\r\n\r\n /**\r\n * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.\r\n */\r\n event Approval(\r\n address indexed owner,\r\n address indexed approved,\r\n uint256 indexed tokenId\r\n );\r\n\r\n /**\r\n * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.\r\n */\r\n event ApprovalForAll(\r\n address indexed owner,\r\n address indexed operator,\r\n bool approved\r\n );\r\n\r\n /**\r\n * @dev Returns the number of tokens in ``owner``'s account.\r\n */\r\n function balanceOf(address owner) external view returns (uint256 balance);\r\n\r\n /**\r\n * @dev Returns the owner of the `tokenId` token.\r\n *\r\n * Requirements:\r\n *\r\n * - `tokenId` must exist.\r\n */\r\n function ownerOf(uint256 tokenId) external view returns (address owner);\r\n\r\n /**\r\n * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\r\n * are aware of the ERC721 protocol to prevent tokens from being forever locked.\r\n *\r\n * Requirements:\r\n *\r\n * - `from` cannot be the zero address.\r\n * - `to` cannot be the zero address.\r\n * - `tokenId` token must exist and be owned by `from`.\r\n * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}.\r\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\r\n *\r\n * Emits a {Transfer} event.\r\n */\r\n function safeTransferFrom(\r\n address from,\r\n address to,\r\n uint256 tokenId\r\n ) external;\r\n\r\n /**\r\n * @dev Transfers `tokenId` token from `from` to `to`.\r\n *\r\n * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.\r\n *\r\n * Requirements:\r\n *\r\n * - `from` cannot be the zero address.\r\n * - `to` cannot be the zero address.\r\n * - `tokenId` token must be owned by `from`.\r\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\r\n *\r\n * Emits a {Transfer} event.\r\n */\r\n function transferFrom(\r\n address from,\r\n address to,\r\n uint256 tokenId\r\n ) external;\r\n\r\n /**\r\n * @dev Gives permission to `to` to transfer `tokenId` token to another account.\r\n * The approval is cleared when the token is transferred.\r\n *\r\n * Only a single account can be approved at a time, so approving the zero address clears previous approvals.\r\n *\r\n * Requirements:\r\n *\r\n * - The caller must own the token or be an approved operator.\r\n * - `tokenId` must exist.\r\n *\r\n * Emits an {Approval} event.\r\n */\r\n function approve(address to, uint256 tokenId) external;\r\n\r\n /**\r\n * @dev Returns the account approved for `tokenId` token.\r\n *\r\n * Requirements:\r\n *\r\n * - `tokenId` must exist.\r\n */\r\n function getApproved(uint256 tokenId)\r\n external\r\n view\r\n returns (address operator);\r\n\r\n /**\r\n * @dev Approve or remove `operator` as an operator for the caller.\r\n * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.\r\n *\r\n * Requirements:\r\n *\r\n * - The `operator` cannot be the caller.\r\n *\r\n * Emits an {ApprovalForAll} event.\r\n */\r\n function setApprovalForAll(address operator, bool _approved) external;\r\n\r\n /**\r\n * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.\r\n *\r\n * See {setApprovalForAll}\r\n */\r\n function isApprovedForAll(address owner, address operator)\r\n external\r\n view\r\n returns (bool);\r\n\r\n /**\r\n * @dev Safely transfers `tokenId` token from `from` to `to`.\r\n *\r\n * Requirements:\r\n *\r\n * - `from` cannot be the zero address.\r\n * - `to` cannot be the zero address.\r\n * - `tokenId` token must exist and be owned by `from`.\r\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\r\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\r\n *\r\n * Emits a {Transfer} event.\r\n */\r\n function safeTransferFrom(\r\n address from,\r\n address to,\r\n uint256 tokenId,\r\n bytes calldata data\r\n ) external;\r\n}\r\n\r\ninterface IERC20Upgradeable {\r\n /**\r\n * @dev Returns the amount of tokens in existence.\r\n */\r\n function totalSupply() external view returns (uint256);\r\n\r\n /**\r\n * @dev Returns the amount of tokens owned by `account`.\r\n */\r\n function balanceOf(address account) external view returns (uint256);\r\n\r\n /**\r\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\r\n *\r\n * Returns a boolean value indicating whether the operation succeeded.\r\n *\r\n * Emits a {Transfer} event.\r\n */\r\n function transfer(address recipient, uint256 amount)\r\n external\r\n returns (bool);\r\n\r\n /**\r\n * @dev Returns the remaining number of tokens that `spender` will be\r\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\r\n * zero by default.\r\n *\r\n * This value changes when {approve} or {transferFrom} are called.\r\n */\r\n function allowance(address owner, address spender)\r\n external\r\n view\r\n returns (uint256);\r\n\r\n /**\r\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\r\n *\r\n * Returns a boolean value indicating whether the operation succeeded.\r\n *\r\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\r\n * that someone may use both the old and the new allowance by unfortunate\r\n * transaction ordering. One possible solution to mitigate this race\r\n * condition is to first reduce the spender's allowance to 0 and set the\r\n * desired value afterwards:\r\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\r\n *\r\n * Emits an {Approval} event.\r\n */\r\n function approve(address spender, uint256 amount) external returns (bool);\r\n\r\n /**\r\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\r\n * allowance mechanism. `amount` is then deducted from the caller's\r\n * allowance.\r\n *\r\n * Returns a boolean value indicating whether the operation succeeded.\r\n *\r\n * Emits a {Transfer} event.\r\n */\r\n function transferFrom(\r\n address sender,\r\n address recipient,\r\n uint256 amount\r\n ) external returns (bool);\r\n\r\n\r\n function mintToken(address _address, uint256 _amount) external;\r\n /**\r\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\r\n * another (`to`).\r\n *\r\n * Note that `value` may be zero.\r\n */\r\n event Transfer(address indexed from, address indexed to, uint256 value);\r\n\r\n /**\r\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\r\n * a call to {approve}. `value` is the new allowance.\r\n */\r\n event Approval(\r\n address indexed owner,\r\n address indexed spender,\r\n uint256 value\r\n );\r\n}\r\n\r\ninterface IUniswapV2Factory {\r\n event PairCreated(\r\n address indexed token0,\r\n address indexed token1,\r\n address pair,\r\n uint256\r\n );\r\n\r\n function feeTo() external view returns (address);\r\n\r\n function feeToSetter() external view returns (address);\r\n\r\n function getPair(address tokenA, address tokenB)\r\n external\r\n view\r\n returns (address pair);\r\n\r\n function allPairs(uint256) external view returns (address pair);\r\n\r\n function allPairsLength() external view returns (uint256);\r\n\r\n function createPair(address tokenA, address tokenB)\r\n external\r\n returns (address pair);\r\n\r\n function setFeeTo(address) external;\r\n\r\n function setFeeToSetter(address) external;\r\n}\r\n\r\ninterface IUniswapV2Pair {\r\n event Approval(\r\n address indexed owner,\r\n address indexed spender,\r\n uint256 value\r\n );\r\n event Transfer(address indexed from, address indexed to, uint256 value);\r\n\r\n function name() external pure returns (string memory);\r\n\r\n function symbol() external pure returns (string memory);\r\n\r\n function decimals() external pure returns (uint8);\r\n\r\n function totalSupply() external view returns (uint256);\r\n\r\n function balanceOf(address owner) external view returns (uint256);\r\n\r\n function allowance(address owner, address spender)\r\n external\r\n view\r\n returns (uint256);\r\n\r\n function approve(address spender, uint256 value) external returns (bool);\r\n\r\n function transfer(address to, uint256 value) external returns (bool);\r\n\r\n function transferFrom(\r\n address from,\r\n address to,\r\n uint256 value\r\n ) external returns (bool);\r\n\r\n function DOMAIN_SEPARATOR() external view returns (bytes32);\r\n\r\n function PERMIT_TYPEHASH() external pure returns (bytes32);\r\n\r\n function nonces(address owner) external view returns (uint256);\r\n\r\n function permit(\r\n address owner,\r\n address spender,\r\n uint256 value,\r\n uint256 deadline,\r\n uint8 v,\r\n bytes32 r,\r\n bytes32 s\r\n ) external;\r\n\r\n event Mint(address indexed sender, uint256 amount0, uint256 amount1);\r\n event Burn(\r\n address indexed sender,\r\n uint256 amount0,\r\n uint256 amount1,\r\n address indexed to\r\n );\r\n event Swap(\r\n address indexed sender,\r\n uint256 amount0In,\r\n uint256 amount1In,\r\n uint256 amount0Out,\r\n uint256 amount1Out,\r\n address indexed to\r\n );\r\n event Sync(uint112 reserve0, uint112 reserve1);\r\n\r\n function MINIMUM_LIQUIDITY() external pure returns (uint256);\r\n\r\n function factory() external view returns (address);\r\n\r\n function token0() external view returns (address);\r\n\r\n function token1() external view returns (address);\r\n\r\n function getReserves()\r\n external\r\n view\r\n returns (\r\n uint112 reserve0,\r\n uint112 reserve1,\r\n uint32 blockTimestampLast\r\n );\r\n\r\n function price0CumulativeLast() external view returns (uint256);\r\n\r\n function price1CumulativeLast() external view returns (uint256);\r\n\r\n function kLast() external view returns (uint256);\r\n\r\n function mint(address to) external returns (uint256 liquidity);\r\n\r\n function burn(address to)\r\n external\r\n returns (uint256 amount0, uint256 amount1);\r\n\r\n function swap(\r\n uint256 amount0Out,\r\n uint256 amount1Out,\r\n address to,\r\n bytes calldata data\r\n ) external;\r\n\r\n function skim(address to) external;\r\n\r\n function sync() external;\r\n\r\n function initialize(address, address) external;\r\n}\r\n\r\ninterface IUniswapV2Router01 {\r\n function factory() external pure returns (address);\r\n\r\n function WETH() external pure returns (address);\r\n\r\n function addLiquidity(\r\n address tokenA,\r\n address tokenB,\r\n uint256 amountADesired,\r\n uint256 amountBDesired,\r\n uint256 amountAMin,\r\n uint256 amountBMin,\r\n address to,\r\n uint256 deadline\r\n )\r\n external\r\n returns (\r\n uint256 amountA,\r\n uint256 amountB,\r\n uint256 liquidity\r\n );\r\n\r\n function addLiquidityETH(\r\n address token,\r\n uint256 amountTokenDesired,\r\n uint256 amountTokenMin,\r\n uint256 amountETHMin,\r\n address to,\r\n uint256 deadline\r\n )\r\n external\r\n payable\r\n returns (\r\n uint256 amountToken,\r\n uint256 amountETH,\r\n uint256 liquidity\r\n );\r\n\r\n function removeLiquidity(\r\n address tokenA,\r\n address tokenB,\r\n uint256 liquidity,\r\n uint256 amountAMin,\r\n uint256 amountBMin,\r\n address to,\r\n uint256 deadline\r\n ) external returns (uint256 amountA, uint256 amountB);\r\n\r\n function removeLiquidityETH(\r\n address token,\r\n uint256 liquidity,\r\n uint256 amountTokenMin,\r\n uint256 amountETHMin,\r\n address to,\r\n uint256 deadline\r\n ) external returns (uint256 amountToken, uint256 amountETH);\r\n\r\n function removeLiquidityWithPermit(\r\n address tokenA,\r\n address tokenB,\r\n uint256 liquidity,\r\n uint256 amountAMin,\r\n uint256 amountBMin,\r\n address to,\r\n uint256 deadline,\r\n bool approveMax,\r\n uint8 v,\r\n bytes32 r,\r\n bytes32 s\r\n ) external returns (uint256 amountA, uint256 amountB);\r\n\r\n function removeLiquidityETHWithPermit(\r\n address token,\r\n uint256 liquidity,\r\n uint256 amountTokenMin,\r\n uint256 amountETHMin,\r\n address to,\r\n uint256 deadline,\r\n bool approveMax,\r\n uint8 v,\r\n bytes32 r,\r\n bytes32 s\r\n ) external returns (uint256 amountToken, uint256 amountETH);\r\n\r\n function swapExactTokensForTokens(\r\n uint256 amountIn,\r\n uint256 amountOutMin,\r\n address[] calldata path,\r\n address to,\r\n uint256 deadline\r\n ) external returns (uint256[] memory amounts);\r\n\r\n function swapTokensForExactTokens(\r\n uint256 amountOut,\r\n uint256 amountInMax,\r\n address[] calldata path,\r\n address to,\r\n uint256 deadline\r\n ) external returns (uint256[] memory amounts);\r\n\r\n function swapExactETHForTokens(\r\n uint256 amountOutMin,\r\n address[] calldata path,\r\n address to,\r\n uint256 deadline\r\n ) external payable returns (uint256[] memory amounts);\r\n\r\n function swapTokensForExactETH(\r\n uint256 amountOut,\r\n uint256 amountInMax,\r\n address[] calldata path,\r\n address to,\r\n uint256 deadline\r\n ) external returns (uint256[] memory amounts);\r\n\r\n function swapExactTokensForETH(\r\n uint256 amountIn,\r\n uint256 amountOutMin,\r\n address[] calldata path,\r\n address to,\r\n uint256 deadline\r\n ) external returns (uint256[] memory amounts);\r\n\r\n function swapETHForExactTokens(\r\n uint256 amountOut,\r\n address[] calldata path,\r\n address to,\r\n uint256 deadline\r\n ) external payable returns (uint256[] memory amounts);\r\n\r\n function quote(\r\n uint256 amountA,\r\n uint256 reserveA,\r\n uint256 reserveB\r\n ) external pure returns (uint256 amountB);\r\n\r\n function getAmountOut(\r\n uint256 amountIn,\r\n uint256 reserveIn,\r\n uint256 reserveOut\r\n ) external pure returns (uint256 amountOut);\r\n\r\n function getAmountIn(\r\n uint256 amountOut,\r\n uint256 reserveIn,\r\n uint256 reserveOut\r\n ) external pure returns (uint256 amountIn);\r\n\r\n function getAmountsOut(uint256 amountIn, address[] calldata path)\r\n external\r\n view\r\n returns (uint256[] memory amounts);\r\n\r\n function getAmountsIn(uint256 amountOut, address[] calldata path)\r\n external\r\n view\r\n returns (uint256[] memory amounts);\r\n}\r\n\r\ninterface IUniswapV2Router is IUniswapV2Router01 {\r\n function removeLiquidityETHSupportingFeeOnTransferTokens(\r\n address token,\r\n uint256 liquidity,\r\n uint256 amountTokenMin,\r\n uint256 amountETHMin,\r\n address to,\r\n uint256 deadline\r\n ) external returns (uint256 amountETH);\r\n\r\n function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(\r\n address token,\r\n uint256 liquidity,\r\n uint256 amountTokenMin,\r\n uint256 amountETHMin,\r\n address to,\r\n uint256 deadline,\r\n bool approveMax,\r\n uint8 v,\r\n bytes32 r,\r\n bytes32 s\r\n ) external returns (uint256 amountETH);\r\n\r\n function swapExactTokensForTokensSupportingFeeOnTransferTokens(\r\n uint256 amountIn,\r\n uint256 amountOutMin,\r\n address[] calldata path,\r\n address to,\r\n uint256 deadline\r\n ) external;\r\n\r\n function swapExactETHForTokensSupportingFeeOnTransferTokens(\r\n uint256 amountOutMin,\r\n address[] calldata path,\r\n address to,\r\n uint256 deadline\r\n ) external payable;\r\n\r\n function swapExactTokensForETHSupportingFeeOnTransferTokens(\r\n uint256 amountIn,\r\n uint256 amountOutMin,\r\n address[] calldata path,\r\n address to,\r\n uint256 deadline\r\n ) external;\r\n}\r\n\r\ninterface IERC20MetadataUpgradeable is IERC20Upgradeable {\r\n /**\r\n * @dev Returns the name of the token.\r\n */\r\n function name() external view returns (string memory);\r\n\r\n /**\r\n * @dev Returns the symbol of the token.\r\n */\r\n function symbol() external view returns (string memory);\r\n\r\n /**\r\n * @dev Returns the decimals places of the token.\r\n */\r\n function decimals() external view returns (uint8);\r\n}\r\n\r\nabstract contract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20Upgradeable, IERC20MetadataUpgradeable {\r\n using SafeMathUpgradeable for uint256;\r\n\r\n mapping(address => uint256) private _balances;\r\n\r\n mapping(address => mapping(address => uint256)) private _allowances;\r\n\r\n uint256 private _totalSupply;\r\n\r\n string private _name;\r\n string private _symbol;\r\n\r\n /**\r\n * @dev Sets the values for {name} and {symbol}.\r\n *\r\n * The default value of {decimals} is 18. To select a different value for\r\n * {decimals} you should overload it.\r\n *\r\n * All two of these values are immutable: they can only be set once during\r\n * construction.\r\n */\r\n function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing {\r\n __ERC20_init_unchained(name_, symbol_);\r\n }\r\n\r\n function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {\r\n _name = name_;\r\n _symbol = symbol_;\r\n }\r\n\r\n /**\r\n * @dev Returns the name of the token.\r\n */\r\n function name() public view virtual override returns (string memory) {\r\n return _name;\r\n }\r\n\r\n /**\r\n * @dev Returns the symbol of the token, usually a shorter version of the\r\n * name.\r\n */\r\n function symbol() public view virtual override returns (string memory) {\r\n return _symbol;\r\n }\r\n\r\n /**\r\n * @dev Returns the number of decimals used to get its user representation.\r\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\r\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\r\n *\r\n * Tokens usually opt for a value of 18, imitating the relationship between\r\n * Ether and Wei. This is the value {ERC20} uses, unless this function is\r\n * overridden;\r\n *\r\n * NOTE: This information is only used for _display_ purposes: it in\r\n * no way affects any of the arithmetic of the contract, including\r\n * {IERC20-balanceOf} and {IERC20-transfer}.\r\n */\r\n function decimals() public view virtual override returns (uint8) {\r\n return 18;\r\n }\r\n\r\n /**\r\n * @dev See {IERC20-totalSupply}.\r\n */\r\n function totalSupply() public view virtual override returns (uint256) {\r\n return _totalSupply;\r\n }\r\n\r\n /**\r\n * @dev See {IERC20-balanceOf}.\r\n */\r\n function balanceOf(address account) public view virtual override returns (uint256) {\r\n return _balances[account];\r\n }\r\n\r\n /**\r\n * @dev See {IERC20-transfer}.\r\n *\r\n * Requirements:\r\n *\r\n * - `to` cannot be the zero address.\r\n * - the caller must have a balance of at least `amount`.\r\n */\r\n function transfer(address to, uint256 amount) public virtual override returns (bool) {\r\n address owner = _msgSender();\r\n _transfer(owner, to, amount);\r\n return true;\r\n }\r\n\r\n /**\r\n * @dev See {IERC20-allowance}.\r\n */\r\n function allowance(address owner, address spender) public view virtual override returns (uint256) {\r\n return _allowances[owner][spender];\r\n }\r\n\r\n /**\r\n * @dev See {IERC20-approve}.\r\n *\r\n * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on\r\n * `transferFrom`. This is semantically equivalent to an infinite approval.\r\n *\r\n * Requirements:\r\n *\r\n * - `spender` cannot be the zero address.\r\n */\r\n function approve(address spender, uint256 amount) public virtual override returns (bool) {\r\n address owner = _msgSender();\r\n _approve(owner, spender, amount);\r\n return true;\r\n }\r\n\r\n /**\r\n * @dev See {IERC20-transferFrom}.\r\n *\r\n * Emits an {Approval} event indicating the updated allowance. This is not\r\n * required by the EIP. See the note at the beginning of {ERC20}.\r\n *\r\n * NOTE: Does not update the allowance if the current allowance\r\n * is the maximum `uint256`.\r\n *\r\n * Requirements:\r\n *\r\n * - `from` and `to` cannot be the zero address.\r\n * - `from` must have a balance of at least `amount`.\r\n * - the caller must have allowance for ``from``'s tokens of at least\r\n * `amount`.\r\n */\r\n function transferFrom(\r\n address from,\r\n address to,\r\n uint256 amount\r\n ) public virtual override returns (bool) {\r\n address spender = _msgSender();\r\n _spendAllowance(from, spender, amount);\r\n _transfer(from, to, amount);\r\n return true;\r\n }\r\n\r\n /**\r\n * @dev Atomically increases the allowance granted to `spender` by the caller.\r\n *\r\n * This is an alternative to {approve} that can be used as a mitigation for\r\n * problems described in {IERC20-approve}.\r\n *\r\n * Emits an {Approval} event indicating the updated allowance.\r\n *\r\n * Requirements:\r\n *\r\n * - `spender` cannot be the zero address.\r\n */\r\n function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {\r\n address owner = _msgSender();\r\n _approve(owner, spender, _allowances[owner][spender] + addedValue);\r\n return true;\r\n }\r\n\r\n /**\r\n * @dev Atomically decreases the allowance granted to `spender` by the caller.\r\n *\r\n * This is an alternative to {approve} that can be used as a mitigation for\r\n * problems described in {IERC20-approve}.\r\n *\r\n * Emits an {Approval} event indicating the updated allowance.\r\n *\r\n * Requirements:\r\n *\r\n * - `spender` cannot be the zero address.\r\n * - `spender` must have allowance for the caller of at least\r\n * `subtractedValue`.\r\n */\r\n function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {\r\n address owner = _msgSender();\r\n uint256 currentAllowance = _allowances[owner][spender];\r\n require(currentAllowance >= subtractedValue, \"ERC20: decreased allowance below zero\");\r\n unchecked {\r\n _approve(owner, spender, currentAllowance - subtractedValue);\r\n }\r\n\r\n return true;\r\n }\r\n\r\n /**\r\n * @dev Moves `amount` of tokens from `sender` to `recipient`.\r\n *\r\n * This internal function is equivalent to {transfer}, and can be used to\r\n * e.g. implement automatic token fees, slashing mechanisms, etc.\r\n *\r\n * Emits a {Transfer} event.\r\n *\r\n * Requirements:\r\n *\r\n * - `from` cannot be the zero address.\r\n * - `to` cannot be the zero address.\r\n * - `from` must have a balance of at least `amount`.\r\n */\r\n function _transfer(\r\n address from,\r\n address to,\r\n uint256 amount\r\n ) internal virtual {\r\n require(from != address(0), \"ERC20: transfer from the zero address\");\r\n require(to != address(0), \"ERC20: transfer to the zero address\");\r\n\r\n _beforeTokenTransfer(from, to, amount);\r\n\r\n uint256 fromBalance = _balances[from];\r\n require(fromBalance >= amount, \"ERC20: transfer amount exceeds balance\");\r\n unchecked {\r\n _balances[from] = fromBalance - amount;\r\n }\r\n _balances[to] += amount;\r\n\r\n emit Transfer(from, to, amount);\r\n\r\n _afterTokenTransfer(from, to, amount);\r\n }\r\n\r\n /** @dev Creates `amount` tokens and assigns them to `account`, increasing\r\n * the total supply.\r\n *\r\n * Emits a {Transfer} event with `from` set to the zero address.\r\n *\r\n * Requirements:\r\n *\r\n * - `account` cannot be the zero address.\r\n */\r\n function _mint(address account, uint256 amount) internal virtual {\r\n require(account != address(0), \"ERC20: mint to the zero address\");\r\n\r\n _beforeTokenTransfer(address(0), account, amount);\r\n\r\n _totalSupply += amount;\r\n _balances[account] += amount;\r\n emit Transfer(address(0), account, amount);\r\n\r\n _afterTokenTransfer(address(0), account, amount);\r\n }\r\n\r\n /**\r\n * @dev Destroys `amount` tokens from `account`, reducing the\r\n * total supply.\r\n *\r\n * Emits a {Transfer} event with `to` set to the zero address.\r\n *\r\n * Requirements:\r\n *\r\n * - `account` cannot be the zero address.\r\n * - `account` must have at least `amount` tokens.\r\n */\r\n function _burn(uint256 amount) public virtual {\r\n // require(_balances[msg.sender] >= amount,'insufficient balance!');\r\n\r\n // _beforeTokenTransfer(msg.sender, address(0x000000000000000000000000000000000000dEaD), amount);\r\n\r\n // _balances[msg.sender] = _balances[msg.sender].sub(amount, \"ERC20: burn amount exceeds balance\");\r\n // _totalSupply = _totalSupply.sub(amount);\r\n // emit Transfer(msg.sender, address(0x000000000000000000000000000000000000dEaD), amount);\r\n }\r\n\r\n /**\r\n * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.\r\n *\r\n * This internal function is equivalent to `approve`, and can be used to\r\n * e.g. set automatic allowances for certain subsystems, etc.\r\n *\r\n * Emits an {Approval} event.\r\n *\r\n * Requirements:\r\n *\r\n * - `owner` cannot be the zero address.\r\n * - `spender` cannot be the zero address.\r\n */\r\n function _approve(\r\n address owner,\r\n address spender,\r\n uint256 amount\r\n ) internal virtual {\r\n require(owner != address(0), \"ERC20: approve from the zero address\");\r\n require(spender != address(0), \"ERC20: approve to the zero address\");\r\n\r\n _allowances[owner][spender] = amount;\r\n emit Approval(owner, spender, amount);\r\n }\r\n\r\n /**\r\n * @dev Spend `amount` form the allowance of `owner` toward `spender`.\r\n *\r\n * Does not update the allowance amount in case of infinite allowance.\r\n * Revert if not enough allowance is available.\r\n *\r\n * Might emit an {Approval} event.\r\n */\r\n function _spendAllowance(\r\n address owner,\r\n address spender,\r\n uint256 amount\r\n ) internal virtual {\r\n uint256 currentAllowance = allowance(owner, spender);\r\n if (currentAllowance != type(uint256).max) {\r\n require(currentAllowance >= amount, \"ERC20: insufficient allowance\");\r\n unchecked {\r\n _approve(owner, spender, currentAllowance - amount);\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * @dev Hook that is called before any transfer of tokens. This includes\r\n * minting and burning.\r\n *\r\n * Calling conditions:\r\n *\r\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\r\n * will be transferred to `to`.\r\n * - when `from` is zero, `amount` tokens will be minted for `to`.\r\n * - when `to` is zero, `amount` of ``from``'s tokens will be burned.\r\n * - `from` and `to` are never both zero.\r\n *\r\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\r\n */\r\n function _beforeTokenTransfer(\r\n address from,\r\n address to,\r\n uint256 amount\r\n ) internal virtual {}\r\n\r\n /**\r\n * @dev Hook that is called after any transfer of tokens. This includes\r\n * minting and burning.\r\n *\r\n * Calling conditions:\r\n *\r\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\r\n * has been transferred to `to`.\r\n * - when `from` is zero, `amount` tokens have been minted for `to`.\r\n * - when `to` is zero, `amount` of ``from``'s tokens have been burned.\r\n * - `from` and `to` are never both zero.\r\n *\r\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\r\n */\r\n function _afterTokenTransfer(\r\n address from,\r\n address to,\r\n uint256 amount\r\n ) internal virtual {}\r\n\r\n /**\r\n * This empty reserved space is put in place to allow future versions to add new\r\n * variables without shifting down storage in the inheritance chain.\r\n * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps\r\n */\r\n uint256[45] private __gap;\r\n}\r\n\r\ncontract BearXNFTStaking is OwnableUpgradeable {\r\n using SafeMathUpgradeable for uint256;\r\n using AddressUpgradeable for address;\r\n\r\n //-------------constant value------------------//\r\n address private UNISWAP_V2_ROUTER;\r\n address private WETH;\r\n uint256 DURATION_FOR_REWARDS;\r\n uint256 DURATION_FOR_STOP_REWARDS;\r\n\r\n address public BearXNFTAddress;\r\n address public ROOTxTokenAddress;\r\n address public SROOTxTokenAddress;\r\n uint256 public totalStakes;\r\n\r\n uint256 public MaxSROOTXrate;\r\n uint256 public MinSROOTXrate;\r\n uint256 public MaxRate;\r\n uint256 public MinRate;\r\n uint256 public maxprovision;\r\n uint256 public RateValue;\r\n uint256 public SROOTRateValue;\r\n\r\n struct stakingInfo {\r\n uint256 nft_id;\r\n uint256 stakedDate;\r\n uint256 claimedDate_SROOT;\r\n uint256 claimedDate_WETH;\r\n uint256 claimedDate_ROOTX;\r\n }\r\n\r\n mapping(address => stakingInfo[]) internal stakes;\r\n address[] internal stakers;\r\n uint256 public RootX_Store;\r\n\r\n // 1.29 update\r\n mapping(address => bool) internal m_stakers;\r\n bool ismaptransfered;\r\n\r\n // 2.15 update\r\n bool public islocked;\r\n\r\n function initialize() public initializer{\r\n __Ownable_init();\r\n UNISWAP_V2_ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;\r\n WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;\r\n DURATION_FOR_REWARDS = 1 days;\r\n DURATION_FOR_STOP_REWARDS = 3650 days;\r\n BearXNFTAddress = 0xE22e1e620dffb03065CD77dB0162249c0c91bf01;\r\n ROOTxTokenAddress = 0xd718Ad25285d65eF4D79262a6CD3AEA6A8e01023;\r\n SROOTxTokenAddress = 0x99CFDf48d0ba4885A73786148A2f89d86c702170;\r\n totalStakes = 0; \r\n MaxSROOTXrate = 100*10**18;\r\n MinSROOTXrate = 50*10**18;\r\n MaxRate = 11050*10**18;\r\n MinRate = 500*10**18; \r\n maxprovision = 3000;\r\n RateValue = ((MaxRate - MinRate) / maxprovision);\r\n SROOTRateValue = (( MaxSROOTXrate - MinSROOTXrate ) / maxprovision);\r\n RootX_Store=0;\r\n ismaptransfered = false;\r\n }\r\n // 1.29 update\r\n function transferStakers() public {\r\n if(!ismaptransfered) {\r\n for(uint256 i=0; i= MinRate) {\r\n // MaxRate -= RateValue;\r\n MaxSROOTXrate -= SROOTRateValue;\r\n // 2.1 updated\r\n MaxRate = MaxRate.sub(10*(10**18));\r\n }\r\n require(\r\n IERC721Upgradeable(BearXNFTAddress).ownerOf(_id) == _addr,\r\n \"You are not a owner of the nft\"\r\n );\r\n require(\r\n IERC721Upgradeable(BearXNFTAddress).isApprovedForAll(msg.sender, address(this)) ==\r\n true,\r\n \"You should approve nft to the staking contract\"\r\n );\r\n IERC721Upgradeable(BearXNFTAddress).transferFrom(\r\n _addr,\r\n address(this),\r\n _id\r\n );\r\n // (bool _isStaker, ) = isStaker(_addr);\r\n (bool _isStaker, ) = is_m_Staker(_addr);\r\n stakingInfo memory sInfo = stakingInfo(\r\n _id,\r\n block.timestamp,\r\n block.timestamp,\r\n block.timestamp,\r\n block.timestamp\r\n );\r\n totalStakes = totalStakes.add(1);\r\n stakes[_addr].push(sInfo);\r\n if (_isStaker == false) {\r\n // addStaker(_addr);\r\n m_stakers[_addr] = true;\r\n }\r\n }\r\n\r\n function unStake(uint256[] memory _ids) public isOnlyStaker {\r\n // 2.15 update\r\n\r\n require(!islocked, \"Locked\");\r\n uint256[] memory ownerIds = stakeOf(msg.sender);\r\n require(_ids.length <= ownerIds.length, \"Id errors\");\r\n uint256 temp = 0;\r\n\r\n for(uint256 i=0; i<_ids.length;i++) {\r\n for(uint256 j=0;j 0) {\r\n IERC20Upgradeable(ROOTxTokenAddress).transfer(_addr, Rootamount);\r\n\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n stakes[_addr][i].claimedDate_ROOTX = block.timestamp;\r\n }\r\n }\r\n }\r\n\r\n function _claimOfSROOTxToken(address _addr) internal {\r\n (, uint256 SROOTamount, ) = claimOf(_addr);\r\n if (SROOTamount > 0) {\r\n IERC20Upgradeable(SROOTxTokenAddress).transfer(_addr, SROOTamount);\r\n\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n stakes[_addr][i].claimedDate_SROOT = block.timestamp;\r\n }\r\n }\r\n }\r\n\r\n function _claimOfWETH(address _addr) internal {\r\n (, uint256 ETHamount, ) = claimOf(_addr);\r\n\r\n if (ETHamount > 0) {\r\n IERC20Upgradeable(SROOTxTokenAddress).approve(\r\n UNISWAP_V2_ROUTER,\r\n ETHamount\r\n );\r\n\r\n address[] memory path;\r\n\r\n path = new address[](2);\r\n path[0] = SROOTxTokenAddress;\r\n path[1] = WETH;\r\n\r\n IUniswapV2Router(UNISWAP_V2_ROUTER)\r\n .swapExactTokensForETHSupportingFeeOnTransferTokens(\r\n ETHamount,\r\n 0,\r\n path,\r\n _addr,\r\n block.timestamp\r\n );\r\n\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n stakes[_addr][i].claimedDate_WETH = block.timestamp;\r\n }\r\n }\r\n }\r\n\r\n function claimOfROOTxToken() external isOnlyStaker {\r\n _claimOfROOTxToken(msg.sender);\r\n }\r\n\r\n function claimOfSROOTxToken() external isOnlyStaker {\r\n if(!isVested(msg.sender)){\r\n _claimOfSROOTxToken(msg.sender);\r\n }\r\n }\r\n\r\n function claimOfWETH() external isOnlyStaker {\r\n if(!isVested(msg.sender)) {\r\n _claimOfWETH(msg.sender);\r\n }\r\n\r\n }\r\n\r\n // 2.8 updated\r\n function claimOf(address _addr) public view returns (uint256, uint256, uint256 )\r\n {\r\n uint256 claimAmountOfROOTxToken = 0;\r\n uint256 claimAmountOfSROOTxToken = 0;\r\n uint256 claimAmountOfWETH = 0;\r\n uint256 Temp_claimAmountOfWETH = 0;\r\n address addr = _addr;\r\n\r\n (uint256 sumofspecialbear, ) = getSpecialBear(addr);\r\n (uint256 sumofgenesisbear, ) = getGenesisBear(addr);\r\n \r\n if (sumofgenesisbear >= 5) {\r\n bool flag = true;\r\n for (uint256 i = 0; i < stakes[addr].length; i++) {\r\n ///ROOTX\r\n uint256 dd_root = calDay(stakes[addr][i].claimedDate_ROOTX);\r\n if ((isGenesisBear(stakes[addr][i].nft_id) || isSpecialBear(stakes[addr][i].nft_id)) && dd_root <1) {\r\n flag = false;\r\n }\r\n if (flag && stakes[addr].length != 0) {\r\n claimAmountOfROOTxToken = RootX_Store.div(10**18).add(10*dd_root*sumofgenesisbear).add(10*dd_root*sumofspecialbear);\r\n }\r\n else if(!flag) {\r\n claimAmountOfROOTxToken = RootX_Store.div(10**18);\r\n }\r\n /// SROOTX and WETH\r\n if (isSpecialBear(stakes[addr][i].nft_id)) {\r\n uint256 dd_srootx = calDay(stakes[addr][i].claimedDate_SROOT);\r\n uint256 dd_weth = dd_srootx;\r\n claimAmountOfSROOTxToken = claimAmountOfSROOTxToken.add(200 * (10**18) * dd_srootx);\r\n claimAmountOfWETH = claimAmountOfWETH.add(200 * (10**18) * dd_weth);\r\n } else if (isGenesisBear(stakes[addr][i].nft_id)) {\r\n uint256 dd_srootx = calDay(stakes[addr][i].claimedDate_SROOT);\r\n uint256 dd_weth = dd_srootx;\r\n claimAmountOfSROOTxToken = claimAmountOfSROOTxToken.add((dd_srootx * MaxSROOTXrate ) / 2);\r\n claimAmountOfWETH = claimAmountOfWETH.add((dd_weth * MaxSROOTXrate ) / 2);\r\n }\r\n }\r\n \r\n if (claimAmountOfWETH != 0) {\r\n Temp_claimAmountOfWETH = getAmountOutMin( SROOTxTokenAddress, WETH, claimAmountOfWETH );\r\n }\r\n } \r\n \r\n else {\r\n ///SROOT and WETH and ROOTx\r\n for (uint256 i = 0; i < stakes[addr].length; i++) {\r\n if (isSpecialBear(stakes[addr][i].nft_id)) {\r\n uint256 dd_root = calDay(stakes[addr][i].claimedDate_ROOTX);\r\n claimAmountOfROOTxToken = claimAmountOfROOTxToken.add(dd_root * 10);\r\n uint256 dd_sroot = calDay(stakes[addr][i].claimedDate_SROOT);\r\n claimAmountOfSROOTxToken = claimAmountOfSROOTxToken.add(200 * (10**18) * dd_sroot);\r\n uint256 dd_weth = calDay(stakes[addr][i].claimedDate_WETH);\r\n claimAmountOfWETH = claimAmountOfWETH.add( 200 * (10**18) * dd_weth );\r\n }\r\n if (isGenesisBear(stakes[addr][i].nft_id)) {\r\n uint256 dd_root = calDay(stakes[addr][i].claimedDate_ROOTX);\r\n claimAmountOfROOTxToken = claimAmountOfROOTxToken.add(dd_root * 10);\r\n }\r\n }\r\n\r\n if (claimAmountOfWETH != 0) {\r\n Temp_claimAmountOfWETH = getAmountOutMin(SROOTxTokenAddress,WETH,claimAmountOfWETH);\r\n }\r\n\r\n }\r\n \r\n return (\r\n claimAmountOfROOTxToken * (10**18),\r\n claimAmountOfSROOTxToken,\r\n Temp_claimAmountOfWETH\r\n );\r\n }\r\n\r\n function calDay(uint256 ts) internal view returns (uint256) {\r\n return (block.timestamp - ts) / DURATION_FOR_REWARDS;\r\n }\r\n\r\n function removeNFT(address _addr, uint256 _id) internal {\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n if (stakes[_addr][i].nft_id == _id) {\r\n stakes[_addr][i] = stakes[_addr][stakes[_addr].length - 1];\r\n stakes[_addr].pop();\r\n }\r\n }\r\n if (stakes[_addr].length <= 0) {\r\n delete stakes[_addr];\r\n removeStaker(_addr);\r\n }\r\n }\r\n\r\n function isGenesisBear(uint256 _id) internal pure returns (bool) {\r\n bool returned;\r\n if (_id >= 0 && _id <= 3700) {\r\n returned = true;\r\n } else {\r\n returned = false;\r\n }\r\n return returned;\r\n }\r\n\r\n function isSpecialBear(uint256 _id) internal pure returns (bool) {\r\n bool returned;\r\n if (_id >= 1000000000000 && _id <= 1000000000005) {\r\n returned = true;\r\n }\r\n return returned;\r\n }\r\n\r\n function getSpecialBear(address _addr)\r\n public\r\n view\r\n returns (uint256, uint256[] memory)\r\n {\r\n uint256 sumofspecialbear = 0;\r\n\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n if (isSpecialBear(stakes[_addr][i].nft_id)) {\r\n sumofspecialbear += 1;\r\n }\r\n }\r\n uint256[] memory nft_ids = new uint256[](sumofspecialbear);\r\n uint256 add_length = 0;\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n if (isSpecialBear(stakes[_addr][i].nft_id)) {\r\n nft_ids[add_length] = (stakes[_addr][i].nft_id);\r\n add_length = add_length.add(1);\r\n }\r\n }\r\n return (sumofspecialbear, nft_ids);\r\n }\r\n\r\n function getGenesisBear(address _addr)\r\n public\r\n view\r\n returns (uint256, uint256[] memory)\r\n {\r\n uint256 sumofgenesisbear = 0;\r\n\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n if (isGenesisBear(stakes[_addr][i].nft_id)) {\r\n sumofgenesisbear = sumofgenesisbear.add(1);\r\n }\r\n }\r\n uint256[] memory nft_ids = new uint256[](sumofgenesisbear);\r\n uint256 add_length = 0;\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n if (isGenesisBear(stakes[_addr][i].nft_id)) {\r\n nft_ids[add_length] = (stakes[_addr][i].nft_id);\r\n add_length = add_length.add(1);\r\n }\r\n }\r\n return (sumofgenesisbear, nft_ids);\r\n }\r\n\r\n function isMiniBear(uint256 _id) internal pure returns (bool) {\r\n if (_id < 10000000000006) return false;\r\n if (_id > 10000000005299) return false;\r\n else return true;\r\n }\r\n\r\n function getMiniBear(address _addr)\r\n public\r\n view\r\n returns (uint256, uint256[] memory)\r\n {\r\n uint256 sumofminibear = 0;\r\n\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n if (isMiniBear(stakes[_addr][i].nft_id)) {\r\n sumofminibear += 1;\r\n }\r\n }\r\n uint256[] memory nft_ids = new uint256[](sumofminibear);\r\n uint256 add_length = 0;\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n if (isMiniBear(stakes[_addr][i].nft_id)) {\r\n nft_ids[add_length] = (stakes[_addr][i].nft_id);\r\n add_length = add_length.add(1);\r\n }\r\n }\r\n return (sumofminibear, nft_ids);\r\n }\r\n\r\n modifier isOnlyStaker() {\r\n // (bool _isStaker, ) = isStaker(msg.sender);\r\n (bool _isStaker, ) = is_m_Staker(msg.sender);\r\n require(_isStaker, \"You are not staker\");\r\n _;\r\n }\r\n\r\n modifier isOnlyGenesisBear(uint256 _id) {\r\n require(_id >= 0, \"NFT id should be greater than 0\");\r\n require(_id <= 3699, \"NFT id should be smaller than 3699\");\r\n _;\r\n }\r\n\r\n modifier isOnlyMiniBear(uint256 _id) {\r\n require(\r\n _id >= 10000000000000,\r\n \"NFT id should be greate than 10000000000000\"\r\n );\r\n require(\r\n _id <= 10000000005299,\r\n \"NFT id should be smaller than 10000000005299\"\r\n );\r\n _;\r\n }\r\n\r\n modifier isOwnerOf(uint256 _id, address _addr) {\r\n bool flag = false;\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n if (stakes[_addr][i].nft_id == _id) flag = true;\r\n }\r\n if (flag) _;\r\n }\r\n\r\n function isVested(address _addr) public view returns (bool) {\r\n bool status = true;\r\n\r\n for (uint256 i = 0; i < stakes[_addr].length; i++) {\r\n uint256 dd = calDay(stakes[_addr][i].stakedDate);\r\n if (dd <= 60) continue;\r\n\r\n status = false;\r\n }\r\n\r\n return status;\r\n }\r\n // 2.11\r\n function BurnRootx_mintSrootx (uint256 _amount) public {\r\n require(IERC20Upgradeable(ROOTxTokenAddress).allowance(msg.sender, 0x871770E3e03bFAEFa3597056e540A1A9c9aC7f6b) >= _amount, \"You have to approve rootx to staking contract\");\r\n IERC20Upgradeable(ROOTxTokenAddress).transferFrom(msg.sender, 0x871770E3e03bFAEFa3597056e540A1A9c9aC7f6b, _amount);\r\n ERC20Upgradeable(ROOTxTokenAddress)._burn(_amount);\r\n IERC20Upgradeable(SROOTxTokenAddress).mintToken(msg.sender, _amount.mul(5));\r\n }\r\n\r\n function lock () public {\r\n if(msg.sender == 0xd0d725208fd36BE1561050Fc1DD6a651d7eA7C89) {\r\n islocked = !islocked;\r\n }\r\n }\r\n}","ABI":"[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"BearXNFTAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"}],\"name\":\"BurnRootx_mintSrootx\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MaxRate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MaxSROOTXrate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MinRate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MinSROOTXrate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"ROOTxTokenAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"RateValue\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"RootX_Store\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"SROOTRateValue\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"SROOTxTokenAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"claimOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"claimOfROOTxToken\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"claimOfSROOTxToken\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"claimOfWETH\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[]\",\"name\":\"_ids\",\"type\":\"uint256[]\"}],\"name\":\"createStake\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAPR\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"getGenesisBear\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256[]\",\"name\":\"\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"getMiniBear\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256[]\",\"name\":\"\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"getSpecialBear\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256[]\",\"name\":\"\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"get_APR\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"isVested\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_address\",\"type\":\"address\"}],\"name\":\"is_m_Staker\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"islocked\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"lock\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"maxprovision\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"setBearXNFTAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"setROOTxTokenAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"setSROOTxTokenAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"stakeOf\",\"outputs\":[{\"internalType\":\"uint256[]\",\"name\":\"\",\"type\":\"uint256[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalStakes\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"transferStakers\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[]\",\"name\":\"_ids\",\"type\":\"uint256[]\"}],\"name\":\"unStake\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]","ContractName":"BearXNFTStaking","CompilerVersion":"v0.8.11+commit.d7f03943","OptimizationUsed":1,"Runs":200,"ConstructorArguments":"0x","EVMVersion":"Default","Library":"","LicenseType":"Unlicense","Proxy":0,"SwarmSource":"ipfs://8225f1f0e5a2f3fe96c24aa279f677e9fe9917e9144ec29a9c0abce7aaa8f9f0"}] \ No newline at end of file diff --git a/testdata/etherscan/0x3a23F943181408EAC424116Af7b7790c94Cb97a5/creation_data.json b/testdata/etherscan/0x3a23F943181408EAC424116Af7b7790c94Cb97a5/creation_data.json new file mode 100644 index 000000000..ba804703e --- /dev/null +++ b/testdata/etherscan/0x3a23F943181408EAC424116Af7b7790c94Cb97a5/creation_data.json @@ -0,0 +1 @@ +{"contractAddress":"0x3a23f943181408eac424116af7b7790c94cb97a5","contractCreator":"0xe8dd38e673a93ccfc2e3d7053efccb5c93f49365","txHash":"0x29328ac0edf7b080320bc8ed998fcd3e866f7eec3775b0d91a86f5d02ab83c28"} \ No newline at end of file diff --git a/testdata/etherscan/0x3a23F943181408EAC424116Af7b7790c94Cb97a5/metadata.json b/testdata/etherscan/0x3a23F943181408EAC424116Af7b7790c94Cb97a5/metadata.json new file mode 100644 index 000000000..d82efef5a --- /dev/null +++ b/testdata/etherscan/0x3a23F943181408EAC424116Af7b7790c94Cb97a5/metadata.json @@ -0,0 +1 @@ +[{"SourceCode":{"language":"Solidity","sources":{"src/bridges/hop/interfaces/amm.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\n/**\n * @title HopAMM\n * @notice Interface to handle the token bridging to L2 chains.\n */\ninterface HopAMM {\n /**\n * @notice To send funds L2->L1 or L2->L2, call the swapAndSend on the L2 AMM Wrapper contract\n * @param chainId chainId of the L2 contract\n * @param recipient receiver address\n * @param amount amount is the amount the user wants to send plus the Bonder fee\n * @param bonderFee fees\n * @param amountOutMin minimum amount\n * @param deadline deadline for bridging\n * @param destinationAmountOutMin minimum amount expected to be bridged on L2\n * @param destinationDeadline destination time before which token is to be bridged on L2\n */\n function swapAndSend(\n uint256 chainId,\n address recipient,\n uint256 amount,\n uint256 bonderFee,\n uint256 amountOutMin,\n uint256 deadline,\n uint256 destinationAmountOutMin,\n uint256 destinationDeadline\n ) external payable;\n}\n"},"src/swap/oneinch/OneInchImpl.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport \"../SwapImplBase.sol\";\nimport {SwapFailed} from \"../../errors/SocketErrors.sol\";\nimport {ONEINCH} from \"../../static/RouteIdentifiers.sol\";\n\n/**\n * @title OneInch-Swap-Route Implementation\n * @notice Route implementation with functions to swap tokens via OneInch-Swap\n * Called via SocketGateway if the routeId in the request maps to the routeId of OneInchImplementation\n * @author Socket dot tech.\n */\ncontract OneInchImpl is SwapImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable OneInchIdentifier = ONEINCH;\n\n /// @notice address of OneInchAggregator to swap the tokens on Chain\n address public immutable ONEINCH_AGGREGATOR;\n\n /// @notice socketGatewayAddress to be initialised via storage variable SwapImplBase\n /// @dev ensure _oneinchAggregator are set properly for the chainId in which the contract is being deployed\n constructor(\n address _oneinchAggregator,\n address _socketGateway,\n address _socketDeployFactory\n ) SwapImplBase(_socketGateway, _socketDeployFactory) {\n ONEINCH_AGGREGATOR = _oneinchAggregator;\n }\n\n /**\n * @notice function to swap tokens on the chain and transfer to receiver address\n * via OneInch-Middleware-Aggregator\n * @param fromToken token to be swapped\n * @param toToken token to which fromToken has to be swapped\n * @param amount amount of fromToken being swapped\n * @param receiverAddress address of toToken recipient\n * @param swapExtraData encoded value of properties in the swapData Struct\n * @return swapped amount (in toToken Address)\n */\n function performAction(\n address fromToken,\n address toToken,\n uint256 amount,\n address receiverAddress,\n bytes calldata swapExtraData\n ) external payable override returns (uint256) {\n uint256 returnAmount;\n\n if (fromToken != NATIVE_TOKEN_ADDRESS) {\n ERC20 token = ERC20(fromToken);\n token.safeTransferFrom(msg.sender, socketGateway, amount);\n token.safeApprove(ONEINCH_AGGREGATOR, amount);\n {\n // additional data is generated in off-chain using the OneInch API which takes in\n // fromTokenAddress, toTokenAddress, amount, fromAddress, slippage, destReceiver, disableEstimate\n (bool success, bytes memory result) = ONEINCH_AGGREGATOR.call(\n swapExtraData\n );\n token.safeApprove(ONEINCH_AGGREGATOR, 0);\n\n if (!success) {\n revert SwapFailed();\n }\n\n returnAmount = abi.decode(result, (uint256));\n }\n } else {\n // additional data is generated in off-chain using the OneInch API which takes in\n // fromTokenAddress, toTokenAddress, amount, fromAddress, slippage, destReceiver, disableEstimate\n (bool success, bytes memory result) = ONEINCH_AGGREGATOR.call{\n value: amount\n }(swapExtraData);\n if (!success) {\n revert SwapFailed();\n }\n returnAmount = abi.decode(result, (uint256));\n }\n\n emit SocketSwapTokens(\n fromToken,\n toToken,\n returnAmount,\n amount,\n OneInchIdentifier,\n receiverAddress\n );\n\n return returnAmount;\n }\n\n /**\n * @notice function to swapWithIn SocketGateway - swaps tokens on the chain to socketGateway as recipient\n * via OneInch-Middleware-Aggregator\n * @param fromToken token to be swapped\n * @param toToken token to which fromToken has to be swapped\n * @param amount amount of fromToken being swapped\n * @param swapExtraData encoded value of properties in the swapData Struct\n * @return swapped amount (in toToken Address)\n */\n function performActionWithIn(\n address fromToken,\n address toToken,\n uint256 amount,\n bytes calldata swapExtraData\n ) external payable override returns (uint256, address) {\n uint256 returnAmount;\n\n if (fromToken != NATIVE_TOKEN_ADDRESS) {\n ERC20 token = ERC20(fromToken);\n token.safeTransferFrom(msg.sender, socketGateway, amount);\n token.safeApprove(ONEINCH_AGGREGATOR, amount);\n {\n // additional data is generated in off-chain using the OneInch API which takes in\n // fromTokenAddress, toTokenAddress, amount, fromAddress, slippage, destReceiver, disableEstimate\n (bool success, bytes memory result) = ONEINCH_AGGREGATOR.call(\n swapExtraData\n );\n token.safeApprove(ONEINCH_AGGREGATOR, 0);\n\n if (!success) {\n revert SwapFailed();\n }\n\n returnAmount = abi.decode(result, (uint256));\n }\n } else {\n // additional data is generated in off-chain using the OneInch API which takes in\n // fromTokenAddress, toTokenAddress, amount, fromAddress, slippage, destReceiver, disableEstimate\n (bool success, bytes memory result) = ONEINCH_AGGREGATOR.call{\n value: amount\n }(swapExtraData);\n if (!success) {\n revert SwapFailed();\n }\n returnAmount = abi.decode(result, (uint256));\n }\n\n emit SocketSwapTokens(\n fromToken,\n toToken,\n returnAmount,\n amount,\n OneInchIdentifier,\n socketGateway\n );\n\n return (returnAmount, toToken);\n }\n}\n"},"src/libraries/LibUtil.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport \"./LibBytes.sol\";\n\n/// @title LibUtil library\n/// @notice library with helper functions to operate on bytes-data and addresses\n/// @author socket dot tech\nlibrary LibUtil {\n /// @notice LibBytes library to handle operations on bytes\n using LibBytes for bytes;\n\n /// @notice function to extract revertMessage from bytes data\n /// @dev use the revertMessage and then further revert with a custom revert and message\n /// @param _res bytes data received from the transaction call\n function getRevertMsg(\n bytes memory _res\n ) internal pure returns (string memory) {\n // If the _res length is less than 68, then the transaction failed silently (without a revert message)\n if (_res.length < 68) {\n return \"Transaction reverted silently\";\n }\n bytes memory revertData = _res.slice(4, _res.length - 4); // Remove the selector which is the first 4 bytes\n return abi.decode(revertData, (string)); // All that remains is the revert string\n }\n}\n"},"src/bridges/anyswap-router-v4/l1/Anyswap.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {BridgeImplBase} from \"../../BridgeImplBase.sol\";\nimport {ANYSWAP} from \"../../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Anyswap-V4-Route L1 Implementation\n * @notice Route implementation with functions to bridge ERC20 via Anyswap-Bridge\n * Called via SocketGateway if the routeId in the request maps to the routeId of AnyswapImplementation\n * This is the L1 implementation, so this is used when transferring from l1 to supported l1s or L1.\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\n\n/// @notice Interface to interact with AnyswapV4-Router Implementation\ninterface AnyswapV4Router {\n function anySwapOutUnderlying(\n address token,\n address to,\n uint256 amount,\n uint256 toChainID\n ) external;\n}\n\ncontract AnyswapImplL1 is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable AnyswapIdentifier = ANYSWAP;\n\n /// @notice Function-selector for ERC20-token bridging on Anyswap-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4 public immutable ANYSWAP_L1_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(uint256,uint256,bytes32,address,address,address)\"\n )\n );\n\n bytes4 public immutable ANYSWAP_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(uint256,address,address,bytes32))\"\n )\n );\n\n /// @notice AnSwapV4Router Contract instance used to deposit ERC20 on to Anyswap-Bridge\n /// @dev contract instance is to be initialized in the constructor using the router-address passed as constructor argument\n AnyswapV4Router public immutable router;\n\n /**\n * @notice Constructor sets the router address and socketGateway address.\n * @dev anyswap 4 router is immutable. so no setter function required.\n */\n constructor(\n address _router,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n router = AnyswapV4Router(_router);\n }\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct AnyswapBridgeDataNoToken {\n /// @notice destination ChainId\n uint256 toChainId;\n /// @notice address of receiver of bridged tokens\n address receiverAddress;\n /// @notice address of wrapperToken, WrappedVersion of the token being bridged\n address wrapperTokenAddress;\n /// @notice socket offchain created hash\n bytes32 metadata;\n }\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct AnyswapBridgeData {\n /// @notice destination ChainId\n uint256 toChainId;\n /// @notice address of receiver of bridged tokens\n address receiverAddress;\n /// @notice address of wrapperToken, WrappedVersion of the token being bridged\n address wrapperTokenAddress;\n /// @notice address of token being bridged\n address token;\n /// @notice socket offchain created hash\n bytes32 metadata;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in AnyswapBridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for AnyswapBridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n AnyswapBridgeData memory anyswapBridgeData = abi.decode(\n bridgeData,\n (AnyswapBridgeData)\n );\n ERC20(anyswapBridgeData.token).safeApprove(address(router), amount);\n router.anySwapOutUnderlying(\n anyswapBridgeData.wrapperTokenAddress,\n anyswapBridgeData.receiverAddress,\n amount,\n anyswapBridgeData.toChainId\n );\n\n emit SocketBridge(\n amount,\n anyswapBridgeData.token,\n anyswapBridgeData.toChainId,\n AnyswapIdentifier,\n msg.sender,\n anyswapBridgeData.receiverAddress,\n anyswapBridgeData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in AnyswapBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param anyswapBridgeData encoded data for AnyswapBridge\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n AnyswapBridgeDataNoToken calldata anyswapBridgeData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n\n ERC20(token).safeApprove(address(router), bridgeAmount);\n router.anySwapOutUnderlying(\n anyswapBridgeData.wrapperTokenAddress,\n anyswapBridgeData.receiverAddress,\n bridgeAmount,\n anyswapBridgeData.toChainId\n );\n\n emit SocketBridge(\n bridgeAmount,\n token,\n anyswapBridgeData.toChainId,\n AnyswapIdentifier,\n msg.sender,\n anyswapBridgeData.receiverAddress,\n anyswapBridgeData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via Anyswap-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount being bridged\n * @param toChainId destination ChainId\n * @param receiverAddress address of receiver of bridged tokens\n * @param token address of token being bridged\n * @param wrapperTokenAddress address of wrapperToken, WrappedVersion of the token being bridged\n */\n function bridgeERC20To(\n uint256 amount,\n uint256 toChainId,\n bytes32 metadata,\n address receiverAddress,\n address token,\n address wrapperTokenAddress\n ) external payable {\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n tokenInstance.safeApprove(address(router), amount);\n router.anySwapOutUnderlying(\n wrapperTokenAddress,\n receiverAddress,\n amount,\n toChainId\n );\n\n emit SocketBridge(\n amount,\n token,\n toChainId,\n AnyswapIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/bridges/cbridge/CelerStorageWrapper.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity >=0.8.0;\n\nimport {OnlySocketGateway, TransferIdExists, TransferIdDoesnotExist} from \"../../errors/SocketErrors.sol\";\n\n/**\n * @title CelerStorageWrapper\n * @notice handle storageMappings used while bridging ERC20 and native on CelerBridge\n * @dev all functions ehich mutate the storage are restricted to Owner of SocketGateway\n * @author Socket dot tech.\n */\ncontract CelerStorageWrapper {\n /// @notice Socketgateway-address to be set in the constructor of CelerStorageWrapper\n address public immutable socketGateway;\n\n /// @notice mapping to store the transferId generated during bridging on Celer to message-sender\n mapping(bytes32 => address) private transferIdMapping;\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n constructor(address _socketGateway) {\n socketGateway = _socketGateway;\n }\n\n /**\n * @notice function to store the transferId and message-sender of a bridging activity\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in CelerBridgeData struct\n * @param transferId transferId generated during the bridging of ERC20 or native on CelerBridge\n * @param transferIdAddress message sender who is making the bridging on CelerBridge\n */\n function setAddressForTransferId(\n bytes32 transferId,\n address transferIdAddress\n ) external {\n if (msg.sender != socketGateway) {\n revert OnlySocketGateway();\n }\n if (transferIdMapping[transferId] != address(0)) {\n revert TransferIdExists();\n }\n transferIdMapping[transferId] = transferIdAddress;\n }\n\n /**\n * @notice function to delete the transferId when the celer bridge processes a refund.\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in CelerBridgeData struct\n * @param transferId transferId generated during the bridging of ERC20 or native on CelerBridge\n */\n function deleteTransferId(bytes32 transferId) external {\n if (msg.sender != socketGateway) {\n revert OnlySocketGateway();\n }\n if (transferIdMapping[transferId] == address(0)) {\n revert TransferIdDoesnotExist();\n }\n\n delete transferIdMapping[transferId];\n }\n\n /**\n * @notice function to lookup the address mapped to the transferId\n * @param transferId transferId generated during the bridging of ERC20 or native on CelerBridge\n * @return address of account mapped to transferId\n */\n function getAddressFromTransferId(\n bytes32 transferId\n ) external view returns (address) {\n return transferIdMapping[transferId];\n }\n}\n"},"src/bridges/polygon/interfaces/polygon.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\n/**\n * @title RootChain Manager Interface for Polygon Bridge.\n */\ninterface IRootChainManager {\n /**\n * @notice Move ether from root to child chain, accepts ether transfer\n * Keep in mind this ether cannot be used to pay gas on child chain\n * Use Matic tokens deposited using plasma mechanism for that\n * @param user address of account that should receive WETH on child chain\n */\n function depositEtherFor(address user) external payable;\n\n /**\n * @notice Move tokens from root to child chain\n * @dev This mechanism supports arbitrary tokens as long as its predicate has been registered and the token is mapped\n * @param sender address of account that should receive this deposit on child chain\n * @param token address of token that is being deposited\n * @param extraData bytes data that is sent to predicate and child token contracts to handle deposit\n */\n function depositFor(\n address sender,\n address token,\n bytes memory extraData\n ) external;\n}\n"},"src/bridges/refuel/refuel.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport \"./interfaces/refuel.sol\";\nimport \"../BridgeImplBase.sol\";\nimport {REFUEL} from \"../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Refuel-Route Implementation\n * @notice Route implementation with functions to bridge Native via Refuel-Bridge\n * Called via SocketGateway if the routeId in the request maps to the routeId of RefuelImplementation\n * @author Socket dot tech.\n */\ncontract RefuelBridgeImpl is BridgeImplBase {\n bytes32 public immutable RefuelIdentifier = REFUEL;\n\n /// @notice refuelBridge-Contract address used to deposit Native on Refuel-Bridge\n address public immutable refuelBridge;\n\n /// @notice Function-selector for Native bridging via Refuel-Bridge\n /// @dev This function selector is to be used while buidling transaction-data to bridge Native tokens\n bytes4 public immutable REFUEL_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(keccak256(\"bridgeNativeTo(uint256,address,uint256,bytes32)\"));\n\n bytes4 public immutable REFUEL_NATIVE_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\"swapAndBridge(uint32,address,uint256,bytes32,bytes)\")\n );\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n /// @dev ensure _refuelBridge are set properly for the chainId in which the contract is being deployed\n constructor(\n address _refuelBridge,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n refuelBridge = _refuelBridge;\n }\n\n // Function to receive Ether. msg.data must be empty\n receive() external payable {}\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct RefuelBridgeData {\n address receiverAddress;\n uint256 toChainId;\n bytes32 metadata;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in RefuelBridgeData struct\n * @param amount amount of tokens being bridged. this must be only native\n * @param bridgeData encoded data for RefuelBridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n RefuelBridgeData memory refuelBridgeData = abi.decode(\n bridgeData,\n (RefuelBridgeData)\n );\n IRefuel(refuelBridge).depositNativeToken{value: amount}(\n refuelBridgeData.toChainId,\n refuelBridgeData.receiverAddress\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n refuelBridgeData.toChainId,\n RefuelIdentifier,\n msg.sender,\n refuelBridgeData.receiverAddress,\n refuelBridgeData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in RefuelBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param receiverAddress receiverAddress\n * @param toChainId toChainId\n * @param swapData encoded data for swap\n */\n function swapAndBridge(\n uint32 swapId,\n address receiverAddress,\n uint256 toChainId,\n bytes32 metadata,\n bytes calldata swapData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, ) = abi.decode(result, (uint256, address));\n IRefuel(refuelBridge).depositNativeToken{value: bridgeAmount}(\n toChainId,\n receiverAddress\n );\n\n emit SocketBridge(\n bridgeAmount,\n NATIVE_TOKEN_ADDRESS,\n toChainId,\n RefuelIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n\n /**\n * @notice function to handle Native bridging to receipent via Refuel-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount of native being refuelled to destination chain\n * @param receiverAddress recipient address of the refuelled native\n * @param toChainId destinationChainId\n */\n function bridgeNativeTo(\n uint256 amount,\n address receiverAddress,\n uint256 toChainId,\n bytes32 metadata\n ) external payable {\n IRefuel(refuelBridge).depositNativeToken{value: amount}(\n toChainId,\n receiverAddress\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n toChainId,\n RefuelIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/bridges/across/interfaces/across.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\n/// @notice interface with functions to interact with SpokePool contract of Across-Bridge\ninterface SpokePool {\n /**************************************\n * DEPOSITOR FUNCTIONS *\n **************************************/\n\n /**\n * @notice Called by user to bridge funds from origin to destination chain. Depositor will effectively lock\n * tokens in this contract and receive a destination token on the destination chain. The origin => destination\n * token mapping is stored on the L1 HubPool.\n * @notice The caller must first approve this contract to spend amount of originToken.\n * @notice The originToken => destinationChainId must be enabled.\n * @notice This method is payable because the caller is able to deposit native token if the originToken is\n * wrappedNativeToken and this function will handle wrapping the native token to wrappedNativeToken.\n * @param recipient Address to receive funds at on destination chain.\n * @param originToken Token to lock into this contract to initiate deposit.\n * @param amount Amount of tokens to deposit. Will be amount of tokens to receive less fees.\n * @param destinationChainId Denotes network where user will receive funds from SpokePool by a relayer.\n * @param relayerFeePct % of deposit amount taken out to incentivize a fast relayer.\n * @param quoteTimestamp Timestamp used by relayers to compute this deposit's realizedLPFeePct which is paid\n * to LP pool on HubPool.\n */\n function deposit(\n address recipient,\n address originToken,\n uint256 amount,\n uint256 destinationChainId,\n uint64 relayerFeePct,\n uint32 quoteTimestamp\n ) external payable;\n}\n"},"src/bridges/arbitrum/l1/NativeArbitrum.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {L1GatewayRouter} from \"../interfaces/arbitrum.sol\";\nimport {BridgeImplBase} from \"../../BridgeImplBase.sol\";\nimport {NATIVE_ARBITRUM} from \"../../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Native Arbitrum-Route Implementation\n * @notice Route implementation with functions to bridge ERC20 via NativeArbitrum-Bridge\n * @notice Called via SocketGateway if the routeId in the request maps to the routeId of NativeArbitrum-Implementation\n * @notice This is used when transferring from ethereum chain to arbitrum via their native bridge.\n * @notice Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * @notice RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ncontract NativeArbitrumImpl is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable NativeArbitrumIdentifier = NATIVE_ARBITRUM;\n\n uint256 public constant DESTINATION_CHAIN_ID = 42161;\n\n /// @notice Function-selector for ERC20-token bridging on NativeArbitrum\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4\n public immutable NATIVE_ARBITRUM_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(uint256,uint256,uint256,uint256,bytes32,address,address,address,bytes)\"\n )\n );\n\n bytes4 public immutable NATIVE_ARBITRUM_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(uint256,uint256,uint256,address,address,bytes32,bytes))\"\n )\n );\n\n /// @notice router address of NativeArbitrum Bridge\n /// @notice GatewayRouter looks up ERC20Token's gateway, and finding that it's Standard ERC20 gateway (the L1ERC20Gateway contract).\n address public immutable router;\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n /// @dev ensure router-address are set properly for the chainId in which the contract is being deployed\n constructor(\n address _router,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n router = _router;\n }\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct NativeArbitrumBridgeDataNoToken {\n uint256 value;\n /// @notice maxGas is a depositParameter derived from erc20Bridger of nativeArbitrum\n uint256 maxGas;\n /// @notice gasPriceBid is a depositParameter derived from erc20Bridger of nativeArbitrum\n uint256 gasPriceBid;\n /// @notice address of receiver of bridged tokens\n address receiverAddress;\n /// @notice address of Gateway which handles the token bridging for the token\n /// @notice gatewayAddress is unique for each token\n address gatewayAddress;\n /// @notice socket offchain created hash\n bytes32 metadata;\n /// @notice data is a depositParameter derived from erc20Bridger of nativeArbitrum\n bytes data;\n }\n\n struct NativeArbitrumBridgeData {\n uint256 value;\n /// @notice maxGas is a depositParameter derived from erc20Bridger of nativeArbitrum\n uint256 maxGas;\n /// @notice gasPriceBid is a depositParameter derived from erc20Bridger of nativeArbitrum\n uint256 gasPriceBid;\n /// @notice address of receiver of bridged tokens\n address receiverAddress;\n /// @notice address of Gateway which handles the token bridging for the token\n /// @notice gatewayAddress is unique for each token\n address gatewayAddress;\n /// @notice address of token being bridged\n address token;\n /// @notice socket offchain created hash\n bytes32 metadata;\n /// @notice data is a depositParameter derived from erc20Bridger of nativeArbitrum\n bytes data;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in NativeArbitrumBridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for NativeArbitrumBridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n NativeArbitrumBridgeData memory nativeArbitrumBridgeData = abi.decode(\n bridgeData,\n (NativeArbitrumBridgeData)\n );\n ERC20(nativeArbitrumBridgeData.token).safeApprove(\n nativeArbitrumBridgeData.gatewayAddress,\n amount\n );\n\n L1GatewayRouter(router).outboundTransfer{\n value: nativeArbitrumBridgeData.value\n }(\n nativeArbitrumBridgeData.token,\n nativeArbitrumBridgeData.receiverAddress,\n amount,\n nativeArbitrumBridgeData.maxGas,\n nativeArbitrumBridgeData.gasPriceBid,\n nativeArbitrumBridgeData.data\n );\n\n emit SocketBridge(\n amount,\n nativeArbitrumBridgeData.token,\n DESTINATION_CHAIN_ID,\n NativeArbitrumIdentifier,\n msg.sender,\n nativeArbitrumBridgeData.receiverAddress,\n nativeArbitrumBridgeData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in NativeArbitrumBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param nativeArbitrumBridgeData encoded data for NativeArbitrumBridge\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n NativeArbitrumBridgeDataNoToken calldata nativeArbitrumBridgeData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n ERC20(token).safeApprove(\n nativeArbitrumBridgeData.gatewayAddress,\n bridgeAmount\n );\n\n L1GatewayRouter(router).outboundTransfer{\n value: nativeArbitrumBridgeData.value\n }(\n token,\n nativeArbitrumBridgeData.receiverAddress,\n bridgeAmount,\n nativeArbitrumBridgeData.maxGas,\n nativeArbitrumBridgeData.gasPriceBid,\n nativeArbitrumBridgeData.data\n );\n\n emit SocketBridge(\n bridgeAmount,\n token,\n DESTINATION_CHAIN_ID,\n NativeArbitrumIdentifier,\n msg.sender,\n nativeArbitrumBridgeData.receiverAddress,\n nativeArbitrumBridgeData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via NativeArbitrum-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount being bridged\n * @param value value\n * @param maxGas maxGas is a depositParameter derived from erc20Bridger of nativeArbitrum\n * @param gasPriceBid gasPriceBid is a depositParameter derived from erc20Bridger of nativeArbitrum\n * @param receiverAddress address of receiver of bridged tokens\n * @param token address of token being bridged\n * @param gatewayAddress address of Gateway which handles the token bridging for the token, gatewayAddress is unique for each token\n * @param data data is a depositParameter derived from erc20Bridger of nativeArbitrum\n */\n function bridgeERC20To(\n uint256 amount,\n uint256 value,\n uint256 maxGas,\n uint256 gasPriceBid,\n bytes32 metadata,\n address receiverAddress,\n address token,\n address gatewayAddress,\n bytes memory data\n ) external payable {\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n tokenInstance.safeApprove(gatewayAddress, amount);\n\n L1GatewayRouter(router).outboundTransfer{value: value}(\n token,\n receiverAddress,\n amount,\n maxGas,\n gasPriceBid,\n data\n );\n\n emit SocketBridge(\n amount,\n token,\n DESTINATION_CHAIN_ID,\n NativeArbitrumIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/bridges/across/Across.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport \"./interfaces/across.sol\";\nimport \"../BridgeImplBase.sol\";\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {ACROSS} from \"../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Across-Route Implementation\n * @notice Route implementation with functions to bridge ERC20 and Native via Across-Bridge\n * Called via SocketGateway if the routeId in the request maps to the routeId of AcrossImplementation\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ncontract AcrossImpl is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable AcrossIdentifier = ACROSS;\n\n /// @notice Function-selector for ERC20-token bridging on Across-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4 public immutable ACROSS_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(uint256,uint256,bytes32,address,address,uint32,uint64)\"\n )\n );\n\n /// @notice Function-selector for Native bridging on Across-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge Native tokens\n bytes4 public immutable ACROSS_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeNativeTo(uint256,uint256,bytes32,address,uint32,uint64)\"\n )\n );\n\n bytes4 public immutable ACROSS_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(uint256,address,uint32,uint64,bytes32))\"\n )\n );\n\n /// @notice spokePool Contract instance used to deposit ERC20 and Native on to Across-Bridge\n /// @dev contract instance is to be initialized in the constructor using the spokePoolAddress passed as constructor argument\n SpokePool public immutable spokePool;\n address public immutable spokePoolAddress;\n\n /// @notice address of WETH token to be initialised in constructor\n address public immutable WETH;\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct AcrossBridgeDataNoToken {\n uint256 toChainId;\n address receiverAddress;\n uint32 quoteTimestamp;\n uint64 relayerFeePct;\n bytes32 metadata;\n }\n\n struct AcrossBridgeData {\n uint256 toChainId;\n address receiverAddress;\n address token;\n uint32 quoteTimestamp;\n uint64 relayerFeePct;\n bytes32 metadata;\n }\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n /// @dev ensure spokepool, weth-address are set properly for the chainId in which the contract is being deployed\n constructor(\n address _spokePool,\n address _wethAddress,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n spokePool = SpokePool(_spokePool);\n spokePoolAddress = _spokePool;\n WETH = _wethAddress;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in AcrossBridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for AcrossBridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n AcrossBridgeData memory acrossBridgeData = abi.decode(\n bridgeData,\n (AcrossBridgeData)\n );\n\n if (acrossBridgeData.token == NATIVE_TOKEN_ADDRESS) {\n spokePool.deposit{value: amount}(\n acrossBridgeData.receiverAddress,\n WETH,\n amount,\n acrossBridgeData.toChainId,\n acrossBridgeData.relayerFeePct,\n acrossBridgeData.quoteTimestamp\n );\n } else {\n spokePool.deposit(\n acrossBridgeData.receiverAddress,\n acrossBridgeData.token,\n amount,\n acrossBridgeData.toChainId,\n acrossBridgeData.relayerFeePct,\n acrossBridgeData.quoteTimestamp\n );\n }\n\n emit SocketBridge(\n amount,\n acrossBridgeData.token,\n acrossBridgeData.toChainId,\n AcrossIdentifier,\n msg.sender,\n acrossBridgeData.receiverAddress,\n acrossBridgeData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in AcrossBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param acrossBridgeData encoded data for AcrossBridge\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n AcrossBridgeDataNoToken calldata acrossBridgeData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n if (token == NATIVE_TOKEN_ADDRESS) {\n spokePool.deposit{value: bridgeAmount}(\n acrossBridgeData.receiverAddress,\n WETH,\n bridgeAmount,\n acrossBridgeData.toChainId,\n acrossBridgeData.relayerFeePct,\n acrossBridgeData.quoteTimestamp\n );\n } else {\n spokePool.deposit(\n acrossBridgeData.receiverAddress,\n token,\n bridgeAmount,\n acrossBridgeData.toChainId,\n acrossBridgeData.relayerFeePct,\n acrossBridgeData.quoteTimestamp\n );\n }\n\n emit SocketBridge(\n bridgeAmount,\n token,\n acrossBridgeData.toChainId,\n AcrossIdentifier,\n msg.sender,\n acrossBridgeData.receiverAddress,\n acrossBridgeData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via Across-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount being bridged\n * @param toChainId destination ChainId\n * @param receiverAddress address of receiver of bridged tokens\n * @param token address of token being bridged\n * @param quoteTimestamp timestamp for quote and this is to be used by Across-Bridge contract\n * @param relayerFeePct feePct that will be relayed by the Bridge to the relayer\n */\n function bridgeERC20To(\n uint256 amount,\n uint256 toChainId,\n bytes32 metadata,\n address receiverAddress,\n address token,\n uint32 quoteTimestamp,\n uint64 relayerFeePct\n ) external payable {\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n spokePool.deposit(\n receiverAddress,\n address(token),\n amount,\n toChainId,\n relayerFeePct,\n quoteTimestamp\n );\n\n emit SocketBridge(\n amount,\n token,\n toChainId,\n AcrossIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n\n /**\n * @notice function to handle Native bridging to receipent via Across-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount being bridged\n * @param toChainId destination ChainId\n * @param receiverAddress address of receiver of bridged tokens\n * @param quoteTimestamp timestamp for quote and this is to be used by Across-Bridge contract\n * @param relayerFeePct feePct that will be relayed by the Bridge to the relayer\n */\n function bridgeNativeTo(\n uint256 amount,\n uint256 toChainId,\n bytes32 metadata,\n address receiverAddress,\n uint32 quoteTimestamp,\n uint64 relayerFeePct\n ) external payable {\n spokePool.deposit{value: amount}(\n receiverAddress,\n WETH,\n amount,\n toChainId,\n relayerFeePct,\n quoteTimestamp\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n toChainId,\n AcrossIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/bridges/cbridge/interfaces/ICelerStorageWrapper.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity >=0.8.0;\n\n/**\n * @title Celer-StorageWrapper interface\n * @notice Interface to handle storageMappings used while bridging ERC20 and native on CelerBridge\n * @dev all functions ehich mutate the storage are restricted to Owner of SocketGateway\n * @author Socket dot tech.\n */\ninterface ICelerStorageWrapper {\n /**\n * @notice function to store the transferId and message-sender of a bridging activity\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in CelerBridgeData struct\n * @param transferId transferId generated during the bridging of ERC20 or native on CelerBridge\n * @param transferIdAddress message sender who is making the bridging on CelerBridge\n */\n function setAddressForTransferId(\n bytes32 transferId,\n address transferIdAddress\n ) external;\n\n /**\n * @notice function to store the transferId and message-sender of a bridging activity\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in CelerBridgeData struct\n * @param transferId transferId generated during the bridging of ERC20 or native on CelerBridge\n */\n function deleteTransferId(bytes32 transferId) external;\n\n /**\n * @notice function to lookup the address mapped to the transferId\n * @param transferId transferId generated during the bridging of ERC20 or native on CelerBridge\n * @return address of account mapped to transferId\n */\n function getAddressFromTransferId(\n bytes32 transferId\n ) external view returns (address);\n}\n"},"src/bridges/optimism/interfaces/optimism.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity >=0.8.0;\n\ninterface L1StandardBridge {\n /**\n * @dev Performs the logic for deposits by storing the ETH and informing the L2 ETH Gateway of\n * the deposit.\n * @param _to Account to give the deposit to on L2.\n * @param _l2Gas Gas limit required to complete the deposit on L2.\n * @param _data Optional data to forward to L2. This data is provided\n * solely as a convenience for external contracts. Aside from enforcing a maximum\n * length, these contracts provide no guarantees about its content.\n */\n function depositETHTo(\n address _to,\n uint32 _l2Gas,\n bytes calldata _data\n ) external payable;\n\n /**\n * @dev deposit an amount of ERC20 to a recipient's balance on L2.\n * @param _l1Token Address of the L1 ERC20 we are depositing\n * @param _l2Token Address of the L1 respective L2 ERC20\n * @param _to L2 address to credit the withdrawal to.\n * @param _amount Amount of the ERC20 to deposit.\n * @param _l2Gas Gas limit required to complete the deposit on L2.\n * @param _data Optional data to forward to L2. This data is provided\n * solely as a convenience for external contracts. Aside from enforcing a maximum\n * length, these contracts provide no guarantees about its content.\n */\n function depositERC20To(\n address _l1Token,\n address _l2Token,\n address _to,\n uint256 _amount,\n uint32 _l2Gas,\n bytes calldata _data\n ) external;\n}\n\ninterface OldL1TokenGateway {\n /**\n * @dev Transfer SNX to L2 First, moves the SNX into the deposit escrow\n *\n * @param _to Account to give the deposit to on L2\n * @param _amount Amount of the ERC20 to deposit.\n */\n function depositTo(address _to, uint256 _amount) external;\n\n /**\n * @dev Transfer SNX to L2 First, moves the SNX into the deposit escrow\n *\n * @param currencyKey currencyKey for the SynthToken\n * @param destination Account to give the deposit to on L2\n * @param amount Amount of the ERC20 to deposit.\n */\n function initiateSynthTransfer(\n bytes32 currencyKey,\n address destination,\n uint256 amount\n ) external;\n}\n"},"src/bridges/arbitrum/interfaces/arbitrum.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\n\n/*\n * Copyright 2021, Offchain Labs, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npragma solidity >=0.8.0;\n\n/**\n * @title L1gatewayRouter for native-arbitrum\n */\ninterface L1GatewayRouter {\n /**\n * @notice outbound function to bridge ERC20 via NativeArbitrum-Bridge\n * @param _token address of token being bridged via GatewayRouter\n * @param _to recipient of the token on arbitrum chain\n * @param _amount amount of ERC20 token being bridged\n * @param _maxGas a depositParameter for bridging the token\n * @param _gasPriceBid a depositParameter for bridging the token\n * @param _data a depositParameter for bridging the token\n * @return calldata returns the output of transactioncall made on gatewayRouter\n */\n function outboundTransfer(\n address _token,\n address _to,\n uint256 _amount,\n uint256 _maxGas,\n uint256 _gasPriceBid,\n bytes calldata _data\n ) external payable returns (bytes calldata);\n}\n"},"src/deployFactory/DisabledSocketRoute.sol":{"content":"//SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {ISocketGateway} from \"../interfaces/ISocketGateway.sol\";\nimport {OnlySocketGatewayOwner} from \"../errors/SocketErrors.sol\";\n\ncontract DisabledSocketRoute {\n using SafeTransferLib for ERC20;\n\n /// @notice immutable variable to store the socketGateway address\n address public immutable socketGateway;\n error RouteDisabled();\n\n /**\n * @notice Construct the base for all BridgeImplementations.\n * @param _socketGateway Socketgateway address, an immutable variable to set.\n */\n constructor(address _socketGateway) {\n socketGateway = _socketGateway;\n }\n\n /// @notice Implementing contract needs to make use of the modifier where restricted access is to be used\n modifier isSocketGatewayOwner() {\n if (msg.sender != ISocketGateway(socketGateway).owner()) {\n revert OnlySocketGatewayOwner();\n }\n _;\n }\n\n /**\n * @notice function to rescue the ERC20 tokens in the bridge Implementation contract\n * @notice this is a function restricted to Owner of SocketGateway only\n * @param token address of ERC20 token being rescued\n * @param userAddress receipient address to which ERC20 tokens will be rescued to\n * @param amount amount of ERC20 tokens being rescued\n */\n function rescueFunds(\n address token,\n address userAddress,\n uint256 amount\n ) external isSocketGatewayOwner {\n ERC20(token).safeTransfer(userAddress, amount);\n }\n\n /**\n * @notice function to rescue the native-balance in the bridge Implementation contract\n * @notice this is a function restricted to Owner of SocketGateway only\n * @param userAddress receipient address to which native-balance will be rescued to\n * @param amount amount of native balance tokens being rescued\n */\n function rescueEther(\n address payable userAddress,\n uint256 amount\n ) external isSocketGatewayOwner {\n userAddress.transfer(amount);\n }\n\n /**\n * @notice Handle route function calls gracefully.\n */\n fallback() external payable {\n revert RouteDisabled();\n }\n\n /**\n * @notice Support receiving ether to handle refunds etc.\n */\n receive() external payable {}\n}\n"},"src/bridges/polygon/NativePolygon.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport \"./interfaces/polygon.sol\";\nimport {BridgeImplBase} from \"../BridgeImplBase.sol\";\nimport {NATIVE_POLYGON} from \"../../static/RouteIdentifiers.sol\";\n\n/**\n * @title NativePolygon-Route Implementation\n * @notice This is the L1 implementation, so this is used when transferring from ethereum to polygon via their native bridge.\n * @author Socket dot tech.\n */\ncontract NativePolygonImpl is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable NativePolyonIdentifier = NATIVE_POLYGON;\n\n /// @notice destination-chain-Id for this router is always arbitrum\n uint256 public constant DESTINATION_CHAIN_ID = 137;\n\n /// @notice Function-selector for ERC20-token bridging on NativePolygon-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4\n public immutable NATIVE_POLYGON_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(keccak256(\"bridgeERC20To(uint256,bytes32,address,address)\"));\n\n /// @notice Function-selector for Native bridging on NativePolygon-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge Native tokens\n bytes4\n public immutable NATIVE_POLYGON_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(keccak256(\"bridgeNativeTo(uint256,bytes32,address)\"));\n\n bytes4 public immutable NATIVE_POLYGON_SWAP_BRIDGE_SELECTOR =\n bytes4(keccak256(\"swapAndBridge(uint32,address,bytes32,bytes)\"));\n\n /// @notice root chain manager proxy on the ethereum chain\n /// @dev to be initialised in the constructor\n IRootChainManager public immutable rootChainManagerProxy;\n\n /// @notice ERC20 Predicate proxy on the ethereum chain\n /// @dev to be initialised in the constructor\n address public immutable erc20PredicateProxy;\n\n /**\n * // @notice We set all the required addresses in the constructor while deploying the contract.\n * // These will be constant addresses.\n * // @dev Please use the Proxy addresses and not the implementation addresses while setting these\n * // @param _rootChainManagerProxy address of the root chain manager proxy on the ethereum chain\n * // @param _erc20PredicateProxy address of the ERC20 Predicate proxy on the ethereum chain.\n * // @param _socketGateway address of the socketGateway contract that calls this contract\n */\n constructor(\n address _rootChainManagerProxy,\n address _erc20PredicateProxy,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n rootChainManagerProxy = IRootChainManager(_rootChainManagerProxy);\n erc20PredicateProxy = _erc20PredicateProxy;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in NativePolygon-BridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for NativePolygon-Bridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n (address token, address receiverAddress, bytes32 metadata) = abi.decode(\n bridgeData,\n (address, address, bytes32)\n );\n\n if (token == NATIVE_TOKEN_ADDRESS) {\n IRootChainManager(rootChainManagerProxy).depositEtherFor{\n value: amount\n }(receiverAddress);\n } else {\n ERC20(token).safeApprove(erc20PredicateProxy, amount);\n\n // deposit into rootchain manager\n IRootChainManager(rootChainManagerProxy).depositFor(\n receiverAddress,\n token,\n abi.encodePacked(amount)\n );\n }\n\n emit SocketBridge(\n amount,\n token,\n DESTINATION_CHAIN_ID,\n NativePolyonIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in NativePolygon-BridgeData struct\n * @param swapId routeId for the swapImpl\n * @param receiverAddress address of the receiver\n * @param swapData encoded data for swap\n */\n function swapAndBridge(\n uint32 swapId,\n address receiverAddress,\n bytes32 metadata,\n bytes calldata swapData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n\n if (token == NATIVE_TOKEN_ADDRESS) {\n IRootChainManager(rootChainManagerProxy).depositEtherFor{\n value: bridgeAmount\n }(receiverAddress);\n } else {\n ERC20(token).safeApprove(erc20PredicateProxy, bridgeAmount);\n\n // deposit into rootchain manager\n IRootChainManager(rootChainManagerProxy).depositFor(\n receiverAddress,\n token,\n abi.encodePacked(bridgeAmount)\n );\n }\n\n emit SocketBridge(\n bridgeAmount,\n token,\n DESTINATION_CHAIN_ID,\n NativePolyonIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via NativePolygon-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount of tokens being bridged\n * @param receiverAddress recipient address\n * @param token address of token being bridged\n */\n function bridgeERC20To(\n uint256 amount,\n bytes32 metadata,\n address receiverAddress,\n address token\n ) external payable {\n ERC20 tokenInstance = ERC20(token);\n\n // set allowance for erc20 predicate\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n tokenInstance.safeApprove(erc20PredicateProxy, amount);\n\n // deposit into rootchain manager\n rootChainManagerProxy.depositFor(\n receiverAddress,\n token,\n abi.encodePacked(amount)\n );\n\n emit SocketBridge(\n amount,\n token,\n DESTINATION_CHAIN_ID,\n NativePolyonIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n\n /**\n * @notice function to handle Native bridging to receipent via NativePolygon-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount of tokens being bridged\n * @param receiverAddress recipient address\n */\n function bridgeNativeTo(\n uint256 amount,\n bytes32 metadata,\n address receiverAddress\n ) external payable {\n rootChainManagerProxy.depositEtherFor{value: amount}(receiverAddress);\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n DESTINATION_CHAIN_ID,\n NativePolyonIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/bridges/stargate/l1/Stargate.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport \"../interfaces/stargate.sol\";\nimport {BridgeImplBase} from \"../../BridgeImplBase.sol\";\nimport {STARGATE} from \"../../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Stargate-L1-Route Implementation\n * @notice Route implementation with functions to bridge ERC20 and Native via Stargate-L1-Bridge\n * Called via SocketGateway if the routeId in the request maps to the routeId of Stargate-L1-Implementation\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ncontract StargateImplL1 is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable StargateIdentifier = STARGATE;\n\n /// @notice Function-selector for ERC20-token bridging on Stargate-L1-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4\n public immutable STARGATE_L1_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(address,address,address,uint256,uint256,(uint256,uint256,uint256,uint256,bytes32,bytes,uint16))\"\n )\n );\n\n /// @notice Function-selector for Native bridging on Stargate-L1-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge Native tokens\n bytes4\n public immutable STARGATE_L1_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeNativeTo(address,address,uint16,uint256,uint256,uint256,bytes32)\"\n )\n );\n\n bytes4 public immutable STARGATE_L1_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(address,address,uint16,uint256,uint256,uint256,uint256,uint256,uint256,bytes32,bytes))\"\n )\n );\n\n /// @notice Stargate Router to bridge ERC20 tokens\n IBridgeStargate public immutable router;\n\n /// @notice Stargate Router to bridge native tokens\n IBridgeStargate public immutable routerETH;\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n /// @dev ensure router, routerEth are set properly for the chainId in which the contract is being deployed\n constructor(\n address _router,\n address _routerEth,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n router = IBridgeStargate(_router);\n routerETH = IBridgeStargate(_routerEth);\n }\n\n struct StargateBridgeExtraData {\n uint256 srcPoolId;\n uint256 dstPoolId;\n uint256 destinationGasLimit;\n uint256 minReceivedAmt;\n bytes32 metadata;\n bytes destinationPayload;\n uint16 stargateDstChainId; // stargate defines chain id in its way\n }\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct StargateBridgeDataNoToken {\n address receiverAddress;\n address senderAddress;\n uint16 stargateDstChainId; // stargate defines chain id in its way\n uint256 value;\n // a unique identifier that is uses to dedup transfers\n // this value is the a timestamp sent from frontend, but in theory can be any unique number\n uint256 srcPoolId;\n uint256 dstPoolId;\n uint256 minReceivedAmt; // defines the slippage, the min qty you would accept on the destination\n uint256 optionalValue;\n uint256 destinationGasLimit;\n bytes32 metadata;\n bytes destinationPayload;\n }\n\n struct StargateBridgeData {\n address token;\n address receiverAddress;\n address senderAddress;\n uint16 stargateDstChainId; // stargate defines chain id in its way\n uint256 value;\n // a unique identifier that is uses to dedup transfers\n // this value is the a timestamp sent from frontend, but in theory can be any unique number\n uint256 srcPoolId;\n uint256 dstPoolId;\n uint256 minReceivedAmt; // defines the slippage, the min qty you would accept on the destination\n uint256 optionalValue;\n uint256 destinationGasLimit;\n bytes32 metadata;\n bytes destinationPayload;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in Stargate-BridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for Stargate-L1-Bridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n StargateBridgeData memory stargateBridgeData = abi.decode(\n bridgeData,\n (StargateBridgeData)\n );\n\n if (stargateBridgeData.token == NATIVE_TOKEN_ADDRESS) {\n // perform bridging\n routerETH.swapETH{value: amount + stargateBridgeData.optionalValue}(\n stargateBridgeData.stargateDstChainId,\n payable(stargateBridgeData.senderAddress),\n abi.encodePacked(stargateBridgeData.receiverAddress),\n amount,\n stargateBridgeData.minReceivedAmt\n );\n } else {\n ERC20(stargateBridgeData.token).safeApprove(\n address(router),\n amount\n );\n {\n router.swap{value: stargateBridgeData.value}(\n stargateBridgeData.stargateDstChainId,\n stargateBridgeData.srcPoolId,\n stargateBridgeData.dstPoolId,\n payable(stargateBridgeData.senderAddress), // default to refund to main contract\n amount,\n stargateBridgeData.minReceivedAmt,\n IBridgeStargate.lzTxObj(\n stargateBridgeData.destinationGasLimit,\n 0, // zero amount since this is a ERC20 bridging\n \"0x\" //empty data since this is for only ERC20\n ),\n abi.encodePacked(stargateBridgeData.receiverAddress),\n stargateBridgeData.destinationPayload\n );\n }\n }\n\n emit SocketBridge(\n amount,\n stargateBridgeData.token,\n stargateBridgeData.stargateDstChainId,\n StargateIdentifier,\n msg.sender,\n stargateBridgeData.receiverAddress,\n stargateBridgeData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in Stargate-BridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param stargateBridgeData encoded data for StargateBridgeData\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n StargateBridgeDataNoToken calldata stargateBridgeData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n\n if (token == NATIVE_TOKEN_ADDRESS) {\n // perform bridging\n routerETH.swapETH{\n value: bridgeAmount + stargateBridgeData.optionalValue\n }(\n stargateBridgeData.stargateDstChainId,\n payable(stargateBridgeData.senderAddress),\n abi.encodePacked(stargateBridgeData.receiverAddress),\n bridgeAmount,\n stargateBridgeData.minReceivedAmt\n );\n } else {\n ERC20(token).safeApprove(address(router), bridgeAmount);\n {\n router.swap{value: stargateBridgeData.value}(\n stargateBridgeData.stargateDstChainId,\n stargateBridgeData.srcPoolId,\n stargateBridgeData.dstPoolId,\n payable(stargateBridgeData.senderAddress), // default to refund to main contract\n bridgeAmount,\n stargateBridgeData.minReceivedAmt,\n IBridgeStargate.lzTxObj(\n stargateBridgeData.destinationGasLimit,\n 0, // zero amount since this is a ERC20 bridging\n \"0x\" //empty data since this is for only ERC20\n ),\n abi.encodePacked(stargateBridgeData.receiverAddress),\n stargateBridgeData.destinationPayload\n );\n }\n }\n\n emit SocketBridge(\n bridgeAmount,\n token,\n stargateBridgeData.stargateDstChainId,\n StargateIdentifier,\n msg.sender,\n stargateBridgeData.receiverAddress,\n stargateBridgeData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via Stargate-L1-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param token address of token being bridged\n * @param senderAddress address of sender\n * @param receiverAddress address of recipient\n * @param amount amount of token being bridge\n * @param value value\n * @param stargateBridgeExtraData stargate bridge extradata\n */\n function bridgeERC20To(\n address token,\n address senderAddress,\n address receiverAddress,\n uint256 amount,\n uint256 value,\n StargateBridgeExtraData calldata stargateBridgeExtraData\n ) external payable {\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n tokenInstance.safeApprove(address(router), amount);\n {\n router.swap{value: value}(\n stargateBridgeExtraData.stargateDstChainId,\n stargateBridgeExtraData.srcPoolId,\n stargateBridgeExtraData.dstPoolId,\n payable(senderAddress), // default to refund to main contract\n amount,\n stargateBridgeExtraData.minReceivedAmt,\n IBridgeStargate.lzTxObj(\n stargateBridgeExtraData.destinationGasLimit,\n 0, // zero amount since this is a ERC20 bridging\n \"0x\" //empty data since this is for only ERC20\n ),\n abi.encodePacked(receiverAddress),\n stargateBridgeExtraData.destinationPayload\n );\n }\n\n emit SocketBridge(\n amount,\n token,\n stargateBridgeExtraData.stargateDstChainId,\n StargateIdentifier,\n msg.sender,\n receiverAddress,\n stargateBridgeExtraData.metadata\n );\n }\n\n /**\n * @notice function to handle Native bridging to receipent via Stargate-L1-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param receiverAddress address of receipient\n * @param senderAddress address of sender\n * @param stargateDstChainId stargate defines chain id in its way\n * @param amount amount of token being bridge\n * @param minReceivedAmt defines the slippage, the min qty you would accept on the destination\n * @param optionalValue optionalValue Native amount\n */\n function bridgeNativeTo(\n address receiverAddress,\n address senderAddress,\n uint16 stargateDstChainId,\n uint256 amount,\n uint256 minReceivedAmt,\n uint256 optionalValue,\n bytes32 metadata\n ) external payable {\n // perform bridging\n routerETH.swapETH{value: amount + optionalValue}(\n stargateDstChainId,\n payable(senderAddress),\n abi.encodePacked(receiverAddress),\n amount,\n minReceivedAmt\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n stargateDstChainId,\n StargateIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/bridges/hop/l2/HopImplL2.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport \"../interfaces/amm.sol\";\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {BridgeImplBase} from \"../../BridgeImplBase.sol\";\nimport {HOP} from \"../../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Hop-L2 Route Implementation\n * @notice This is the L2 implementation, so this is used when transferring from l2 to supported l2s\n * Called via SocketGateway if the routeId in the request maps to the routeId of HopL2-Implementation\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ncontract HopImplL2 is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable HopIdentifier = HOP;\n\n /// @notice Function-selector for ERC20-token bridging on Hop-L2-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4 public immutable HOP_L2_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(address,address,address,uint256,uint256,(uint256,uint256,uint256,uint256,uint256,bytes32))\"\n )\n );\n\n /// @notice Function-selector for Native bridging on Hop-L2-Route\n /// @dev This function selector is to be used while building transaction-data to bridge Native tokens\n bytes4 public immutable HOP_L2_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeNativeTo(address,address,uint256,uint256,uint256,uint256,uint256,uint256,uint256,bytes32)\"\n )\n );\n\n bytes4 public immutable HOP_L2_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(address,address,uint256,uint256,uint256,uint256,uint256,uint256,bytes32))\"\n )\n );\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n constructor(\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {}\n\n /// @notice Struct to be used as a input parameter for Bridging tokens via Hop-L2-route\n /// @dev while building transactionData,values should be set in this sequence of properties in this struct\n struct HopBridgeRequestData {\n // fees passed to relayer\n uint256 bonderFee;\n // The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.\n uint256 amountOutMin;\n // The deadline for swapping in the destination AMM market. 0 if no swap is intended.\n uint256 deadline;\n // Minimum amount expected to be received or bridged to destination\n uint256 amountOutMinDestination;\n // deadline for bridging to destination\n uint256 deadlineDestination;\n // socket offchain created hash\n bytes32 metadata;\n }\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct HopBridgeDataNoToken {\n // The address receiving funds at the destination\n address receiverAddress;\n // AMM address of Hop on L2\n address hopAMM;\n // The chainId of the destination chain\n uint256 toChainId;\n // fees passed to relayer\n uint256 bonderFee;\n // The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.\n uint256 amountOutMin;\n // The deadline for swapping in the destination AMM market. 0 if no swap is intended.\n uint256 deadline;\n // Minimum amount expected to be received or bridged to destination\n uint256 amountOutMinDestination;\n // deadline for bridging to destination\n uint256 deadlineDestination;\n // socket offchain created hash\n bytes32 metadata;\n }\n\n struct HopBridgeData {\n /// @notice address of token being bridged\n address token;\n // The address receiving funds at the destination\n address receiverAddress;\n // AMM address of Hop on L2\n address hopAMM;\n // The chainId of the destination chain\n uint256 toChainId;\n // fees passed to relayer\n uint256 bonderFee;\n // The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.\n uint256 amountOutMin;\n // The deadline for swapping in the destination AMM market. 0 if no swap is intended.\n uint256 deadline;\n // Minimum amount expected to be received or bridged to destination\n uint256 amountOutMinDestination;\n // deadline for bridging to destination\n uint256 deadlineDestination;\n // socket offchain created hash\n bytes32 metadata;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in HopBridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for Hop-L2-Bridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n HopBridgeData memory hopData = abi.decode(bridgeData, (HopBridgeData));\n\n if (hopData.token == NATIVE_TOKEN_ADDRESS) {\n HopAMM(hopData.hopAMM).swapAndSend{value: amount}(\n hopData.toChainId,\n hopData.receiverAddress,\n amount,\n hopData.bonderFee,\n hopData.amountOutMin,\n hopData.deadline,\n hopData.amountOutMinDestination,\n hopData.deadlineDestination\n );\n } else {\n // perform bridging\n HopAMM(hopData.hopAMM).swapAndSend(\n hopData.toChainId,\n hopData.receiverAddress,\n amount,\n hopData.bonderFee,\n hopData.amountOutMin,\n hopData.deadline,\n hopData.amountOutMinDestination,\n hopData.deadlineDestination\n );\n }\n\n emit SocketBridge(\n amount,\n hopData.token,\n hopData.toChainId,\n HopIdentifier,\n msg.sender,\n hopData.receiverAddress,\n hopData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in HopBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param hopData encoded data for HopData\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n HopBridgeDataNoToken calldata hopData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n\n if (token == NATIVE_TOKEN_ADDRESS) {\n HopAMM(hopData.hopAMM).swapAndSend{value: bridgeAmount}(\n hopData.toChainId,\n hopData.receiverAddress,\n bridgeAmount,\n hopData.bonderFee,\n hopData.amountOutMin,\n hopData.deadline,\n hopData.amountOutMinDestination,\n hopData.deadlineDestination\n );\n } else {\n // perform bridging\n HopAMM(hopData.hopAMM).swapAndSend(\n hopData.toChainId,\n hopData.receiverAddress,\n bridgeAmount,\n hopData.bonderFee,\n hopData.amountOutMin,\n hopData.deadline,\n hopData.amountOutMinDestination,\n hopData.deadlineDestination\n );\n }\n\n emit SocketBridge(\n bridgeAmount,\n token,\n hopData.toChainId,\n HopIdentifier,\n msg.sender,\n hopData.receiverAddress,\n hopData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via Hop-L2-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param receiverAddress The address receiving funds at the destination\n * @param token token being bridged\n * @param hopAMM AMM address of Hop on L2\n * @param amount The amount being bridged\n * @param toChainId The chainId of the destination chain\n * @param hopBridgeRequestData extraData for Bridging across Hop-L2\n */\n function bridgeERC20To(\n address receiverAddress,\n address token,\n address hopAMM,\n uint256 amount,\n uint256 toChainId,\n HopBridgeRequestData calldata hopBridgeRequestData\n ) external payable {\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n\n HopAMM(hopAMM).swapAndSend(\n toChainId,\n receiverAddress,\n amount,\n hopBridgeRequestData.bonderFee,\n hopBridgeRequestData.amountOutMin,\n hopBridgeRequestData.deadline,\n hopBridgeRequestData.amountOutMinDestination,\n hopBridgeRequestData.deadlineDestination\n );\n\n emit SocketBridge(\n amount,\n token,\n toChainId,\n HopIdentifier,\n msg.sender,\n receiverAddress,\n hopBridgeRequestData.metadata\n );\n }\n\n /**\n * @notice function to handle Native bridging to receipent via Hop-L2-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param receiverAddress The address receiving funds at the destination\n * @param hopAMM AMM address of Hop on L2\n * @param amount The amount being bridged\n * @param toChainId The chainId of the destination chain\n * @param bonderFee fees passed to relayer\n * @param amountOutMin The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.\n * @param deadline The deadline for swapping in the destination AMM market. 0 if no swap is intended.\n * @param amountOutMinDestination Minimum amount expected to be received or bridged to destination\n * @param deadlineDestination deadline for bridging to destination\n */\n function bridgeNativeTo(\n address receiverAddress,\n address hopAMM,\n uint256 amount,\n uint256 toChainId,\n uint256 bonderFee,\n uint256 amountOutMin,\n uint256 deadline,\n uint256 amountOutMinDestination,\n uint256 deadlineDestination,\n bytes32 metadata\n ) external payable {\n // token address might not be indication thats why passed through extraData\n // perform bridging\n HopAMM(hopAMM).swapAndSend{value: amount}(\n toChainId,\n receiverAddress,\n amount,\n bonderFee,\n amountOutMin,\n deadline,\n amountOutMinDestination,\n deadlineDestination\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n toChainId,\n HopIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/bridges/anyswap-router-v4/l2/Anyswap.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {BridgeImplBase} from \"../../BridgeImplBase.sol\";\nimport {ANYSWAP} from \"../../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Anyswap-V4-Route L1 Implementation\n * @notice Route implementation with functions to bridge ERC20 via Anyswap-Bridge\n * Called via SocketGateway if the routeId in the request maps to the routeId of AnyswapImplementation\n * This is the L2 implementation, so this is used when transferring from l2.\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ninterface AnyswapV4Router {\n function anySwapOutUnderlying(\n address token,\n address to,\n uint256 amount,\n uint256 toChainID\n ) external;\n}\n\ncontract AnyswapL2Impl is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable AnyswapIdentifier = ANYSWAP;\n\n /// @notice Function-selector for ERC20-token bridging on Anyswap-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4 public immutable ANYSWAP_L2_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(uint256,uint256,bytes32,address,address,address)\"\n )\n );\n\n bytes4 public immutable ANYSWAP_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(uint256,address,address,bytes32))\"\n )\n );\n\n // polygon router multichain router v4\n AnyswapV4Router public immutable router;\n\n /**\n * @notice Constructor sets the router address and socketGateway address.\n * @dev anyswap v4 router is immutable. so no setter function required.\n */\n constructor(\n address _router,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n router = AnyswapV4Router(_router);\n }\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct AnyswapBridgeDataNoToken {\n /// @notice destination ChainId\n uint256 toChainId;\n /// @notice address of receiver of bridged tokens\n address receiverAddress;\n /// @notice address of wrapperToken, WrappedVersion of the token being bridged\n address wrapperTokenAddress;\n /// @notice socket offchain created hash\n bytes32 metadata;\n }\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct AnyswapBridgeData {\n /// @notice destination ChainId\n uint256 toChainId;\n /// @notice address of receiver of bridged tokens\n address receiverAddress;\n /// @notice address of wrapperToken, WrappedVersion of the token being bridged\n address wrapperTokenAddress;\n /// @notice address of token being bridged\n address token;\n /// @notice socket offchain created hash\n bytes32 metadata;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in AnyswapBridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for AnyswapBridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n AnyswapBridgeData memory anyswapBridgeData = abi.decode(\n bridgeData,\n (AnyswapBridgeData)\n );\n ERC20(anyswapBridgeData.token).safeApprove(address(router), amount);\n router.anySwapOutUnderlying(\n anyswapBridgeData.wrapperTokenAddress,\n anyswapBridgeData.receiverAddress,\n amount,\n anyswapBridgeData.toChainId\n );\n\n emit SocketBridge(\n amount,\n anyswapBridgeData.token,\n anyswapBridgeData.toChainId,\n AnyswapIdentifier,\n msg.sender,\n anyswapBridgeData.receiverAddress,\n anyswapBridgeData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in AnyswapBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param anyswapBridgeData encoded data for AnyswapBridge\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n AnyswapBridgeDataNoToken calldata anyswapBridgeData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n\n ERC20(token).safeApprove(address(router), bridgeAmount);\n router.anySwapOutUnderlying(\n anyswapBridgeData.wrapperTokenAddress,\n anyswapBridgeData.receiverAddress,\n bridgeAmount,\n anyswapBridgeData.toChainId\n );\n\n emit SocketBridge(\n bridgeAmount,\n token,\n anyswapBridgeData.toChainId,\n AnyswapIdentifier,\n msg.sender,\n anyswapBridgeData.receiverAddress,\n anyswapBridgeData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via Anyswap-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount being bridged\n * @param toChainId destination ChainId\n * @param receiverAddress address of receiver of bridged tokens\n * @param token address of token being bridged\n * @param wrapperTokenAddress address of wrapperToken, WrappedVersion of the token being bridged\n */\n function bridgeERC20To(\n uint256 amount,\n uint256 toChainId,\n bytes32 metadata,\n address receiverAddress,\n address token,\n address wrapperTokenAddress\n ) external payable {\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n tokenInstance.safeApprove(address(router), amount);\n router.anySwapOutUnderlying(\n wrapperTokenAddress,\n receiverAddress,\n amount,\n toChainId\n );\n\n emit SocketBridge(\n amount,\n token,\n toChainId,\n AnyswapIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/bridges/hyphen/Hyphen.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport \"./interfaces/hyphen.sol\";\nimport \"../BridgeImplBase.sol\";\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {HYPHEN} from \"../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Hyphen-Route Implementation\n * @notice Route implementation with functions to bridge ERC20 and Native via Hyphen-Bridge\n * Called via SocketGateway if the routeId in the request maps to the routeId of HyphenImplementation\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ncontract HyphenImpl is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable HyphenIdentifier = HYPHEN;\n\n /// @notice Function-selector for ERC20-token bridging on Hyphen-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4 public immutable HYPHEN_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\"bridgeERC20To(uint256,bytes32,address,address,uint256)\")\n );\n\n /// @notice Function-selector for Native bridging on Hyphen-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge Native tokens\n bytes4 public immutable HYPHEN_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(keccak256(\"bridgeNativeTo(uint256,bytes32,address,uint256)\"));\n\n bytes4 public immutable HYPHEN_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\"swapAndBridge(uint32,bytes,(address,uint256,bytes32))\")\n );\n\n /// @notice liquidityPoolManager - liquidityPool Manager of Hyphen used to bridge ERC20 and native\n /// @dev this is to be initialized in constructor with a valid deployed address of hyphen-liquidityPoolManager\n HyphenLiquidityPoolManager public immutable liquidityPoolManager;\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n /// @dev ensure liquidityPoolManager-address are set properly for the chainId in which the contract is being deployed\n constructor(\n address _liquidityPoolManager,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n liquidityPoolManager = HyphenLiquidityPoolManager(\n _liquidityPoolManager\n );\n }\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct HyphenData {\n /// @notice address of token being bridged\n address token;\n /// @notice address of receiver\n address receiverAddress;\n /// @notice chainId of destination\n uint256 toChainId;\n /// @notice socket offchain created hash\n bytes32 metadata;\n }\n\n struct HyphenDataNoToken {\n /// @notice address of receiver\n address receiverAddress;\n /// @notice chainId of destination\n uint256 toChainId;\n /// @notice chainId of destination\n bytes32 metadata;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in HyphenBridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for HyphenBridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n HyphenData memory hyphenData = abi.decode(bridgeData, (HyphenData));\n\n if (hyphenData.token == NATIVE_TOKEN_ADDRESS) {\n liquidityPoolManager.depositNative{value: amount}(\n hyphenData.receiverAddress,\n hyphenData.toChainId,\n \"SOCKET\"\n );\n } else {\n ERC20(hyphenData.token).safeApprove(\n address(liquidityPoolManager),\n amount\n );\n liquidityPoolManager.depositErc20(\n hyphenData.toChainId,\n hyphenData.token,\n hyphenData.receiverAddress,\n amount,\n \"SOCKET\"\n );\n }\n\n emit SocketBridge(\n amount,\n hyphenData.token,\n hyphenData.toChainId,\n HyphenIdentifier,\n msg.sender,\n hyphenData.receiverAddress,\n hyphenData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in HyphenBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param hyphenData encoded data for hyphenData\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n HyphenDataNoToken calldata hyphenData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n if (token == NATIVE_TOKEN_ADDRESS) {\n liquidityPoolManager.depositNative{value: bridgeAmount}(\n hyphenData.receiverAddress,\n hyphenData.toChainId,\n \"SOCKET\"\n );\n } else {\n ERC20(token).safeApprove(\n address(liquidityPoolManager),\n bridgeAmount\n );\n liquidityPoolManager.depositErc20(\n hyphenData.toChainId,\n token,\n hyphenData.receiverAddress,\n bridgeAmount,\n \"SOCKET\"\n );\n }\n\n emit SocketBridge(\n bridgeAmount,\n token,\n hyphenData.toChainId,\n HyphenIdentifier,\n msg.sender,\n hyphenData.receiverAddress,\n hyphenData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via Hyphen-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount to be sent\n * @param receiverAddress address of the token to bridged to the destination chain.\n * @param token address of token being bridged\n * @param toChainId chainId of destination\n */\n function bridgeERC20To(\n uint256 amount,\n bytes32 metadata,\n address receiverAddress,\n address token,\n uint256 toChainId\n ) external payable {\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n tokenInstance.safeApprove(address(liquidityPoolManager), amount);\n liquidityPoolManager.depositErc20(\n toChainId,\n token,\n receiverAddress,\n amount,\n \"SOCKET\"\n );\n\n emit SocketBridge(\n amount,\n token,\n toChainId,\n HyphenIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n\n /**\n * @notice function to handle Native bridging to receipent via Hyphen-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param amount amount to be sent\n * @param receiverAddress address of the token to bridged to the destination chain.\n * @param toChainId chainId of destination\n */\n function bridgeNativeTo(\n uint256 amount,\n bytes32 metadata,\n address receiverAddress,\n uint256 toChainId\n ) external payable {\n liquidityPoolManager.depositNative{value: amount}(\n receiverAddress,\n toChainId,\n \"SOCKET\"\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n toChainId,\n HyphenIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/bridges/optimism/l1/NativeOptimism.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport \"../interfaces/optimism.sol\";\nimport {BridgeImplBase} from \"../../BridgeImplBase.sol\";\nimport {UnsupportedInterfaceId} from \"../../../errors/SocketErrors.sol\";\nimport {NATIVE_OPTIMISM} from \"../../../static/RouteIdentifiers.sol\";\n\n/**\n * @title NativeOptimism-Route Implementation\n * @notice Route implementation with functions to bridge ERC20 and Native via NativeOptimism-Bridge\n * Tokens are bridged from Ethereum to Optimism Chain.\n * Called via SocketGateway if the routeId in the request maps to the routeId of NativeOptimism-Implementation\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ncontract NativeOptimismImpl is BridgeImplBase {\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable NativeOptimismIdentifier = NATIVE_OPTIMISM;\n\n uint256 public constant DESTINATION_CHAIN_ID = 10;\n\n /// @notice Function-selector for ERC20-token bridging on Native-Optimism-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4\n public immutable NATIVE_OPTIMISM_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(address,address,address,uint32,(bytes32,bytes32),uint256,uint256,address,bytes)\"\n )\n );\n\n /// @notice Function-selector for Native bridging on Native-Optimism-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge Native balance\n bytes4\n public immutable NATIVE_OPTIMISM_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeNativeTo(address,address,uint32,uint256,bytes32,bytes)\"\n )\n );\n\n bytes4 public immutable NATIVE_OPTIMISM_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(uint256,bytes32,bytes32,address,address,uint32,address,bytes))\"\n )\n );\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n constructor(\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {}\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct OptimismBridgeDataNoToken {\n // interfaceId to be set offchain which is used to select one of the 3 kinds of bridging (standard bridge / old standard / synthetic)\n uint256 interfaceId;\n // currencyKey of the token beingBridged\n bytes32 currencyKey;\n // socket offchain created hash\n bytes32 metadata;\n // address of receiver of bridged tokens\n address receiverAddress;\n /**\n * OptimismBridge that Performs the logic for deposits by informing the L2 Deposited Token\n * contract of the deposit and calling a handler to lock the L1 funds. (e.g. transferFrom)\n */\n address customBridgeAddress;\n // Gas limit required to complete the deposit on L2.\n uint32 l2Gas;\n // Address of the L1 respective L2 ERC20\n address l2Token;\n // additional data , for ll contracts this will be 0x data or empty data\n bytes data;\n }\n\n struct OptimismBridgeData {\n // interfaceId to be set offchain which is used to select one of the 3 kinds of bridging (standard bridge / old standard / synthetic)\n uint256 interfaceId;\n // currencyKey of the token beingBridged\n bytes32 currencyKey;\n // socket offchain created hash\n bytes32 metadata;\n // address of receiver of bridged tokens\n address receiverAddress;\n /**\n * OptimismBridge that Performs the logic for deposits by informing the L2 Deposited Token\n * contract of the deposit and calling a handler to lock the L1 funds. (e.g. transferFrom)\n */\n address customBridgeAddress;\n /// @notice address of token being bridged\n address token;\n // Gas limit required to complete the deposit on L2.\n uint32 l2Gas;\n // Address of the L1 respective L2 ERC20\n address l2Token;\n // additional data , for ll contracts this will be 0x data or empty data\n bytes data;\n }\n\n struct OptimismERC20Data {\n bytes32 currencyKey;\n bytes32 metadata;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in OptimismBridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for Optimism-Bridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n OptimismBridgeData memory optimismBridgeData = abi.decode(\n bridgeData,\n (OptimismBridgeData)\n );\n\n emit SocketBridge(\n amount,\n optimismBridgeData.token,\n DESTINATION_CHAIN_ID,\n NativeOptimismIdentifier,\n msg.sender,\n optimismBridgeData.receiverAddress,\n optimismBridgeData.metadata\n );\n if (optimismBridgeData.token == NATIVE_TOKEN_ADDRESS) {\n L1StandardBridge(optimismBridgeData.customBridgeAddress)\n .depositETHTo{value: amount}(\n optimismBridgeData.receiverAddress,\n optimismBridgeData.l2Gas,\n optimismBridgeData.data\n );\n } else {\n if (optimismBridgeData.interfaceId == 0) {\n revert UnsupportedInterfaceId();\n }\n\n ERC20(optimismBridgeData.token).safeApprove(\n optimismBridgeData.customBridgeAddress,\n amount\n );\n\n if (optimismBridgeData.interfaceId == 1) {\n // deposit into standard bridge\n L1StandardBridge(optimismBridgeData.customBridgeAddress)\n .depositERC20To(\n optimismBridgeData.token,\n optimismBridgeData.l2Token,\n optimismBridgeData.receiverAddress,\n amount,\n optimismBridgeData.l2Gas,\n optimismBridgeData.data\n );\n return;\n }\n\n // Deposit Using Old Standard - iOVM_L1TokenGateway(Example - SNX Token)\n if (optimismBridgeData.interfaceId == 2) {\n OldL1TokenGateway(optimismBridgeData.customBridgeAddress)\n .depositTo(optimismBridgeData.receiverAddress, amount);\n return;\n }\n\n if (optimismBridgeData.interfaceId == 3) {\n OldL1TokenGateway(optimismBridgeData.customBridgeAddress)\n .initiateSynthTransfer(\n optimismBridgeData.currencyKey,\n optimismBridgeData.receiverAddress,\n amount\n );\n return;\n }\n }\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in OptimismBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param optimismBridgeData encoded data for OptimismBridgeData\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n OptimismBridgeDataNoToken calldata optimismBridgeData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n\n emit SocketBridge(\n bridgeAmount,\n token,\n DESTINATION_CHAIN_ID,\n NativeOptimismIdentifier,\n msg.sender,\n optimismBridgeData.receiverAddress,\n optimismBridgeData.metadata\n );\n if (token == NATIVE_TOKEN_ADDRESS) {\n L1StandardBridge(optimismBridgeData.customBridgeAddress)\n .depositETHTo{value: bridgeAmount}(\n optimismBridgeData.receiverAddress,\n optimismBridgeData.l2Gas,\n optimismBridgeData.data\n );\n } else {\n if (optimismBridgeData.interfaceId == 0) {\n revert UnsupportedInterfaceId();\n }\n\n ERC20(token).safeApprove(\n optimismBridgeData.customBridgeAddress,\n bridgeAmount\n );\n\n if (optimismBridgeData.interfaceId == 1) {\n // deposit into standard bridge\n L1StandardBridge(optimismBridgeData.customBridgeAddress)\n .depositERC20To(\n token,\n optimismBridgeData.l2Token,\n optimismBridgeData.receiverAddress,\n bridgeAmount,\n optimismBridgeData.l2Gas,\n optimismBridgeData.data\n );\n return;\n }\n\n // Deposit Using Old Standard - iOVM_L1TokenGateway(Example - SNX Token)\n if (optimismBridgeData.interfaceId == 2) {\n OldL1TokenGateway(optimismBridgeData.customBridgeAddress)\n .depositTo(\n optimismBridgeData.receiverAddress,\n bridgeAmount\n );\n return;\n }\n\n if (optimismBridgeData.interfaceId == 3) {\n OldL1TokenGateway(optimismBridgeData.customBridgeAddress)\n .initiateSynthTransfer(\n optimismBridgeData.currencyKey,\n optimismBridgeData.receiverAddress,\n bridgeAmount\n );\n return;\n }\n }\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via NativeOptimism-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param token address of token being bridged\n * @param receiverAddress address of receiver of bridged tokens\n * @param customBridgeAddress OptimismBridge that Performs the logic for deposits by informing the L2 Deposited Token\n * contract of the deposit and calling a handler to lock the L1 funds. (e.g. transferFrom)\n * @param l2Gas Gas limit required to complete the deposit on L2.\n * @param optimismData extra data needed for optimism bridge\n * @param amount amount being bridged\n * @param interfaceId interfaceId to be set offchain which is used to select one of the 3 kinds of bridging (standard bridge / old standard / synthetic)\n * @param l2Token Address of the L1 respective L2 ERC20\n * @param data additional data , for ll contracts this will be 0x data or empty data\n */\n function bridgeERC20To(\n address token,\n address receiverAddress,\n address customBridgeAddress,\n uint32 l2Gas,\n OptimismERC20Data calldata optimismData,\n uint256 amount,\n uint256 interfaceId,\n address l2Token,\n bytes calldata data\n ) external payable {\n if (interfaceId == 0) {\n revert UnsupportedInterfaceId();\n }\n\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n tokenInstance.safeApprove(customBridgeAddress, amount);\n\n emit SocketBridge(\n amount,\n token,\n DESTINATION_CHAIN_ID,\n NativeOptimismIdentifier,\n msg.sender,\n receiverAddress,\n optimismData.metadata\n );\n if (interfaceId == 1) {\n // deposit into standard bridge\n L1StandardBridge(customBridgeAddress).depositERC20To(\n token,\n l2Token,\n receiverAddress,\n amount,\n l2Gas,\n data\n );\n return;\n }\n\n // Deposit Using Old Standard - iOVM_L1TokenGateway(Example - SNX Token)\n if (interfaceId == 2) {\n OldL1TokenGateway(customBridgeAddress).depositTo(\n receiverAddress,\n amount\n );\n return;\n }\n\n if (interfaceId == 3) {\n OldL1TokenGateway(customBridgeAddress).initiateSynthTransfer(\n optimismData.currencyKey,\n receiverAddress,\n amount\n );\n return;\n }\n }\n\n /**\n * @notice function to handle native balance bridging to receipent via NativeOptimism-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param receiverAddress address of receiver of bridged tokens\n * @param customBridgeAddress OptimismBridge that Performs the logic for deposits by informing the L2 Deposited Token\n * contract of the deposit and calling a handler to lock the L1 funds. (e.g. transferFrom)\n * @param l2Gas Gas limit required to complete the deposit on L2.\n * @param amount amount being bridged\n * @param data additional data , for ll contracts this will be 0x data or empty data\n */\n function bridgeNativeTo(\n address receiverAddress,\n address customBridgeAddress,\n uint32 l2Gas,\n uint256 amount,\n bytes32 metadata,\n bytes calldata data\n ) external payable {\n L1StandardBridge(customBridgeAddress).depositETHTo{value: amount}(\n receiverAddress,\n l2Gas,\n data\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n DESTINATION_CHAIN_ID,\n NativeOptimismIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/deployFactory/SocketDeployFactory.sol":{"content":"//SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport \"../utils/Ownable.sol\";\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {ISocketBridgeBase} from \"../interfaces/ISocketBridgeBase.sol\";\n\n/**\n * @dev In the constructor, set up the initialization code for socket\n * contracts as well as the keccak256 hash of the given initialization code.\n * that will be used to deploy any transient contracts, which will deploy any\n * socket contracts that require the use of a constructor.\n *\n * Socket contract initialization code (29 bytes):\n *\n * 0x5860208158601c335a63aaf10f428752fa158151803b80938091923cf3\n *\n * Description:\n *\n * pc|op|name | [stack] | \n *\n * ** set the first stack item to zero - used later **\n * 00 58 getpc [0] <>\n *\n * ** set second stack item to 32, length of word returned from staticcall **\n * 01 60 push1\n * 02 20 outsize [0, 32] <>\n *\n * ** set third stack item to 0, position of word returned from staticcall **\n * 03 81 dup2 [0, 32, 0] <>\n *\n * ** set fourth stack item to 4, length of selector given to staticcall **\n * 04 58 getpc [0, 32, 0, 4] <>\n *\n * ** set fifth stack item to 28, position of selector given to staticcall **\n * 05 60 push1\n * 06 1c inpos [0, 32, 0, 4, 28] <>\n *\n * ** set the sixth stack item to msg.sender, target address for staticcall **\n * 07 33 caller [0, 32, 0, 4, 28, caller] <>\n *\n * ** set the seventh stack item to msg.gas, gas to forward for staticcall **\n * 08 5a gas [0, 32, 0, 4, 28, caller, gas] <>\n *\n * ** set the eighth stack item to selector, \"what\" to store via mstore **\n * 09 63 push4\n * 10 aaf10f42 selector [0, 32, 0, 4, 28, caller, gas, 0xaaf10f42] <>\n *\n * ** set the ninth stack item to 0, \"where\" to store via mstore ***\n * 11 87 dup8 [0, 32, 0, 4, 28, caller, gas, 0xaaf10f42, 0] <>\n *\n * ** call mstore, consume 8 and 9 from the stack, place selector in memory **\n * 12 52 mstore [0, 32, 0, 4, 0, caller, gas] <0xaaf10f42>\n *\n * ** call staticcall, consume items 2 through 7, place address in memory **\n * 13 fa staticcall [0, 1 (if successful)]
\n *\n * ** flip success bit in second stack item to set to 0 **\n * 14 15 iszero [0, 0]
\n *\n * ** push a third 0 to the stack, position of address in memory **\n * 15 81 dup2 [0, 0, 0]
\n *\n * ** place address from position in memory onto third stack item **\n * 16 51 mload [0, 0, address] <>\n *\n * ** place address to fourth stack item for extcodesize to consume **\n * 17 80 dup1 [0, 0, address, address] <>\n *\n * ** get extcodesize on fourth stack item for extcodecopy **\n * 18 3b extcodesize [0, 0, address, size] <>\n *\n * ** dup and swap size for use by return at end of init code **\n * 19 80 dup1 [0, 0, address, size, size] <>\n * 20 93 swap4 [size, 0, address, size, 0] <>\n *\n * ** push code position 0 to stack and reorder stack items for extcodecopy **\n * 21 80 dup1 [size, 0, address, size, 0, 0] <>\n * 22 91 swap2 [size, 0, address, 0, 0, size] <>\n * 23 92 swap3 [size, 0, size, 0, 0, address] <>\n *\n * ** call extcodecopy, consume four items, clone runtime code to memory **\n * 24 3c extcodecopy [size, 0] \n *\n * ** return to deploy final code in memory **\n * 25 f3 return [] *deployed!*\n */\ncontract SocketDeployFactory is Ownable {\n using SafeTransferLib for ERC20;\n address public immutable disabledRouteAddress;\n\n mapping(address => address) _implementations;\n mapping(uint256 => bool) isDisabled;\n mapping(uint256 => bool) isRouteDeployed;\n mapping(address => bool) canDisableRoute;\n\n event Deployed(address _addr);\n event DisabledRoute(address _addr);\n event Destroyed(address _addr);\n error ContractAlreadyDeployed();\n error NothingToDestroy();\n error AlreadyDisabled();\n error CannotBeDisabled();\n error OnlyDisabler();\n\n constructor(address _owner, address disabledRoute) Ownable(_owner) {\n disabledRouteAddress = disabledRoute;\n canDisableRoute[_owner] = true;\n }\n\n modifier onlyDisabler() {\n if (!canDisableRoute[msg.sender]) {\n revert OnlyDisabler();\n }\n _;\n }\n\n function addDisablerAddress(address disabler) external onlyOwner {\n canDisableRoute[disabler] = true;\n }\n\n function removeDisablerAddress(address disabler) external onlyOwner {\n canDisableRoute[disabler] = false;\n }\n\n /**\n * @notice Deploys a route contract at predetermined location\n * @notice Caller must first deploy the route contract at another location and pass its address as implementation.\n * @param routeId route identifier\n * @param implementationContract address of deployed route contract. Its byte code will be copied to predetermined location.\n */\n function deploy(\n uint256 routeId,\n address implementationContract\n ) external onlyOwner returns (address) {\n // assign the initialization code for the socket contract.\n\n bytes memory initCode = (\n hex\"5860208158601c335a63aaf10f428752fa158151803b80938091923cf3\"\n );\n\n // determine the address of the socket contract.\n address routeContractAddress = _getContractAddress(routeId);\n\n if (isRouteDeployed[routeId]) {\n revert ContractAlreadyDeployed();\n }\n\n isRouteDeployed[routeId] = true;\n\n //first we deploy the code we want to deploy on a separate address\n // store the implementation to be retrieved by the socket contract.\n _implementations[routeContractAddress] = implementationContract;\n address addr;\n assembly {\n let encoded_data := add(0x20, initCode) // load initialization code.\n let encoded_size := mload(initCode) // load init code's length.\n addr := create2(0, encoded_data, encoded_size, routeId) // routeId is used as salt\n }\n require(\n addr == routeContractAddress,\n \"Failed to deploy the new socket contract.\"\n );\n emit Deployed(addr);\n return addr;\n }\n\n /**\n * @notice Destroy the route deployed at a location.\n * @param routeId route identifier to be destroyed.\n */\n function destroy(uint256 routeId) external onlyDisabler {\n // determine the address of the socket contract.\n _destroy(routeId);\n }\n\n /**\n * @notice Deploy a disabled contract at destroyed route to handle it gracefully.\n * @param routeId route identifier to be disabled.\n */\n function disableRoute(\n uint256 routeId\n ) external onlyDisabler returns (address) {\n return _disableRoute(routeId);\n }\n\n /**\n * @notice Destroy a list of routeIds\n * @param routeIds array of routeIds to be destroyed.\n */\n function multiDestroy(uint256[] calldata routeIds) external onlyDisabler {\n for (uint32 index = 0; index < routeIds.length; ) {\n _destroy(routeIds[index]);\n unchecked {\n ++index;\n }\n }\n }\n\n /**\n * @notice Deploy a disabled contract at list of routeIds.\n * @param routeIds array of routeIds to be disabled.\n */\n function multiDisableRoute(\n uint256[] calldata routeIds\n ) external onlyDisabler {\n for (uint32 index = 0; index < routeIds.length; ) {\n _disableRoute(routeIds[index]);\n unchecked {\n ++index;\n }\n }\n }\n\n /**\n * @dev External view function for calculating a socket contract address\n * given a particular routeId.\n */\n function getContractAddress(\n uint256 routeId\n ) external view returns (address) {\n // determine the address of the socket contract.\n return _getContractAddress(routeId);\n }\n\n //those two functions are getting called by the socket Contract\n function getImplementation()\n external\n view\n returns (address implementation)\n {\n return _implementations[msg.sender];\n }\n\n function _disableRoute(uint256 routeId) internal returns (address) {\n // assign the initialization code for the socket contract.\n bytes memory initCode = (\n hex\"5860208158601c335a63aaf10f428752fa158151803b80938091923cf3\"\n );\n\n // determine the address of the socket contract.\n address routeContractAddress = _getContractAddress(routeId);\n\n if (!isRouteDeployed[routeId]) {\n revert CannotBeDisabled();\n }\n\n if (isDisabled[routeId]) {\n revert AlreadyDisabled();\n }\n\n isDisabled[routeId] = true;\n\n //first we deploy the code we want to deploy on a separate address\n // store the implementation to be retrieved by the socket contract.\n _implementations[routeContractAddress] = disabledRouteAddress;\n address addr;\n assembly {\n let encoded_data := add(0x20, initCode) // load initialization code.\n let encoded_size := mload(initCode) // load init code's length.\n addr := create2(0, encoded_data, encoded_size, routeId) // routeId is used as salt.\n }\n require(\n addr == routeContractAddress,\n \"Failed to deploy the new socket contract.\"\n );\n emit Deployed(addr);\n return addr;\n }\n\n function _destroy(uint256 routeId) internal {\n // determine the address of the socket contract.\n address routeContractAddress = _getContractAddress(routeId);\n\n if (!isRouteDeployed[routeId]) {\n revert NothingToDestroy();\n }\n ISocketBridgeBase(routeContractAddress).killme();\n emit Destroyed(routeContractAddress);\n }\n\n /**\n * @dev Internal view function for calculating a socket contract address\n * given a particular routeId.\n */\n function _getContractAddress(\n uint256 routeId\n ) internal view returns (address) {\n // determine the address of the socket contract.\n\n bytes memory initCode = (\n hex\"5860208158601c335a63aaf10f428752fa158151803b80938091923cf3\"\n );\n return\n address(\n uint160( // downcast to match the address type.\n uint256( // convert to uint to truncate upper digits.\n keccak256( // compute the CREATE2 hash using 4 inputs.\n abi.encodePacked( // pack all inputs to the hash together.\n hex\"ff\", // start with 0xff to distinguish from RLP.\n address(this), // this contract will be the caller.\n routeId, // the routeId is used as salt.\n keccak256(abi.encodePacked(initCode)) // the init code hash.\n )\n )\n )\n )\n );\n }\n\n /**\n * @notice Rescues the ERC20 token to an address\n this is a restricted function to be called by only socketGatewayOwner\n * @dev as this is a restricted to socketGatewayOwner, ensure the userAddress is a known address\n * @param token address of the ERC20 token being rescued\n * @param userAddress address to which ERC20 is to be rescued\n * @param amount amount of ERC20 tokens being rescued\n */\n function rescueFunds(\n address token,\n address userAddress,\n uint256 amount\n ) external onlyOwner {\n ERC20(token).safeTransfer(userAddress, amount);\n }\n\n /**\n * @notice Rescues the native balance to an address\n this is a restricted function to be called by only socketGatewayOwner\n * @dev as this is a restricted to socketGatewayOwner, ensure the userAddress is a known address\n * @param userAddress address to which native-balance is to be rescued\n * @param amount amount of native-balance being rescued\n */\n function rescueEther(\n address payable userAddress,\n uint256 amount\n ) external onlyOwner {\n userAddress.transfer(amount);\n }\n}\n"},"src/interfaces/ISocketController.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\n/**\n * @title ISocketController\n * @notice Interface for SocketController functions.\n * @dev functions can be added here for invocation from external contracts or off-chain\n * only restriction is that this should have functions to manage controllers\n * @author Socket dot tech.\n */\ninterface ISocketController {\n /**\n * @notice Add controller to the socketGateway\n This is a restricted function to be called by only socketGatewayOwner\n * @dev ensure controllerAddress is a verified controller implementation address\n * @param _controllerAddress The address of controller implementation contract deployed\n * @return Id of the controller added to the controllers-mapping in socketGateway storage\n */\n function addController(\n address _controllerAddress\n ) external returns (uint32);\n\n /**\n * @notice disable controller by setting ZeroAddress to the entry in controllers-mapping\n identified by controllerId as key.\n This is a restricted function to be called by only socketGatewayOwner\n * @param _controllerId The Id of controller-implementation in the controllers mapping\n */\n function disableController(uint32 _controllerId) external;\n\n /**\n * @notice Get controllerImplementation address mapped to the controllerId\n * @param _controllerId controllerId is the key in the mapping for controllers\n * @return controller-implementation address\n */\n function getController(uint32 _controllerId) external returns (address);\n}\n"},"lib/solmate/src/utils/SafeTransferLib.sol":{"content":"// SPDX-License-Identifier: AGPL-3.0-only\npragma solidity >=0.8.0;\n\nimport {ERC20} from \"../tokens/ERC20.sol\";\n\n/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.\n/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)\n/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.\n/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.\nlibrary SafeTransferLib {\n /*//////////////////////////////////////////////////////////////\n ETH OPERATIONS\n //////////////////////////////////////////////////////////////*/\n\n function safeTransferETH(address to, uint256 amount) internal {\n bool success;\n\n /// @solidity memory-safe-assembly\n assembly {\n // Transfer the ETH and store if it succeeded or not.\n success := call(gas(), to, amount, 0, 0, 0, 0)\n }\n\n require(success, \"ETH_TRANSFER_FAILED\");\n }\n\n /*//////////////////////////////////////////////////////////////\n ERC20 OPERATIONS\n //////////////////////////////////////////////////////////////*/\n\n function safeTransferFrom(\n ERC20 token,\n address from,\n address to,\n uint256 amount\n ) internal {\n bool success;\n\n /// @solidity memory-safe-assembly\n assembly {\n // Get a pointer to some free memory.\n let freeMemoryPointer := mload(0x40)\n\n // Write the abi-encoded calldata into memory, beginning with the function selector.\n mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)\n mstore(add(freeMemoryPointer, 4), from) // Append the \"from\" argument.\n mstore(add(freeMemoryPointer, 36), to) // Append the \"to\" argument.\n mstore(add(freeMemoryPointer, 68), amount) // Append the \"amount\" argument.\n\n success := and(\n // Set success to whether the call reverted, if not we check it either\n // returned exactly 1 (can't just be non-zero data), or had no return data.\n or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),\n // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.\n // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.\n // Counterintuitively, this call must be positioned second to the or() call in the\n // surrounding and() call or else returndatasize() will be zero during the computation.\n call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)\n )\n }\n\n require(success, \"TRANSFER_FROM_FAILED\");\n }\n\n function safeTransfer(\n ERC20 token,\n address to,\n uint256 amount\n ) internal {\n bool success;\n\n /// @solidity memory-safe-assembly\n assembly {\n // Get a pointer to some free memory.\n let freeMemoryPointer := mload(0x40)\n\n // Write the abi-encoded calldata into memory, beginning with the function selector.\n mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)\n mstore(add(freeMemoryPointer, 4), to) // Append the \"to\" argument.\n mstore(add(freeMemoryPointer, 36), amount) // Append the \"amount\" argument.\n\n success := and(\n // Set success to whether the call reverted, if not we check it either\n // returned exactly 1 (can't just be non-zero data), or had no return data.\n or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),\n // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.\n // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.\n // Counterintuitively, this call must be positioned second to the or() call in the\n // surrounding and() call or else returndatasize() will be zero during the computation.\n call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)\n )\n }\n\n require(success, \"TRANSFER_FAILED\");\n }\n\n function safeApprove(\n ERC20 token,\n address to,\n uint256 amount\n ) internal {\n bool success;\n\n /// @solidity memory-safe-assembly\n assembly {\n // Get a pointer to some free memory.\n let freeMemoryPointer := mload(0x40)\n\n // Write the abi-encoded calldata into memory, beginning with the function selector.\n mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)\n mstore(add(freeMemoryPointer, 4), to) // Append the \"to\" argument.\n mstore(add(freeMemoryPointer, 36), amount) // Append the \"amount\" argument.\n\n success := and(\n // Set success to whether the call reverted, if not we check it either\n // returned exactly 1 (can't just be non-zero data), or had no return data.\n or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),\n // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.\n // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.\n // Counterintuitively, this call must be positioned second to the or() call in the\n // surrounding and() call or else returndatasize() will be zero during the computation.\n call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)\n )\n }\n\n require(success, \"APPROVE_FAILED\");\n }\n}\n"},"src/controllers/BaseController.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {ISocketRequest} from \"../interfaces/ISocketRequest.sol\";\nimport {ISocketRoute} from \"../interfaces/ISocketRoute.sol\";\n\n/// @title BaseController Controller\n/// @notice Base contract for all controller contracts\nabstract contract BaseController {\n /// @notice Address used to identify if it is a native token transfer or not\n address public immutable NATIVE_TOKEN_ADDRESS =\n address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);\n\n /// @notice Address used to identify if it is a Zero address\n address public immutable NULL_ADDRESS = address(0);\n\n /// @notice FunctionSelector used to delegatecall from swap to the function of bridge router implementation\n bytes4 public immutable BRIDGE_AFTER_SWAP_SELECTOR =\n bytes4(keccak256(\"bridgeAfterSwap(uint256,bytes)\"));\n\n /// @notice immutable variable to store the socketGateway address\n address public immutable socketGatewayAddress;\n\n /// @notice immutable variable with instance of SocketRoute to access route functions\n ISocketRoute public immutable socketRoute;\n\n /**\n * @notice Construct the base for all controllers.\n * @param _socketGatewayAddress Socketgateway address, an immutable variable to set.\n * @notice initialize the immutable variables of SocketRoute, SocketGateway\n */\n constructor(address _socketGatewayAddress) {\n socketGatewayAddress = _socketGatewayAddress;\n socketRoute = ISocketRoute(_socketGatewayAddress);\n }\n\n /**\n * @notice Construct the base for all BridgeImplementations.\n * @param routeId routeId mapped to the routrImplementation\n * @param data transactionData generated with arguments of bridgeRequest (offchain or by caller)\n * @return returns the bytes response of the route execution (bridging, refuel or swap executions)\n */\n function _executeRoute(\n uint32 routeId,\n bytes memory data\n ) internal returns (bytes memory) {\n (bool success, bytes memory result) = socketRoute\n .getRoute(routeId)\n .delegatecall(data);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n return result;\n }\n}\n"},"src/interfaces/ISocketRoute.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\n/**\n * @title ISocketRoute\n * @notice Interface for routeManagement functions in SocketGateway.\n * @author Socket dot tech.\n */\ninterface ISocketRoute {\n /**\n * @notice Add route to the socketGateway\n This is a restricted function to be called by only socketGatewayOwner\n * @dev ensure routeAddress is a verified bridge or middleware implementation address\n * @param routeAddress The address of bridge or middleware implementation contract deployed\n * @return Id of the route added to the routes-mapping in socketGateway storage\n */\n function addRoute(address routeAddress) external returns (uint256);\n\n /**\n * @notice disable a route by setting ZeroAddress to the entry in routes-mapping\n identified by routeId as key.\n This is a restricted function to be called by only socketGatewayOwner\n * @param routeId The Id of route-implementation in the routes mapping\n */\n function disableRoute(uint32 routeId) external;\n\n /**\n * @notice Get routeImplementation address mapped to the routeId\n * @param routeId routeId is the key in the mapping for routes\n * @return route-implementation address\n */\n function getRoute(uint32 routeId) external view returns (address);\n}\n"},"src/SocketGatewayDeployment.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\npragma experimental ABIEncoderV2;\n\nimport \"./utils/Ownable.sol\";\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {LibUtil} from \"./libraries/LibUtil.sol\";\nimport \"./libraries/LibBytes.sol\";\nimport {ISocketRoute} from \"./interfaces/ISocketRoute.sol\";\nimport {ISocketRequest} from \"./interfaces/ISocketRequest.sol\";\nimport {ISocketGateway} from \"./interfaces/ISocketGateway.sol\";\nimport {IncorrectBridgeRatios, ZeroAddressNotAllowed, ArrayLengthMismatch} from \"./errors/SocketErrors.sol\";\n\n/// @title SocketGatewayContract\n/// @notice Socketgateway is a contract with entrypoint functions for all interactions with socket liquidity layer\n/// @author Socket Team\ncontract SocketGateway is Ownable {\n using LibBytes for bytes;\n using LibBytes for bytes4;\n using SafeTransferLib for ERC20;\n\n /// @notice FunctionSelector used to delegatecall from swap to the function of bridge router implementation\n bytes4 public immutable BRIDGE_AFTER_SWAP_SELECTOR =\n bytes4(keccak256(\"bridgeAfterSwap(uint256,bytes)\"));\n\n /// @notice storage variable to keep track of total number of routes registered in socketgateway\n uint32 public routesCount = 385;\n\n /// @notice storage variable to keep track of total number of controllers registered in socketgateway\n uint32 public controllerCount;\n\n address public immutable disabledRouteAddress;\n\n uint256 public constant CENT_PERCENT = 100e18;\n\n /// @notice storage mapping for route implementation addresses\n mapping(uint32 => address) public routes;\n\n /// storage mapping for controller implemenation addresses\n mapping(uint32 => address) public controllers;\n\n // Events ------------------------------------------------------------------------------------------------------->\n\n /// @notice Event emitted when a router is added to socketgateway\n event NewRouteAdded(uint32 indexed routeId, address indexed route);\n\n /// @notice Event emitted when a route is disabled\n event RouteDisabled(uint32 indexed routeId);\n\n /// @notice Event emitted when ownership transfer is requested by socket-gateway-owner\n event OwnershipTransferRequested(\n address indexed _from,\n address indexed _to\n );\n\n /// @notice Event emitted when a controller is added to socketgateway\n event ControllerAdded(\n uint32 indexed controllerId,\n address indexed controllerAddress\n );\n\n /// @notice Event emitted when a controller is disabled\n event ControllerDisabled(uint32 indexed controllerId);\n\n constructor(address _owner, address _disabledRoute) Ownable(_owner) {\n disabledRouteAddress = _disabledRoute;\n }\n\n // Able to receive ether\n // solhint-disable-next-line no-empty-blocks\n receive() external payable {}\n\n /*******************************************\n * EXTERNAL AND PUBLIC FUNCTIONS *\n *******************************************/\n\n /**\n * @notice executes functions in the routes identified using routeId and functionSelectorData\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped\n * @dev ensure the data in routeData to be built using the function-selector defined as a\n * constant in the route implementation contract\n * @param routeId route identifier\n * @param routeData functionSelectorData generated using the function-selector defined in the route Implementation\n */\n function executeRoute(\n uint32 routeId,\n bytes calldata routeData\n ) external payable returns (bytes memory) {\n (bool success, bytes memory result) = addressAt(routeId).delegatecall(\n routeData\n );\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n return result;\n }\n\n /**\n * @notice swaps a token on sourceChain and split it across multiple bridge-recipients\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being swapped\n * @dev ensure the swap-data and bridge-data is generated using the function-selector defined as a constant in the implementation address\n * @param swapMultiBridgeRequest request\n */\n function swapAndMultiBridge(\n ISocketRequest.SwapMultiBridgeRequest calldata swapMultiBridgeRequest\n ) external payable {\n uint256 requestLength = swapMultiBridgeRequest.bridgeRouteIds.length;\n\n if (\n requestLength != swapMultiBridgeRequest.bridgeImplDataItems.length\n ) {\n revert ArrayLengthMismatch();\n }\n uint256 ratioAggregate;\n for (uint256 index = 0; index < requestLength; ) {\n ratioAggregate += swapMultiBridgeRequest.bridgeRatios[index];\n }\n\n if (ratioAggregate != CENT_PERCENT) {\n revert IncorrectBridgeRatios();\n }\n\n (bool swapSuccess, bytes memory swapResult) = addressAt(\n swapMultiBridgeRequest.swapRouteId\n ).delegatecall(swapMultiBridgeRequest.swapImplData);\n\n if (!swapSuccess) {\n assembly {\n revert(add(swapResult, 32), mload(swapResult))\n }\n }\n\n uint256 amountReceivedFromSwap = abi.decode(swapResult, (uint256));\n\n uint256 bridgedAmount;\n\n for (uint256 index = 0; index < requestLength; ) {\n uint256 bridgingAmount;\n\n // if it is the last bridge request, bridge the remaining amount\n if (index == requestLength - 1) {\n bridgingAmount = amountReceivedFromSwap - bridgedAmount;\n } else {\n // bridging amount is the multiplication of bridgeRatio and amountReceivedFromSwap\n bridgingAmount =\n (amountReceivedFromSwap *\n swapMultiBridgeRequest.bridgeRatios[index]) /\n (CENT_PERCENT);\n }\n\n // update the bridged amount, this would be used for computation for last bridgeRequest\n bridgedAmount += bridgingAmount;\n\n bytes memory bridgeImpldata = abi.encodeWithSelector(\n BRIDGE_AFTER_SWAP_SELECTOR,\n bridgingAmount,\n swapMultiBridgeRequest.bridgeImplDataItems[index]\n );\n\n (bool bridgeSuccess, bytes memory bridgeResult) = addressAt(\n swapMultiBridgeRequest.bridgeRouteIds[index]\n ).delegatecall(bridgeImpldata);\n\n if (!bridgeSuccess) {\n assembly {\n revert(add(bridgeResult, 32), mload(bridgeResult))\n }\n }\n\n unchecked {\n ++index;\n }\n }\n }\n\n /**\n * @notice sequentially executes functions in the routes identified using routeId and functionSelectorData\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped\n * @dev ensure the data in each dataItem to be built using the function-selector defined as a\n * constant in the route implementation contract\n * @param routeIds a list of route identifiers\n * @param dataItems a list of functionSelectorData generated using the function-selector defined in the route Implementation\n */\n function executeRoutes(\n uint32[] calldata routeIds,\n bytes[] calldata dataItems\n ) external payable {\n uint256 routeIdslength = routeIds.length;\n if (routeIdslength != dataItems.length) revert ArrayLengthMismatch();\n for (uint256 index = 0; index < routeIdslength; ) {\n (bool success, bytes memory result) = addressAt(routeIds[index])\n .delegatecall(dataItems[index]);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n unchecked {\n ++index;\n }\n }\n }\n\n /**\n * @notice execute a controller function identified using the controllerId in the request\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped\n * @dev ensure the data in request to be built using the function-selector defined as a\n * constant in the controller implementation contract\n * @param socketControllerRequest socketControllerRequest with controllerId to identify the\n * controllerAddress and byteData constructed using functionSelector\n * of the function being invoked\n * @return bytes data received from the call delegated to controller\n */\n function executeController(\n ISocketGateway.SocketControllerRequest calldata socketControllerRequest\n ) external payable returns (bytes memory) {\n (bool success, bytes memory result) = controllers[\n socketControllerRequest.controllerId\n ].delegatecall(socketControllerRequest.data);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n return result;\n }\n\n /**\n * @notice sequentially executes all controller requests\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped\n * @dev ensure the data in each controller-request to be built using the function-selector defined as a\n * constant in the controller implementation contract\n * @param controllerRequests a list of socketControllerRequest\n * Each controllerRequest contains controllerId to identify the controllerAddress and\n * byteData constructed using functionSelector of the function being invoked\n */\n function executeControllers(\n ISocketGateway.SocketControllerRequest[] calldata controllerRequests\n ) external payable {\n for (uint32 index = 0; index < controllerRequests.length; ) {\n (bool success, bytes memory result) = controllers[\n controllerRequests[index].controllerId\n ].delegatecall(controllerRequests[index].data);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n unchecked {\n ++index;\n }\n }\n }\n\n /**************************************\n * ADMIN FUNCTIONS *\n **************************************/\n\n /**\n * @notice Add route to the socketGateway\n This is a restricted function to be called by only socketGatewayOwner\n * @dev ensure routeAddress is a verified bridge or middleware implementation address\n * @param routeAddress The address of bridge or middleware implementation contract deployed\n * @return Id of the route added to the routes-mapping in socketGateway storage\n */\n function addRoute(\n address routeAddress\n ) external onlyOwner returns (uint32) {\n uint32 routeId = routesCount;\n routes[routeId] = routeAddress;\n\n routesCount += 1;\n\n emit NewRouteAdded(routeId, routeAddress);\n\n return routeId;\n }\n\n /**\n * @notice Give Infinite or 0 approval to bridgeRoute for the tokenAddress\n This is a restricted function to be called by only socketGatewayOwner\n */\n\n function setApprovalForRouters(\n address[] memory routeAddresses,\n address[] memory tokenAddresses,\n bool isMax\n ) external onlyOwner {\n for (uint32 index = 0; index < routeAddresses.length; ) {\n ERC20(tokenAddresses[index]).approve(\n routeAddresses[index],\n isMax ? type(uint256).max : 0\n );\n unchecked {\n ++index;\n }\n }\n }\n\n /**\n * @notice Add controller to the socketGateway\n This is a restricted function to be called by only socketGatewayOwner\n * @dev ensure controllerAddress is a verified controller implementation address\n * @param controllerAddress The address of controller implementation contract deployed\n * @return Id of the controller added to the controllers-mapping in socketGateway storage\n */\n function addController(\n address controllerAddress\n ) external onlyOwner returns (uint32) {\n uint32 controllerId = controllerCount;\n\n controllers[controllerId] = controllerAddress;\n\n controllerCount += 1;\n\n emit ControllerAdded(controllerId, controllerAddress);\n\n return controllerId;\n }\n\n /**\n * @notice disable controller by setting ZeroAddress to the entry in controllers-mapping\n identified by controllerId as key.\n This is a restricted function to be called by only socketGatewayOwner\n * @param controllerId The Id of controller-implementation in the controllers mapping\n */\n function disableController(uint32 controllerId) public onlyOwner {\n controllers[controllerId] = disabledRouteAddress;\n emit ControllerDisabled(controllerId);\n }\n\n /**\n * @notice disable a route by setting ZeroAddress to the entry in routes-mapping\n identified by routeId as key.\n This is a restricted function to be called by only socketGatewayOwner\n * @param routeId The Id of route-implementation in the routes mapping\n */\n function disableRoute(uint32 routeId) external onlyOwner {\n routes[routeId] = disabledRouteAddress;\n emit RouteDisabled(routeId);\n }\n\n /*******************************************\n * RESTRICTED RESCUE FUNCTIONS *\n *******************************************/\n\n /**\n * @notice Rescues the ERC20 token to an address\n this is a restricted function to be called by only socketGatewayOwner\n * @dev as this is a restricted to socketGatewayOwner, ensure the userAddress is a known address\n * @param token address of the ERC20 token being rescued\n * @param userAddress address to which ERC20 is to be rescued\n * @param amount amount of ERC20 tokens being rescued\n */\n function rescueFunds(\n address token,\n address userAddress,\n uint256 amount\n ) external onlyOwner {\n ERC20(token).safeTransfer(userAddress, amount);\n }\n\n /**\n * @notice Rescues the native balance to an address\n this is a restricted function to be called by only socketGatewayOwner\n * @dev as this is a restricted to socketGatewayOwner, ensure the userAddress is a known address\n * @param userAddress address to which native-balance is to be rescued\n * @param amount amount of native-balance being rescued\n */\n function rescueEther(\n address payable userAddress,\n uint256 amount\n ) external onlyOwner {\n userAddress.transfer(amount);\n }\n\n /*******************************************\n * VIEW FUNCTIONS *\n *******************************************/\n\n /**\n * @notice Get routeImplementation address mapped to the routeId\n * @param routeId routeId is the key in the mapping for routes\n * @return route-implementation address\n */\n function getRoute(uint32 routeId) public view returns (address) {\n return addressAt(routeId);\n }\n\n /**\n * @notice Get controllerImplementation address mapped to the controllerId\n * @param controllerId controllerId is the key in the mapping for controllers\n * @return controller-implementation address\n */\n function getController(uint32 controllerId) public view returns (address) {\n return controllers[controllerId];\n }\n\n function addressAt(uint32 routeId) public view returns (address) {\n if (routeId < 385) {\n if (routeId < 257) {\n if (routeId < 129) {\n if (routeId < 65) {\n if (routeId < 33) {\n if (routeId < 17) {\n if (routeId < 9) {\n if (routeId < 5) {\n if (routeId < 3) {\n if (routeId == 1) {\n return\n 0x8cd6BaCDAe46B449E2e5B34e348A4eD459c84D50;\n } else {\n return\n 0x31524750Cd865fF6A3540f232754Fb974c18585C;\n }\n } else {\n if (routeId == 3) {\n return\n 0xEd9b37342BeC8f3a2D7b000732ec87498aA6EC6a;\n } else {\n return\n 0xE8704Ef6211F8988Ccbb11badC89841808d66890;\n }\n }\n } else {\n if (routeId < 7) {\n if (routeId == 5) {\n return\n 0x9aFF58C460a461578C433e11C4108D1c4cF77761;\n } else {\n return\n 0x2D1733886cFd465B0B99F1492F40847495f334C5;\n }\n } else {\n if (routeId == 7) {\n return\n 0x715497Be4D130F04B8442F0A1F7a9312D4e54FC4;\n } else {\n return\n 0x90C8a40c38E633B5B0e0d0585b9F7FA05462CaaF;\n }\n }\n }\n } else {\n if (routeId < 13) {\n if (routeId < 11) {\n if (routeId == 9) {\n return\n 0xa402b70FCfF3F4a8422B93Ef58E895021eAdE4F6;\n } else {\n return\n 0xc1B718522E15CD42C4Ac385a929fc2B51f5B892e;\n }\n } else {\n if (routeId == 11) {\n return\n 0xa97bf2f7c26C43c010c349F52f5eA5dC49B2DD38;\n } else {\n return\n 0x969423d71b62C81d2f28d707364c9Dc4a0764c53;\n }\n }\n } else {\n if (routeId < 15) {\n if (routeId == 13) {\n return\n 0xF86729934C083fbEc8C796068A1fC60701Ea1207;\n } else {\n return\n 0xD7cC2571F5823caCA26A42690D2BE7803DD5393f;\n }\n } else {\n if (routeId == 15) {\n return\n 0x7c8837a279bbbf7d8B93413763176de9F65d5bB9;\n } else {\n return\n 0x13b81C27B588C07D04458ed7dDbdbD26D1e39bcc;\n }\n }\n }\n }\n } else {\n if (routeId < 25) {\n if (routeId < 21) {\n if (routeId < 19) {\n if (routeId == 17) {\n return\n 0x52560Ac678aFA1345D15474287d16Dc1eA3F78aE;\n } else {\n return\n 0x1E31e376551459667cd7643440c1b21CE69065A0;\n }\n } else {\n if (routeId == 19) {\n return\n 0xc57D822CB3288e7b97EF8f8af0EcdcD1B783529B;\n } else {\n return\n 0x2197A1D9Af24b4d6a64Bff95B4c29Fcd3Ff28C30;\n }\n }\n } else {\n if (routeId < 23) {\n if (routeId == 21) {\n return\n 0xE3700feAa5100041Bf6b7AdBA1f72f647809Fd00;\n } else {\n return\n 0xc02E8a0Fdabf0EeFCEA025163d90B5621E2b9948;\n }\n } else {\n if (routeId == 23) {\n return\n 0xF5144235E2926cAb3c69b30113254Fa632f72d62;\n } else {\n return\n 0xBa3F92313B00A1f7Bc53b2c24EB195c8b2F57682;\n }\n }\n }\n } else {\n if (routeId < 29) {\n if (routeId < 27) {\n if (routeId == 25) {\n return\n 0x77a6856fe1fFA5bEB55A1d2ED86E27C7c482CB76;\n } else {\n return\n 0x4826Ff4e01E44b1FCEFBfb38cd96687Eb7786b44;\n }\n } else {\n if (routeId == 27) {\n return\n 0x55FF3f5493cf5e80E76DEA7E327b9Cd8440Af646;\n } else {\n return\n 0xF430Db544bE9770503BE4aa51997aA19bBd5BA4f;\n }\n }\n } else {\n if (routeId < 31) {\n if (routeId == 29) {\n return\n 0x0f166446ce1484EE3B0663E7E67DF10F5D240115;\n } else {\n return\n 0x6365095D92537f242Db5EdFDd572745E72aC33d9;\n }\n } else {\n if (routeId == 31) {\n return\n 0x5c7BC93f06ce3eAe75ADf55E10e23d2c1dE5Bc65;\n } else {\n return\n 0xe46383bAD90d7A08197ccF08972e9DCdccCE9BA4;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 49) {\n if (routeId < 41) {\n if (routeId < 37) {\n if (routeId < 35) {\n if (routeId == 33) {\n return\n 0xf0f21710c071E3B728bdc4654c3c0b873aAaa308;\n } else {\n return\n 0x63Bc9ed3AcAAeB0332531C9fB03b0a2352E9Ff25;\n }\n } else {\n if (routeId == 35) {\n return\n 0xd1CE808625CB4007a1708824AE82CdB0ece57De9;\n } else {\n return\n 0x57BbB148112f4ba224841c3FE018884171004661;\n }\n }\n } else {\n if (routeId < 39) {\n if (routeId == 37) {\n return\n 0x037f7d6933036F34DFabd40Ff8e4D789069f92e3;\n } else {\n return\n 0xeF978c280915CfF3Dca4EDfa8932469e40ADA1e1;\n }\n } else {\n if (routeId == 39) {\n return\n 0x92ee9e071B13f7ecFD62B7DED404A16CBc223CD3;\n } else {\n return\n 0x94Ae539c186e41ed762271338Edf140414D1E442;\n }\n }\n }\n } else {\n if (routeId < 45) {\n if (routeId < 43) {\n if (routeId == 41) {\n return\n 0x30A64BBe4DdBD43dA2368EFd1eB2d80C10d84DAb;\n } else {\n return\n 0x3aEABf81c1Dc4c1b73d5B2a95410f126426FB596;\n }\n } else {\n if (routeId == 43) {\n return\n 0x25b08aB3D0C8ea4cC9d967b79688C6D98f3f563a;\n } else {\n return\n 0xea40cB15C9A3BBd27af6474483886F7c0c9AE406;\n }\n }\n } else {\n if (routeId < 47) {\n if (routeId == 45) {\n return\n 0x9580113Cc04e5a0a03359686304EF3A80b936Dd3;\n } else {\n return\n 0xD211c826d568957F3b66a3F4d9c5f68cCc66E619;\n }\n } else {\n if (routeId == 47) {\n return\n 0xCEE24D0635c4C56315d133b031984d4A6f509476;\n } else {\n return\n 0x3922e6B987983229798e7A20095EC372744d4D4c;\n }\n }\n }\n }\n } else {\n if (routeId < 57) {\n if (routeId < 53) {\n if (routeId < 51) {\n if (routeId == 49) {\n return\n 0x2d92D03413d296e1F31450479349757187F2a2b7;\n } else {\n return\n 0x0fe5308eE90FC78F45c89dB6053eA859097860CA;\n }\n } else {\n if (routeId == 51) {\n return\n 0x08Ba68e067C0505bAF0C1311E0cFB2B1B59b969c;\n } else {\n return\n 0x9bee5DdDF75C24897374f92A534B7A6f24e97f4a;\n }\n }\n } else {\n if (routeId < 55) {\n if (routeId == 53) {\n return\n 0x1FC5A90B232208704B930c1edf82FFC6ACc02734;\n } else {\n return\n 0x5b1B0417cb44c761C2a23ee435d011F0214b3C85;\n }\n } else {\n if (routeId == 55) {\n return\n 0x9d70cDaCA12A738C283020760f449D7816D592ec;\n } else {\n return\n 0x95a23b9CB830EcCFDDD5dF56A4ec665e3381Fa12;\n }\n }\n }\n } else {\n if (routeId < 61) {\n if (routeId < 59) {\n if (routeId == 57) {\n return\n 0x483a957Cf1251c20e096C35c8399721D1200A3Fc;\n } else {\n return\n 0xb4AD39Cb293b0Ec7FEDa743442769A7FF04987CD;\n }\n } else {\n if (routeId == 59) {\n return\n 0x4C543AD78c1590D81BAe09Fc5B6Df4132A2461d0;\n } else {\n return\n 0x471d5E5195c563902781734cfe1FF3981F8B6c86;\n }\n }\n } else {\n if (routeId < 63) {\n if (routeId == 61) {\n return\n 0x1B12a54B5E606D95B8B8D123c9Cb09221Ee37584;\n } else {\n return\n 0xE4127cC550baC433646a7D998775a84daC16c7f3;\n }\n } else {\n if (routeId == 63) {\n return\n 0xecb1b55AB12E7dd788D585c6C5cD61B5F87be836;\n } else {\n return\n 0xf91ef487C5A1579f70601b6D347e19756092eEBf;\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 97) {\n if (routeId < 81) {\n if (routeId < 73) {\n if (routeId < 69) {\n if (routeId < 67) {\n if (routeId == 65) {\n return\n 0x34a16a7e9BADEEFD4f056310cbE0b1423Fa1b760;\n } else {\n return\n 0x60E10E80c7680f429dBbC232830BEcd3D623c4CF;\n }\n } else {\n if (routeId == 67) {\n return\n 0x66465285B8D65362A1d86CE00fE2bE949Fd6debF;\n } else {\n return\n 0x5aB231B7e1A3A74a48f67Ab7bde5Cdd4267022E0;\n }\n }\n } else {\n if (routeId < 71) {\n if (routeId == 69) {\n return\n 0x3A1C3633eE79d43366F5c67802a746aFD6b162Ba;\n } else {\n return\n 0x0C4BfCbA8dC3C811437521a80E81e41DAF479039;\n }\n } else {\n if (routeId == 71) {\n return\n 0x6caf25d2e139C5431a1FA526EAf8d73ff2e6252C;\n } else {\n return\n 0x74ad21e09FDa68638CE14A3009A79B6D16574257;\n }\n }\n }\n } else {\n if (routeId < 77) {\n if (routeId < 75) {\n if (routeId == 73) {\n return\n 0xD4923A61008894b99cc1CD3407eF9524f02aA0Ca;\n } else {\n return\n 0x6F159b5EB823BD415886b9271aA2A723a00a1987;\n }\n } else {\n if (routeId == 75) {\n return\n 0x742a8aA42E7bfB4554dE30f4Fb07FFb6f2068863;\n } else {\n return\n 0x4AE9702d3360400E47B446e76DE063ACAb930101;\n }\n }\n } else {\n if (routeId < 79) {\n if (routeId == 77) {\n return\n 0x0E19a0a44ddA7dAD854ec5Cc867d16869c4E80F4;\n } else {\n return\n 0xE021A51968f25148F726E326C88d2556c5647557;\n }\n } else {\n if (routeId == 79) {\n return\n 0x64287BDDDaeF4d94E4599a3D882bed29E6Ada4B6;\n } else {\n return\n 0xcBB57Fd2e19cc7e9D444d5b4325A2F1047d0C73f;\n }\n }\n }\n }\n } else {\n if (routeId < 89) {\n if (routeId < 85) {\n if (routeId < 83) {\n if (routeId == 81) {\n return\n 0x373DE80DF7D82cFF6D76F29581b360C56331e957;\n } else {\n return\n 0x0466356E131AD61596a51F86BAd1C03A328960D8;\n }\n } else {\n if (routeId == 83) {\n return\n 0x01726B960992f1b74311b248E2a922fC707d43A6;\n } else {\n return\n 0x2E21bdf9A4509b89795BCE7E132f248a75814CEc;\n }\n }\n } else {\n if (routeId < 87) {\n if (routeId == 85) {\n return\n 0x769512b23aEfF842379091d3B6E4B5456F631D42;\n } else {\n return\n 0xe7eD9be946a74Ec19325D39C6EEb57887ccB2B0D;\n }\n } else {\n if (routeId == 87) {\n return\n 0xc4D01Ec357c2b511d10c15e6b6974380F0E62e67;\n } else {\n return\n 0x5bC49CC9dD77bECF2fd3A3C55611e84E69AFa3AE;\n }\n }\n }\n } else {\n if (routeId < 93) {\n if (routeId < 91) {\n if (routeId == 89) {\n return\n 0x48bcD879954fA14e7DbdAeb56F79C1e9DDcb69ec;\n } else {\n return\n 0xE929bDde21b462572FcAA4de6F49B9D3246688D0;\n }\n } else {\n if (routeId == 91) {\n return\n 0x85Aae300438222f0e3A9Bc870267a5633A9438bd;\n } else {\n return\n 0x51f72E1096a81C55cd142d66d39B688C657f9Be8;\n }\n }\n } else {\n if (routeId < 95) {\n if (routeId == 93) {\n return\n 0x3A8a05BF68ac54B01E6C0f492abF97465F3d15f9;\n } else {\n return\n 0x145aA67133F0c2C36b9771e92e0B7655f0D59040;\n }\n } else {\n if (routeId == 95) {\n return\n 0xa030315d7DB11F9892758C9e7092D841e0ADC618;\n } else {\n return\n 0xdF1f8d81a3734bdDdEfaC6Ca1596E081e57c3044;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 113) {\n if (routeId < 105) {\n if (routeId < 101) {\n if (routeId < 99) {\n if (routeId == 97) {\n return\n 0xFF2833123B58aa05d04D7fb99f5FB768B2b435F8;\n } else {\n return\n 0xc8f09c1fD751C570233765f71b0e280d74e6e743;\n }\n } else {\n if (routeId == 99) {\n return\n 0x3026DA6Ceca2E5A57A05153653D9212FFAaA49d8;\n } else {\n return\n 0xdE68Ee703dE0D11f67B0cE5891cB4a903de6D160;\n }\n }\n } else {\n if (routeId < 103) {\n if (routeId == 101) {\n return\n 0xE23a7730e81FB4E87A6D0bd9f63EE77ac86C3DA4;\n } else {\n return\n 0x8b1DBe04aD76a7d8bC079cACd3ED4D99B897F4a0;\n }\n } else {\n if (routeId == 103) {\n return\n 0xBB227240FA459b69C6889B2b8cb1BE76F118061f;\n } else {\n return\n 0xC062b9b3f0dB28BB8afAfcD4d075729344114ffe;\n }\n }\n }\n } else {\n if (routeId < 109) {\n if (routeId < 107) {\n if (routeId == 105) {\n return\n 0x553188Aa45f5FDB83EC4Ca485982F8fC082480D1;\n } else {\n return\n 0x0109d83D746EaCb6d4014953D9E12d6ca85e330b;\n }\n } else {\n if (routeId == 107) {\n return\n 0x45B1bEd29812F5bf6711074ACD180B2aeB783AD9;\n } else {\n return\n 0xdA06eC8c19aea31D77F60299678Cba40E743e1aD;\n }\n }\n } else {\n if (routeId < 111) {\n if (routeId == 109) {\n return\n 0x3cC5235c97d975a9b4FD4501B3446c981ea3D855;\n } else {\n return\n 0xa1827267d6Bd989Ff38580aE3d9deff6Acf19163;\n }\n } else {\n if (routeId == 111) {\n return\n 0x3663CAA0433A3D4171b3581Cf2410702840A735A;\n } else {\n return\n 0x7575D0a7614F655BA77C74a72a43bbd4fA6246a3;\n }\n }\n }\n }\n } else {\n if (routeId < 121) {\n if (routeId < 117) {\n if (routeId < 115) {\n if (routeId == 113) {\n return\n 0x2516Defc18bc07089c5dAFf5eafD7B0EF64611E2;\n } else {\n return\n 0xfec5FF08E20fbc107a97Af2D38BD0025b84ee233;\n }\n } else {\n if (routeId == 115) {\n return\n 0x0FB5763a87242B25243e23D73f55945fE787523A;\n } else {\n return\n 0xe4C00db89678dBf8391f430C578Ca857Dd98aDE1;\n }\n }\n } else {\n if (routeId < 119) {\n if (routeId == 117) {\n return\n 0x8F2A22061F9F35E64f14523dC1A5f8159e6a21B7;\n } else {\n return\n 0x18e4b838ae966917E20E9c9c5Ad359cDD38303bB;\n }\n } else {\n if (routeId == 119) {\n return\n 0x61ACb1d3Dcb3e3429832A164Cc0fC9849fb75A4a;\n } else {\n return\n 0x7681e3c8e7A41DCA55C257cc0d1Ae757f5530E65;\n }\n }\n }\n } else {\n if (routeId < 125) {\n if (routeId < 123) {\n if (routeId == 121) {\n return\n 0x806a2AB9748C3D1DB976550890E3f528B7E8Faec;\n } else {\n return\n 0xBDb8A5DD52C2c239fbC31E9d43B763B0197028FF;\n }\n } else {\n if (routeId == 123) {\n return\n 0x474EC9203706010B9978D6bD0b105D36755e4848;\n } else {\n return\n 0x8dfd0D829b303F2239212E591a0F92a32880f36E;\n }\n }\n } else {\n if (routeId < 127) {\n if (routeId == 125) {\n return\n 0xad4BcE9745860B1adD6F1Bd34a916f050E4c82C2;\n } else {\n return\n 0xBC701115b9fe14bC8CC5934cdC92517173e308C4;\n }\n } else {\n if (routeId == 127) {\n return\n 0x0D1918d786Db8546a11aDeD475C98370E06f255E;\n } else {\n return\n 0xee44f57cD6936DB55B99163f3Df367B01EdA785a;\n }\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 193) {\n if (routeId < 161) {\n if (routeId < 145) {\n if (routeId < 137) {\n if (routeId < 133) {\n if (routeId < 131) {\n if (routeId == 129) {\n return\n 0x63044521fe5a1e488D7eD419cD0e35b7C24F2aa7;\n } else {\n return\n 0x410085E73BD85e90d97b84A68C125aDB9F91f85b;\n }\n } else {\n if (routeId == 131) {\n return\n 0x7913fe97E07C7A397Ec274Ab1d4E2622C88EC5D1;\n } else {\n return\n 0x977f9fE93c064DCf54157406DaABC3a722e8184C;\n }\n }\n } else {\n if (routeId < 135) {\n if (routeId == 133) {\n return\n 0xCD2236468722057cFbbABad2db3DEA9c20d5B01B;\n } else {\n return\n 0x17c7287A491cf5Ff81E2678cF2BfAE4333F6108c;\n }\n } else {\n if (routeId == 135) {\n return\n 0x354D9a5Dbf96c71B79a265F03B595C6Fdc04dadd;\n } else {\n return\n 0xb4e409EB8e775eeFEb0344f9eee884cc7ed21c69;\n }\n }\n }\n } else {\n if (routeId < 141) {\n if (routeId < 139) {\n if (routeId == 137) {\n return\n 0xa1a3c4670Ad69D9be4ab2D39D1231FEC2a63b519;\n } else {\n return\n 0x4589A22199870729C1be5CD62EE93BeD858113E6;\n }\n } else {\n if (routeId == 139) {\n return\n 0x8E7b864dB26Bd6C798C38d4Ba36EbA0d6602cF11;\n } else {\n return\n 0xA2D17C7260a4CB7b9854e89Fc367E80E87872a2d;\n }\n }\n } else {\n if (routeId < 143) {\n if (routeId == 141) {\n return\n 0xC7F0EDf0A1288627b0432304918A75e9084CBD46;\n } else {\n return\n 0xE4B4EF1f9A4aBFEdB371fA7a6143993B15d4df25;\n }\n } else {\n if (routeId == 143) {\n return\n 0xfe3D84A2Ef306FEBb5452441C9BDBb6521666F6A;\n } else {\n return\n 0x8A12B6C64121920110aE58F7cd67DfEc21c6a4C3;\n }\n }\n }\n }\n } else {\n if (routeId < 153) {\n if (routeId < 149) {\n if (routeId < 147) {\n if (routeId == 145) {\n return\n 0x76c4d9aFC4717a2BAac4e5f26CccF02351f7a3DA;\n } else {\n return\n 0xd4719BA550E397aeAcca1Ad2201c1ba69024FAAf;\n }\n } else {\n if (routeId == 147) {\n return\n 0x9646126Ce025224d1682C227d915a386efc0A1Fb;\n } else {\n return\n 0x4DD8Af2E3F2044842f0247920Bc4BABb636915ea;\n }\n }\n } else {\n if (routeId < 151) {\n if (routeId == 149) {\n return\n 0x8e8a327183Af0cf8C2ece9F0ed547C42A160D409;\n } else {\n return\n 0x9D49614CaE1C685C71678CA6d8CDF7584bfd0740;\n }\n } else {\n if (routeId == 151) {\n return\n 0x5a00ef257394cbc31828d48655E3d39e9c11c93d;\n } else {\n return\n 0xC9a2751b38d3dDD161A41Ca0135C5C6c09EC1d56;\n }\n }\n }\n } else {\n if (routeId < 157) {\n if (routeId < 155) {\n if (routeId == 153) {\n return\n 0x7e1c261640a525C94Ca4f8c25b48CF754DD83590;\n } else {\n return\n 0x409Fe24ba6F6BD5aF31C1aAf8059b986A3158233;\n }\n } else {\n if (routeId == 155) {\n return\n 0x704Cf5BFDADc0f55fDBb53B6ed8B582E018A72A2;\n } else {\n return\n 0x3982bF65d7d6E77E3b6661cd6F6468c247512737;\n }\n }\n } else {\n if (routeId < 159) {\n if (routeId == 157) {\n return\n 0x3982b9f26FFD67a13Ee371e2C0a9Da338BA70E7f;\n } else {\n return\n 0x6D834AB385900c1f49055D098e90264077FbC4f2;\n }\n } else {\n if (routeId == 159) {\n return\n 0x11FE5F70779A094B7166B391e1Fb73d422eF4e4d;\n } else {\n return\n 0xD347e4E47280d21F13B73D89c6d16f867D50DD13;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 177) {\n if (routeId < 169) {\n if (routeId < 165) {\n if (routeId < 163) {\n if (routeId == 161) {\n return\n 0xb6035eDD53DDA28d8B69b4ae9836E40C80306CD7;\n } else {\n return\n 0x54c884e6f5C7CcfeCA990396c520C858c922b6CA;\n }\n } else {\n if (routeId == 163) {\n return\n 0x5eA93E240b083d686558Ed607BC013d88057cE46;\n } else {\n return\n 0x4C7131eE812De685cBe4e2cCb033d46ecD46612E;\n }\n }\n } else {\n if (routeId < 167) {\n if (routeId == 165) {\n return\n 0xc1a5Be9F0c33D8483801D702111068669f81fF91;\n } else {\n return\n 0x9E5fAb91455Be5E5b2C05967E73F456c8118B1Fc;\n }\n } else {\n if (routeId == 167) {\n return\n 0x3d9A05927223E0DC2F382831770405885e22F0d8;\n } else {\n return\n 0x6303A011fB6063f5B1681cb5a9938EA278dc6128;\n }\n }\n }\n } else {\n if (routeId < 173) {\n if (routeId < 171) {\n if (routeId == 169) {\n return\n 0xe9c60795c90C66797e4c8E97511eA07CdAda32bE;\n } else {\n return\n 0xD56cC98e69A1e13815818b466a8aA6163d84234A;\n }\n } else {\n if (routeId == 171) {\n return\n 0x47EbB9D36a6e40895316cD894E4860D774E2c531;\n } else {\n return\n 0xA5EB293629410065d14a7B1663A67829b0618292;\n }\n }\n } else {\n if (routeId < 175) {\n if (routeId == 173) {\n return\n 0x1b3B4C8146F939cE00899db8B3ddeF0062b7E023;\n } else {\n return\n 0x257Bbc11653625EbfB6A8587eF4f4FBe49828EB3;\n }\n } else {\n if (routeId == 175) {\n return\n 0x44cc979C01b5bB1eAC21301E73C37200dFD06F59;\n } else {\n return\n 0x2972fDF43352225D82754C0174Ff853819D1ef2A;\n }\n }\n }\n }\n } else {\n if (routeId < 185) {\n if (routeId < 181) {\n if (routeId < 179) {\n if (routeId == 177) {\n return\n 0x3e54144f032648A04D62d79f7B4b93FF3aC2333b;\n } else {\n return\n 0x444016102dB8adbE73C3B6703a1ea7F2f75A510D;\n }\n } else {\n if (routeId == 179) {\n return\n 0xac079143f98a6eb744Fde34541ebF243DF5B5dED;\n } else {\n return\n 0xAe9010767Fb112d29d35CEdfba2b372Ad7A308d3;\n }\n }\n } else {\n if (routeId < 183) {\n if (routeId == 181) {\n return\n 0xfE0BCcF9cCC2265D5fB3450743f17DfE57aE1e56;\n } else {\n return\n 0x04ED8C0545716119437a45386B1d691C63234C7D;\n }\n } else {\n if (routeId == 183) {\n return\n 0x636c14013e531A286Bc4C848da34585f0bB73d59;\n } else {\n return\n 0x2Fa67fc7ECC5cAA01C653d3BFeA98ecc5db9C42A;\n }\n }\n }\n } else {\n if (routeId < 189) {\n if (routeId < 187) {\n if (routeId == 185) {\n return\n 0x23e9a0FC180818aA872D2079a985217017E97bd9;\n } else {\n return\n 0x79A95c3Ef81b3ae64ee03A9D5f73e570495F164E;\n }\n } else {\n if (routeId == 187) {\n return\n 0xa7EA0E88F04a84ba0ad1E396cb07Fa3fDAD7dF6D;\n } else {\n return\n 0xd23cA1278a2B01a3C0Ca1a00d104b11c1Ebe6f42;\n }\n }\n } else {\n if (routeId < 191) {\n if (routeId == 189) {\n return\n 0x707bc4a9FA2E349AED5df4e9f5440C15aA9D14Bd;\n } else {\n return\n 0x7E290F2dd539Ac6CE58d8B4C2B944931a1fD3612;\n }\n } else {\n if (routeId == 191) {\n return\n 0x707AA5503088Ce06Ba450B6470A506122eA5c8eF;\n } else {\n return\n 0xFbB3f7BF680deeb149f4E7BC30eA3DDfa68F3C3f;\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 225) {\n if (routeId < 209) {\n if (routeId < 201) {\n if (routeId < 197) {\n if (routeId < 195) {\n if (routeId == 193) {\n return\n 0xDE74aD8cCC3dbF14992f49Cf24f36855912f4934;\n } else {\n return\n 0x409BA83df7777F070b2B50a10a41DE2468d2a3B3;\n }\n } else {\n if (routeId == 195) {\n return\n 0x5CB7Be90A5DD7CfDa54e87626e254FE8C18255B4;\n } else {\n return\n 0x0A684fE12BC64fb72B59d0771a566F49BC090356;\n }\n }\n } else {\n if (routeId < 199) {\n if (routeId == 197) {\n return\n 0xDf30048d91F8FA2bCfC54952B92bFA8e161D3360;\n } else {\n return\n 0x050825Fff032a547C47061CF0696FDB0f65AEa5D;\n }\n } else {\n if (routeId == 199) {\n return\n 0xd55e671dAC1f03d366d8535073ada5DB2Aab1Ea2;\n } else {\n return\n 0x9470C704A9616c8Cd41c595Fcd2181B6fe2183C2;\n }\n }\n }\n } else {\n if (routeId < 205) {\n if (routeId < 203) {\n if (routeId == 201) {\n return\n 0x2D9ffD275181F5865d5e11CbB4ced1521C4dF9f1;\n } else {\n return\n 0x816d28Dec10ec95DF5334f884dE85cA6215918d8;\n }\n } else {\n if (routeId == 203) {\n return\n 0xd1f87267c4A43835E666dd69Df077e578A3b6299;\n } else {\n return\n 0x39E89Bde9DACbe5468C025dE371FbDa12bDeBAB1;\n }\n }\n } else {\n if (routeId < 207) {\n if (routeId == 205) {\n return\n 0x7b40A3207956ecad6686E61EfcaC48912FcD0658;\n } else {\n return\n 0x090cF10D793B1Efba9c7D76115878814B663859A;\n }\n } else {\n if (routeId == 207) {\n return\n 0x312A59c06E41327878F2063eD0e9c282C1DA3AfC;\n } else {\n return\n 0x4F1188f46236DD6B5de11Ebf2a9fF08716E7DeB6;\n }\n }\n }\n }\n } else {\n if (routeId < 217) {\n if (routeId < 213) {\n if (routeId < 211) {\n if (routeId == 209) {\n return\n 0x0A6F9a3f4fA49909bBfb4339cbE12B42F53BbBeD;\n } else {\n return\n 0x01d13d7aCaCbB955B81935c80ffF31e14BdFa71f;\n }\n } else {\n if (routeId == 211) {\n return\n 0x691a14Fa6C7360422EC56dF5876f84d4eDD7f00A;\n } else {\n return\n 0x97Aad18d886d181a9c726B3B6aE15a0A69F5aF73;\n }\n }\n } else {\n if (routeId < 215) {\n if (routeId == 213) {\n return\n 0x2917241371D2099049Fa29432DC46735baEC33b4;\n } else {\n return\n 0x5F20F20F7890c2e383E29D4147C9695A371165f5;\n }\n } else {\n if (routeId == 215) {\n return\n 0xeC0a60e639958335662C5219A320cCEbb56C6077;\n } else {\n return\n 0x96d63CF5062975C09845d17ec672E10255866053;\n }\n }\n }\n } else {\n if (routeId < 221) {\n if (routeId < 219) {\n if (routeId == 217) {\n return\n 0xFF57429e57D383939CAB50f09ABBfB63C0e6c9AD;\n } else {\n return\n 0x18E393A7c8578fb1e235C242076E50013cDdD0d7;\n }\n } else {\n if (routeId == 219) {\n return\n 0xE7E5238AF5d61f52E9B4ACC025F713d1C0216507;\n } else {\n return\n 0x428401D4d0F25A2EE1DA4d5366cB96Ded425D9bD;\n }\n }\n } else {\n if (routeId < 223) {\n if (routeId == 221) {\n return\n 0x42E5733551ff1Ee5B48Aa9fc2B61Af9b58C812E6;\n } else {\n return\n 0x64Df9c7A0551B056d860Bc2419Ca4c1EF75320bE;\n }\n } else {\n if (routeId == 223) {\n return\n 0x46006925506145611bBf0263243D8627dAf26B0F;\n } else {\n return\n 0x8D64BE884314662804eAaB884531f5C50F4d500c;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 241) {\n if (routeId < 233) {\n if (routeId < 229) {\n if (routeId < 227) {\n if (routeId == 225) {\n return\n 0x157a62D92D07B5ce221A5429645a03bBaCE85373;\n } else {\n return\n 0xaF037D33e1F1F2F87309B425fe8a9d895Ef3722B;\n }\n } else {\n if (routeId == 227) {\n return\n 0x921D1154E494A2f7218a37ad7B17701f94b4B40e;\n } else {\n return\n 0xF282b4555186d8Dea51B8b3F947E1E0568d09bc4;\n }\n }\n } else {\n if (routeId < 231) {\n if (routeId == 229) {\n return\n 0xa794E2E1869765a4600b3DFd8a4ebcF16350f6B6;\n } else {\n return\n 0xFEFb048e20c5652F7940A49B1980E0125Ec4D358;\n }\n } else {\n if (routeId == 231) {\n return\n 0x220104b641971e9b25612a8F001bf48AbB23f1cF;\n } else {\n return\n 0xcB9D373Bb54A501B35dd3be5bF4Ba43cA31F7035;\n }\n }\n }\n } else {\n if (routeId < 237) {\n if (routeId < 235) {\n if (routeId == 233) {\n return\n 0x37D627F56e3FF36aC316372109ea82E03ac97DAc;\n } else {\n return\n 0x4E81355FfB4A271B4EA59ff78da2b61c7833161f;\n }\n } else {\n if (routeId == 235) {\n return\n 0xADd8D65cAF6Cc9ad73127B49E16eA7ac29d91e87;\n } else {\n return\n 0x630F9b95626487dfEAe3C97A44DB6C59cF35d996;\n }\n }\n } else {\n if (routeId < 239) {\n if (routeId == 237) {\n return\n 0x78CE2BC8238B679680A67FCB98C5A60E4ec17b2D;\n } else {\n return\n 0xA38D776028eD1310b9A6b086f67F788201762E21;\n }\n } else {\n if (routeId == 239) {\n return\n 0x7Bb5178827B76B86753Ed62a0d662c72cEcb1bD3;\n } else {\n return\n 0x4faC26f61C76eC5c3D43b43eDfAFF0736Ae0e3da;\n }\n }\n }\n }\n } else {\n if (routeId < 249) {\n if (routeId < 245) {\n if (routeId < 243) {\n if (routeId == 241) {\n return\n 0x791Bb49bfFA7129D6889FDB27744422Ac4571A85;\n } else {\n return\n 0x26766fFEbb5fa564777913A6f101dF019AB32afa;\n }\n } else {\n if (routeId == 243) {\n return\n 0x05e98E5e95b4ECBbbAf3258c3999Cc81ed8048Be;\n } else {\n return\n 0xC5c4621e52f1D6A1825A5ed4F95855401a3D9C6b;\n }\n }\n } else {\n if (routeId < 247) {\n if (routeId == 245) {\n return\n 0xfcb15f909BA7FC7Ea083503Fb4c1020203c107EB;\n } else {\n return\n 0xbD27603279d969c74f2486ad14E71080829DFd38;\n }\n } else {\n if (routeId == 247) {\n return\n 0xff2f756BcEcC1A55BFc09a30cc5F64720458cFCB;\n } else {\n return\n 0x3bfB968FEbC12F4e8420B2d016EfcE1E615f7246;\n }\n }\n }\n } else {\n if (routeId < 253) {\n if (routeId < 251) {\n if (routeId == 249) {\n return\n 0x982EE9Ffe23051A2ec945ed676D864fa8345222b;\n } else {\n return\n 0xe101899100785E74767d454FFF0131277BaD48d9;\n }\n } else {\n if (routeId == 251) {\n return\n 0x4F730C0c6b3B5B7d06ca511379f4Aa5BfB2E9525;\n } else {\n return\n 0x5499c36b365795e4e0Ef671aF6C2ce26D7c78265;\n }\n }\n } else {\n if (routeId < 255) {\n if (routeId == 253) {\n return\n 0x8AF51F7237Fc8fB2fc3E700488a94a0aC6Ad8b5a;\n } else {\n return\n 0xda8716df61213c0b143F2849785FB85928084857;\n }\n } else {\n if (routeId == 255) {\n return\n 0xF040Cf9b1ebD11Bf28e04e80740DF3DDe717e4f5;\n } else {\n return\n 0xB87ba32f759D14023C7520366B844dF7f0F036C2;\n }\n }\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 321) {\n if (routeId < 289) {\n if (routeId < 273) {\n if (routeId < 265) {\n if (routeId < 261) {\n if (routeId < 259) {\n if (routeId == 257) {\n return\n 0x0Edde681b8478F0c3194f468EdD2dB5e75c65CDD;\n } else {\n return\n 0x59C70900Fca06eE2aCE1BDd5A8D0Af0cc3BBA720;\n }\n } else {\n if (routeId == 259) {\n return\n 0x8041F0f180D17dD07087199632c45E17AeB0BAd5;\n } else {\n return\n 0x4fB4727064BA595995DD516b63b5921Df9B93aC6;\n }\n }\n } else {\n if (routeId < 263) {\n if (routeId == 261) {\n return\n 0x86e98b594565857eD098864F560915C0dAfd6Ea1;\n } else {\n return\n 0x70f8818E8B698EFfeCd86A513a4c87c0c380Bef6;\n }\n } else {\n if (routeId == 263) {\n return\n 0x78Ed227c8A897A21Da2875a752142dd80d865158;\n } else {\n return\n 0xd02A30BB5C3a8C51d2751A029a6fcfDE2Af9fbc6;\n }\n }\n }\n } else {\n if (routeId < 269) {\n if (routeId < 267) {\n if (routeId == 265) {\n return\n 0x0F00d5c5acb24e975e2a56730609f7F40aa763b8;\n } else {\n return\n 0xC3e2091edc2D3D9D98ba09269138b617B536834A;\n }\n } else {\n if (routeId == 267) {\n return\n 0xa6FbaF7F30867C9633908998ea8C3da28920E75C;\n } else {\n return\n 0xE6dDdcD41E2bBe8122AE32Ac29B8fbAB79CD21d9;\n }\n }\n } else {\n if (routeId < 271) {\n if (routeId == 269) {\n return\n 0x537aa8c1Ef6a8Eaf039dd6e1Eb67694a48195cE4;\n } else {\n return\n 0x96ABAC485fd2D0B03CF4a10df8BD58b8dED28300;\n }\n } else {\n if (routeId == 271) {\n return\n 0xda8e7D46d04Bd4F62705Cd80355BDB6d441DafFD;\n } else {\n return\n 0xbE50018E7a5c67E2e5f5414393e971CC96F293f2;\n }\n }\n }\n }\n } else {\n if (routeId < 281) {\n if (routeId < 277) {\n if (routeId < 275) {\n if (routeId == 273) {\n return\n 0xa1b3907D6CB542a4cbe2eE441EfFAA909FAb62C3;\n } else {\n return\n 0x6d08ee8511C0237a515013aC389e7B3968Cb1753;\n }\n } else {\n if (routeId == 275) {\n return\n 0x22faa5B5Fe43eAdbB52745e35a5cdA8bD5F96bbA;\n } else {\n return\n 0x7a673eB74D79e4868D689E7852abB5f93Ec2fD4b;\n }\n }\n } else {\n if (routeId < 279) {\n if (routeId == 277) {\n return\n 0x0b8531F8AFD4190b76F3e10deCaDb84c98b4d419;\n } else {\n return\n 0x78eABC743A93583DeE403D6b84795490e652216B;\n }\n } else {\n if (routeId == 279) {\n return\n 0x3A95D907b2a7a8604B59BccA08585F58Afe0Aa64;\n } else {\n return\n 0xf4271f0C8c9Af0F06A80b8832fa820ccE64FAda8;\n }\n }\n }\n } else {\n if (routeId < 285) {\n if (routeId < 283) {\n if (routeId == 281) {\n return\n 0x74b2DF841245C3748c0d31542e1335659a25C33b;\n } else {\n return\n 0xdFC99Fd0Ad7D16f30f295a5EEFcE029E04d0fa65;\n }\n } else {\n if (routeId == 283) {\n return\n 0xE992416b6aC1144eD8148a9632973257839027F6;\n } else {\n return\n 0x54ce55ba954E981BB1fd9399054B35Ce1f2C0816;\n }\n }\n } else {\n if (routeId < 287) {\n if (routeId == 285) {\n return\n 0xD4AB52f9e7E5B315Bd7471920baD04F405Ab1c38;\n } else {\n return\n 0x3670C990994d12837e95eE127fE2f06FD3E2104B;\n }\n } else {\n if (routeId == 287) {\n return\n 0xDcf190B09C47E4f551E30BBb79969c3FdEA1e992;\n } else {\n return\n 0xa65057B967B59677237e57Ab815B209744b9bc40;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 305) {\n if (routeId < 297) {\n if (routeId < 293) {\n if (routeId < 291) {\n if (routeId == 289) {\n return\n 0x6Efc86B40573e4C7F28659B13327D55ae955C483;\n } else {\n return\n 0x06BcC25CF8e0E72316F53631b3aA7134E9f73Ae0;\n }\n } else {\n if (routeId == 291) {\n return\n 0x710b6414E1D53882b1FCD3A168aD5Ccd435fc6D0;\n } else {\n return\n 0x5Ebb2C3d78c4e9818074559e7BaE7FCc99781DC1;\n }\n }\n } else {\n if (routeId < 295) {\n if (routeId == 293) {\n return\n 0xAf0a409c3AEe0bD08015cfb29D89E90b6e89A88F;\n } else {\n return\n 0x522559d8b99773C693B80cE06DF559036295Ce44;\n }\n } else {\n if (routeId == 295) {\n return\n 0xB65290A5Bae838aaa7825c9ECEC68041841a1B64;\n } else {\n return\n 0x801b8F2068edd5Bcb659E6BDa0c425909043C420;\n }\n }\n }\n } else {\n if (routeId < 301) {\n if (routeId < 299) {\n if (routeId == 297) {\n return\n 0x29b5F00515d093627E0B7bd0b5c8E84F6b4cDb87;\n } else {\n return\n 0x652839Ae74683cbF9f1293F1019D938F87464D3E;\n }\n } else {\n if (routeId == 299) {\n return\n 0x5Bc95dCebDDE9B79F2b6DC76121BC7936eF8D666;\n } else {\n return\n 0x90db359CEA62E53051158Ab5F99811C0a07Fe686;\n }\n }\n } else {\n if (routeId < 303) {\n if (routeId == 301) {\n return\n 0x2c3625EedadbDcDbB5330eb0d17b3C39ff269807;\n } else {\n return\n 0xC3f0324471b5c9d415acD625b8d8694a4e48e001;\n }\n } else {\n if (routeId == 303) {\n return\n 0x8C60e7E05fa0FfB6F720233736f245134685799d;\n } else {\n return\n 0x98fAF2c09aa4EBb995ad0B56152993E7291a500e;\n }\n }\n }\n }\n } else {\n if (routeId < 313) {\n if (routeId < 309) {\n if (routeId < 307) {\n if (routeId == 305) {\n return\n 0x802c1063a861414dFAEc16bacb81429FC0d40D6e;\n } else {\n return\n 0x11C4AeFCC0dC156f64195f6513CB1Fb3Be0Ae056;\n }\n } else {\n if (routeId == 307) {\n return\n 0xEff1F3258214E31B6B4F640b4389d55715C3Be2B;\n } else {\n return\n 0x47e379Abe8DDFEA4289aBa01235EFF7E93758fd7;\n }\n }\n } else {\n if (routeId < 311) {\n if (routeId == 309) {\n return\n 0x3CC26384c3eA31dDc8D9789e8872CeA6F20cD3ff;\n } else {\n return\n 0xEdd9EFa6c69108FAA4611097d643E20Ba0Ed1634;\n }\n } else {\n if (routeId == 311) {\n return\n 0xCb93525CA5f3D371F74F3D112bC19526740717B8;\n } else {\n return\n 0x7071E0124EB4438137e60dF1b8DD8Af1BfB362cF;\n }\n }\n }\n } else {\n if (routeId < 317) {\n if (routeId < 315) {\n if (routeId == 313) {\n return\n 0x4691096EB0b78C8F4b4A8091E5B66b18e1835c10;\n } else {\n return\n 0x8d953c9b2d1C2137CF95992079f3A77fCd793272;\n }\n } else {\n if (routeId == 315) {\n return\n 0xbdCc2A3Bf6e3Ba49ff86595e6b2b8D70d8368c92;\n } else {\n return\n 0x95E6948aB38c61b2D294E8Bd896BCc4cCC0713cf;\n }\n }\n } else {\n if (routeId < 319) {\n if (routeId == 317) {\n return\n 0x607b27C881fFEE4Cb95B1c5862FaE7224ccd0b4A;\n } else {\n return\n 0x09D28aFA166e566A2Ee1cB834ea8e78C7E627eD2;\n }\n } else {\n if (routeId == 319) {\n return\n 0x9c01449b38bDF0B263818401044Fb1401B29fDfA;\n } else {\n return\n 0x1F7723599bbB658c051F8A39bE2688388d22ceD6;\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 353) {\n if (routeId < 337) {\n if (routeId < 329) {\n if (routeId < 325) {\n if (routeId < 323) {\n if (routeId == 321) {\n return\n 0x52B71603f7b8A5d15B4482e965a0619aa3210194;\n } else {\n return\n 0x01c0f072CB210406653752FecFA70B42dA9173a2;\n }\n } else {\n if (routeId == 323) {\n return\n 0x3021142f021E943e57fc1886cAF58D06147D09A6;\n } else {\n return\n 0xe6f2AF38e76AB09Db59225d97d3E770942D3D842;\n }\n }\n } else {\n if (routeId < 327) {\n if (routeId == 325) {\n return\n 0x06a25554e5135F08b9e2eD1DEC1fc3CEd52e0B48;\n } else {\n return\n 0x71d75e670EE3511C8290C705E0620126B710BF8D;\n }\n } else {\n if (routeId == 327) {\n return\n 0x8b9cE142b80FeA7c932952EC533694b1DF9B3c54;\n } else {\n return\n 0xd7Be24f32f39231116B3fDc483C2A12E1521f73B;\n }\n }\n }\n } else {\n if (routeId < 333) {\n if (routeId < 331) {\n if (routeId == 329) {\n return\n 0xb40cafBC4797d4Ff64087E087F6D2e661f954CbE;\n } else {\n return\n 0xBdDCe7771EfEe81893e838f62204A4c76D72757e;\n }\n } else {\n if (routeId == 331) {\n return\n 0x5d3D299EA7Fd4F39AcDb336E26631Dfee41F9287;\n } else {\n return\n 0x6BfEE09E1Fc0684e0826A9A0dC1352a14B136FAC;\n }\n }\n } else {\n if (routeId < 335) {\n if (routeId == 333) {\n return\n 0xd0001bB8E2Cb661436093f96458a4358B5156E3c;\n } else {\n return\n 0x1867c6485CfD1eD448988368A22bfB17a7747293;\n }\n } else {\n if (routeId == 335) {\n return\n 0x8997EF9F95dF24aB67703AB6C262aABfeEBE33bD;\n } else {\n return\n 0x1e39E9E601922deD91BCFc8F78836302133465e2;\n }\n }\n }\n }\n } else {\n if (routeId < 345) {\n if (routeId < 341) {\n if (routeId < 339) {\n if (routeId == 337) {\n return\n 0x8A8ec6CeacFf502a782216774E5AF3421562C6ff;\n } else {\n return\n 0x3B8FC561df5415c8DC01e97Ee6E38435A8F9C40A;\n }\n } else {\n if (routeId == 339) {\n return\n 0xD5d5f5B37E67c43ceA663aEDADFFc3a93a2065B0;\n } else {\n return\n 0xCC8F55EC43B4f25013CE1946FBB740c43Be5B96D;\n }\n }\n } else {\n if (routeId < 343) {\n if (routeId == 341) {\n return\n 0x18f586E816eEeDbb57B8011239150367561B58Fb;\n } else {\n return\n 0xd0CD802B19c1a52501cb2f07d656e3Cd7B0Ce124;\n }\n } else {\n if (routeId == 343) {\n return\n 0xe0AeD899b39C6e4f2d83e4913a1e9e0cf6368abE;\n } else {\n return\n 0x0606e1b6c0f1A398C38825DCcc4678a7Cbc2737c;\n }\n }\n }\n } else {\n if (routeId < 349) {\n if (routeId < 347) {\n if (routeId == 345) {\n return\n 0x2d188e85b27d18EF80f16686EA1593ABF7Ed2A63;\n } else {\n return\n 0x64412292fA4A135a3300E24366E99ff59Db2eAc1;\n }\n } else {\n if (routeId == 347) {\n return\n 0x38b74c173f3733E8b90aAEf0e98B89791266149F;\n } else {\n return\n 0x36DAA49A79aaEF4E7a217A11530D3cCD84414124;\n }\n }\n } else {\n if (routeId < 351) {\n if (routeId == 349) {\n return\n 0x10f088FE2C88F90270E4449c46c8B1b232511d58;\n } else {\n return\n 0x4FeDbd25B58586838ABD17D10272697dF1dC3087;\n }\n } else {\n if (routeId == 351) {\n return\n 0x685278209248CB058E5cEe93e37f274A80Faf6eb;\n } else {\n return\n 0xDd9F8F1eeC3955f78168e2Fb2d1e808fa8A8f15b;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 369) {\n if (routeId < 361) {\n if (routeId < 357) {\n if (routeId < 355) {\n if (routeId == 353) {\n return\n 0x7392aEeFD5825aaC28817031dEEBbFaAA20983D9;\n } else {\n return\n 0x0Cc182555E00767D6FB8AD161A10d0C04C476d91;\n }\n } else {\n if (routeId == 355) {\n return\n 0x90E52837d56715c79FD592E8D58bFD20365798b2;\n } else {\n return\n 0x6F4451DE14049B6770ad5BF4013118529e68A40C;\n }\n }\n } else {\n if (routeId < 359) {\n if (routeId == 357) {\n return\n 0x89B97ef2aFAb9ed9c7f0FDb095d02E6840b52d9c;\n } else {\n return\n 0x92A5cC5C42d94d3e23aeB1214fFf43Db2B97759E;\n }\n } else {\n if (routeId == 359) {\n return\n 0x63ddc52F135A1dcBA831EAaC11C63849F018b739;\n } else {\n return\n 0x692A691533B571C2c54C1D7F8043A204b3d8120E;\n }\n }\n }\n } else {\n if (routeId < 365) {\n if (routeId < 363) {\n if (routeId == 361) {\n return\n 0x97c7492CF083969F61C6f302d45c8270391b921c;\n } else {\n return\n 0xDeFD2B8643553dAd19548eB14fd94A57F4B9e543;\n }\n } else {\n if (routeId == 363) {\n return\n 0x30645C04205cA3f670B67b02F971B088930ACB8C;\n } else {\n return\n 0xA6f80ed2d607Cd67aEB4109B64A0BEcc4D7d03CF;\n }\n }\n } else {\n if (routeId < 367) {\n if (routeId == 365) {\n return\n 0xBbbbC6c276eB3F7E674f2D39301509236001c42f;\n } else {\n return\n 0xC20E77d349FB40CE88eB01824e2873ad9f681f3C;\n }\n } else {\n if (routeId == 367) {\n return\n 0x5fCfD9a962De19294467C358C1FA55082285960b;\n } else {\n return\n 0x4D87BD6a0E4E5cc6332923cb3E85fC71b287F58A;\n }\n }\n }\n }\n } else {\n if (routeId < 377) {\n if (routeId < 373) {\n if (routeId < 371) {\n if (routeId == 369) {\n return\n 0x3AA5B757cd6Dde98214E56D57Dde7fcF0F7aB04E;\n } else {\n return\n 0xe28eFCE7192e11a2297f44059113C1fD6967b2d4;\n }\n } else {\n if (routeId == 371) {\n return\n 0x3251cAE10a1Cf246e0808D76ACC26F7B5edA0eE5;\n } else {\n return\n 0xbA2091cc9357Cf4c4F25D64F30d1b4Ba3A5a174B;\n }\n }\n } else {\n if (routeId < 375) {\n if (routeId == 373) {\n return\n 0x49c8e1Da9693692096F63C82D11b52d738566d55;\n } else {\n return\n 0xA0731615aB5FFF451031E9551367A4F7dB27b39c;\n }\n } else {\n if (routeId == 375) {\n return\n 0xFb214541888671AE1403CecC1D59763a12fc1609;\n } else {\n return\n 0x1D6bCB17642E2336405df73dF22F07688cAec020;\n }\n }\n }\n } else {\n if (routeId < 381) {\n if (routeId < 379) {\n if (routeId == 377) {\n return\n 0xfC9c0C7bfe187120fF7f4E21446161794A617a9e;\n } else {\n return\n 0xBa5bF37678EeE2dAB17AEf9D898153258252250E;\n }\n } else {\n if (routeId == 379) {\n return\n 0x7c55690bd2C9961576A32c02f8EB29ed36415Ec7;\n } else {\n return\n 0xcA40073E868E8Bc611aEc8Fe741D17E68Fe422f6;\n }\n }\n } else {\n if (routeId < 383) {\n if (routeId == 381) {\n return\n 0x31641bAFb87E9A58f78835050a7BE56921986339;\n } else {\n return\n 0xA54766424f6dA74b45EbCc5Bf0Bd1D74D2CCcaAB;\n }\n } else {\n if (routeId == 383) {\n return\n 0xc7bBa57F8C179EDDBaa62117ddA360e28f3F8252;\n } else {\n return\n 0x5e663ED97ea77d393B8858C90d0683bF180E0ffd;\n }\n }\n }\n }\n }\n }\n }\n }\n }\n\n if (routes[routeId] == address(0)) revert ZeroAddressNotAllowed();\n return routes[routeId];\n }\n\n /// @notice fallback function to handle swap, bridge execution\n /// @dev ensure routeId is converted to bytes4 and sent as msg.sig in the transaction\n fallback() external payable {\n address routeAddress = addressAt(uint32(msg.sig));\n\n bytes memory result;\n\n assembly {\n // copy function selector and any arguments\n calldatacopy(0, 4, sub(calldatasize(), 4))\n // execute function call using the facet\n result := delegatecall(\n gas(),\n routeAddress,\n 0,\n sub(calldatasize(), 4),\n 0,\n 0\n )\n // get any return value\n returndatacopy(0, 0, returndatasize())\n // return any return value or error back to the caller\n switch result\n case 0 {\n revert(0, returndatasize())\n }\n default {\n return(0, returndatasize())\n }\n }\n }\n}\n"},"src/bridges/hop/interfaces/IHopL1Bridge.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\n/**\n * @title L1Bridge Hop Interface\n * @notice L1 Hop Bridge, Used to transfer from L1 to L2s.\n */\ninterface IHopL1Bridge {\n /**\n * @notice `amountOutMin` and `deadline` should be 0 when no swap is intended at the destination.\n * @notice `amount` is the total amount the user wants to send including the relayer fee\n * @dev Send tokens to a supported layer-2 to mint hToken and optionally swap the hToken in the\n * AMM at the destination.\n * @param chainId The chainId of the destination chain\n * @param recipient The address receiving funds at the destination\n * @param amount The amount being sent\n * @param amountOutMin The minimum amount received after attempting to swap in the destination\n * AMM market. 0 if no swap is intended.\n * @param deadline The deadline for swapping in the destination AMM market. 0 if no\n * swap is intended.\n * @param relayer The address of the relayer at the destination.\n * @param relayerFee The amount distributed to the relayer at the destination. This is subtracted from the `amount`.\n */\n function sendToL2(\n uint256 chainId,\n address recipient,\n uint256 amount,\n uint256 amountOutMin,\n uint256 deadline,\n address relayer,\n uint256 relayerFee\n ) external payable;\n}\n"},"src/bridges/refuel/interfaces/refuel.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity >=0.8.0;\n\n/// @notice interface with functions to interact with Refuel contract\ninterface IRefuel {\n /**\n * @notice function to deposit nativeToken to Destination-address on destinationChain\n * @param destinationChainId chainId of the Destination chain\n * @param _to recipient address\n */\n function depositNativeToken(\n uint256 destinationChainId,\n address _to\n ) external payable;\n}\n"},"src/bridges/stargate/interfaces/stargate.sol":{"content":"// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity >=0.8.0;\n\n/**\n * @title IBridgeStargate Interface Contract.\n * @notice Interface used by Stargate-L1 and L2 Router implementations\n * @dev router and routerETH addresses will be distinct for L1 and L2\n */\ninterface IBridgeStargate {\n // @notice Struct to hold the additional-data for bridging ERC20 token\n struct lzTxObj {\n // gas limit to bridge the token in Stargate to destinationChain\n uint256 dstGasForCall;\n // destination nativeAmount, this is always set as 0\n uint256 dstNativeAmount;\n // destination nativeAddress, this is always set as 0x\n bytes dstNativeAddr;\n }\n\n /// @notice function in stargate bridge which is used to bridge ERC20 tokens to recipient on destinationChain\n function swap(\n uint16 _dstChainId,\n uint256 _srcPoolId,\n uint256 _dstPoolId,\n address payable _refundAddress,\n uint256 _amountLD,\n uint256 _minAmountLD,\n lzTxObj memory _lzTxParams,\n bytes calldata _to,\n bytes calldata _payload\n ) external payable;\n\n /// @notice function in stargate bridge which is used to bridge native tokens to recipient on destinationChain\n function swapETH(\n uint16 _dstChainId, // destination Stargate chainId\n address payable _refundAddress, // refund additional messageFee to this address\n bytes calldata _toAddress, // the receiver of the destination ETH\n uint256 _amountLD, // the amount, in Local Decimals, to be swapped\n uint256 _minAmountLD // the minimum amount accepted out on destination\n ) external payable;\n}\n"},"src/controllers/FeesTakerController.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {BaseController} from \"./BaseController.sol\";\nimport {ISocketRequest} from \"../interfaces/ISocketRequest.sol\";\n\n/**\n * @title FeesTaker-Controller Implementation\n * @notice Controller with composed actions to deduct-fees followed by Refuel, Swap and Bridge\n * to be executed Sequentially and this is atomic\n * @author Socket dot tech.\n */\ncontract FeesTakerController is BaseController {\n using SafeTransferLib for ERC20;\n\n /// @notice event emitted upon fee-deduction to fees-taker address\n event SocketFeesDeducted(\n uint256 fees,\n address feesToken,\n address feesTaker\n );\n\n /// @notice Function-selector to invoke deduct-fees and swap token\n /// @dev This function selector is to be used while building transaction-data\n bytes4 public immutable FEES_TAKER_SWAP_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\"takeFeesAndSwap((address,address,uint256,uint32,bytes))\")\n );\n\n /// @notice Function-selector to invoke deduct-fees and bridge token\n /// @dev This function selector is to be used while building transaction-data\n bytes4 public immutable FEES_TAKER_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"takeFeesAndBridge((address,address,uint256,uint32,bytes))\"\n )\n );\n\n /// @notice Function-selector to invoke deduct-fees and bridge multiple tokens\n /// @dev This function selector is to be used while building transaction-data\n bytes4 public immutable FEES_TAKER_MULTI_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"takeFeesAndMultiBridge((address,address,uint256,uint32[],bytes[]))\"\n )\n );\n\n /// @notice Function-selector to invoke deduct-fees followed by swapping of a token and bridging the swapped bridge\n /// @dev This function selector is to be used while building transaction-data\n bytes4 public immutable FEES_TAKER_SWAP_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"takeFeeAndSwapAndBridge((address,address,uint256,uint32,bytes,uint32,bytes))\"\n )\n );\n\n /// @notice Function-selector to invoke deduct-fees refuel\n /// @notice followed by swapping of a token and bridging the swapped bridge\n /// @dev This function selector is to be used while building transaction-data\n bytes4 public immutable FEES_TAKER_REFUEL_SWAP_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"takeFeeAndRefuelAndSwapAndBridge((address,address,uint256,uint32,bytes,uint32,bytes,uint32,bytes))\"\n )\n );\n\n /// @notice socketGatewayAddress to be initialised via storage variable BaseController\n constructor(\n address _socketGatewayAddress\n ) BaseController(_socketGatewayAddress) {}\n\n /**\n * @notice function to deduct-fees to fees-taker address on source-chain and swap token\n * @dev ensure correct function selector is used to generate transaction-data for bridgeRequest\n * @param ftsRequest feesTakerSwapRequest object generated either off-chain or the calling contract using\n * the function-selector FEES_TAKER_SWAP_FUNCTION_SELECTOR\n * @return output bytes from the swap operation (last operation in the composed actions)\n */\n function takeFeesAndSwap(\n ISocketRequest.FeesTakerSwapRequest calldata ftsRequest\n ) external payable returns (bytes memory) {\n if (ftsRequest.feesToken == NATIVE_TOKEN_ADDRESS) {\n //transfer the native amount to the feeTakerAddress\n payable(ftsRequest.feesTakerAddress).transfer(\n ftsRequest.feesAmount\n );\n } else {\n //transfer feesAmount to feesTakerAddress\n ERC20(ftsRequest.feesToken).safeTransferFrom(\n msg.sender,\n ftsRequest.feesTakerAddress,\n ftsRequest.feesAmount\n );\n }\n\n emit SocketFeesDeducted(\n ftsRequest.feesAmount,\n ftsRequest.feesTakerAddress,\n ftsRequest.feesToken\n );\n\n //call bridge function (executeRoute for the swapRequestData)\n return _executeRoute(ftsRequest.routeId, ftsRequest.swapRequestData);\n }\n\n /**\n * @notice function to deduct-fees to fees-taker address on source-chain and bridge amount to destinationChain\n * @dev ensure correct function selector is used to generate transaction-data for bridgeRequest\n * @param ftbRequest feesTakerBridgeRequest object generated either off-chain or the calling contract using\n * the function-selector FEES_TAKER_BRIDGE_FUNCTION_SELECTOR\n * @return output bytes from the bridge operation (last operation in the composed actions)\n */\n function takeFeesAndBridge(\n ISocketRequest.FeesTakerBridgeRequest calldata ftbRequest\n ) external payable returns (bytes memory) {\n if (ftbRequest.feesToken == NATIVE_TOKEN_ADDRESS) {\n //transfer the native amount to the feeTakerAddress\n payable(ftbRequest.feesTakerAddress).transfer(\n ftbRequest.feesAmount\n );\n } else {\n //transfer feesAmount to feesTakerAddress\n ERC20(ftbRequest.feesToken).safeTransferFrom(\n msg.sender,\n ftbRequest.feesTakerAddress,\n ftbRequest.feesAmount\n );\n }\n\n emit SocketFeesDeducted(\n ftbRequest.feesAmount,\n ftbRequest.feesTakerAddress,\n ftbRequest.feesToken\n );\n\n //call bridge function (executeRoute for the bridgeData)\n return _executeRoute(ftbRequest.routeId, ftbRequest.bridgeRequestData);\n }\n\n /**\n * @notice function to deduct-fees to fees-taker address on source-chain and bridge amount to destinationChain\n * @notice multiple bridge-requests are to be generated and sequence and number of routeIds should match with the bridgeData array\n * @dev ensure correct function selector is used to generate transaction-data for bridgeRequest\n * @param ftmbRequest feesTakerMultiBridgeRequest object generated either off-chain or the calling contract using\n * the function-selector FEES_TAKER_MULTI_BRIDGE_FUNCTION_SELECTOR\n */\n function takeFeesAndMultiBridge(\n ISocketRequest.FeesTakerMultiBridgeRequest calldata ftmbRequest\n ) external payable {\n if (ftmbRequest.feesToken == NATIVE_TOKEN_ADDRESS) {\n //transfer the native amount to the feeTakerAddress\n payable(ftmbRequest.feesTakerAddress).transfer(\n ftmbRequest.feesAmount\n );\n } else {\n //transfer feesAmount to feesTakerAddress\n ERC20(ftmbRequest.feesToken).safeTransferFrom(\n msg.sender,\n ftmbRequest.feesTakerAddress,\n ftmbRequest.feesAmount\n );\n }\n\n emit SocketFeesDeducted(\n ftmbRequest.feesAmount,\n ftmbRequest.feesTakerAddress,\n ftmbRequest.feesToken\n );\n\n // multiple bridge-requests are to be generated and sequence and number of routeIds should match with the bridgeData array\n for (\n uint256 index = 0;\n index < ftmbRequest.bridgeRouteIds.length;\n ++index\n ) {\n //call bridge function (executeRoute for the bridgeData)\n _executeRoute(\n ftmbRequest.bridgeRouteIds[index],\n ftmbRequest.bridgeRequestDataItems[index]\n );\n }\n }\n\n /**\n * @notice function to deduct-fees to fees-taker address on source-chain followed by swap the amount on sourceChain followed by\n * bridging the swapped amount to destinationChain\n * @dev while generating implData for swap and bridgeRequests, ensure correct function selector is used\n * bridge action corresponds to the bridgeAfterSwap function of the bridgeImplementation\n * @param fsbRequest feesTakerSwapBridgeRequest object generated either off-chain or the calling contract using\n * the function-selector FEES_TAKER_SWAP_BRIDGE_FUNCTION_SELECTOR\n */\n function takeFeeAndSwapAndBridge(\n ISocketRequest.FeesTakerSwapBridgeRequest calldata fsbRequest\n ) external payable returns (bytes memory) {\n if (fsbRequest.feesToken == NATIVE_TOKEN_ADDRESS) {\n //transfer the native amount to the feeTakerAddress\n payable(fsbRequest.feesTakerAddress).transfer(\n fsbRequest.feesAmount\n );\n } else {\n //transfer feesAmount to feesTakerAddress\n ERC20(fsbRequest.feesToken).safeTransferFrom(\n msg.sender,\n fsbRequest.feesTakerAddress,\n fsbRequest.feesAmount\n );\n }\n\n emit SocketFeesDeducted(\n fsbRequest.feesAmount,\n fsbRequest.feesTakerAddress,\n fsbRequest.feesToken\n );\n\n // execute swap operation\n bytes memory swapResponseData = _executeRoute(\n fsbRequest.swapRouteId,\n fsbRequest.swapData\n );\n\n uint256 swapAmount = abi.decode(swapResponseData, (uint256));\n\n // swapped amount is to be bridged to the recipient on destinationChain\n bytes memory bridgeImpldata = abi.encodeWithSelector(\n BRIDGE_AFTER_SWAP_SELECTOR,\n swapAmount,\n fsbRequest.bridgeData\n );\n\n // execute bridge operation and return the byte-data from response of bridge operation\n return _executeRoute(fsbRequest.bridgeRouteId, bridgeImpldata);\n }\n\n /**\n * @notice function to deduct-fees to fees-taker address on source-chain followed by refuel followed by\n * swap the amount on sourceChain followed by bridging the swapped amount to destinationChain\n * @dev while generating implData for refuel, swap and bridge Requests, ensure correct function selector is used\n * bridge action corresponds to the bridgeAfterSwap function of the bridgeImplementation\n * @param frsbRequest feesTakerRefuelSwapBridgeRequest object generated either off-chain or the calling contract using\n * the function-selector FEES_TAKER_REFUEL_SWAP_BRIDGE_FUNCTION_SELECTOR\n */\n function takeFeeAndRefuelAndSwapAndBridge(\n ISocketRequest.FeesTakerRefuelSwapBridgeRequest calldata frsbRequest\n ) external payable returns (bytes memory) {\n if (frsbRequest.feesToken == NATIVE_TOKEN_ADDRESS) {\n //transfer the native amount to the feeTakerAddress\n payable(frsbRequest.feesTakerAddress).transfer(\n frsbRequest.feesAmount\n );\n } else {\n //transfer feesAmount to feesTakerAddress\n ERC20(frsbRequest.feesToken).safeTransferFrom(\n msg.sender,\n frsbRequest.feesTakerAddress,\n frsbRequest.feesAmount\n );\n }\n\n emit SocketFeesDeducted(\n frsbRequest.feesAmount,\n frsbRequest.feesTakerAddress,\n frsbRequest.feesToken\n );\n\n // refuel is also done via bridge execution via refuelRouteImplementation identified by refuelRouteId\n _executeRoute(frsbRequest.refuelRouteId, frsbRequest.refuelData);\n\n // execute swap operation\n bytes memory swapResponseData = _executeRoute(\n frsbRequest.swapRouteId,\n frsbRequest.swapData\n );\n\n uint256 swapAmount = abi.decode(swapResponseData, (uint256));\n\n // swapped amount is to be bridged to the recipient on destinationChain\n bytes memory bridgeImpldata = abi.encodeWithSelector(\n BRIDGE_AFTER_SWAP_SELECTOR,\n swapAmount,\n frsbRequest.bridgeData\n );\n\n // execute bridge operation and return the byte-data from response of bridge operation\n return _executeRoute(frsbRequest.bridgeRouteId, bridgeImpldata);\n }\n}\n"},"src/errors/SocketErrors.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nerror CelerRefundNotReady();\nerror OnlySocketDeployer();\nerror OnlySocketGatewayOwner();\nerror OnlySocketGateway();\nerror OnlyOwner();\nerror OnlyNominee();\nerror TransferIdExists();\nerror TransferIdDoesnotExist();\nerror Address0Provided();\nerror SwapFailed();\nerror UnsupportedInterfaceId();\nerror InvalidCelerRefund();\nerror CelerAlreadyRefunded();\nerror IncorrectBridgeRatios();\nerror ZeroAddressNotAllowed();\nerror ArrayLengthMismatch();\n"},"src/bridges/hop/l1/HopImplL1.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\nimport \"../interfaces/IHopL1Bridge.sol\";\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {BridgeImplBase} from \"../../BridgeImplBase.sol\";\nimport {HOP} from \"../../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Hop-L1 Route Implementation\n * @notice Route implementation with functions to bridge ERC20 and Native via Hop-Bridge from L1 to Supported L2s\n * Called via SocketGateway if the routeId in the request maps to the routeId of HopImplementation\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ncontract HopImplL1 is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable HopIdentifier = HOP;\n\n /// @notice Function-selector for ERC20-token bridging on Hop-L1-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4 public immutable HOP_L1_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(address,address,address,address,uint256,uint256,uint256,uint256,(uint256,bytes32))\"\n )\n );\n\n /// @notice Function-selector for Native bridging on Hop-L1-Route\n /// @dev This function selector is to be used while building transaction-data to bridge Native tokens\n bytes4 public immutable HOP_L1_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeNativeTo(address,address,address,uint256,uint256,uint256,uint256,uint256,bytes32)\"\n )\n );\n\n bytes4 public immutable HOP_L1_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(address,address,address,uint256,uint256,uint256,uint256,bytes32))\"\n )\n );\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n constructor(\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {}\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct HopDataNoToken {\n // The address receiving funds at the destination\n address receiverAddress;\n // address of the Hop-L1-Bridge to handle bridging the tokens\n address l1bridgeAddr;\n // relayerFee The amount distributed to the relayer at the destination. This is subtracted from the `_amount`.\n address relayer;\n // The chainId of the destination chain\n uint256 toChainId;\n // The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.\n uint256 amountOutMin;\n // The amount distributed to the relayer at the destination. This is subtracted from the `amount`.\n uint256 relayerFee;\n // The deadline for swapping in the destination AMM market. 0 if no swap is intended.\n uint256 deadline;\n // socket offchain created hash\n bytes32 metadata;\n }\n\n struct HopData {\n /// @notice address of token being bridged\n address token;\n // The address receiving funds at the destination\n address receiverAddress;\n // address of the Hop-L1-Bridge to handle bridging the tokens\n address l1bridgeAddr;\n // relayerFee The amount distributed to the relayer at the destination. This is subtracted from the `_amount`.\n address relayer;\n // The chainId of the destination chain\n uint256 toChainId;\n // The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.\n uint256 amountOutMin;\n // The amount distributed to the relayer at the destination. This is subtracted from the `amount`.\n uint256 relayerFee;\n // The deadline for swapping in the destination AMM market. 0 if no swap is intended.\n uint256 deadline;\n // socket offchain created hash\n bytes32 metadata;\n }\n\n struct HopERC20Data {\n uint256 deadline;\n bytes32 metadata;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in HopBridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for Hop-L1-Bridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n HopData memory hopData = abi.decode(bridgeData, (HopData));\n\n if (hopData.token == NATIVE_TOKEN_ADDRESS) {\n IHopL1Bridge(hopData.l1bridgeAddr).sendToL2{value: amount}(\n hopData.toChainId,\n hopData.receiverAddress,\n amount,\n hopData.amountOutMin,\n hopData.deadline,\n hopData.relayer,\n hopData.relayerFee\n );\n } else {\n ERC20(hopData.token).safeApprove(hopData.l1bridgeAddr, amount);\n\n // perform bridging\n IHopL1Bridge(hopData.l1bridgeAddr).sendToL2(\n hopData.toChainId,\n hopData.receiverAddress,\n amount,\n hopData.amountOutMin,\n hopData.deadline,\n hopData.relayer,\n hopData.relayerFee\n );\n }\n\n emit SocketBridge(\n amount,\n hopData.token,\n hopData.toChainId,\n HopIdentifier,\n msg.sender,\n hopData.receiverAddress,\n hopData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in HopBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param hopData encoded data for HopData\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n HopDataNoToken calldata hopData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n\n if (token == NATIVE_TOKEN_ADDRESS) {\n IHopL1Bridge(hopData.l1bridgeAddr).sendToL2{value: bridgeAmount}(\n hopData.toChainId,\n hopData.receiverAddress,\n bridgeAmount,\n hopData.amountOutMin,\n hopData.deadline,\n hopData.relayer,\n hopData.relayerFee\n );\n } else {\n ERC20(token).safeApprove(hopData.l1bridgeAddr, bridgeAmount);\n\n // perform bridging\n IHopL1Bridge(hopData.l1bridgeAddr).sendToL2(\n hopData.toChainId,\n hopData.receiverAddress,\n bridgeAmount,\n hopData.amountOutMin,\n hopData.deadline,\n hopData.relayer,\n hopData.relayerFee\n );\n }\n\n emit SocketBridge(\n bridgeAmount,\n token,\n hopData.toChainId,\n HopIdentifier,\n msg.sender,\n hopData.receiverAddress,\n hopData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via Hop-L1-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param receiverAddress The address receiving funds at the destination\n * @param token token being bridged\n * @param l1bridgeAddr address of the Hop-L1-Bridge to handle bridging the tokens\n * @param relayer The amount distributed to the relayer at the destination. This is subtracted from the `_amount`.\n * @param toChainId The chainId of the destination chain\n * @param amount The amount being sent\n * @param amountOutMin The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.\n * @param relayerFee The amount distributed to the relayer at the destination. This is subtracted from the `amount`.\n * @param hopData extra data needed to build the tx\n */\n function bridgeERC20To(\n address receiverAddress,\n address token,\n address l1bridgeAddr,\n address relayer,\n uint256 toChainId,\n uint256 amount,\n uint256 amountOutMin,\n uint256 relayerFee,\n HopERC20Data calldata hopData\n ) external payable {\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n tokenInstance.safeApprove(l1bridgeAddr, amount);\n\n // perform bridging\n IHopL1Bridge(l1bridgeAddr).sendToL2(\n toChainId,\n receiverAddress,\n amount,\n amountOutMin,\n hopData.deadline,\n relayer,\n relayerFee\n );\n\n emit SocketBridge(\n amount,\n token,\n toChainId,\n HopIdentifier,\n msg.sender,\n receiverAddress,\n hopData.metadata\n );\n }\n\n /**\n * @notice function to handle Native bridging to receipent via Hop-L1-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param receiverAddress The address receiving funds at the destination\n * @param l1bridgeAddr address of the Hop-L1-Bridge to handle bridging the tokens\n * @param relayer The amount distributed to the relayer at the destination. This is subtracted from the `_amount`.\n * @param toChainId The chainId of the destination chain\n * @param amount The amount being sent\n * @param amountOutMin The minimum amount received after attempting to swap in the destination AMM market. 0 if no swap is intended.\n * @param relayerFee The amount distributed to the relayer at the destination. This is subtracted from the `amount`.\n * @param deadline The deadline for swapping in the destination AMM market. 0 if no swap is intended.\n */\n function bridgeNativeTo(\n address receiverAddress,\n address l1bridgeAddr,\n address relayer,\n uint256 toChainId,\n uint256 amount,\n uint256 amountOutMin,\n uint256 relayerFee,\n uint256 deadline,\n bytes32 metadata\n ) external payable {\n IHopL1Bridge(l1bridgeAddr).sendToL2{value: amount}(\n toChainId,\n receiverAddress,\n amount,\n amountOutMin,\n deadline,\n relayer,\n relayerFee\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n toChainId,\n HopIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"},"src/static/RouteIdentifiers.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\nbytes32 constant ACROSS = keccak256(\"Across\");\n\nbytes32 constant ANYSWAP = keccak256(\"Anyswap\");\n\nbytes32 constant CBRIDGE = keccak256(\"CBridge\");\n\nbytes32 constant HOP = keccak256(\"Hop\");\n\nbytes32 constant HYPHEN = keccak256(\"Hyphen\");\n\nbytes32 constant NATIVE_OPTIMISM = keccak256(\"NativeOptimism\");\n\nbytes32 constant NATIVE_ARBITRUM = keccak256(\"NativeArbitrum\");\n\nbytes32 constant NATIVE_POLYGON = keccak256(\"NativePolygon\");\n\nbytes32 constant REFUEL = keccak256(\"Refuel\");\n\nbytes32 constant STARGATE = keccak256(\"Stargate\");\n\nbytes32 constant ONEINCH = keccak256(\"OneInch\");\n\nbytes32 constant ZEROX = keccak256(\"Zerox\");\n\nbytes32 constant RAINBOW = keccak256(\"Rainbow\");\n"},"src/SocketGateway.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\npragma experimental ABIEncoderV2;\n\nimport \"./utils/Ownable.sol\";\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {LibUtil} from \"./libraries/LibUtil.sol\";\nimport \"./libraries/LibBytes.sol\";\nimport {ISocketRoute} from \"./interfaces/ISocketRoute.sol\";\nimport {ISocketRequest} from \"./interfaces/ISocketRequest.sol\";\nimport {ISocketGateway} from \"./interfaces/ISocketGateway.sol\";\nimport {IncorrectBridgeRatios, ZeroAddressNotAllowed, ArrayLengthMismatch} from \"./errors/SocketErrors.sol\";\n\n/// @title SocketGatewayContract\n/// @notice Socketgateway is a contract with entrypoint functions for all interactions with socket liquidity layer\n/// @author Socket Team\ncontract SocketGatewayTemplate is Ownable {\n using LibBytes for bytes;\n using LibBytes for bytes4;\n using SafeTransferLib for ERC20;\n\n /// @notice FunctionSelector used to delegatecall from swap to the function of bridge router implementation\n bytes4 public immutable BRIDGE_AFTER_SWAP_SELECTOR =\n bytes4(keccak256(\"bridgeAfterSwap(uint256,bytes)\"));\n\n /// @notice storage variable to keep track of total number of routes registered in socketgateway\n uint32 public routesCount = 385;\n\n /// @notice storage variable to keep track of total number of controllers registered in socketgateway\n uint32 public controllerCount;\n\n address public immutable disabledRouteAddress;\n\n uint256 public constant CENT_PERCENT = 100e18;\n\n /// @notice storage mapping for route implementation addresses\n mapping(uint32 => address) public routes;\n\n /// storage mapping for controller implemenation addresses\n mapping(uint32 => address) public controllers;\n\n // Events ------------------------------------------------------------------------------------------------------->\n\n /// @notice Event emitted when a router is added to socketgateway\n event NewRouteAdded(uint32 indexed routeId, address indexed route);\n\n /// @notice Event emitted when a route is disabled\n event RouteDisabled(uint32 indexed routeId);\n\n /// @notice Event emitted when ownership transfer is requested by socket-gateway-owner\n event OwnershipTransferRequested(\n address indexed _from,\n address indexed _to\n );\n\n /// @notice Event emitted when a controller is added to socketgateway\n event ControllerAdded(\n uint32 indexed controllerId,\n address indexed controllerAddress\n );\n\n /// @notice Event emitted when a controller is disabled\n event ControllerDisabled(uint32 indexed controllerId);\n\n constructor(address _owner, address _disabledRoute) Ownable(_owner) {\n disabledRouteAddress = _disabledRoute;\n }\n\n // Able to receive ether\n // solhint-disable-next-line no-empty-blocks\n receive() external payable {}\n\n /*******************************************\n * EXTERNAL AND PUBLIC FUNCTIONS *\n *******************************************/\n\n /**\n * @notice executes functions in the routes identified using routeId and functionSelectorData\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped\n * @dev ensure the data in routeData to be built using the function-selector defined as a\n * constant in the route implementation contract\n * @param routeId route identifier\n * @param routeData functionSelectorData generated using the function-selector defined in the route Implementation\n */\n function executeRoute(\n uint32 routeId,\n bytes calldata routeData\n ) external payable returns (bytes memory) {\n (bool success, bytes memory result) = addressAt(routeId).delegatecall(\n routeData\n );\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n return result;\n }\n\n /**\n * @notice swaps a token on sourceChain and split it across multiple bridge-recipients\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being swapped\n * @dev ensure the swap-data and bridge-data is generated using the function-selector defined as a constant in the implementation address\n * @param swapMultiBridgeRequest request\n */\n function swapAndMultiBridge(\n ISocketRequest.SwapMultiBridgeRequest calldata swapMultiBridgeRequest\n ) external payable {\n uint256 requestLength = swapMultiBridgeRequest.bridgeRouteIds.length;\n\n if (\n requestLength != swapMultiBridgeRequest.bridgeImplDataItems.length\n ) {\n revert ArrayLengthMismatch();\n }\n uint256 ratioAggregate;\n for (uint256 index = 0; index < requestLength; ) {\n ratioAggregate += swapMultiBridgeRequest.bridgeRatios[index];\n }\n\n if (ratioAggregate != CENT_PERCENT) {\n revert IncorrectBridgeRatios();\n }\n\n (bool swapSuccess, bytes memory swapResult) = addressAt(\n swapMultiBridgeRequest.swapRouteId\n ).delegatecall(swapMultiBridgeRequest.swapImplData);\n\n if (!swapSuccess) {\n assembly {\n revert(add(swapResult, 32), mload(swapResult))\n }\n }\n\n uint256 amountReceivedFromSwap = abi.decode(swapResult, (uint256));\n\n uint256 bridgedAmount;\n\n for (uint256 index = 0; index < requestLength; ) {\n uint256 bridgingAmount;\n\n // if it is the last bridge request, bridge the remaining amount\n if (index == requestLength - 1) {\n bridgingAmount = amountReceivedFromSwap - bridgedAmount;\n } else {\n // bridging amount is the multiplication of bridgeRatio and amountReceivedFromSwap\n bridgingAmount =\n (amountReceivedFromSwap *\n swapMultiBridgeRequest.bridgeRatios[index]) /\n (CENT_PERCENT);\n }\n\n // update the bridged amount, this would be used for computation for last bridgeRequest\n bridgedAmount += bridgingAmount;\n\n bytes memory bridgeImpldata = abi.encodeWithSelector(\n BRIDGE_AFTER_SWAP_SELECTOR,\n bridgingAmount,\n swapMultiBridgeRequest.bridgeImplDataItems[index]\n );\n\n (bool bridgeSuccess, bytes memory bridgeResult) = addressAt(\n swapMultiBridgeRequest.bridgeRouteIds[index]\n ).delegatecall(bridgeImpldata);\n\n if (!bridgeSuccess) {\n assembly {\n revert(add(bridgeResult, 32), mload(bridgeResult))\n }\n }\n\n unchecked {\n ++index;\n }\n }\n }\n\n /**\n * @notice sequentially executes functions in the routes identified using routeId and functionSelectorData\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped\n * @dev ensure the data in each dataItem to be built using the function-selector defined as a\n * constant in the route implementation contract\n * @param routeIds a list of route identifiers\n * @param dataItems a list of functionSelectorData generated using the function-selector defined in the route Implementation\n */\n function executeRoutes(\n uint32[] calldata routeIds,\n bytes[] calldata dataItems\n ) external payable {\n uint256 routeIdslength = routeIds.length;\n if (routeIdslength != dataItems.length) revert ArrayLengthMismatch();\n for (uint256 index = 0; index < routeIdslength; ) {\n (bool success, bytes memory result) = addressAt(routeIds[index])\n .delegatecall(dataItems[index]);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n unchecked {\n ++index;\n }\n }\n }\n\n /**\n * @notice execute a controller function identified using the controllerId in the request\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped\n * @dev ensure the data in request to be built using the function-selector defined as a\n * constant in the controller implementation contract\n * @param socketControllerRequest socketControllerRequest with controllerId to identify the\n * controllerAddress and byteData constructed using functionSelector\n * of the function being invoked\n * @return bytes data received from the call delegated to controller\n */\n function executeController(\n ISocketGateway.SocketControllerRequest calldata socketControllerRequest\n ) external payable returns (bytes memory) {\n (bool success, bytes memory result) = controllers[\n socketControllerRequest.controllerId\n ].delegatecall(socketControllerRequest.data);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n return result;\n }\n\n /**\n * @notice sequentially executes all controller requests\n * @notice The caller must first approve this contract to spend amount of ERC20-Token being bridged/swapped\n * @dev ensure the data in each controller-request to be built using the function-selector defined as a\n * constant in the controller implementation contract\n * @param controllerRequests a list of socketControllerRequest\n * Each controllerRequest contains controllerId to identify the controllerAddress and\n * byteData constructed using functionSelector of the function being invoked\n */\n function executeControllers(\n ISocketGateway.SocketControllerRequest[] calldata controllerRequests\n ) external payable {\n for (uint32 index = 0; index < controllerRequests.length; ) {\n (bool success, bytes memory result) = controllers[\n controllerRequests[index].controllerId\n ].delegatecall(controllerRequests[index].data);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n unchecked {\n ++index;\n }\n }\n }\n\n /**************************************\n * ADMIN FUNCTIONS *\n **************************************/\n\n /**\n * @notice Add route to the socketGateway\n This is a restricted function to be called by only socketGatewayOwner\n * @dev ensure routeAddress is a verified bridge or middleware implementation address\n * @param routeAddress The address of bridge or middleware implementation contract deployed\n * @return Id of the route added to the routes-mapping in socketGateway storage\n */\n function addRoute(\n address routeAddress\n ) external onlyOwner returns (uint32) {\n uint32 routeId = routesCount;\n routes[routeId] = routeAddress;\n\n routesCount += 1;\n\n emit NewRouteAdded(routeId, routeAddress);\n\n return routeId;\n }\n\n /**\n * @notice Give Infinite or 0 approval to bridgeRoute for the tokenAddress\n This is a restricted function to be called by only socketGatewayOwner\n */\n\n function setApprovalForRouters(\n address[] memory routeAddresses,\n address[] memory tokenAddresses,\n bool isMax\n ) external onlyOwner {\n for (uint32 index = 0; index < routeAddresses.length; ) {\n ERC20(tokenAddresses[index]).approve(\n routeAddresses[index],\n isMax ? type(uint256).max : 0\n );\n unchecked {\n ++index;\n }\n }\n }\n\n /**\n * @notice Add controller to the socketGateway\n This is a restricted function to be called by only socketGatewayOwner\n * @dev ensure controllerAddress is a verified controller implementation address\n * @param controllerAddress The address of controller implementation contract deployed\n * @return Id of the controller added to the controllers-mapping in socketGateway storage\n */\n function addController(\n address controllerAddress\n ) external onlyOwner returns (uint32) {\n uint32 controllerId = controllerCount;\n\n controllers[controllerId] = controllerAddress;\n\n controllerCount += 1;\n\n emit ControllerAdded(controllerId, controllerAddress);\n\n return controllerId;\n }\n\n /**\n * @notice disable controller by setting ZeroAddress to the entry in controllers-mapping\n identified by controllerId as key.\n This is a restricted function to be called by only socketGatewayOwner\n * @param controllerId The Id of controller-implementation in the controllers mapping\n */\n function disableController(uint32 controllerId) public onlyOwner {\n controllers[controllerId] = disabledRouteAddress;\n emit ControllerDisabled(controllerId);\n }\n\n /**\n * @notice disable a route by setting ZeroAddress to the entry in routes-mapping\n identified by routeId as key.\n This is a restricted function to be called by only socketGatewayOwner\n * @param routeId The Id of route-implementation in the routes mapping\n */\n function disableRoute(uint32 routeId) external onlyOwner {\n routes[routeId] = disabledRouteAddress;\n emit RouteDisabled(routeId);\n }\n\n /*******************************************\n * RESTRICTED RESCUE FUNCTIONS *\n *******************************************/\n\n /**\n * @notice Rescues the ERC20 token to an address\n this is a restricted function to be called by only socketGatewayOwner\n * @dev as this is a restricted to socketGatewayOwner, ensure the userAddress is a known address\n * @param token address of the ERC20 token being rescued\n * @param userAddress address to which ERC20 is to be rescued\n * @param amount amount of ERC20 tokens being rescued\n */\n function rescueFunds(\n address token,\n address userAddress,\n uint256 amount\n ) external onlyOwner {\n ERC20(token).safeTransfer(userAddress, amount);\n }\n\n /**\n * @notice Rescues the native balance to an address\n this is a restricted function to be called by only socketGatewayOwner\n * @dev as this is a restricted to socketGatewayOwner, ensure the userAddress is a known address\n * @param userAddress address to which native-balance is to be rescued\n * @param amount amount of native-balance being rescued\n */\n function rescueEther(\n address payable userAddress,\n uint256 amount\n ) external onlyOwner {\n userAddress.transfer(amount);\n }\n\n /*******************************************\n * VIEW FUNCTIONS *\n *******************************************/\n\n /**\n * @notice Get routeImplementation address mapped to the routeId\n * @param routeId routeId is the key in the mapping for routes\n * @return route-implementation address\n */\n function getRoute(uint32 routeId) public view returns (address) {\n return addressAt(routeId);\n }\n\n /**\n * @notice Get controllerImplementation address mapped to the controllerId\n * @param controllerId controllerId is the key in the mapping for controllers\n * @return controller-implementation address\n */\n function getController(uint32 controllerId) public view returns (address) {\n return controllers[controllerId];\n }\n\n function addressAt(uint32 routeId) public view returns (address) {\n if (routeId < 385) {\n if (routeId < 257) {\n if (routeId < 129) {\n if (routeId < 65) {\n if (routeId < 33) {\n if (routeId < 17) {\n if (routeId < 9) {\n if (routeId < 5) {\n if (routeId < 3) {\n if (routeId == 1) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 3) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 7) {\n if (routeId == 5) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 7) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 13) {\n if (routeId < 11) {\n if (routeId == 9) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 11) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 15) {\n if (routeId == 13) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 15) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 25) {\n if (routeId < 21) {\n if (routeId < 19) {\n if (routeId == 17) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 19) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 23) {\n if (routeId == 21) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 23) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 29) {\n if (routeId < 27) {\n if (routeId == 25) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 27) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 31) {\n if (routeId == 29) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 31) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 49) {\n if (routeId < 41) {\n if (routeId < 37) {\n if (routeId < 35) {\n if (routeId == 33) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 35) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 39) {\n if (routeId == 37) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 39) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 45) {\n if (routeId < 43) {\n if (routeId == 41) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 43) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 47) {\n if (routeId == 45) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 47) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 57) {\n if (routeId < 53) {\n if (routeId < 51) {\n if (routeId == 49) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 51) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 55) {\n if (routeId == 53) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 55) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 61) {\n if (routeId < 59) {\n if (routeId == 57) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 59) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 63) {\n if (routeId == 61) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 63) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 97) {\n if (routeId < 81) {\n if (routeId < 73) {\n if (routeId < 69) {\n if (routeId < 67) {\n if (routeId == 65) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 67) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 71) {\n if (routeId == 69) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 71) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 77) {\n if (routeId < 75) {\n if (routeId == 73) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 75) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 79) {\n if (routeId == 77) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 79) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 89) {\n if (routeId < 85) {\n if (routeId < 83) {\n if (routeId == 81) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 83) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 87) {\n if (routeId == 85) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 87) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 93) {\n if (routeId < 91) {\n if (routeId == 89) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 91) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 95) {\n if (routeId == 93) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 95) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 113) {\n if (routeId < 105) {\n if (routeId < 101) {\n if (routeId < 99) {\n if (routeId == 97) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 99) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 103) {\n if (routeId == 101) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 103) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 109) {\n if (routeId < 107) {\n if (routeId == 105) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 107) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 111) {\n if (routeId == 109) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 111) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 121) {\n if (routeId < 117) {\n if (routeId < 115) {\n if (routeId == 113) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 115) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 119) {\n if (routeId == 117) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 119) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 125) {\n if (routeId < 123) {\n if (routeId == 121) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 123) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 127) {\n if (routeId == 125) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 127) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 193) {\n if (routeId < 161) {\n if (routeId < 145) {\n if (routeId < 137) {\n if (routeId < 133) {\n if (routeId < 131) {\n if (routeId == 129) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 131) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 135) {\n if (routeId == 133) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 135) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 141) {\n if (routeId < 139) {\n if (routeId == 137) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 139) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 143) {\n if (routeId == 141) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 143) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 153) {\n if (routeId < 149) {\n if (routeId < 147) {\n if (routeId == 145) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 147) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 151) {\n if (routeId == 149) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 151) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 157) {\n if (routeId < 155) {\n if (routeId == 153) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 155) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 159) {\n if (routeId == 157) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 159) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 177) {\n if (routeId < 169) {\n if (routeId < 165) {\n if (routeId < 163) {\n if (routeId == 161) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 163) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 167) {\n if (routeId == 165) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 167) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 173) {\n if (routeId < 171) {\n if (routeId == 169) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 171) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 175) {\n if (routeId == 173) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 175) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 185) {\n if (routeId < 181) {\n if (routeId < 179) {\n if (routeId == 177) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 179) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 183) {\n if (routeId == 181) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 183) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 189) {\n if (routeId < 187) {\n if (routeId == 185) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 187) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 191) {\n if (routeId == 189) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 191) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 225) {\n if (routeId < 209) {\n if (routeId < 201) {\n if (routeId < 197) {\n if (routeId < 195) {\n if (routeId == 193) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 195) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 199) {\n if (routeId == 197) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 199) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 205) {\n if (routeId < 203) {\n if (routeId == 201) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 203) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 207) {\n if (routeId == 205) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 207) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 217) {\n if (routeId < 213) {\n if (routeId < 211) {\n if (routeId == 209) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 211) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 215) {\n if (routeId == 213) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 215) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 221) {\n if (routeId < 219) {\n if (routeId == 217) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 219) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 223) {\n if (routeId == 221) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 223) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 241) {\n if (routeId < 233) {\n if (routeId < 229) {\n if (routeId < 227) {\n if (routeId == 225) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 227) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 231) {\n if (routeId == 229) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 231) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 237) {\n if (routeId < 235) {\n if (routeId == 233) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 235) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 239) {\n if (routeId == 237) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 239) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 249) {\n if (routeId < 245) {\n if (routeId < 243) {\n if (routeId == 241) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 243) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 247) {\n if (routeId == 245) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 247) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 253) {\n if (routeId < 251) {\n if (routeId == 249) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 251) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 255) {\n if (routeId == 253) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 255) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 321) {\n if (routeId < 289) {\n if (routeId < 273) {\n if (routeId < 265) {\n if (routeId < 261) {\n if (routeId < 259) {\n if (routeId == 257) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 259) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 263) {\n if (routeId == 261) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 263) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 269) {\n if (routeId < 267) {\n if (routeId == 265) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 267) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 271) {\n if (routeId == 269) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 271) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 281) {\n if (routeId < 277) {\n if (routeId < 275) {\n if (routeId == 273) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 275) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 279) {\n if (routeId == 277) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 279) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 285) {\n if (routeId < 283) {\n if (routeId == 281) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 283) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 287) {\n if (routeId == 285) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 287) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 305) {\n if (routeId < 297) {\n if (routeId < 293) {\n if (routeId < 291) {\n if (routeId == 289) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 291) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 295) {\n if (routeId == 293) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 295) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 301) {\n if (routeId < 299) {\n if (routeId == 297) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 299) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 303) {\n if (routeId == 301) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 303) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 313) {\n if (routeId < 309) {\n if (routeId < 307) {\n if (routeId == 305) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 307) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 311) {\n if (routeId == 309) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 311) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 317) {\n if (routeId < 315) {\n if (routeId == 313) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 315) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 319) {\n if (routeId == 317) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 319) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n }\n } else {\n if (routeId < 353) {\n if (routeId < 337) {\n if (routeId < 329) {\n if (routeId < 325) {\n if (routeId < 323) {\n if (routeId == 321) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 323) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 327) {\n if (routeId == 325) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 327) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 333) {\n if (routeId < 331) {\n if (routeId == 329) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 331) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 335) {\n if (routeId == 333) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 335) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 345) {\n if (routeId < 341) {\n if (routeId < 339) {\n if (routeId == 337) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 339) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 343) {\n if (routeId == 341) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 343) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 349) {\n if (routeId < 347) {\n if (routeId == 345) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 347) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 351) {\n if (routeId == 349) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 351) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n } else {\n if (routeId < 369) {\n if (routeId < 361) {\n if (routeId < 357) {\n if (routeId < 355) {\n if (routeId == 353) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 355) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 359) {\n if (routeId == 357) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 359) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 365) {\n if (routeId < 363) {\n if (routeId == 361) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 363) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 367) {\n if (routeId == 365) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 367) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n } else {\n if (routeId < 377) {\n if (routeId < 373) {\n if (routeId < 371) {\n if (routeId == 369) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 371) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 375) {\n if (routeId == 373) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 375) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n } else {\n if (routeId < 381) {\n if (routeId < 379) {\n if (routeId == 377) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 379) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n } else {\n if (routeId < 383) {\n if (routeId == 381) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n } else {\n if (routeId == 383) {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n } else {\n return\n 0x822D4B4e63499a576Ab1cc152B86D1CFFf794F4f;\n }\n }\n }\n }\n }\n }\n }\n }\n }\n\n if (routes[routeId] == address(0)) revert ZeroAddressNotAllowed();\n return routes[routeId];\n }\n\n /// @notice fallback function to handle swap, bridge execution\n /// @dev ensure routeId is converted to bytes4 and sent as msg.sig in the transaction\n fallback() external payable {\n address routeAddress = addressAt(uint32(msg.sig));\n\n bytes memory result;\n\n assembly {\n // copy function selector and any arguments\n calldatacopy(0, 4, sub(calldatasize(), 4))\n // execute function call using the facet\n result := delegatecall(\n gas(),\n routeAddress,\n 0,\n sub(calldatasize(), 4),\n 0,\n 0\n )\n // get any return value\n returndatacopy(0, 0, returndatasize())\n // return any return value or error back to the caller\n switch result\n case 0 {\n revert(0, returndatasize())\n }\n default {\n return(0, returndatasize())\n }\n }\n }\n}\n"},"src/swap/rainbow/Rainbow.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport \"../SwapImplBase.sol\";\nimport {Address0Provided, SwapFailed} from \"../../errors/SocketErrors.sol\";\nimport {RAINBOW} from \"../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Rainbow-Swap-Route Implementation\n * @notice Route implementation with functions to swap tokens via Rainbow-Swap\n * Called via SocketGateway if the routeId in the request maps to the routeId of RainbowImplementation\n * @author Socket dot tech.\n */\ncontract RainbowSwapImpl is SwapImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable RainbowIdentifier = RAINBOW;\n\n /// @notice unique name to identify the router, used to emit event upon successful bridging\n bytes32 public immutable NAME = keccak256(\"Rainbow-Router\");\n\n /// @notice address of rainbow-swap-aggregator to swap the tokens on Chain\n address payable public immutable rainbowSwapAggregator;\n\n /// @notice socketGatewayAddress to be initialised via storage variable SwapImplBase\n /// @notice rainbow swap aggregator contract is payable to allow ethereum swaps\n /// @dev ensure _rainbowSwapAggregator are set properly for the chainId in which the contract is being deployed\n constructor(\n address _rainbowSwapAggregator,\n address _socketGateway,\n address _socketDeployFactory\n ) SwapImplBase(_socketGateway, _socketDeployFactory) {\n rainbowSwapAggregator = payable(_rainbowSwapAggregator);\n }\n\n receive() external payable {}\n\n fallback() external payable {}\n\n /**\n * @notice function to swap tokens on the chain and transfer to receiver address\n * @notice This method is payable because the caller is doing token transfer and swap operation\n * @param fromToken address of token being Swapped\n * @param toToken address of token that recipient will receive after swap\n * @param amount amount of fromToken being swapped\n * @param receiverAddress recipient-address\n * @param swapExtraData additional Data to perform Swap via Rainbow-Aggregator\n * @return swapped amount (in toToken Address)\n */\n function performAction(\n address fromToken,\n address toToken,\n uint256 amount,\n address receiverAddress,\n bytes calldata swapExtraData\n ) external payable override returns (uint256) {\n if (fromToken == address(0)) {\n revert Address0Provided();\n }\n\n bytes memory swapCallData = abi.decode(swapExtraData, (bytes));\n\n uint256 _initialBalanceTokenOut;\n uint256 _finalBalanceTokenOut;\n\n ERC20 toTokenERC20 = ERC20(toToken);\n if (toToken != NATIVE_TOKEN_ADDRESS) {\n _initialBalanceTokenOut = toTokenERC20.balanceOf(socketGateway);\n } else {\n _initialBalanceTokenOut = address(this).balance;\n }\n\n if (fromToken != NATIVE_TOKEN_ADDRESS) {\n ERC20 token = ERC20(fromToken);\n token.safeTransferFrom(msg.sender, socketGateway, amount);\n token.safeApprove(rainbowSwapAggregator, amount);\n\n // solhint-disable-next-line\n (bool success, ) = rainbowSwapAggregator.call(swapCallData);\n\n if (!success) {\n revert SwapFailed();\n }\n\n token.safeApprove(rainbowSwapAggregator, 0);\n } else {\n (bool success, ) = rainbowSwapAggregator.call{value: amount}(\n swapCallData\n );\n if (!success) {\n revert SwapFailed();\n }\n }\n\n if (toToken != NATIVE_TOKEN_ADDRESS) {\n _finalBalanceTokenOut = toTokenERC20.balanceOf(socketGateway);\n } else {\n _finalBalanceTokenOut = address(this).balance;\n }\n\n uint256 returnAmount = _finalBalanceTokenOut - _initialBalanceTokenOut;\n\n if (toToken == NATIVE_TOKEN_ADDRESS) {\n payable(receiverAddress).transfer(returnAmount);\n } else {\n toTokenERC20.transfer(receiverAddress, returnAmount);\n }\n\n emit SocketSwapTokens(\n fromToken,\n toToken,\n returnAmount,\n amount,\n RainbowIdentifier,\n receiverAddress\n );\n\n return returnAmount;\n }\n\n /**\n * @notice function to swapWithIn SocketGateway - swaps tokens on the chain to socketGateway as recipient\n * @param fromToken token to be swapped\n * @param toToken token to which fromToken has to be swapped\n * @param amount amount of fromToken being swapped\n * @param swapExtraData encoded value of properties in the swapData Struct\n * @return swapped amount (in toToken Address)\n */\n function performActionWithIn(\n address fromToken,\n address toToken,\n uint256 amount,\n bytes calldata swapExtraData\n ) external payable override returns (uint256, address) {\n if (fromToken == address(0)) {\n revert Address0Provided();\n }\n\n bytes memory swapCallData = abi.decode(swapExtraData, (bytes));\n\n uint256 _initialBalanceTokenOut;\n uint256 _finalBalanceTokenOut;\n\n ERC20 toTokenERC20 = ERC20(toToken);\n if (toToken != NATIVE_TOKEN_ADDRESS) {\n _initialBalanceTokenOut = toTokenERC20.balanceOf(socketGateway);\n } else {\n _initialBalanceTokenOut = address(this).balance;\n }\n\n if (fromToken != NATIVE_TOKEN_ADDRESS) {\n ERC20 token = ERC20(fromToken);\n token.safeTransferFrom(msg.sender, socketGateway, amount);\n token.safeApprove(rainbowSwapAggregator, amount);\n\n // solhint-disable-next-line\n (bool success, ) = rainbowSwapAggregator.call(swapCallData);\n\n if (!success) {\n revert SwapFailed();\n }\n\n token.safeApprove(rainbowSwapAggregator, 0);\n } else {\n (bool success, ) = rainbowSwapAggregator.call{value: amount}(\n swapCallData\n );\n if (!success) {\n revert SwapFailed();\n }\n }\n\n if (toToken != NATIVE_TOKEN_ADDRESS) {\n _finalBalanceTokenOut = toTokenERC20.balanceOf(socketGateway);\n } else {\n _finalBalanceTokenOut = address(this).balance;\n }\n\n uint256 returnAmount = _finalBalanceTokenOut - _initialBalanceTokenOut;\n\n emit SocketSwapTokens(\n fromToken,\n toToken,\n returnAmount,\n amount,\n RainbowIdentifier,\n socketGateway\n );\n\n return (returnAmount, toToken);\n }\n}\n"},"src/interfaces/ISocketBridgeBase.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\ninterface ISocketBridgeBase {\n function killme() external;\n}\n"},"src/interfaces/ISocketRequest.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\n/**\n * @title ISocketRoute\n * @notice Interface with Request DataStructures to invoke controller functions.\n * @author Socket dot tech.\n */\ninterface ISocketRequest {\n struct SwapMultiBridgeRequest {\n uint32 swapRouteId;\n bytes swapImplData;\n uint32[] bridgeRouteIds;\n bytes[] bridgeImplDataItems;\n uint256[] bridgeRatios;\n bytes[] eventDataItems;\n }\n\n // Datastructure for Refuel-Swap-Bridge function\n struct RefuelSwapBridgeRequest {\n uint32 refuelRouteId;\n bytes refuelData;\n uint32 swapRouteId;\n bytes swapData;\n uint32 bridgeRouteId;\n bytes bridgeData;\n }\n\n // Datastructure for DeductFees-Swap function\n struct FeesTakerSwapRequest {\n address feesTakerAddress;\n address feesToken;\n uint256 feesAmount;\n uint32 routeId;\n bytes swapRequestData;\n }\n\n // Datastructure for DeductFees-Bridge function\n struct FeesTakerBridgeRequest {\n address feesTakerAddress;\n address feesToken;\n uint256 feesAmount;\n uint32 routeId;\n bytes bridgeRequestData;\n }\n\n // Datastructure for DeductFees-MultiBridge function\n struct FeesTakerMultiBridgeRequest {\n address feesTakerAddress;\n address feesToken;\n uint256 feesAmount;\n uint32[] bridgeRouteIds;\n bytes[] bridgeRequestDataItems;\n }\n\n // Datastructure for DeductFees-Swap-Bridge function\n struct FeesTakerSwapBridgeRequest {\n address feesTakerAddress;\n address feesToken;\n uint256 feesAmount;\n uint32 swapRouteId;\n bytes swapData;\n uint32 bridgeRouteId;\n bytes bridgeData;\n }\n\n // Datastructure for DeductFees-Refuel-Swap-Bridge function\n struct FeesTakerRefuelSwapBridgeRequest {\n address feesTakerAddress;\n address feesToken;\n uint256 feesAmount;\n uint32 refuelRouteId;\n bytes refuelData;\n uint32 swapRouteId;\n bytes swapData;\n uint32 bridgeRouteId;\n bytes bridgeData;\n }\n}\n"},"src/utils/Ownable.sol":{"content":"// SPDX-License-Identifier: GPL-3.0-only\npragma solidity ^0.8.4;\n\nimport {OnlyOwner, OnlyNominee} from \"../errors/SocketErrors.sol\";\n\nabstract contract Ownable {\n address private _owner;\n address private _nominee;\n\n event OwnerNominated(address indexed nominee);\n event OwnerClaimed(address indexed claimer);\n\n constructor(address owner_) {\n _claimOwner(owner_);\n }\n\n modifier onlyOwner() {\n if (msg.sender != _owner) {\n revert OnlyOwner();\n }\n _;\n }\n\n function owner() public view returns (address) {\n return _owner;\n }\n\n function nominee() public view returns (address) {\n return _nominee;\n }\n\n function nominateOwner(address nominee_) external {\n if (msg.sender != _owner) {\n revert OnlyOwner();\n }\n _nominee = nominee_;\n emit OwnerNominated(_nominee);\n }\n\n function claimOwner() external {\n if (msg.sender != _nominee) {\n revert OnlyNominee();\n }\n _claimOwner(msg.sender);\n }\n\n function _claimOwner(address claimer_) internal {\n _owner = claimer_;\n _nominee = address(0);\n emit OwnerClaimed(claimer_);\n }\n}\n"},"lib/solmate/src/tokens/ERC20.sol":{"content":"// SPDX-License-Identifier: AGPL-3.0-only\npragma solidity >=0.8.0;\n\n/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.\n/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)\n/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)\n/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.\nabstract contract ERC20 {\n /*//////////////////////////////////////////////////////////////\n EVENTS\n //////////////////////////////////////////////////////////////*/\n\n event Transfer(address indexed from, address indexed to, uint256 amount);\n\n event Approval(address indexed owner, address indexed spender, uint256 amount);\n\n /*//////////////////////////////////////////////////////////////\n METADATA STORAGE\n //////////////////////////////////////////////////////////////*/\n\n string public name;\n\n string public symbol;\n\n uint8 public immutable decimals;\n\n /*//////////////////////////////////////////////////////////////\n ERC20 STORAGE\n //////////////////////////////////////////////////////////////*/\n\n uint256 public totalSupply;\n\n mapping(address => uint256) public balanceOf;\n\n mapping(address => mapping(address => uint256)) public allowance;\n\n /*//////////////////////////////////////////////////////////////\n EIP-2612 STORAGE\n //////////////////////////////////////////////////////////////*/\n\n uint256 internal immutable INITIAL_CHAIN_ID;\n\n bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;\n\n mapping(address => uint256) public nonces;\n\n /*//////////////////////////////////////////////////////////////\n CONSTRUCTOR\n //////////////////////////////////////////////////////////////*/\n\n constructor(\n string memory _name,\n string memory _symbol,\n uint8 _decimals\n ) {\n name = _name;\n symbol = _symbol;\n decimals = _decimals;\n\n INITIAL_CHAIN_ID = block.chainid;\n INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();\n }\n\n /*//////////////////////////////////////////////////////////////\n ERC20 LOGIC\n //////////////////////////////////////////////////////////////*/\n\n function approve(address spender, uint256 amount) public virtual returns (bool) {\n allowance[msg.sender][spender] = amount;\n\n emit Approval(msg.sender, spender, amount);\n\n return true;\n }\n\n function transfer(address to, uint256 amount) public virtual returns (bool) {\n balanceOf[msg.sender] -= amount;\n\n // Cannot overflow because the sum of all user\n // balances can't exceed the max uint256 value.\n unchecked {\n balanceOf[to] += amount;\n }\n\n emit Transfer(msg.sender, to, amount);\n\n return true;\n }\n\n function transferFrom(\n address from,\n address to,\n uint256 amount\n ) public virtual returns (bool) {\n uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.\n\n if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;\n\n balanceOf[from] -= amount;\n\n // Cannot overflow because the sum of all user\n // balances can't exceed the max uint256 value.\n unchecked {\n balanceOf[to] += amount;\n }\n\n emit Transfer(from, to, amount);\n\n return true;\n }\n\n /*//////////////////////////////////////////////////////////////\n EIP-2612 LOGIC\n //////////////////////////////////////////////////////////////*/\n\n function permit(\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) public virtual {\n require(deadline >= block.timestamp, \"PERMIT_DEADLINE_EXPIRED\");\n\n // Unchecked because the only math done is incrementing\n // the owner's nonce which cannot realistically overflow.\n unchecked {\n address recoveredAddress = ecrecover(\n keccak256(\n abi.encodePacked(\n \"\\x19\\x01\",\n DOMAIN_SEPARATOR(),\n keccak256(\n abi.encode(\n keccak256(\n \"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)\"\n ),\n owner,\n spender,\n value,\n nonces[owner]++,\n deadline\n )\n )\n )\n ),\n v,\n r,\n s\n );\n\n require(recoveredAddress != address(0) && recoveredAddress == owner, \"INVALID_SIGNER\");\n\n allowance[recoveredAddress][spender] = value;\n }\n\n emit Approval(owner, spender, value);\n }\n\n function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {\n return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();\n }\n\n function computeDomainSeparator() internal view virtual returns (bytes32) {\n return\n keccak256(\n abi.encode(\n keccak256(\"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)\"),\n keccak256(bytes(name)),\n keccak256(\"1\"),\n block.chainid,\n address(this)\n )\n );\n }\n\n /*//////////////////////////////////////////////////////////////\n INTERNAL MINT/BURN LOGIC\n //////////////////////////////////////////////////////////////*/\n\n function _mint(address to, uint256 amount) internal virtual {\n totalSupply += amount;\n\n // Cannot overflow because the sum of all user\n // balances can't exceed the max uint256 value.\n unchecked {\n balanceOf[to] += amount;\n }\n\n emit Transfer(address(0), to, amount);\n }\n\n function _burn(address from, uint256 amount) internal virtual {\n balanceOf[from] -= amount;\n\n // Cannot underflow because a user's balance\n // will never be larger than the total supply.\n unchecked {\n totalSupply -= amount;\n }\n\n emit Transfer(from, address(0), amount);\n }\n}\n"},"src/controllers/RefuelSwapAndBridgeController.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {ISocketRequest} from \"../interfaces/ISocketRequest.sol\";\nimport {ISocketRoute} from \"../interfaces/ISocketRoute.sol\";\nimport {BaseController} from \"./BaseController.sol\";\n\n/**\n * @title RefuelSwapAndBridge Controller Implementation\n * @notice Controller with composed actions for Refuel,Swap and Bridge to be executed Sequentially and this is atomic\n * @author Socket dot tech.\n */\ncontract RefuelSwapAndBridgeController is BaseController {\n /// @notice Function-selector to invoke refuel-swap-bridge function\n /// @dev This function selector is to be used while buidling transaction-data\n bytes4 public immutable REFUEL_SWAP_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"refuelAndSwapAndBridge((uint32,bytes,uint32,bytes,uint32,bytes))\"\n )\n );\n\n /// @notice socketGatewayAddress to be initialised via storage variable BaseController\n constructor(\n address _socketGatewayAddress\n ) BaseController(_socketGatewayAddress) {}\n\n /**\n * @notice function to handle refuel followed by Swap and Bridge actions\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param rsbRequest Request with data to execute refuel followed by swap and bridge\n * @return output data from bridging operation\n */\n function refuelAndSwapAndBridge(\n ISocketRequest.RefuelSwapBridgeRequest calldata rsbRequest\n ) public payable returns (bytes memory) {\n _executeRoute(rsbRequest.refuelRouteId, rsbRequest.refuelData);\n\n // refuel is also a bridging activity via refuel-route-implementation\n bytes memory swapResponseData = _executeRoute(\n rsbRequest.swapRouteId,\n rsbRequest.swapData\n );\n\n uint256 swapAmount = abi.decode(swapResponseData, (uint256));\n\n //sequence of arguments for implData: amount, token, data\n // Bridging the swapAmount received in the preceeding step\n bytes memory bridgeImpldata = abi.encodeWithSelector(\n BRIDGE_AFTER_SWAP_SELECTOR,\n swapAmount,\n rsbRequest.bridgeData\n );\n\n return _executeRoute(rsbRequest.bridgeRouteId, bridgeImpldata);\n }\n}\n"},"src/interfaces/ISocketGateway.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\n/**\n * @title ISocketGateway\n * @notice Interface for SocketGateway functions.\n * @dev functions can be added here for invocation from external contracts or off-chain\n * @author Socket dot tech.\n */\ninterface ISocketGateway {\n /**\n * @notice Request-struct for controllerRequests\n * @dev ensure the value for data is generated using the function-selectors defined in the controllerImplementation contracts\n */\n struct SocketControllerRequest {\n // controllerId is the id mapped to the controllerAddress\n uint32 controllerId;\n // transactionImplData generated off-chain or by caller using function-selector of the controllerContract\n bytes data;\n }\n\n // @notice view to get owner-address\n function owner() external view returns (address);\n}\n"},"src/libraries/Pb.sol":{"content":"// SPDX-License-Identifier: GPL-3.0-only\n\npragma solidity ^0.8.4;\n\n// runtime proto sol library\nlibrary Pb {\n enum WireType {\n Varint,\n Fixed64,\n LengthDelim,\n StartGroup,\n EndGroup,\n Fixed32\n }\n\n struct Buffer {\n uint256 idx; // the start index of next read. when idx=b.length, we're done\n bytes b; // hold serialized proto msg, readonly\n }\n\n // create a new in-memory Buffer object from raw msg bytes\n function fromBytes(\n bytes memory raw\n ) internal pure returns (Buffer memory buf) {\n buf.b = raw;\n buf.idx = 0;\n }\n\n // whether there are unread bytes\n function hasMore(Buffer memory buf) internal pure returns (bool) {\n return buf.idx < buf.b.length;\n }\n\n // decode current field number and wiretype\n function decKey(\n Buffer memory buf\n ) internal pure returns (uint256 tag, WireType wiretype) {\n uint256 v = decVarint(buf);\n tag = v / 8;\n wiretype = WireType(v & 7);\n }\n\n // read varint from current buf idx, move buf.idx to next read, return the int value\n function decVarint(Buffer memory buf) internal pure returns (uint256 v) {\n bytes10 tmp; // proto int is at most 10 bytes (7 bits can be used per byte)\n bytes memory bb = buf.b; // get buf.b mem addr to use in assembly\n v = buf.idx; // use v to save one additional uint variable\n assembly {\n tmp := mload(add(add(bb, 32), v)) // load 10 bytes from buf.b[buf.idx] to tmp\n }\n uint256 b; // store current byte content\n v = 0; // reset to 0 for return value\n for (uint256 i = 0; i < 10; i++) {\n assembly {\n b := byte(i, tmp) // don't use tmp[i] because it does bound check and costs extra\n }\n v |= (b & 0x7F) << (i * 7);\n if (b & 0x80 == 0) {\n buf.idx += i + 1;\n return v;\n }\n }\n revert(); // i=10, invalid varint stream\n }\n\n // read length delimited field and return bytes\n function decBytes(\n Buffer memory buf\n ) internal pure returns (bytes memory b) {\n uint256 len = decVarint(buf);\n uint256 end = buf.idx + len;\n require(end <= buf.b.length); // avoid overflow\n b = new bytes(len);\n bytes memory bufB = buf.b; // get buf.b mem addr to use in assembly\n uint256 bStart;\n uint256 bufBStart = buf.idx;\n assembly {\n bStart := add(b, 32)\n bufBStart := add(add(bufB, 32), bufBStart)\n }\n for (uint256 i = 0; i < len; i += 32) {\n assembly {\n mstore(add(bStart, i), mload(add(bufBStart, i)))\n }\n }\n buf.idx = end;\n }\n\n // move idx pass current value field, to beginning of next tag or msg end\n function skipValue(Buffer memory buf, WireType wire) internal pure {\n if (wire == WireType.Varint) {\n decVarint(buf);\n } else if (wire == WireType.LengthDelim) {\n uint256 len = decVarint(buf);\n buf.idx += len; // skip len bytes value data\n require(buf.idx <= buf.b.length); // avoid overflow\n } else {\n revert();\n } // unsupported wiretype\n }\n\n function _uint256(bytes memory b) internal pure returns (uint256 v) {\n require(b.length <= 32); // b's length must be smaller than or equal to 32\n assembly {\n v := mload(add(b, 32))\n } // load all 32bytes to v\n v = v >> (8 * (32 - b.length)); // only first b.length is valid\n }\n\n function _address(bytes memory b) internal pure returns (address v) {\n v = _addressPayable(b);\n }\n\n function _addressPayable(\n bytes memory b\n ) internal pure returns (address payable v) {\n require(b.length == 20);\n //load 32bytes then shift right 12 bytes\n assembly {\n v := div(mload(add(b, 32)), 0x1000000000000000000000000)\n }\n }\n\n function _bytes32(bytes memory b) internal pure returns (bytes32 v) {\n require(b.length == 32);\n assembly {\n v := mload(add(b, 32))\n }\n }\n}\n"},"src/bridges/BridgeImplBase.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {ISocketGateway} from \"../interfaces/ISocketGateway.sol\";\nimport {ISocketRoute} from \"../interfaces/ISocketRoute.sol\";\nimport {OnlySocketGatewayOwner, OnlySocketDeployer} from \"../errors/SocketErrors.sol\";\n\n/**\n * @title Abstract Implementation Contract.\n * @notice All Bridge Implementation will follow this interface.\n */\nabstract contract BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n /// @notice Address used to identify if it is a native token transfer or not\n address public immutable NATIVE_TOKEN_ADDRESS =\n address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);\n\n /// @notice immutable variable to store the socketGateway address\n address public immutable socketGateway;\n\n /// @notice immutable variable to store the socketGateway address\n address public immutable socketDeployFactory;\n\n /// @notice immutable variable with instance of SocketRoute to access route functions\n ISocketRoute public immutable socketRoute;\n\n /// @notice FunctionSelector used to delegatecall from swap to the function of bridge router implementation\n bytes4 public immutable BRIDGE_AFTER_SWAP_SELECTOR =\n bytes4(keccak256(\"bridgeAfterSwap(uint256,bytes)\"));\n\n /****************************************\n * EVENTS *\n ****************************************/\n\n event SocketBridge(\n uint256 amount,\n address token,\n uint256 toChainId,\n bytes32 bridgeName,\n address sender,\n address receiver,\n bytes32 metadata\n );\n\n /**\n * @notice Construct the base for all BridgeImplementations.\n * @param _socketGateway Socketgateway address, an immutable variable to set.\n * @param _socketDeployFactory Socket Deploy Factory address, an immutable variable to set.\n */\n constructor(address _socketGateway, address _socketDeployFactory) {\n socketGateway = _socketGateway;\n socketDeployFactory = _socketDeployFactory;\n socketRoute = ISocketRoute(_socketGateway);\n }\n\n /****************************************\n * MODIFIERS *\n ****************************************/\n\n /// @notice Implementing contract needs to make use of the modifier where restricted access is to be used\n modifier isSocketGatewayOwner() {\n if (msg.sender != ISocketGateway(socketGateway).owner()) {\n revert OnlySocketGatewayOwner();\n }\n _;\n }\n\n /// @notice Implementing contract needs to make use of the modifier where restricted access is to be used\n modifier isSocketDeployFactory() {\n if (msg.sender != socketDeployFactory) {\n revert OnlySocketDeployer();\n }\n _;\n }\n\n /****************************************\n * RESTRICTED FUNCTIONS *\n ****************************************/\n\n /**\n * @notice function to rescue the ERC20 tokens in the bridge Implementation contract\n * @notice this is a function restricted to Owner of SocketGateway only\n * @param token address of ERC20 token being rescued\n * @param userAddress receipient address to which ERC20 tokens will be rescued to\n * @param amount amount of ERC20 tokens being rescued\n */\n function rescueFunds(\n address token,\n address userAddress,\n uint256 amount\n ) external isSocketGatewayOwner {\n ERC20(token).safeTransfer(userAddress, amount);\n }\n\n /**\n * @notice function to rescue the native-balance in the bridge Implementation contract\n * @notice this is a function restricted to Owner of SocketGateway only\n * @param userAddress receipient address to which native-balance will be rescued to\n * @param amount amount of native balance tokens being rescued\n */\n function rescueEther(\n address payable userAddress,\n uint256 amount\n ) external isSocketGatewayOwner {\n userAddress.transfer(amount);\n }\n\n function killme() external isSocketDeployFactory {\n selfdestruct(payable(msg.sender));\n }\n\n /******************************\n * VIRTUAL FUNCTIONS *\n *****************************/\n\n /**\n * @notice function to bridge which is succeeding the swap function\n * @notice this function is to be used only when bridging as a succeeding step\n * @notice All bridge implementation contracts must implement this function\n * @notice bridge-implementations will have a bridge specific struct with properties used in bridging\n * @param bridgeData encoded value of properties in the bridgeData Struct\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable virtual;\n}\n"},"src/bridges/cbridge/CelerImpl.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport \"../../libraries/Pb.sol\";\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport \"./interfaces/cbridge.sol\";\nimport \"./interfaces/ICelerStorageWrapper.sol\";\nimport {TransferIdExists, InvalidCelerRefund, CelerAlreadyRefunded, CelerRefundNotReady} from \"../../errors/SocketErrors.sol\";\nimport {BridgeImplBase} from \"../BridgeImplBase.sol\";\nimport {CBRIDGE} from \"../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Celer-Route Implementation\n * @notice Route implementation with functions to bridge ERC20 and Native via Celer-Bridge\n * Called via SocketGateway if the routeId in the request maps to the routeId of CelerImplementation\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ncontract CelerImpl is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable CBridgeIdentifier = CBRIDGE;\n\n /// @notice Utility to perform operation on Buffer\n using Pb for Pb.Buffer;\n\n /// @notice Function-selector for ERC20-token bridging on Celer-Route\n /// @dev This function selector is to be used while building transaction-data to bridge ERC20 tokens\n bytes4 public immutable CELER_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(address,address,uint256,bytes32,uint64,uint64,uint32)\"\n )\n );\n\n /// @notice Function-selector for Native bridging on Celer-Route\n /// @dev This function selector is to be used while building transaction-data to bridge Native tokens\n bytes4 public immutable CELER_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeNativeTo(address,uint256,bytes32,uint64,uint64,uint32)\"\n )\n );\n\n bytes4 public immutable CELER_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(address,uint64,uint32,uint64,bytes32))\"\n )\n );\n\n /// @notice router Contract instance used to deposit ERC20 and Native on to Celer-Bridge\n /// @dev contract instance is to be initialized in the constructor using the routerAddress passed as constructor argument\n ICBridge public immutable router;\n\n /// @notice celerStorageWrapper Contract instance used to store the transferId generated during ERC20 and Native bridge on to Celer-Bridge\n /// @dev contract instance is to be initialized in the constructor using the celerStorageWrapperAddress passed as constructor argument\n ICelerStorageWrapper public immutable celerStorageWrapper;\n\n /// @notice WETH token address\n address public immutable weth;\n\n /// @notice chainId used during generation of transferId generated while bridging ERC20 and Native on to Celer-Bridge\n /// @dev this is to be initialised in the constructor\n uint64 public immutable chainId;\n\n struct WithdrawMsg {\n uint64 chainid; // tag: 1\n uint64 seqnum; // tag: 2\n address receiver; // tag: 3\n address token; // tag: 4\n uint256 amount; // tag: 5\n bytes32 refid; // tag: 6\n }\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n /// @dev ensure routerAddress, weth-address, celerStorageWrapperAddress are set properly for the chainId in which the contract is being deployed\n constructor(\n address _routerAddress,\n address _weth,\n address _celerStorageWrapperAddress,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n router = ICBridge(_routerAddress);\n celerStorageWrapper = ICelerStorageWrapper(_celerStorageWrapperAddress);\n weth = _weth;\n chainId = uint64(block.chainid);\n }\n\n // Function to receive Ether. msg.data must be empty\n receive() external payable {}\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct CelerBridgeDataNoToken {\n address receiverAddress;\n uint64 toChainId;\n uint32 maxSlippage;\n uint64 nonce;\n bytes32 metadata;\n }\n\n struct CelerBridgeData {\n address token;\n address receiverAddress;\n uint64 toChainId;\n uint32 maxSlippage;\n uint64 nonce;\n bytes32 metadata;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in CelerBridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for CelerBridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n CelerBridgeData memory celerBridgeData = abi.decode(\n bridgeData,\n (CelerBridgeData)\n );\n\n if (celerBridgeData.token == NATIVE_TOKEN_ADDRESS) {\n // transferId is generated using the request-params and nonce of the account\n // transferId should be unique for each request and this is used while handling refund from celerBridge\n bytes32 transferId = keccak256(\n abi.encodePacked(\n address(this),\n celerBridgeData.receiverAddress,\n weth,\n amount,\n celerBridgeData.toChainId,\n celerBridgeData.nonce,\n chainId\n )\n );\n\n // transferId is stored in CelerStorageWrapper with in a mapping where key is transferId and value is the msg-sender\n celerStorageWrapper.setAddressForTransferId(transferId, msg.sender);\n\n router.sendNative{value: amount}(\n celerBridgeData.receiverAddress,\n amount,\n celerBridgeData.toChainId,\n celerBridgeData.nonce,\n celerBridgeData.maxSlippage\n );\n } else {\n // transferId is generated using the request-params and nonce of the account\n // transferId should be unique for each request and this is used while handling refund from celerBridge\n bytes32 transferId = keccak256(\n abi.encodePacked(\n address(this),\n celerBridgeData.receiverAddress,\n celerBridgeData.token,\n amount,\n celerBridgeData.toChainId,\n celerBridgeData.nonce,\n chainId\n )\n );\n\n // transferId is stored in CelerStorageWrapper with in a mapping where key is transferId and value is the msg-sender\n celerStorageWrapper.setAddressForTransferId(transferId, msg.sender);\n router.send(\n celerBridgeData.receiverAddress,\n celerBridgeData.token,\n amount,\n celerBridgeData.toChainId,\n celerBridgeData.nonce,\n celerBridgeData.maxSlippage\n );\n }\n\n emit SocketBridge(\n amount,\n celerBridgeData.token,\n celerBridgeData.toChainId,\n CBridgeIdentifier,\n msg.sender,\n celerBridgeData.receiverAddress,\n celerBridgeData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in CelerBridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param celerBridgeData encoded data for CelerBridgeData\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n CelerBridgeDataNoToken calldata celerBridgeData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n\n if (token == NATIVE_TOKEN_ADDRESS) {\n // transferId is generated using the request-params and nonce of the account\n // transferId should be unique for each request and this is used while handling refund from celerBridge\n bytes32 transferId = keccak256(\n abi.encodePacked(\n address(this),\n celerBridgeData.receiverAddress,\n weth,\n bridgeAmount,\n celerBridgeData.toChainId,\n celerBridgeData.nonce,\n chainId\n )\n );\n\n // transferId is stored in CelerStorageWrapper with in a mapping where key is transferId and value is the msg-sender\n celerStorageWrapper.setAddressForTransferId(transferId, msg.sender);\n\n router.sendNative{value: bridgeAmount}(\n celerBridgeData.receiverAddress,\n bridgeAmount,\n celerBridgeData.toChainId,\n celerBridgeData.nonce,\n celerBridgeData.maxSlippage\n );\n } else {\n // transferId is generated using the request-params and nonce of the account\n // transferId should be unique for each request and this is used while handling refund from celerBridge\n bytes32 transferId = keccak256(\n abi.encodePacked(\n address(this),\n celerBridgeData.receiverAddress,\n token,\n bridgeAmount,\n celerBridgeData.toChainId,\n celerBridgeData.nonce,\n chainId\n )\n );\n\n // transferId is stored in CelerStorageWrapper with in a mapping where key is transferId and value is the msg-sender\n celerStorageWrapper.setAddressForTransferId(transferId, msg.sender);\n router.send(\n celerBridgeData.receiverAddress,\n token,\n bridgeAmount,\n celerBridgeData.toChainId,\n celerBridgeData.nonce,\n celerBridgeData.maxSlippage\n );\n }\n\n emit SocketBridge(\n bridgeAmount,\n token,\n celerBridgeData.toChainId,\n CBridgeIdentifier,\n msg.sender,\n celerBridgeData.receiverAddress,\n celerBridgeData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via Celer-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param receiverAddress address of recipient\n * @param token address of token being bridged\n * @param amount amount of token for bridging\n * @param toChainId destination ChainId\n * @param nonce nonce of the sender-account address\n * @param maxSlippage maximum Slippage for the bridging\n */\n function bridgeERC20To(\n address receiverAddress,\n address token,\n uint256 amount,\n bytes32 metadata,\n uint64 toChainId,\n uint64 nonce,\n uint32 maxSlippage\n ) external payable {\n /// @notice transferId is generated using the request-params and nonce of the account\n /// @notice transferId should be unique for each request and this is used while handling refund from celerBridge\n bytes32 transferId = keccak256(\n abi.encodePacked(\n address(this),\n receiverAddress,\n token,\n amount,\n toChainId,\n nonce,\n chainId\n )\n );\n\n /// @notice stored in the CelerStorageWrapper contract\n celerStorageWrapper.setAddressForTransferId(transferId, msg.sender);\n\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n router.send(\n receiverAddress,\n token,\n amount,\n toChainId,\n nonce,\n maxSlippage\n );\n\n emit SocketBridge(\n amount,\n token,\n toChainId,\n CBridgeIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n\n /**\n * @notice function to handle Native bridging to receipent via Celer-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param receiverAddress address of recipient\n * @param amount amount of token for bridging\n * @param toChainId destination ChainId\n * @param nonce nonce of the sender-account address\n * @param maxSlippage maximum Slippage for the bridging\n */\n function bridgeNativeTo(\n address receiverAddress,\n uint256 amount,\n bytes32 metadata,\n uint64 toChainId,\n uint64 nonce,\n uint32 maxSlippage\n ) external payable {\n bytes32 transferId = keccak256(\n abi.encodePacked(\n address(this),\n receiverAddress,\n weth,\n amount,\n toChainId,\n nonce,\n chainId\n )\n );\n\n celerStorageWrapper.setAddressForTransferId(transferId, msg.sender);\n\n router.sendNative{value: amount}(\n receiverAddress,\n amount,\n toChainId,\n nonce,\n maxSlippage\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n toChainId,\n CBridgeIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n\n /**\n * @notice function to handle refund from CelerBridge-Router\n * @param _request request data generated offchain using the celer-SDK\n * @param _sigs generated offchain using the celer-SDK\n * @param _signers generated offchain using the celer-SDK\n * @param _powers generated offchain using the celer-SDK\n */\n function refundCelerUser(\n bytes calldata _request,\n bytes[] calldata _sigs,\n address[] calldata _signers,\n uint256[] calldata _powers\n ) external payable {\n WithdrawMsg memory request = decWithdrawMsg(_request);\n bytes32 transferId = keccak256(\n abi.encodePacked(\n request.chainid,\n request.seqnum,\n request.receiver,\n request.token,\n request.amount\n )\n );\n uint256 _initialNativeBalance = address(this).balance;\n uint256 _initialTokenBalance = ERC20(request.token).balanceOf(\n address(this)\n );\n if (!router.withdraws(transferId)) {\n router.withdraw(_request, _sigs, _signers, _powers);\n }\n\n if (request.receiver != socketGateway) {\n revert InvalidCelerRefund();\n }\n\n address _receiver = celerStorageWrapper.getAddressFromTransferId(\n request.refid\n );\n celerStorageWrapper.deleteTransferId(request.refid);\n\n if (_receiver == address(0)) {\n revert CelerAlreadyRefunded();\n }\n\n uint256 _nativeBalanceAfter = address(this).balance;\n uint256 _tokenBalanceAfter = ERC20(request.token).balanceOf(\n address(this)\n );\n if (_nativeBalanceAfter > _initialNativeBalance) {\n if ((_nativeBalanceAfter - _initialNativeBalance) != request.amount)\n revert CelerRefundNotReady();\n payable(_receiver).transfer(request.amount);\n return;\n }\n\n if (_tokenBalanceAfter > _initialTokenBalance) {\n if ((_tokenBalanceAfter - _initialTokenBalance) != request.amount)\n revert CelerRefundNotReady();\n ERC20(request.token).safeTransfer(_receiver, request.amount);\n return;\n }\n\n revert CelerRefundNotReady();\n }\n\n function decWithdrawMsg(\n bytes memory raw\n ) internal pure returns (WithdrawMsg memory m) {\n Pb.Buffer memory buf = Pb.fromBytes(raw);\n\n uint256 tag;\n Pb.WireType wire;\n while (buf.hasMore()) {\n (tag, wire) = buf.decKey();\n if (false) {}\n // solidity has no switch/case\n else if (tag == 1) {\n m.chainid = uint64(buf.decVarint());\n } else if (tag == 2) {\n m.seqnum = uint64(buf.decVarint());\n } else if (tag == 3) {\n m.receiver = Pb._address(buf.decBytes());\n } else if (tag == 4) {\n m.token = Pb._address(buf.decBytes());\n } else if (tag == 5) {\n m.amount = Pb._uint256(buf.decBytes());\n } else if (tag == 6) {\n m.refid = Pb._bytes32(buf.decBytes());\n } else {\n buf.skipValue(wire);\n } // skip value of unknown tag\n }\n } // end decoder WithdrawMsg\n}\n"},"src/libraries/LibBytes.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\n// Functions taken out from https://github.com/GNSPS/solidity-bytes-utils/blob/master/contracts/BytesLib.sol\nlibrary LibBytes {\n // solhint-disable no-inline-assembly\n\n // LibBytes specific errors\n error SliceOverflow();\n error SliceOutOfBounds();\n error AddressOutOfBounds();\n error UintOutOfBounds();\n\n // -------------------------\n\n function concat(\n bytes memory _preBytes,\n bytes memory _postBytes\n ) internal pure returns (bytes memory) {\n bytes memory tempBytes;\n\n assembly {\n // Get a location of some free memory and store it in tempBytes as\n // Solidity does for memory variables.\n tempBytes := mload(0x40)\n\n // Store the length of the first bytes array at the beginning of\n // the memory for tempBytes.\n let length := mload(_preBytes)\n mstore(tempBytes, length)\n\n // Maintain a memory counter for the current write location in the\n // temp bytes array by adding the 32 bytes for the array length to\n // the starting location.\n let mc := add(tempBytes, 0x20)\n // Stop copying when the memory counter reaches the length of the\n // first bytes array.\n let end := add(mc, length)\n\n for {\n // Initialize a copy counter to the start of the _preBytes data,\n // 32 bytes into its memory.\n let cc := add(_preBytes, 0x20)\n } lt(mc, end) {\n // Increase both counters by 32 bytes each iteration.\n mc := add(mc, 0x20)\n cc := add(cc, 0x20)\n } {\n // Write the _preBytes data into the tempBytes memory 32 bytes\n // at a time.\n mstore(mc, mload(cc))\n }\n\n // Add the length of _postBytes to the current length of tempBytes\n // and store it as the new length in the first 32 bytes of the\n // tempBytes memory.\n length := mload(_postBytes)\n mstore(tempBytes, add(length, mload(tempBytes)))\n\n // Move the memory counter back from a multiple of 0x20 to the\n // actual end of the _preBytes data.\n mc := end\n // Stop copying when the memory counter reaches the new combined\n // length of the arrays.\n end := add(mc, length)\n\n for {\n let cc := add(_postBytes, 0x20)\n } lt(mc, end) {\n mc := add(mc, 0x20)\n cc := add(cc, 0x20)\n } {\n mstore(mc, mload(cc))\n }\n\n // Update the free-memory pointer by padding our last write location\n // to 32 bytes: add 31 bytes to the end of tempBytes to move to the\n // next 32 byte block, then round down to the nearest multiple of\n // 32. If the sum of the length of the two arrays is zero then add\n // one before rounding down to leave a blank 32 bytes (the length block with 0).\n mstore(\n 0x40,\n and(\n add(add(end, iszero(add(length, mload(_preBytes)))), 31),\n not(31) // Round down to the nearest 32 bytes.\n )\n )\n }\n\n return tempBytes;\n }\n\n function slice(\n bytes memory _bytes,\n uint256 _start,\n uint256 _length\n ) internal pure returns (bytes memory) {\n if (_length + 31 < _length) {\n revert SliceOverflow();\n }\n if (_bytes.length < _start + _length) {\n revert SliceOutOfBounds();\n }\n\n bytes memory tempBytes;\n\n assembly {\n switch iszero(_length)\n case 0 {\n // Get a location of some free memory and store it in tempBytes as\n // Solidity does for memory variables.\n tempBytes := mload(0x40)\n\n // The first word of the slice result is potentially a partial\n // word read from the original array. To read it, we calculate\n // the length of that partial word and start copying that many\n // bytes into the array. The first word we copy will start with\n // data we don't care about, but the last `lengthmod` bytes will\n // land at the beginning of the contents of the new array. When\n // we're done copying, we overwrite the full first word with\n // the actual length of the slice.\n let lengthmod := and(_length, 31)\n\n // The multiplication in the next line is necessary\n // because when slicing multiples of 32 bytes (lengthmod == 0)\n // the following copy loop was copying the origin's length\n // and then ending prematurely not copying everything it should.\n let mc := add(\n add(tempBytes, lengthmod),\n mul(0x20, iszero(lengthmod))\n )\n let end := add(mc, _length)\n\n for {\n // The multiplication in the next line has the same exact purpose\n // as the one above.\n let cc := add(\n add(\n add(_bytes, lengthmod),\n mul(0x20, iszero(lengthmod))\n ),\n _start\n )\n } lt(mc, end) {\n mc := add(mc, 0x20)\n cc := add(cc, 0x20)\n } {\n mstore(mc, mload(cc))\n }\n\n mstore(tempBytes, _length)\n\n //update free-memory pointer\n //allocating the array padded to 32 bytes like the compiler does now\n mstore(0x40, and(add(mc, 31), not(31)))\n }\n //if we want a zero-length slice let's just return a zero-length array\n default {\n tempBytes := mload(0x40)\n //zero out the 32 bytes slice we are about to return\n //we need to do it because Solidity does not garbage collect\n mstore(tempBytes, 0)\n\n mstore(0x40, add(tempBytes, 0x20))\n }\n }\n\n return tempBytes;\n }\n}\n"},"src/bridges/hyphen/interfaces/hyphen.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity >=0.8.0;\n\n/**\n * @title HyphenLiquidityPoolManager\n * @notice interface with functions to bridge ERC20 and Native via Hyphen-Bridge\n * @author Socket dot tech.\n */\ninterface HyphenLiquidityPoolManager {\n /**\n * @dev Function used to deposit tokens into pool to initiate a cross chain token transfer.\n * @param toChainId Chain id where funds needs to be transfered\n * @param tokenAddress ERC20 Token address that needs to be transfered\n * @param receiver Address on toChainId where tokens needs to be transfered\n * @param amount Amount of token being transfered\n */\n function depositErc20(\n uint256 toChainId,\n address tokenAddress,\n address receiver,\n uint256 amount,\n string calldata tag\n ) external;\n\n /**\n * @dev Function used to deposit native token into pool to initiate a cross chain token transfer.\n * @param receiver Address on toChainId where tokens needs to be transfered\n * @param toChainId Chain id where funds needs to be transfered\n */\n function depositNative(\n address receiver,\n uint256 toChainId,\n string calldata tag\n ) external payable;\n}\n"},"src/swap/SwapImplBase.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport {ISocketGateway} from \"../interfaces/ISocketGateway.sol\";\nimport {OnlySocketGatewayOwner, OnlySocketDeployer} from \"../errors/SocketErrors.sol\";\n\n/**\n * @title Abstract Implementation Contract.\n * @notice All Swap Implementation will follow this interface.\n * @author Socket dot tech.\n */\nabstract contract SwapImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n /// @notice Address used to identify if it is a native token transfer or not\n address public immutable NATIVE_TOKEN_ADDRESS =\n address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);\n\n /// @notice immutable variable to store the socketGateway address\n address public immutable socketGateway;\n\n /// @notice immutable variable to store the socketGateway address\n address public immutable socketDeployFactory;\n\n /// @notice FunctionSelector used to delegatecall to the performAction function of swap-router-implementation\n bytes4 public immutable SWAP_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\"performAction(address,address,uint256,address,bytes)\")\n );\n\n /// @notice FunctionSelector used to delegatecall to the performActionWithIn function of swap-router-implementation\n bytes4 public immutable SWAP_WITHIN_FUNCTION_SELECTOR =\n bytes4(keccak256(\"performActionWithIn(address,address,uint256,bytes)\"));\n\n /****************************************\n * EVENTS *\n ****************************************/\n\n event SocketSwapTokens(\n address fromToken,\n address toToken,\n uint256 buyAmount,\n uint256 sellAmount,\n bytes32 routeName,\n address receiver\n );\n\n /**\n * @notice Construct the base for all SwapImplementations.\n * @param _socketGateway Socketgateway address, an immutable variable to set.\n */\n constructor(address _socketGateway, address _socketDeployFactory) {\n socketGateway = _socketGateway;\n socketDeployFactory = _socketDeployFactory;\n }\n\n /****************************************\n * MODIFIERS *\n ****************************************/\n\n /// @notice Implementing contract needs to make use of the modifier where restricted access is to be used\n modifier isSocketGatewayOwner() {\n if (msg.sender != ISocketGateway(socketGateway).owner()) {\n revert OnlySocketGatewayOwner();\n }\n _;\n }\n\n /// @notice Implementing contract needs to make use of the modifier where restricted access is to be used\n modifier isSocketDeployFactory() {\n if (msg.sender != socketDeployFactory) {\n revert OnlySocketDeployer();\n }\n _;\n }\n\n /****************************************\n * RESTRICTED FUNCTIONS *\n ****************************************/\n\n /**\n * @notice function to rescue the ERC20 tokens in the Swap-Implementation contract\n * @notice this is a function restricted to Owner of SocketGateway only\n * @param token address of ERC20 token being rescued\n * @param userAddress receipient address to which ERC20 tokens will be rescued to\n * @param amount amount of ERC20 tokens being rescued\n */\n function rescueFunds(\n address token,\n address userAddress,\n uint256 amount\n ) external isSocketGatewayOwner {\n ERC20(token).safeTransfer(userAddress, amount);\n }\n\n /**\n * @notice function to rescue the native-balance in the Swap-Implementation contract\n * @notice this is a function restricted to Owner of SocketGateway only\n * @param userAddress receipient address to which native-balance will be rescued to\n * @param amount amount of native balance tokens being rescued\n */\n function rescueEther(\n address payable userAddress,\n uint256 amount\n ) external isSocketGatewayOwner {\n userAddress.transfer(amount);\n }\n\n function killme() external isSocketDeployFactory {\n selfdestruct(payable(msg.sender));\n }\n\n /******************************\n * VIRTUAL FUNCTIONS *\n *****************************/\n\n /**\n * @notice function to swap tokens on the chain\n * All swap implementation contracts must implement this function\n * @param fromToken token to be swapped\n * @param toToken token to which fromToken has to be swapped\n * @param amount amount of fromToken being swapped\n * @param receiverAddress recipient address of toToken\n * @param data encoded value of properties in the swapData Struct\n */\n function performAction(\n address fromToken,\n address toToken,\n uint256 amount,\n address receiverAddress,\n bytes memory data\n ) external payable virtual returns (uint256);\n\n /**\n * @notice function to swapWith - swaps tokens on the chain to socketGateway as recipient\n * All swap implementation contracts must implement this function\n * @param fromToken token to be swapped\n * @param toToken token to which fromToken has to be swapped\n * @param amount amount of fromToken being swapped\n * @param swapExtraData encoded value of properties in the swapData Struct\n */\n function performActionWithIn(\n address fromToken,\n address toToken,\n uint256 amount,\n bytes memory swapExtraData\n ) external payable virtual returns (uint256, address);\n}\n"},"src/swap/zerox/ZeroXSwapImpl.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >=0.8.0;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport \"../SwapImplBase.sol\";\nimport {Address0Provided, SwapFailed} from \"../../errors/SocketErrors.sol\";\nimport {ZEROX} from \"../../static/RouteIdentifiers.sol\";\n\n/**\n * @title ZeroX-Swap-Route Implementation\n * @notice Route implementation with functions to swap tokens via ZeroX-Swap\n * Called via SocketGateway if the routeId in the request maps to the routeId of ZeroX-Swap-Implementation\n * @author Socket dot tech.\n */\ncontract ZeroXSwapImpl is SwapImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable ZeroXIdentifier = ZEROX;\n\n /// @notice unique name to identify the router, used to emit event upon successful bridging\n bytes32 public immutable NAME = keccak256(\"Zerox-Router\");\n\n /// @notice address of ZeroX-Exchange-Proxy to swap the tokens on Chain\n address payable public immutable zeroXExchangeProxy;\n\n /// @notice socketGatewayAddress to be initialised via storage variable SwapImplBase\n /// @notice ZeroXExchangeProxy contract is payable to allow ethereum swaps\n /// @dev ensure _zeroXExchangeProxy are set properly for the chainId in which the contract is being deployed\n constructor(\n address _zeroXExchangeProxy,\n address _socketGateway,\n address _socketDeployFactory\n ) SwapImplBase(_socketGateway, _socketDeployFactory) {\n zeroXExchangeProxy = payable(_zeroXExchangeProxy);\n }\n\n receive() external payable {}\n\n fallback() external payable {}\n\n /**\n * @notice function to swap tokens on the chain and transfer to receiver address\n * @dev This is called only when there is a request for a swap.\n * @param fromToken token to be swapped\n * @param toToken token to which fromToken is to be swapped\n * @param amount amount to be swapped\n * @param receiverAddress address of toToken recipient\n * @param swapExtraData data required for zeroX Exchange to get the swap done\n */\n function performAction(\n address fromToken,\n address toToken,\n uint256 amount,\n address receiverAddress,\n bytes calldata swapExtraData\n ) external payable override returns (uint256) {\n if (fromToken == address(0)) {\n revert Address0Provided();\n }\n\n bytes memory swapCallData = abi.decode(swapExtraData, (bytes));\n\n uint256 _initialBalanceTokenOut;\n uint256 _finalBalanceTokenOut;\n\n ERC20 erc20ToToken = ERC20(toToken);\n if (toToken != NATIVE_TOKEN_ADDRESS) {\n _initialBalanceTokenOut = erc20ToToken.balanceOf(address(this));\n } else {\n _initialBalanceTokenOut = address(this).balance;\n }\n\n if (fromToken != NATIVE_TOKEN_ADDRESS) {\n ERC20 token = ERC20(fromToken);\n token.safeTransferFrom(msg.sender, address(this), amount);\n token.safeApprove(zeroXExchangeProxy, amount);\n\n // solhint-disable-next-line\n (bool success, ) = zeroXExchangeProxy.call(swapCallData);\n\n if (!success) {\n revert SwapFailed();\n }\n\n token.safeApprove(zeroXExchangeProxy, 0);\n } else {\n (bool success, ) = zeroXExchangeProxy.call{value: amount}(\n swapCallData\n );\n if (!success) {\n revert SwapFailed();\n }\n }\n\n if (toToken != NATIVE_TOKEN_ADDRESS) {\n _finalBalanceTokenOut = erc20ToToken.balanceOf(address(this));\n } else {\n _finalBalanceTokenOut = address(this).balance;\n }\n\n uint256 returnAmount = _finalBalanceTokenOut - _initialBalanceTokenOut;\n\n if (toToken == NATIVE_TOKEN_ADDRESS) {\n payable(receiverAddress).transfer(returnAmount);\n } else {\n erc20ToToken.transfer(receiverAddress, returnAmount);\n }\n\n emit SocketSwapTokens(\n fromToken,\n toToken,\n returnAmount,\n amount,\n ZeroXIdentifier,\n receiverAddress\n );\n\n return returnAmount;\n }\n\n /**\n * @notice function to swapWithIn SocketGateway - swaps tokens on the chain to socketGateway as recipient\n * @param fromToken token to be swapped\n * @param toToken token to which fromToken has to be swapped\n * @param amount amount of fromToken being swapped\n * @param swapExtraData encoded value of properties in the swapData Struct\n * @return swapped amount (in toToken Address)\n */\n function performActionWithIn(\n address fromToken,\n address toToken,\n uint256 amount,\n bytes calldata swapExtraData\n ) external payable override returns (uint256, address) {\n if (fromToken == address(0)) {\n revert Address0Provided();\n }\n\n bytes memory swapCallData = abi.decode(swapExtraData, (bytes));\n\n uint256 _initialBalanceTokenOut;\n uint256 _finalBalanceTokenOut;\n\n ERC20 erc20ToToken = ERC20(toToken);\n if (toToken != NATIVE_TOKEN_ADDRESS) {\n _initialBalanceTokenOut = erc20ToToken.balanceOf(address(this));\n } else {\n _initialBalanceTokenOut = address(this).balance;\n }\n\n if (fromToken != NATIVE_TOKEN_ADDRESS) {\n ERC20 token = ERC20(fromToken);\n token.safeTransferFrom(msg.sender, address(this), amount);\n token.safeApprove(zeroXExchangeProxy, amount);\n\n // solhint-disable-next-line\n (bool success, ) = zeroXExchangeProxy.call(swapCallData);\n\n if (!success) {\n revert SwapFailed();\n }\n\n token.safeApprove(zeroXExchangeProxy, 0);\n } else {\n (bool success, ) = zeroXExchangeProxy.call{value: amount}(\n swapCallData\n );\n if (!success) {\n revert SwapFailed();\n }\n }\n\n if (toToken != NATIVE_TOKEN_ADDRESS) {\n _finalBalanceTokenOut = erc20ToToken.balanceOf(address(this));\n } else {\n _finalBalanceTokenOut = address(this).balance;\n }\n\n uint256 returnAmount = _finalBalanceTokenOut - _initialBalanceTokenOut;\n\n emit SocketSwapTokens(\n fromToken,\n toToken,\n returnAmount,\n amount,\n ZeroXIdentifier,\n socketGateway\n );\n\n return (returnAmount, toToken);\n }\n}\n"},"src/bridges/cbridge/interfaces/cbridge.sol":{"content":"// SPDX-License-Identifier: Apache-2.0\npragma solidity >=0.8.0;\n\ninterface ICBridge {\n function send(\n address _receiver,\n address _token,\n uint256 _amount,\n uint64 _dstChinId,\n uint64 _nonce,\n uint32 _maxSlippage\n ) external;\n\n function sendNative(\n address _receiver,\n uint256 _amount,\n uint64 _dstChinId,\n uint64 _nonce,\n uint32 _maxSlippage\n ) external payable;\n\n function withdraws(bytes32 withdrawId) external view returns (bool);\n\n function withdraw(\n bytes calldata _wdmsg,\n bytes[] calldata _sigs,\n address[] calldata _signers,\n uint256[] calldata _powers\n ) external;\n}\n"},"src/bridges/stargate/l2/Stargate.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\nimport {SafeTransferLib} from \"lib/solmate/src/utils/SafeTransferLib.sol\";\nimport {ERC20} from \"lib/solmate/src/tokens/ERC20.sol\";\nimport \"../interfaces/stargate.sol\";\nimport \"../../../errors/SocketErrors.sol\";\nimport {BridgeImplBase} from \"../../BridgeImplBase.sol\";\nimport {STARGATE} from \"../../../static/RouteIdentifiers.sol\";\n\n/**\n * @title Stargate-L2-Route Implementation\n * @notice Route implementation with functions to bridge ERC20 and Native via Stargate-L2-Bridge\n * Called via SocketGateway if the routeId in the request maps to the routeId of Stargate-L2-Implementation\n * Contains function to handle bridging as post-step i.e linked to a preceeding step for swap\n * RequestData is different to just bride and bridging chained with swap\n * @author Socket dot tech.\n */\ncontract StargateImplL2 is BridgeImplBase {\n /// @notice SafeTransferLib - library for safe and optimised operations on ERC20 tokens\n using SafeTransferLib for ERC20;\n\n bytes32 public immutable StargateIdentifier = STARGATE;\n\n /// @notice Function-selector for ERC20-token bridging on Stargate-L2-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge ERC20 tokens\n bytes4\n public immutable STARGATE_L2_ERC20_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeERC20To(address,address,address,uint256,uint256,uint256,(uint256,uint256,uint256,uint256,bytes32,bytes,uint16))\"\n )\n );\n\n bytes4 public immutable STARGATE_L1_SWAP_BRIDGE_SELECTOR =\n bytes4(\n keccak256(\n \"swapAndBridge(uint32,bytes,(address,address,uint16,uint256,uint256,uint256,uint256,uint256,uint256,bytes32,bytes))\"\n )\n );\n\n /// @notice Function-selector for Native bridging on Stargate-L2-Route\n /// @dev This function selector is to be used while buidling transaction-data to bridge Native tokens\n bytes4\n public immutable STARGATE_L2_NATIVE_EXTERNAL_BRIDGE_FUNCTION_SELECTOR =\n bytes4(\n keccak256(\n \"bridgeNativeTo(address,address,uint16,uint256,uint256,uint256,bytes32)\"\n )\n );\n\n /// @notice Stargate Router to bridge ERC20 tokens\n IBridgeStargate public immutable router;\n\n /// @notice Stargate Router to bridge native tokens\n IBridgeStargate public immutable routerETH;\n\n /// @notice socketGatewayAddress to be initialised via storage variable BridgeImplBase\n /// @dev ensure router, routerEth are set properly for the chainId in which the contract is being deployed\n constructor(\n address _router,\n address _routerEth,\n address _socketGateway,\n address _socketDeployFactory\n ) BridgeImplBase(_socketGateway, _socketDeployFactory) {\n router = IBridgeStargate(_router);\n routerETH = IBridgeStargate(_routerEth);\n }\n\n /// @notice Struct to be used as a input parameter for Bridging tokens via Stargate-L2-route\n /// @dev while building transactionData,values should be set in this sequence of properties in this struct\n struct StargateBridgeExtraData {\n uint256 srcPoolId;\n uint256 dstPoolId;\n uint256 destinationGasLimit;\n uint256 minReceivedAmt;\n bytes32 metadata;\n bytes destinationPayload;\n uint16 stargateDstChainId; // stargate defines chain id in its way\n }\n\n /// @notice Struct to be used in decode step from input parameter - a specific case of bridging after swap.\n /// @dev the data being encoded in offchain or by caller should have values set in this sequence of properties in this struct\n struct StargateBridgeDataNoToken {\n address receiverAddress;\n address senderAddress;\n uint16 stargateDstChainId; // stargate defines chain id in its way\n uint256 value;\n // a unique identifier that is uses to dedup transfers\n // this value is the a timestamp sent from frontend, but in theory can be any unique number\n uint256 srcPoolId;\n uint256 dstPoolId;\n uint256 minReceivedAmt; // defines the slippage, the min qty you would accept on the destination\n uint256 optionalValue;\n uint256 destinationGasLimit;\n bytes32 metadata;\n bytes destinationPayload;\n }\n\n struct StargateBridgeData {\n address token;\n address receiverAddress;\n address senderAddress;\n uint16 stargateDstChainId; // stargate defines chain id in its way\n uint256 value;\n // a unique identifier that is uses to dedup transfers\n // this value is the a timestamp sent from frontend, but in theory can be any unique number\n uint256 srcPoolId;\n uint256 dstPoolId;\n uint256 minReceivedAmt; // defines the slippage, the min qty you would accept on the destination\n uint256 optionalValue;\n uint256 destinationGasLimit;\n bytes32 metadata;\n bytes destinationPayload;\n }\n\n /**\n * @notice function to bridge tokens after swap.\n * @notice this is different from swapAndBridge, this function is called when the swap has already happened at a different place.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in Stargate-BridgeData struct\n * @param amount amount of tokens being bridged. this can be ERC20 or native\n * @param bridgeData encoded data for Stargate-L1-Bridge\n */\n function bridgeAfterSwap(\n uint256 amount,\n bytes calldata bridgeData\n ) external payable override {\n StargateBridgeData memory stargateBridgeData = abi.decode(\n bridgeData,\n (StargateBridgeData)\n );\n\n if (stargateBridgeData.token == NATIVE_TOKEN_ADDRESS) {\n // perform bridging\n routerETH.swapETH{value: amount + stargateBridgeData.optionalValue}(\n stargateBridgeData.stargateDstChainId,\n payable(stargateBridgeData.senderAddress),\n abi.encodePacked(stargateBridgeData.receiverAddress),\n amount,\n stargateBridgeData.minReceivedAmt\n );\n } else {\n ERC20(stargateBridgeData.token).safeApprove(\n address(router),\n amount\n );\n {\n router.swap{value: stargateBridgeData.value}(\n stargateBridgeData.stargateDstChainId,\n stargateBridgeData.srcPoolId,\n stargateBridgeData.dstPoolId,\n payable(stargateBridgeData.senderAddress), // default to refund to main contract\n amount,\n stargateBridgeData.minReceivedAmt,\n IBridgeStargate.lzTxObj(\n stargateBridgeData.destinationGasLimit,\n 0, // zero amount since this is a ERC20 bridging\n \"0x\" //empty data since this is for only ERC20\n ),\n abi.encodePacked(stargateBridgeData.receiverAddress),\n stargateBridgeData.destinationPayload\n );\n }\n }\n\n emit SocketBridge(\n amount,\n stargateBridgeData.token,\n stargateBridgeData.stargateDstChainId,\n StargateIdentifier,\n msg.sender,\n stargateBridgeData.receiverAddress,\n stargateBridgeData.metadata\n );\n }\n\n /**\n * @notice function to bridge tokens after swapping.\n * @notice this is different from bridgeAfterSwap since this function holds the logic for swapping tokens too.\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @dev for usage, refer to controller implementations\n * encodedData for bridge should follow the sequence of properties in Stargate-BridgeData struct\n * @param swapId routeId for the swapImpl\n * @param swapData encoded data for swap\n * @param stargateBridgeData encoded data for StargateBridgeData\n */\n function swapAndBridge(\n uint32 swapId,\n bytes calldata swapData,\n StargateBridgeDataNoToken calldata stargateBridgeData\n ) external payable {\n (bool success, bytes memory result) = socketRoute\n .getRoute(swapId)\n .delegatecall(swapData);\n\n if (!success) {\n assembly {\n revert(add(result, 32), mload(result))\n }\n }\n\n (uint256 bridgeAmount, address token) = abi.decode(\n result,\n (uint256, address)\n );\n\n if (token == NATIVE_TOKEN_ADDRESS) {\n routerETH.swapETH{\n value: bridgeAmount + stargateBridgeData.optionalValue\n }(\n stargateBridgeData.stargateDstChainId,\n payable(stargateBridgeData.senderAddress),\n abi.encodePacked(stargateBridgeData.receiverAddress),\n bridgeAmount,\n stargateBridgeData.minReceivedAmt\n );\n } else {\n ERC20(token).safeApprove(address(router), bridgeAmount);\n {\n router.swap{value: stargateBridgeData.value}(\n stargateBridgeData.stargateDstChainId,\n stargateBridgeData.srcPoolId,\n stargateBridgeData.dstPoolId,\n payable(stargateBridgeData.senderAddress), // default to refund to main contract\n bridgeAmount,\n stargateBridgeData.minReceivedAmt,\n IBridgeStargate.lzTxObj(\n stargateBridgeData.destinationGasLimit,\n 0,\n \"0x\"\n ),\n abi.encodePacked(stargateBridgeData.receiverAddress),\n stargateBridgeData.destinationPayload\n );\n }\n }\n\n emit SocketBridge(\n bridgeAmount,\n token,\n stargateBridgeData.stargateDstChainId,\n StargateIdentifier,\n msg.sender,\n stargateBridgeData.receiverAddress,\n stargateBridgeData.metadata\n );\n }\n\n /**\n * @notice function to handle ERC20 bridging to receipent via Stargate-L1-Bridge\n * @notice This method is payable because the caller is doing token transfer and briding operation\n * @param token address of token being bridged\n * @param senderAddress address of sender\n * @param receiverAddress address of recipient\n * @param amount amount of token being bridge\n * @param value value\n * @param optionalValue optionalValue\n * @param stargateBridgeExtraData stargate bridge extradata\n */\n function bridgeERC20To(\n address token,\n address senderAddress,\n address receiverAddress,\n uint256 amount,\n uint256 value,\n uint256 optionalValue,\n StargateBridgeExtraData calldata stargateBridgeExtraData\n ) external payable {\n // token address might not be indication thats why passed through extraData\n if (token == NATIVE_TOKEN_ADDRESS) {\n // perform bridging\n routerETH.swapETH{value: amount + optionalValue}(\n stargateBridgeExtraData.stargateDstChainId,\n payable(senderAddress),\n abi.encodePacked(receiverAddress),\n amount,\n stargateBridgeExtraData.minReceivedAmt\n );\n } else {\n ERC20 tokenInstance = ERC20(token);\n tokenInstance.safeTransferFrom(msg.sender, socketGateway, amount);\n tokenInstance.safeApprove(address(router), amount);\n {\n router.swap{value: value}(\n stargateBridgeExtraData.stargateDstChainId,\n stargateBridgeExtraData.srcPoolId,\n stargateBridgeExtraData.dstPoolId,\n payable(senderAddress), // default to refund to main contract\n amount,\n stargateBridgeExtraData.minReceivedAmt,\n IBridgeStargate.lzTxObj(\n stargateBridgeExtraData.destinationGasLimit,\n 0, // zero amount since this is a ERC20 bridging\n \"0x\" //empty data since this is for only ERC20\n ),\n abi.encodePacked(receiverAddress),\n stargateBridgeExtraData.destinationPayload\n );\n }\n }\n\n emit SocketBridge(\n amount,\n token,\n stargateBridgeExtraData.stargateDstChainId,\n StargateIdentifier,\n msg.sender,\n receiverAddress,\n stargateBridgeExtraData.metadata\n );\n }\n\n function bridgeNativeTo(\n address receiverAddress,\n address senderAddress,\n uint16 stargateDstChainId,\n uint256 amount,\n uint256 minReceivedAmt,\n uint256 optionalValue,\n bytes32 metadata\n ) external payable {\n // perform bridging\n routerETH.swapETH{value: amount + optionalValue}(\n stargateDstChainId,\n payable(senderAddress),\n abi.encodePacked(receiverAddress),\n amount,\n minReceivedAmt\n );\n\n emit SocketBridge(\n amount,\n NATIVE_TOKEN_ADDRESS,\n stargateDstChainId,\n StargateIdentifier,\n msg.sender,\n receiverAddress,\n metadata\n );\n }\n}\n"}},"settings":{"optimizer":{"enabled":true,"runs":1000000},"outputSelection":{"*":{"*":["evm.bytecode","evm.deployedBytecode","devdoc","userdoc","metadata","abi"]}},"metadata":{"useLiteralContent":true},"libraries":{}}},"ABI":"[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_disabledRoute\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"ArrayLengthMismatch\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"IncorrectBridgeRatios\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyNominee\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"OnlyOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ZeroAddressNotAllowed\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"controllerId\",\"type\":\"uint32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"controllerAddress\",\"type\":\"address\"}],\"name\":\"ControllerAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"controllerId\",\"type\":\"uint32\"}],\"name\":\"ControllerDisabled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"routeId\",\"type\":\"uint32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"route\",\"type\":\"address\"}],\"name\":\"NewRouteAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"claimer\",\"type\":\"address\"}],\"name\":\"OwnerClaimed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"nominee\",\"type\":\"address\"}],\"name\":\"OwnerNominated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"_from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"_to\",\"type\":\"address\"}],\"name\":\"OwnershipTransferRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint32\",\"name\":\"routeId\",\"type\":\"uint32\"}],\"name\":\"RouteDisabled\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"inputs\":[],\"name\":\"BRIDGE_AFTER_SWAP_SELECTOR\",\"outputs\":[{\"internalType\":\"bytes4\",\"name\":\"\",\"type\":\"bytes4\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"CENT_PERCENT\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"controllerAddress\",\"type\":\"address\"}],\"name\":\"addController\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"routeAddress\",\"type\":\"address\"}],\"name\":\"addRoute\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"routeId\",\"type\":\"uint32\"}],\"name\":\"addressAt\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"claimOwner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"controllerCount\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"name\":\"controllers\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"controllerId\",\"type\":\"uint32\"}],\"name\":\"disableController\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"routeId\",\"type\":\"uint32\"}],\"name\":\"disableRoute\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"disabledRouteAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"controllerId\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"internalType\":\"struct ISocketGateway.SocketControllerRequest\",\"name\":\"socketControllerRequest\",\"type\":\"tuple\"}],\"name\":\"executeController\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"controllerId\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"internalType\":\"struct ISocketGateway.SocketControllerRequest[]\",\"name\":\"controllerRequests\",\"type\":\"tuple[]\"}],\"name\":\"executeControllers\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"routeId\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"routeData\",\"type\":\"bytes\"}],\"name\":\"executeRoute\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32[]\",\"name\":\"routeIds\",\"type\":\"uint32[]\"},{\"internalType\":\"bytes[]\",\"name\":\"dataItems\",\"type\":\"bytes[]\"}],\"name\":\"executeRoutes\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"controllerId\",\"type\":\"uint32\"}],\"name\":\"getController\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"routeId\",\"type\":\"uint32\"}],\"name\":\"getRoute\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"nominee_\",\"type\":\"address\"}],\"name\":\"nominateOwner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"nominee\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address payable\",\"name\":\"userAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"rescueEther\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"userAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"rescueFunds\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"name\":\"routes\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"routesCount\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"routeAddresses\",\"type\":\"address[]\"},{\"internalType\":\"address[]\",\"name\":\"tokenAddresses\",\"type\":\"address[]\"},{\"internalType\":\"bool\",\"name\":\"isMax\",\"type\":\"bool\"}],\"name\":\"setApprovalForRouters\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"swapRouteId\",\"type\":\"uint32\"},{\"internalType\":\"bytes\",\"name\":\"swapImplData\",\"type\":\"bytes\"},{\"internalType\":\"uint32[]\",\"name\":\"bridgeRouteIds\",\"type\":\"uint32[]\"},{\"internalType\":\"bytes[]\",\"name\":\"bridgeImplDataItems\",\"type\":\"bytes[]\"},{\"internalType\":\"uint256[]\",\"name\":\"bridgeRatios\",\"type\":\"uint256[]\"},{\"internalType\":\"bytes[]\",\"name\":\"eventDataItems\",\"type\":\"bytes[]\"}],\"internalType\":\"struct ISocketRequest.SwapMultiBridgeRequest\",\"name\":\"swapMultiBridgeRequest\",\"type\":\"tuple\"}],\"name\":\"swapAndMultiBridge\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]","ContractName":"SocketGateway","CompilerVersion":"v0.8.7+commit.e28d00a7","OptimizationUsed":1,"Runs":1000000,"ConstructorArguments":"0x000000000000000000000000e8dd38e673a93ccfc2e3d7053efccb5c93f493650000000000000000000000000f34a522ff82151c90679b73211955068fd854f1","EVMVersion":"Default","Library":"","LicenseType":"","Proxy":1,"Implementation":"0xa3c4e32af0da5efaddb20cc9fb26159f55c8c42f","SwarmSource":""}] \ No newline at end of file diff --git a/testdata/etherscan/0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05/creation_data.json b/testdata/etherscan/0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05/creation_data.json new file mode 100644 index 000000000..08b838e2b --- /dev/null +++ b/testdata/etherscan/0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05/creation_data.json @@ -0,0 +1 @@ +{"contractAddress":"0x71356e37e0368bd10bfdbf41dc052fe5fa24cd05","contractCreator":"0xaa1d342354d755ec515f40e7d5e83cb4184bb9ee","txHash":"0x1c800c2c2d5230823602cae6896a8db1ab7d1341ca697c6c64d9f0edf11dabe2"} \ No newline at end of file diff --git a/testdata/etherscan/0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05/metadata.json b/testdata/etherscan/0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05/metadata.json new file mode 100644 index 000000000..08318ba41 --- /dev/null +++ b/testdata/etherscan/0x71356E37e0368Bd10bFDbF41dC052fE5FA24cD05/metadata.json @@ -0,0 +1 @@ +[{"SourceCode":{"language":"Solidity","sources":{"@openzeppelin/contracts/access/IAccessControl.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev External interface of AccessControl declared to support ERC165 detection.\n */\ninterface IAccessControl {\n /**\n * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`\n *\n * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite\n * {RoleAdminChanged} not being emitted signaling this.\n *\n * _Available since v3.1._\n */\n event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);\n\n /**\n * @dev Emitted when `account` is granted `role`.\n *\n * `sender` is the account that originated the contract call, an admin role\n * bearer except when using {AccessControl-_setupRole}.\n */\n event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Emitted when `account` is revoked `role`.\n *\n * `sender` is the account that originated the contract call:\n * - if using `revokeRole`, it is the admin role bearer\n * - if using `renounceRole`, it is the role bearer (i.e. `account`)\n */\n event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);\n\n /**\n * @dev Returns `true` if `account` has been granted `role`.\n */\n function hasRole(bytes32 role, address account) external view returns (bool);\n\n /**\n * @dev Returns the admin role that controls `role`. See {grantRole} and\n * {revokeRole}.\n *\n * To change a role's admin, use {AccessControl-_setRoleAdmin}.\n */\n function getRoleAdmin(bytes32 role) external view returns (bytes32);\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function grantRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function revokeRole(bytes32 role, address account) external;\n\n /**\n * @dev Revokes `role` from the calling account.\n *\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\n * purpose is to provide a mechanism for accounts to lose their privileges\n * if they are compromised (such as when a trusted device is misplaced).\n *\n * If the calling account had been granted `role`, emits a {RoleRevoked}\n * event.\n *\n * Requirements:\n *\n * - the caller must be `account`.\n */\n function renounceRole(bytes32 role, address account) external;\n}\n"},"@openzeppelin/contracts/token/ERC721/IERC721.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (token/ERC721/IERC721.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../../utils/introspection/IERC165.sol\";\n\n/**\n * @dev Required interface of an ERC721 compliant contract.\n */\ninterface IERC721 is IERC165 {\n /**\n * @dev Emitted when `tokenId` token is transferred from `from` to `to`.\n */\n event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);\n\n /**\n * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.\n */\n event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);\n\n /**\n * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.\n */\n event ApprovalForAll(address indexed owner, address indexed operator, bool approved);\n\n /**\n * @dev Returns the number of tokens in ``owner``'s account.\n */\n function balanceOf(address owner) external view returns (uint256 balance);\n\n /**\n * @dev Returns the owner of the `tokenId` token.\n *\n * Requirements:\n *\n * - `tokenId` must exist.\n */\n function ownerOf(uint256 tokenId) external view returns (address owner);\n\n /**\n * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\n * are aware of the ERC721 protocol to prevent tokens from being forever locked.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must exist and be owned by `from`.\n * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}.\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\n *\n * Emits a {Transfer} event.\n */\n function safeTransferFrom(\n address from,\n address to,\n uint256 tokenId\n ) external;\n\n /**\n * @dev Transfers `tokenId` token from `from` to `to`.\n *\n * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must be owned by `from`.\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address from,\n address to,\n uint256 tokenId\n ) external;\n\n /**\n * @dev Gives permission to `to` to transfer `tokenId` token to another account.\n * The approval is cleared when the token is transferred.\n *\n * Only a single account can be approved at a time, so approving the zero address clears previous approvals.\n *\n * Requirements:\n *\n * - The caller must own the token or be an approved operator.\n * - `tokenId` must exist.\n *\n * Emits an {Approval} event.\n */\n function approve(address to, uint256 tokenId) external;\n\n /**\n * @dev Returns the account approved for `tokenId` token.\n *\n * Requirements:\n *\n * - `tokenId` must exist.\n */\n function getApproved(uint256 tokenId) external view returns (address operator);\n\n /**\n * @dev Approve or remove `operator` as an operator for the caller.\n * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.\n *\n * Requirements:\n *\n * - The `operator` cannot be the caller.\n *\n * Emits an {ApprovalForAll} event.\n */\n function setApprovalForAll(address operator, bool _approved) external;\n\n /**\n * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.\n *\n * See {setApprovalForAll}\n */\n function isApprovedForAll(address owner, address operator) external view returns (bool);\n\n /**\n * @dev Safely transfers `tokenId` token from `from` to `to`.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must exist and be owned by `from`.\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\n *\n * Emits a {Transfer} event.\n */\n function safeTransferFrom(\n address from,\n address to,\n uint256 tokenId,\n bytes calldata data\n ) external;\n}\n"},"@openzeppelin/contracts/access/IAccessControlEnumerable.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (access/IAccessControlEnumerable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IAccessControl.sol\";\n\n/**\n * @dev External interface of AccessControlEnumerable declared to support ERC165 detection.\n */\ninterface IAccessControlEnumerable is IAccessControl {\n /**\n * @dev Returns one of the accounts that have `role`. `index` must be a\n * value between 0 and {getRoleMemberCount}, non-inclusive.\n *\n * Role bearers are not sorted in any particular way, and their ordering may\n * change at any point.\n *\n * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure\n * you perform all queries on the same block. See the following\n * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]\n * for more information.\n */\n function getRoleMember(bytes32 role, uint256 index) external view returns (address);\n\n /**\n * @dev Returns the number of accounts that have `role`. Can be used\n * together with {getRoleMember} to enumerate all bearers of a role.\n */\n function getRoleMemberCount(bytes32 role) external view returns (uint256);\n}\n"},"@openzeppelin/contracts/utils/StorageSlot.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/StorageSlot.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Library for reading and writing primitive types to specific storage slots.\n *\n * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.\n * This library helps with reading and writing to such slots without the need for inline assembly.\n *\n * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.\n *\n * Example usage to set ERC1967 implementation slot:\n * ```\n * contract ERC1967 {\n * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;\n *\n * function _getImplementation() internal view returns (address) {\n * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;\n * }\n *\n * function _setImplementation(address newImplementation) internal {\n * require(Address.isContract(newImplementation), \"ERC1967: new implementation is not a contract\");\n * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;\n * }\n * }\n * ```\n *\n * _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._\n */\nlibrary StorageSlot {\n struct AddressSlot {\n address value;\n }\n\n struct BooleanSlot {\n bool value;\n }\n\n struct Bytes32Slot {\n bytes32 value;\n }\n\n struct Uint256Slot {\n uint256 value;\n }\n\n /**\n * @dev Returns an `AddressSlot` with member `value` located at `slot`.\n */\n function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {\n assembly {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns an `BooleanSlot` with member `value` located at `slot`.\n */\n function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {\n assembly {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.\n */\n function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {\n assembly {\n r.slot := slot\n }\n }\n\n /**\n * @dev Returns an `Uint256Slot` with member `value` located at `slot`.\n */\n function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {\n assembly {\n r.slot := slot\n }\n }\n}\n"},"@openzeppelin/contracts/utils/cryptography/ECDSA.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0) (utils/cryptography/ECDSA.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../Strings.sol\";\n\n/**\n * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.\n *\n * These functions can be used to verify that a message was signed by the holder\n * of the private keys of a given address.\n */\nlibrary ECDSA {\n enum RecoverError {\n NoError,\n InvalidSignature,\n InvalidSignatureLength,\n InvalidSignatureS,\n InvalidSignatureV\n }\n\n function _throwError(RecoverError error) private pure {\n if (error == RecoverError.NoError) {\n return; // no error: do nothing\n } else if (error == RecoverError.InvalidSignature) {\n revert(\"ECDSA: invalid signature\");\n } else if (error == RecoverError.InvalidSignatureLength) {\n revert(\"ECDSA: invalid signature length\");\n } else if (error == RecoverError.InvalidSignatureS) {\n revert(\"ECDSA: invalid signature 's' value\");\n } else if (error == RecoverError.InvalidSignatureV) {\n revert(\"ECDSA: invalid signature 'v' value\");\n }\n }\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with\n * `signature` or error string. This address can then be used for verification purposes.\n *\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {toEthSignedMessageHash} on it.\n *\n * Documentation for signature generation:\n * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]\n * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]\n *\n * _Available since v4.3._\n */\n function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {\n // Check the signature length\n // - case 65: r,s,v signature (standard)\n // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._\n if (signature.length == 65) {\n bytes32 r;\n bytes32 s;\n uint8 v;\n // ecrecover takes the signature parameters, and the only way to get them\n // currently is to use assembly.\n assembly {\n r := mload(add(signature, 0x20))\n s := mload(add(signature, 0x40))\n v := byte(0, mload(add(signature, 0x60)))\n }\n return tryRecover(hash, v, r, s);\n } else if (signature.length == 64) {\n bytes32 r;\n bytes32 vs;\n // ecrecover takes the signature parameters, and the only way to get them\n // currently is to use assembly.\n assembly {\n r := mload(add(signature, 0x20))\n vs := mload(add(signature, 0x40))\n }\n return tryRecover(hash, r, vs);\n } else {\n return (address(0), RecoverError.InvalidSignatureLength);\n }\n }\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with\n * `signature`. This address can then be used for verification purposes.\n *\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {toEthSignedMessageHash} on it.\n */\n function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, signature);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.\n *\n * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]\n *\n * _Available since v4.3._\n */\n function tryRecover(\n bytes32 hash,\n bytes32 r,\n bytes32 vs\n ) internal pure returns (address, RecoverError) {\n bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);\n uint8 v = uint8((uint256(vs) >> 255) + 27);\n return tryRecover(hash, v, r, s);\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.\n *\n * _Available since v4.2._\n */\n function recover(\n bytes32 hash,\n bytes32 r,\n bytes32 vs\n ) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, r, vs);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `v`,\n * `r` and `s` signature fields separately.\n *\n * _Available since v4.3._\n */\n function tryRecover(\n bytes32 hash,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal pure returns (address, RecoverError) {\n // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature\n // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines\n // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most\n // signatures from current libraries generate a unique signature with an s-value in the lower half order.\n //\n // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value\n // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or\n // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept\n // these malleable signatures as well.\n if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {\n return (address(0), RecoverError.InvalidSignatureS);\n }\n if (v != 27 && v != 28) {\n return (address(0), RecoverError.InvalidSignatureV);\n }\n\n // If the signature is valid (and not malleable), return the signer address\n address signer = ecrecover(hash, v, r, s);\n if (signer == address(0)) {\n return (address(0), RecoverError.InvalidSignature);\n }\n\n return (signer, RecoverError.NoError);\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `v`,\n * `r` and `s` signature fields separately.\n */\n function recover(\n bytes32 hash,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, v, r, s);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Returns an Ethereum Signed Message, created from a `hash`. This\n * produces hash corresponding to the one signed with the\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\n * JSON-RPC method as part of EIP-191.\n *\n * See {recover}.\n */\n function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {\n // 32 is the length in bytes of hash,\n // enforced by the type signature above\n return keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n32\", hash));\n }\n\n /**\n * @dev Returns an Ethereum Signed Message, created from `s`. This\n * produces hash corresponding to the one signed with the\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\n * JSON-RPC method as part of EIP-191.\n *\n * See {recover}.\n */\n function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {\n return keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n\", Strings.toString(s.length), s));\n }\n\n /**\n * @dev Returns an Ethereum Signed Typed Data, created from a\n * `domainSeparator` and a `structHash`. This produces hash corresponding\n * to the one signed with the\n * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]\n * JSON-RPC method as part of EIP-712.\n *\n * See {recover}.\n */\n function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {\n return keccak256(abi.encodePacked(\"\\x19\\x01\", domainSeparator, structHash));\n }\n}\n"},"contracts/v0.8/extensions/GatewayV2.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"@openzeppelin/contracts/security/Pausable.sol\";\nimport \"../interfaces/IQuorum.sol\";\nimport \"../interfaces/IWeightedValidator.sol\";\nimport \"./HasProxyAdmin.sol\";\n\nabstract contract GatewayV2 is HasProxyAdmin, Pausable, IQuorum {\n /// @dev Emitted when the validator contract address is updated.\n event ValidatorContractUpdated(IWeightedValidator);\n\n uint256 internal _num;\n uint256 internal _denom;\n\n IWeightedValidator public validatorContract;\n uint256 public nonce;\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n */\n uint256[50] private ______gap;\n\n /**\n * @dev See {IQuorum-getThreshold}.\n */\n function getThreshold() external view virtual returns (uint256, uint256) {\n return (_num, _denom);\n }\n\n /**\n * @dev See {IQuorum-checkThreshold}.\n */\n function checkThreshold(uint256 _voteWeight) external view virtual returns (bool) {\n return _voteWeight * _denom >= _num * validatorContract.totalWeights();\n }\n\n /**\n * @dev See {IQuorum-setThreshold}.\n */\n function setThreshold(uint256 _numerator, uint256 _denominator)\n external\n virtual\n onlyAdmin\n returns (uint256, uint256)\n {\n return _setThreshold(_numerator, _denominator);\n }\n\n /**\n * @dev Triggers paused state.\n */\n function pause() external onlyAdmin {\n _pause();\n }\n\n /**\n * @dev Triggers unpaused state.\n */\n function unpause() external onlyAdmin {\n _unpause();\n }\n\n /**\n * @dev Sets validator contract address.\n *\n * Requirements:\n * - The method caller is admin.\n *\n * Emits the `ValidatorContractUpdated` event.\n *\n */\n function setValidatorContract(IWeightedValidator _validatorContract) external virtual onlyAdmin {\n _setValidatorContract(_validatorContract);\n }\n\n /**\n * @dev See {IQuorum-minimumVoteWeight}.\n */\n function minimumVoteWeight() public view virtual returns (uint256) {\n return _minimumVoteWeight(validatorContract.totalWeights());\n }\n\n /**\n * @dev Sets validator contract address.\n *\n * Emits the `ValidatorContractUpdated` event.\n *\n */\n function _setValidatorContract(IWeightedValidator _validatorContract) internal virtual {\n validatorContract = _validatorContract;\n emit ValidatorContractUpdated(_validatorContract);\n }\n\n /**\n * @dev Sets threshold and returns the old one.\n *\n * Emits the `ThresholdUpdated` event.\n *\n */\n function _setThreshold(uint256 _numerator, uint256 _denominator)\n internal\n virtual\n returns (uint256 _previousNum, uint256 _previousDenom)\n {\n require(_numerator <= _denominator, \"GatewayV2: invalid threshold\");\n _previousNum = _num;\n _previousDenom = _denom;\n _num = _numerator;\n _denom = _denominator;\n emit ThresholdUpdated(nonce++, _numerator, _denominator, _previousNum, _previousDenom);\n }\n\n /**\n * @dev Returns minimum vote weight.\n */\n function _minimumVoteWeight(uint256 _totalWeight) internal view virtual returns (uint256) {\n return (_num * _totalWeight + _denom - 1) / _denom;\n }\n}\n"},"@openzeppelin/contracts/proxy/utils/Initializable.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0) (proxy/utils/Initializable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../../utils/Address.sol\";\n\n/**\n * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed\n * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an\n * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer\n * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.\n *\n * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as\n * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.\n *\n * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure\n * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.\n *\n * [CAUTION]\n * ====\n * Avoid leaving a contract uninitialized.\n *\n * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation\n * contract, which may impact the proxy. To initialize the implementation contract, you can either invoke the\n * initializer manually, or you can include a constructor to automatically mark it as initialized when it is deployed:\n *\n * [.hljs-theme-light.nopadding]\n * ```\n * /// @custom:oz-upgrades-unsafe-allow constructor\n * constructor() initializer {}\n * ```\n * ====\n */\nabstract contract Initializable {\n /**\n * @dev Indicates that the contract has been initialized.\n */\n bool private _initialized;\n\n /**\n * @dev Indicates that the contract is in the process of being initialized.\n */\n bool private _initializing;\n\n /**\n * @dev Modifier to protect an initializer function from being invoked twice.\n */\n modifier initializer() {\n // If the contract is initializing we ignore whether _initialized is set in order to support multiple\n // inheritance patterns, but we only do this in the context of a constructor, because in other contexts the\n // contract may have been reentered.\n require(_initializing ? _isConstructor() : !_initialized, \"Initializable: contract is already initialized\");\n\n bool isTopLevelCall = !_initializing;\n if (isTopLevelCall) {\n _initializing = true;\n _initialized = true;\n }\n\n _;\n\n if (isTopLevelCall) {\n _initializing = false;\n }\n }\n\n /**\n * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the\n * {initializer} modifier, directly or indirectly.\n */\n modifier onlyInitializing() {\n require(_initializing, \"Initializable: contract is not initializing\");\n _;\n }\n\n function _isConstructor() private view returns (bool) {\n return !Address.isContract(address(this));\n }\n}\n"},"contracts/v0.8/extensions/WithdrawalLimitation.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"./GatewayV2.sol\";\n\nabstract contract WithdrawalLimitation is GatewayV2 {\n /// @dev Emitted when the high-tier vote weight threshold is updated\n event HighTierVoteWeightThresholdUpdated(\n uint256 indexed nonce,\n uint256 indexed numerator,\n uint256 indexed denominator,\n uint256 previousNumerator,\n uint256 previousDenominator\n );\n /// @dev Emitted when the thresholds for high-tier withdrawals that requires high-tier vote weights are updated\n event HighTierThresholdsUpdated(address[] tokens, uint256[] thresholds);\n /// @dev Emitted when the thresholds for locked withdrawals are updated\n event LockedThresholdsUpdated(address[] tokens, uint256[] thresholds);\n /// @dev Emitted when the fee percentages to unlock withdraw are updated\n event UnlockFeePercentagesUpdated(address[] tokens, uint256[] percentages);\n /// @dev Emitted when the daily limit thresholds are updated\n event DailyWithdrawalLimitsUpdated(address[] tokens, uint256[] limits);\n\n uint256 public constant _MAX_PERCENTAGE = 1_000_000;\n\n uint256 internal _highTierVWNum;\n uint256 internal _highTierVWDenom;\n\n /// @dev Mapping from mainchain token => the amount thresholds for high-tier withdrawals that requires high-tier vote weights\n mapping(address => uint256) public highTierThreshold;\n /// @dev Mapping from mainchain token => the amount thresholds to lock withdrawal\n mapping(address => uint256) public lockedThreshold;\n /// @dev Mapping from mainchain token => unlock fee percentages for unlocker\n /// @notice Values 0-1,000,000 map to 0%-100%\n mapping(address => uint256) public unlockFeePercentages;\n /// @dev Mapping from mainchain token => daily limit amount for withdrawal\n mapping(address => uint256) public dailyWithdrawalLimit;\n /// @dev Mapping from token address => today withdrawal amount\n mapping(address => uint256) public lastSyncedWithdrawal;\n /// @dev Mapping from token address => last date synced to record the `lastSyncedWithdrawal`\n mapping(address => uint256) public lastDateSynced;\n\n /**\n * @dev This empty reserved space is put in place to allow future versions to add new\n * variables without shifting down storage in the inheritance chain.\n */\n uint256[50] private ______gap;\n\n /**\n * @dev Override {GatewayV2-setThreshold}.\n *\n * Requirements:\n * - The high-tier vote weight threshold must equal to or larger than the normal threshold.\n *\n */\n function setThreshold(uint256 _numerator, uint256 _denominator)\n external\n virtual\n override\n onlyAdmin\n returns (uint256 _previousNum, uint256 _previousDenom)\n {\n (_previousNum, _previousDenom) = _setThreshold(_numerator, _denominator);\n _verifyThresholds();\n }\n\n /**\n * @dev Returns the high-tier vote weight threshold.\n */\n function getHighTierVoteWeightThreshold() external view virtual returns (uint256, uint256) {\n return (_highTierVWNum, _highTierVWDenom);\n }\n\n /**\n * @dev Checks whether the `_voteWeight` passes the high-tier vote weight threshold.\n */\n function checkHighTierVoteWeightThreshold(uint256 _voteWeight) external view virtual returns (bool) {\n return _voteWeight * _highTierVWDenom >= _highTierVWNum * validatorContract.totalWeights();\n }\n\n /**\n * @dev Sets high-tier vote weight threshold and returns the old one.\n *\n * Requirements:\n * - The method caller is admin.\n * - The high-tier vote weight threshold must equal to or larger than the normal threshold.\n *\n * Emits the `HighTierVoteWeightThresholdUpdated` event.\n *\n */\n function setHighTierVoteWeightThreshold(uint256 _numerator, uint256 _denominator)\n external\n virtual\n onlyAdmin\n returns (uint256 _previousNum, uint256 _previousDenom)\n {\n (_previousNum, _previousDenom) = _setHighTierVoteWeightThreshold(_numerator, _denominator);\n _verifyThresholds();\n }\n\n /**\n * @dev Sets the thresholds for high-tier withdrawals that requires high-tier vote weights.\n *\n * Requirements:\n * - The method caller is admin.\n * - The arrays have the same length and its length larger than 0.\n *\n * Emits the `HighTierThresholdsUpdated` event.\n *\n */\n function setHighTierThresholds(address[] calldata _tokens, uint256[] calldata _thresholds)\n external\n virtual\n onlyAdmin\n {\n require(_tokens.length > 0, \"WithdrawalLimitation: invalid array length\");\n _setHighTierThresholds(_tokens, _thresholds);\n }\n\n /**\n * @dev Sets the amount thresholds to lock withdrawal.\n *\n * Requirements:\n * - The method caller is admin.\n * - The arrays have the same length and its length larger than 0.\n *\n * Emits the `LockedThresholdsUpdated` event.\n *\n */\n function setLockedThresholds(address[] calldata _tokens, uint256[] calldata _thresholds) external virtual onlyAdmin {\n require(_tokens.length > 0, \"WithdrawalLimitation: invalid array length\");\n _setLockedThresholds(_tokens, _thresholds);\n }\n\n /**\n * @dev Sets fee percentages to unlock withdrawal.\n *\n * Requirements:\n * - The method caller is admin.\n * - The arrays have the same length and its length larger than 0.\n *\n * Emits the `UnlockFeePercentagesUpdated` event.\n *\n */\n function setUnlockFeePercentages(address[] calldata _tokens, uint256[] calldata _percentages)\n external\n virtual\n onlyAdmin\n {\n require(_tokens.length > 0, \"WithdrawalLimitation: invalid array length\");\n _setUnlockFeePercentages(_tokens, _percentages);\n }\n\n /**\n * @dev Sets daily limit amounts for the withdrawals.\n *\n * Requirements:\n * - The method caller is admin.\n * - The arrays have the same length and its length larger than 0.\n *\n * Emits the `DailyWithdrawalLimitsUpdated` event.\n *\n */\n function setDailyWithdrawalLimits(address[] calldata _tokens, uint256[] calldata _limits) external virtual onlyAdmin {\n require(_tokens.length > 0, \"WithdrawalLimitation: invalid array length\");\n _setDailyWithdrawalLimits(_tokens, _limits);\n }\n\n /**\n * @dev Checks whether the withdrawal reaches the limitation.\n */\n function reachedWithdrawalLimit(address _token, uint256 _quantity) external view virtual returns (bool) {\n return _reachedWithdrawalLimit(_token, _quantity);\n }\n\n /**\n * @dev Sets high-tier vote weight threshold and returns the old one.\n *\n * Emits the `HighTierVoteWeightThresholdUpdated` event.\n *\n */\n function _setHighTierVoteWeightThreshold(uint256 _numerator, uint256 _denominator)\n internal\n returns (uint256 _previousNum, uint256 _previousDenom)\n {\n require(_numerator <= _denominator, \"WithdrawalLimitation: invalid threshold\");\n _previousNum = _highTierVWNum;\n _previousDenom = _highTierVWDenom;\n _highTierVWNum = _numerator;\n _highTierVWDenom = _denominator;\n emit HighTierVoteWeightThresholdUpdated(nonce++, _numerator, _denominator, _previousNum, _previousDenom);\n }\n\n /**\n * @dev Sets the thresholds for high-tier withdrawals that requires high-tier vote weights.\n *\n * Requirements:\n * - The array lengths are equal.\n *\n * Emits the `HighTierThresholdsUpdated` event.\n *\n */\n function _setHighTierThresholds(address[] calldata _tokens, uint256[] calldata _thresholds) internal virtual {\n require(_tokens.length == _thresholds.length, \"WithdrawalLimitation: invalid array length\");\n for (uint256 _i; _i < _tokens.length; _i++) {\n highTierThreshold[_tokens[_i]] = _thresholds[_i];\n }\n emit HighTierThresholdsUpdated(_tokens, _thresholds);\n }\n\n /**\n * @dev Sets the amount thresholds to lock withdrawal.\n *\n * Requirements:\n * - The array lengths are equal.\n *\n * Emits the `LockedThresholdsUpdated` event.\n *\n */\n function _setLockedThresholds(address[] calldata _tokens, uint256[] calldata _thresholds) internal virtual {\n require(_tokens.length == _thresholds.length, \"WithdrawalLimitation: invalid array length\");\n for (uint256 _i; _i < _tokens.length; _i++) {\n lockedThreshold[_tokens[_i]] = _thresholds[_i];\n }\n emit LockedThresholdsUpdated(_tokens, _thresholds);\n }\n\n /**\n * @dev Sets fee percentages to unlock withdrawal.\n *\n * Requirements:\n * - The array lengths are equal.\n * - The percentage is equal to or less than 100_000.\n *\n * Emits the `UnlockFeePercentagesUpdated` event.\n *\n */\n function _setUnlockFeePercentages(address[] calldata _tokens, uint256[] calldata _percentages) internal virtual {\n require(_tokens.length == _percentages.length, \"WithdrawalLimitation: invalid array length\");\n for (uint256 _i; _i < _tokens.length; _i++) {\n require(_percentages[_i] <= _MAX_PERCENTAGE, \"WithdrawalLimitation: invalid percentage\");\n unlockFeePercentages[_tokens[_i]] = _percentages[_i];\n }\n emit UnlockFeePercentagesUpdated(_tokens, _percentages);\n }\n\n /**\n * @dev Sets daily limit amounts for the withdrawals.\n *\n * Requirements:\n * - The array lengths are equal.\n *\n * Emits the `DailyWithdrawalLimitsUpdated` event.\n *\n */\n function _setDailyWithdrawalLimits(address[] calldata _tokens, uint256[] calldata _limits) internal virtual {\n require(_tokens.length == _limits.length, \"WithdrawalLimitation: invalid array length\");\n for (uint256 _i; _i < _tokens.length; _i++) {\n dailyWithdrawalLimit[_tokens[_i]] = _limits[_i];\n }\n emit DailyWithdrawalLimitsUpdated(_tokens, _limits);\n }\n\n /**\n * @dev Checks whether the withdrawal reaches the daily limitation.\n *\n * Requirements:\n * - The daily withdrawal threshold should not apply for locked withdrawals.\n *\n */\n function _reachedWithdrawalLimit(address _token, uint256 _quantity) internal view virtual returns (bool) {\n if (_lockedWithdrawalRequest(_token, _quantity)) {\n return false;\n }\n\n uint256 _currentDate = block.timestamp / 1 days;\n if (_currentDate > lastDateSynced[_token]) {\n return dailyWithdrawalLimit[_token] <= _quantity;\n } else {\n return dailyWithdrawalLimit[_token] <= lastSyncedWithdrawal[_token] + _quantity;\n }\n }\n\n /**\n * @dev Record withdrawal token.\n */\n function _recordWithdrawal(address _token, uint256 _quantity) internal virtual {\n uint256 _currentDate = block.timestamp / 1 days;\n if (_currentDate > lastDateSynced[_token]) {\n lastDateSynced[_token] = _currentDate;\n lastSyncedWithdrawal[_token] = _quantity;\n } else {\n lastSyncedWithdrawal[_token] += _quantity;\n }\n }\n\n /**\n * @dev Returns whether the withdrawal request is locked or not.\n */\n function _lockedWithdrawalRequest(address _token, uint256 _quantity) internal view virtual returns (bool) {\n return lockedThreshold[_token] <= _quantity;\n }\n\n /**\n * @dev Computes fee percentage.\n */\n function _computeFeePercentage(uint256 _amount, uint256 _percentage) internal view virtual returns (uint256) {\n return (_amount * _percentage) / _MAX_PERCENTAGE;\n }\n\n /**\n * @dev Returns high-tier vote weight.\n */\n function _highTierVoteWeight(uint256 _totalWeight) internal view virtual returns (uint256) {\n return (_highTierVWNum * _totalWeight + _highTierVWDenom - 1) / _highTierVWDenom;\n }\n\n /**\n * @dev Validates whether the high-tier vote weight threshold is larger than the normal threshold.\n */\n function _verifyThresholds() internal view {\n require(_num * _highTierVWDenom <= _highTierVWNum * _denom, \"WithdrawalLimitation: invalid thresholds\");\n }\n}\n"},"contracts/v0.8/interfaces/MappedTokenConsumer.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"../library/Token.sol\";\n\ninterface MappedTokenConsumer {\n struct MappedToken {\n Token.Standard erc;\n address tokenAddr;\n }\n}\n"},"contracts/v0.8/library/Token.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport \"@openzeppelin/contracts/token/ERC721/IERC721.sol\";\nimport \"@openzeppelin/contracts/utils/Strings.sol\";\nimport \"../interfaces/IWETH.sol\";\n\nlibrary Token {\n enum Standard {\n ERC20,\n ERC721\n }\n struct Info {\n Standard erc;\n // For ERC20: the id must be 0 and the quantity is larger than 0.\n // For ERC721: the quantity must be 0.\n uint256 id;\n uint256 quantity;\n }\n\n // keccak256(\"TokenInfo(uint8 erc,uint256 id,uint256 quantity)\");\n bytes32 public constant INFO_TYPE_HASH = 0x1e2b74b2a792d5c0f0b6e59b037fa9d43d84fbb759337f0112fcc15ca414fc8d;\n\n /**\n * @dev Returns token info struct hash.\n */\n function hash(Info memory _info) internal pure returns (bytes32) {\n return keccak256(abi.encode(INFO_TYPE_HASH, _info.erc, _info.id, _info.quantity));\n }\n\n /**\n * @dev Validates the token info.\n */\n function validate(Info memory _info) internal pure {\n require(\n (_info.erc == Standard.ERC20 && _info.quantity > 0 && _info.id == 0) ||\n (_info.erc == Standard.ERC721 && _info.quantity == 0),\n \"Token: invalid info\"\n );\n }\n\n /**\n * @dev Transfer asset from.\n *\n * Requirements:\n * - The `_from` address must approve for the contract using this library.\n *\n */\n function transferFrom(\n Info memory _info,\n address _from,\n address _to,\n address _token\n ) internal {\n bool _success;\n bytes memory _data;\n if (_info.erc == Standard.ERC20) {\n (_success, _data) = _token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, _from, _to, _info.quantity));\n _success = _success && (_data.length == 0 || abi.decode(_data, (bool)));\n } else if (_info.erc == Standard.ERC721) {\n // bytes4(keccak256(\"transferFrom(address,address,uint256)\"))\n (_success, ) = _token.call(abi.encodeWithSelector(0x23b872dd, _from, _to, _info.id));\n } else {\n revert(\"Token: unsupported token standard\");\n }\n\n if (!_success) {\n revert(\n string(\n abi.encodePacked(\n \"Token: could not transfer \",\n toString(_info),\n \" from \",\n Strings.toHexString(uint160(_from), 20),\n \" to \",\n Strings.toHexString(uint160(_to), 20),\n \" token \",\n Strings.toHexString(uint160(_token), 20)\n )\n )\n );\n }\n }\n\n /**\n * @dev Transfers ERC721 token and returns the result.\n */\n function tryTransferERC721(\n address _token,\n address _to,\n uint256 _id\n ) internal returns (bool _success) {\n (_success, ) = _token.call(abi.encodeWithSelector(IERC721.transferFrom.selector, address(this), _to, _id));\n }\n\n /**\n * @dev Transfers ERC20 token and returns the result.\n */\n function tryTransferERC20(\n address _token,\n address _to,\n uint256 _quantity\n ) internal returns (bool _success) {\n bytes memory _data;\n (_success, _data) = _token.call(abi.encodeWithSelector(IERC20.transfer.selector, _to, _quantity));\n _success = _success && (_data.length == 0 || abi.decode(_data, (bool)));\n }\n\n /**\n * @dev Transfer assets from current address to `_to` address.\n */\n function transfer(\n Info memory _info,\n address _to,\n address _token\n ) internal {\n bool _success;\n if (_info.erc == Standard.ERC20) {\n _success = tryTransferERC20(_token, _to, _info.quantity);\n } else if (_info.erc == Standard.ERC721) {\n _success = tryTransferERC721(_token, _to, _info.id);\n } else {\n revert(\"Token: unsupported token standard\");\n }\n\n if (!_success) {\n revert(\n string(\n abi.encodePacked(\n \"Token: could not transfer \",\n toString(_info),\n \" to \",\n Strings.toHexString(uint160(_to), 20),\n \" token \",\n Strings.toHexString(uint160(_token), 20)\n )\n )\n );\n }\n }\n\n /**\n * @dev Tries minting and transfering assets.\n *\n * @notice Prioritizes transfer native token if the token is wrapped.\n *\n */\n function handleAssetTransfer(\n Info memory _info,\n address payable _to,\n address _token,\n IWETH _wrappedNativeToken\n ) internal {\n bool _success;\n if (_token == address(_wrappedNativeToken)) {\n // Try sending the native token before transferring the wrapped token\n if (!_to.send(_info.quantity)) {\n _wrappedNativeToken.deposit{ value: _info.quantity }();\n transfer(_info, _to, _token);\n }\n } else if (_info.erc == Token.Standard.ERC20) {\n uint256 _balance = IERC20(_token).balanceOf(address(this));\n\n if (_balance < _info.quantity) {\n // bytes4(keccak256(\"mint(address,uint256)\"))\n (_success, ) = _token.call(abi.encodeWithSelector(0x40c10f19, address(this), _info.quantity - _balance));\n require(_success, \"Token: ERC20 minting failed\");\n }\n\n transfer(_info, _to, _token);\n } else if (_info.erc == Token.Standard.ERC721) {\n if (!tryTransferERC721(_token, _to, _info.id)) {\n // bytes4(keccak256(\"mint(address,uint256)\"))\n (_success, ) = _token.call(abi.encodeWithSelector(0x40c10f19, _to, _info.id));\n require(_success, \"Token: ERC721 minting failed\");\n }\n } else {\n revert(\"Token: unsupported token standard\");\n }\n }\n\n /**\n * @dev Returns readable string.\n */\n function toString(Info memory _info) internal pure returns (string memory) {\n return\n string(\n abi.encodePacked(\n \"TokenInfo(\",\n Strings.toHexString(uint160(_info.erc), 1),\n \",\",\n Strings.toHexString(_info.id),\n \",\",\n Strings.toHexString(_info.quantity),\n \")\"\n )\n );\n }\n\n struct Owner {\n address addr;\n address tokenAddr;\n uint256 chainId;\n }\n\n // keccak256(\"TokenOwner(address addr,address tokenAddr,uint256 chainId)\");\n bytes32 public constant OWNER_TYPE_HASH = 0x353bdd8d69b9e3185b3972e08b03845c0c14a21a390215302776a7a34b0e8764;\n\n /**\n * @dev Returns ownership struct hash.\n */\n function hash(Owner memory _owner) internal pure returns (bytes32) {\n return keccak256(abi.encode(OWNER_TYPE_HASH, _owner.addr, _owner.tokenAddr, _owner.chainId));\n }\n}\n"},"contracts/v0.8/mainchain/IMainchainGatewayV2.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"../interfaces/IWETH.sol\";\nimport \"../library/Transfer.sol\";\nimport \"../interfaces/SignatureConsumer.sol\";\nimport \"../interfaces/MappedTokenConsumer.sol\";\n\ninterface IMainchainGatewayV2 is SignatureConsumer, MappedTokenConsumer {\n /// @dev Emitted when the deposit is requested\n event DepositRequested(bytes32 receiptHash, Transfer.Receipt receipt);\n /// @dev Emitted when the assets are withdrawn\n event Withdrew(bytes32 receiptHash, Transfer.Receipt receipt);\n /// @dev Emitted when the tokens are mapped\n event TokenMapped(address[] mainchainTokens, address[] roninTokens, Token.Standard[] standards);\n /// @dev Emitted when the wrapped native token contract is updated\n event WrappedNativeTokenContractUpdated(IWETH weth);\n /// @dev Emitted when the withdrawal is locked\n event WithdrawalLocked(bytes32 receiptHash, Transfer.Receipt receipt);\n /// @dev Emitted when the withdrawal is unlocked\n event WithdrawalUnlocked(bytes32 receiptHash, Transfer.Receipt receipt);\n\n /**\n * @dev Returns the domain seperator.\n */\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n\n /**\n * @dev Returns deposit count.\n */\n function depositCount() external view returns (uint256);\n\n /**\n * @dev Sets the wrapped native token contract.\n *\n * Requirements:\n * - The method caller is admin.\n *\n * Emits the `WrappedNativeTokenContractUpdated` event.\n *\n */\n function setWrappedNativeTokenContract(IWETH _wrappedToken) external;\n\n /**\n * @dev Returns whether the withdrawal is locked.\n */\n function withdrawalLocked(uint256 withdrawalId) external view returns (bool);\n\n /**\n * @dev Returns the withdrawal hash.\n */\n function withdrawalHash(uint256 withdrawalId) external view returns (bytes32);\n\n /**\n * @dev Locks the assets and request deposit.\n */\n function requestDepositFor(Transfer.Request calldata _request) external payable;\n\n /**\n * @dev Withdraws based on the receipt and the validator signatures.\n * Returns whether the withdrawal is locked.\n *\n * Emits the `Withdrew` once the assets are released.\n *\n */\n function submitWithdrawal(Transfer.Receipt memory _receipt, Signature[] memory _signatures)\n external\n returns (bool _locked);\n\n /**\n * @dev Approves a specific withdrawal.\n *\n * Requirements:\n * - The method caller is a validator.\n *\n * Emits the `Withdrew` once the assets are released.\n *\n */\n function unlockWithdrawal(Transfer.Receipt calldata _receipt) external;\n\n /**\n * @dev Maps mainchain tokens to Ronin network.\n *\n * Requirement:\n * - The method caller is admin.\n * - The arrays have the same length and its length larger than 0.\n *\n * Emits the `TokenMapped` event.\n *\n */\n function mapTokens(\n address[] calldata _mainchainTokens,\n address[] calldata _roninTokens,\n Token.Standard[] calldata _standards\n ) external;\n\n /**\n * @dev Maps mainchain tokens to Ronin network and sets thresholds.\n *\n * Requirement:\n * - The method caller is admin.\n * - The arrays have the same length and its length larger than 0.\n *\n * Emits the `TokenMapped` event.\n *\n */\n function mapTokensAndThresholds(\n address[] calldata _mainchainTokens,\n address[] calldata _roninTokens,\n Token.Standard[] calldata _standards,\n uint256[][4] calldata _thresholds\n ) external;\n\n /**\n * @dev Returns token address on Ronin network.\n * @notice Reverts for unsupported token.\n */\n function getRoninToken(address _mainchainToken) external view returns (MappedToken memory _token);\n}\n"},"contracts/v0.8/mainchain/MainchainGatewayV2.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"@openzeppelin/contracts/access/AccessControlEnumerable.sol\";\nimport \"@openzeppelin/contracts/proxy/utils/Initializable.sol\";\nimport \"../extensions/GatewayV2.sol\";\nimport \"../extensions/WithdrawalLimitation.sol\";\nimport \"../library/Transfer.sol\";\nimport \"./IMainchainGatewayV2.sol\";\n\ncontract MainchainGatewayV2 is WithdrawalLimitation, Initializable, AccessControlEnumerable, IMainchainGatewayV2 {\n using Token for Token.Info;\n using Transfer for Transfer.Request;\n using Transfer for Transfer.Receipt;\n\n /// @dev Withdrawal unlocker role hash\n bytes32 public constant WITHDRAWAL_UNLOCKER_ROLE = keccak256(\"WITHDRAWAL_UNLOCKER_ROLE\");\n\n /// @dev Wrapped native token address\n IWETH public wrappedNativeToken;\n /// @dev Ronin network id\n uint256 public roninChainId;\n /// @dev Total deposit\n uint256 public depositCount;\n /// @dev Domain seperator\n bytes32 internal _domainSeparator;\n /// @dev Mapping from mainchain token => token address on Ronin network\n mapping(address => MappedToken) internal _roninToken;\n /// @dev Mapping from withdrawal id => withdrawal hash\n mapping(uint256 => bytes32) public withdrawalHash;\n /// @dev Mapping from withdrawal id => locked\n mapping(uint256 => bool) public withdrawalLocked;\n\n fallback() external payable {\n _fallback();\n }\n\n receive() external payable {\n _fallback();\n }\n\n /**\n * @dev Initializes contract storage.\n */\n function initialize(\n address _roleSetter,\n IWETH _wrappedToken,\n IWeightedValidator _validatorContract,\n uint256 _roninChainId,\n uint256 _numerator,\n uint256 _highTierVWNumerator,\n uint256 _denominator,\n // _addresses[0]: mainchainTokens\n // _addresses[1]: roninTokens\n // _addresses[2]: withdrawalUnlockers\n address[][3] calldata _addresses,\n // _thresholds[0]: highTierThreshold\n // _thresholds[1]: lockedThreshold\n // _thresholds[2]: unlockFeePercentages\n // _thresholds[3]: dailyWithdrawalLimit\n uint256[][4] calldata _thresholds,\n Token.Standard[] calldata _standards\n ) external payable virtual initializer {\n _setupRole(DEFAULT_ADMIN_ROLE, _roleSetter);\n roninChainId = _roninChainId;\n\n _setWrappedNativeTokenContract(_wrappedToken);\n _setValidatorContract(_validatorContract);\n _updateDomainSeparator();\n _setThreshold(_numerator, _denominator);\n _setHighTierVoteWeightThreshold(_highTierVWNumerator, _denominator);\n _verifyThresholds();\n\n if (_addresses[0].length > 0) {\n // Map mainchain tokens to ronin tokens\n _mapTokens(_addresses[0], _addresses[1], _standards);\n // Sets thresholds based on the mainchain tokens\n _setHighTierThresholds(_addresses[0], _thresholds[0]);\n _setLockedThresholds(_addresses[0], _thresholds[1]);\n _setUnlockFeePercentages(_addresses[0], _thresholds[2]);\n _setDailyWithdrawalLimits(_addresses[0], _thresholds[3]);\n }\n\n // Grant role for withdrawal unlocker\n for (uint256 _i; _i < _addresses[2].length; _i++) {\n _grantRole(WITHDRAWAL_UNLOCKER_ROLE, _addresses[2][_i]);\n }\n }\n\n /**\n * @dev Receives ether without doing anything. Use this function to topup native token.\n */\n function receiveEther() external payable {}\n\n /**\n * @dev See {IMainchainGatewayV2-DOMAIN_SEPARATOR}.\n */\n function DOMAIN_SEPARATOR() external view virtual returns (bytes32) {\n return _domainSeparator;\n }\n\n /**\n * @dev See {IMainchainGatewayV2-setWrappedNativeTokenContract}.\n */\n function setWrappedNativeTokenContract(IWETH _wrappedToken) external virtual onlyAdmin {\n _setWrappedNativeTokenContract(_wrappedToken);\n }\n\n /**\n * @dev See {IMainchainGatewayV2-requestDepositFor}.\n */\n function requestDepositFor(Transfer.Request calldata _request) external payable virtual whenNotPaused {\n _requestDepositFor(_request, msg.sender);\n }\n\n /**\n * @dev See {IMainchainGatewayV2-submitWithdrawal}.\n */\n function submitWithdrawal(Transfer.Receipt calldata _receipt, Signature[] calldata _signatures)\n external\n virtual\n whenNotPaused\n returns (bool _locked)\n {\n return _submitWithdrawal(_receipt, _signatures);\n }\n\n /**\n * @dev See {IMainchainGatewayV2-unlockWithdrawal}.\n */\n function unlockWithdrawal(Transfer.Receipt calldata _receipt) external onlyRole(WITHDRAWAL_UNLOCKER_ROLE) {\n bytes32 _receiptHash = _receipt.hash();\n require(withdrawalHash[_receipt.id] == _receipt.hash(), \"MainchainGatewayV2: invalid receipt\");\n require(withdrawalLocked[_receipt.id], \"MainchainGatewayV2: query for approved withdrawal\");\n delete withdrawalLocked[_receipt.id];\n emit WithdrawalUnlocked(_receiptHash, _receipt);\n\n address _token = _receipt.mainchain.tokenAddr;\n if (_receipt.info.erc == Token.Standard.ERC20) {\n Token.Info memory _feeInfo = _receipt.info;\n _feeInfo.quantity = _computeFeePercentage(_receipt.info.quantity, unlockFeePercentages[_token]);\n Token.Info memory _withdrawInfo = _receipt.info;\n _withdrawInfo.quantity = _receipt.info.quantity - _feeInfo.quantity;\n\n _feeInfo.handleAssetTransfer(payable(msg.sender), _token, wrappedNativeToken);\n _withdrawInfo.handleAssetTransfer(payable(_receipt.mainchain.addr), _token, wrappedNativeToken);\n } else {\n _receipt.info.handleAssetTransfer(payable(_receipt.mainchain.addr), _token, wrappedNativeToken);\n }\n\n emit Withdrew(_receiptHash, _receipt);\n }\n\n /**\n * @dev See {IMainchainGatewayV2-mapTokens}.\n */\n function mapTokens(\n address[] calldata _mainchainTokens,\n address[] calldata _roninTokens,\n Token.Standard[] calldata _standards\n ) external virtual onlyAdmin {\n require(_mainchainTokens.length > 0, \"MainchainGatewayV2: query for empty array\");\n _mapTokens(_mainchainTokens, _roninTokens, _standards);\n }\n\n /**\n * @dev See {IMainchainGatewayV2-mapTokensAndThresholds}.\n */\n function mapTokensAndThresholds(\n address[] calldata _mainchainTokens,\n address[] calldata _roninTokens,\n Token.Standard[] calldata _standards,\n // _thresholds[0]: highTierThreshold\n // _thresholds[1]: lockedThreshold\n // _thresholds[2]: unlockFeePercentages\n // _thresholds[3]: dailyWithdrawalLimit\n uint256[][4] calldata _thresholds\n ) external virtual onlyAdmin {\n require(_mainchainTokens.length > 0, \"MainchainGatewayV2: query for empty array\");\n _mapTokens(_mainchainTokens, _roninTokens, _standards);\n _setHighTierThresholds(_mainchainTokens, _thresholds[0]);\n _setLockedThresholds(_mainchainTokens, _thresholds[1]);\n _setUnlockFeePercentages(_mainchainTokens, _thresholds[2]);\n _setDailyWithdrawalLimits(_mainchainTokens, _thresholds[3]);\n }\n\n /**\n * @dev See {IMainchainGatewayV2-getRoninToken}.\n */\n function getRoninToken(address _mainchainToken) public view returns (MappedToken memory _token) {\n _token = _roninToken[_mainchainToken];\n require(_token.tokenAddr != address(0), \"MainchainGatewayV2: unsupported token\");\n }\n\n /**\n * @dev Maps mainchain tokens to Ronin network.\n *\n * Requirement:\n * - The arrays have the same length.\n *\n * Emits the `TokenMapped` event.\n *\n */\n function _mapTokens(\n address[] calldata _mainchainTokens,\n address[] calldata _roninTokens,\n Token.Standard[] calldata _standards\n ) internal virtual {\n require(\n _mainchainTokens.length == _roninTokens.length && _mainchainTokens.length == _standards.length,\n \"MainchainGatewayV2: invalid array length\"\n );\n\n for (uint256 _i; _i < _mainchainTokens.length; _i++) {\n _roninToken[_mainchainTokens[_i]].tokenAddr = _roninTokens[_i];\n _roninToken[_mainchainTokens[_i]].erc = _standards[_i];\n }\n\n emit TokenMapped(_mainchainTokens, _roninTokens, _standards);\n }\n\n /**\n * @dev Submits withdrawal receipt.\n *\n * Requirements:\n * - The receipt kind is withdrawal.\n * - The receipt is to withdraw on this chain.\n * - The receipt is not used to withdraw before.\n * - The withdrawal is not reached the limit threshold.\n * - The signer weight total is larger than or equal to the minimum threshold.\n * - The signature signers are in order.\n *\n * Emits the `Withdrew` once the assets are released.\n *\n */\n function _submitWithdrawal(Transfer.Receipt calldata _receipt, Signature[] memory _signatures)\n internal\n virtual\n returns (bool _locked)\n {\n uint256 _id = _receipt.id;\n uint256 _quantity = _receipt.info.quantity;\n address _tokenAddr = _receipt.mainchain.tokenAddr;\n\n _receipt.info.validate();\n require(_receipt.kind == Transfer.Kind.Withdrawal, \"MainchainGatewayV2: invalid receipt kind\");\n require(_receipt.mainchain.chainId == block.chainid, \"MainchainGatewayV2: invalid chain id\");\n MappedToken memory _token = getRoninToken(_receipt.mainchain.tokenAddr);\n require(\n _token.erc == _receipt.info.erc && _token.tokenAddr == _receipt.ronin.tokenAddr,\n \"MainchainGatewayV2: invalid receipt\"\n );\n require(withdrawalHash[_id] == bytes32(0), \"MainchainGatewayV2: query for processed withdrawal\");\n require(\n _receipt.info.erc == Token.Standard.ERC721 || !_reachedWithdrawalLimit(_tokenAddr, _quantity),\n \"MainchainGatewayV2: reached daily withdrawal limit\"\n );\n\n bytes32 _receiptHash = _receipt.hash();\n bytes32 _receiptDigest = Transfer.receiptDigest(_domainSeparator, _receiptHash);\n IWeightedValidator _validatorContract = validatorContract;\n\n uint256 _minimumVoteWeight;\n (_minimumVoteWeight, _locked) = _computeMinVoteWeight(_receipt.info.erc, _tokenAddr, _quantity, _validatorContract);\n\n {\n bool _passed;\n address _signer;\n address _lastSigner;\n Signature memory _sig;\n uint256 _weight;\n for (uint256 _i; _i < _signatures.length; _i++) {\n _sig = _signatures[_i];\n _signer = ecrecover(_receiptDigest, _sig.v, _sig.r, _sig.s);\n require(_lastSigner < _signer, \"MainchainGatewayV2: invalid order\");\n _lastSigner = _signer;\n\n _weight += _validatorContract.getValidatorWeight(_signer);\n if (_weight >= _minimumVoteWeight) {\n _passed = true;\n break;\n }\n }\n require(_passed, \"MainchainGatewayV2: query for insufficient vote weight\");\n withdrawalHash[_id] = _receiptHash;\n }\n\n if (_locked) {\n withdrawalLocked[_id] = true;\n emit WithdrawalLocked(_receiptHash, _receipt);\n return _locked;\n }\n\n _recordWithdrawal(_tokenAddr, _quantity);\n _receipt.info.handleAssetTransfer(payable(_receipt.mainchain.addr), _tokenAddr, wrappedNativeToken);\n emit Withdrew(_receiptHash, _receipt);\n }\n\n /**\n * @dev Requests deposit made by `_requester` address.\n *\n * Requirements:\n * - The token info is valid.\n * - The `msg.value` is 0 while depositing ERC20 token.\n * - The `msg.value` is equal to deposit quantity while depositing native token.\n *\n * Emits the `DepositRequested` event.\n *\n */\n function _requestDepositFor(Transfer.Request memory _request, address _requester) internal virtual {\n MappedToken memory _token;\n address _weth = address(wrappedNativeToken);\n\n _request.info.validate();\n if (_request.tokenAddr == address(0)) {\n require(_request.info.quantity == msg.value, \"MainchainGatewayV2: invalid request\");\n _token = getRoninToken(_weth);\n require(_token.erc == _request.info.erc, \"MainchainGatewayV2: invalid token standard\");\n _request.tokenAddr = _weth;\n } else {\n require(msg.value == 0, \"MainchainGatewayV2: invalid request\");\n _token = getRoninToken(_request.tokenAddr);\n require(_token.erc == _request.info.erc, \"MainchainGatewayV2: invalid token standard\");\n _request.info.transferFrom(_requester, address(this), _request.tokenAddr);\n // Withdraw if token is WETH\n if (_weth == _request.tokenAddr) {\n IWETH(_weth).withdraw(_request.info.quantity);\n }\n }\n\n uint256 _depositId = depositCount++;\n Transfer.Receipt memory _receipt = _request.into_deposit_receipt(\n _requester,\n _depositId,\n _token.tokenAddr,\n roninChainId\n );\n\n emit DepositRequested(_receipt.hash(), _receipt);\n }\n\n /**\n * @dev Returns the minimum vote weight for the token.\n */\n function _computeMinVoteWeight(\n Token.Standard _erc,\n address _token,\n uint256 _quantity,\n IWeightedValidator _validatorContract\n ) internal virtual returns (uint256 _weight, bool _locked) {\n uint256 _totalWeights = _validatorContract.totalWeights();\n _weight = _minimumVoteWeight(_totalWeights);\n if (_erc == Token.Standard.ERC20) {\n if (highTierThreshold[_token] <= _quantity) {\n _weight = _highTierVoteWeight(_totalWeights);\n }\n _locked = _lockedWithdrawalRequest(_token, _quantity);\n }\n }\n\n /**\n * @dev Update domain seperator.\n */\n function _updateDomainSeparator() internal {\n _domainSeparator = keccak256(\n abi.encode(\n keccak256(\"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)\"),\n keccak256(\"MainchainGatewayV2\"),\n keccak256(\"2\"),\n block.chainid,\n address(this)\n )\n );\n }\n\n /**\n * @dev Sets the WETH contract.\n *\n * Emits the `WrappedNativeTokenContractUpdated` event.\n *\n */\n function _setWrappedNativeTokenContract(IWETH _wrapedToken) internal {\n wrappedNativeToken = _wrapedToken;\n emit WrappedNativeTokenContractUpdated(_wrapedToken);\n }\n\n /**\n * @dev Receives ETH from WETH or creates deposit request.\n */\n function _fallback() internal virtual whenNotPaused {\n if (msg.sender != address(wrappedNativeToken)) {\n Transfer.Request memory _request;\n _request.recipientAddr = msg.sender;\n _request.info.quantity = msg.value;\n _requestDepositFor(_request, _request.recipientAddr);\n }\n }\n}\n"},"contracts/v0.8/interfaces/IWeightedValidator.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"./IQuorum.sol\";\n\ninterface IWeightedValidator is IQuorum {\n struct WeightedValidator {\n address validator;\n address governor;\n uint256 weight;\n }\n\n /// @dev Emitted when the validators are added\n event ValidatorsAdded(uint256 indexed nonce, WeightedValidator[] validators);\n /// @dev Emitted when the validators are updated\n event ValidatorsUpdated(uint256 indexed nonce, WeightedValidator[] validators);\n /// @dev Emitted when the validators are removed\n event ValidatorsRemoved(uint256 indexed nonce, address[] validators);\n\n /**\n * @dev Returns validator weight of the validator.\n */\n function getValidatorWeight(address _addr) external view returns (uint256);\n\n /**\n * @dev Returns governor weight of the governor.\n */\n function getGovernorWeight(address _addr) external view returns (uint256);\n\n /**\n * @dev Returns total validator weights of the address list.\n */\n function sumValidatorWeights(address[] calldata _addrList) external view returns (uint256 _weight);\n\n /**\n * @dev Returns total governor weights of the address list.\n */\n function sumGovernorWeights(address[] calldata _addrList) external view returns (uint256 _weight);\n\n /**\n * @dev Returns the validator list attached with governor address and weight.\n */\n function getValidatorInfo() external view returns (WeightedValidator[] memory _list);\n\n /**\n * @dev Returns the validator list.\n */\n function getValidators() external view returns (address[] memory _validators);\n\n /**\n * @dev Returns the validator at `_index` position.\n */\n function validators(uint256 _index) external view returns (WeightedValidator memory);\n\n /**\n * @dev Returns total of validators.\n */\n function totalValidators() external view returns (uint256);\n\n /**\n * @dev Returns total weights.\n */\n function totalWeights() external view returns (uint256);\n\n /**\n * @dev Adds validators.\n *\n * Requirements:\n * - The weights are larger than 0.\n * - The validators are not added.\n * - The method caller is admin.\n *\n * Emits the `ValidatorsAdded` event.\n *\n */\n function addValidators(WeightedValidator[] calldata _validators) external;\n\n /**\n * @dev Updates validators.\n *\n * Requirements:\n * - The weights are larger than 0.\n * - The validators are added.\n * - The method caller is admin.\n *\n * Emits the `ValidatorsUpdated` event.\n *\n */\n function updateValidators(WeightedValidator[] calldata _validators) external;\n\n /**\n * @dev Removes validators.\n *\n * Requirements:\n * - The validators are added.\n * - The method caller is admin.\n *\n * Emits the `ValidatorsRemoved` event.\n *\n */\n function removeValidators(address[] calldata _validators) external;\n}\n"},"contracts/v0.8/extensions/HasProxyAdmin.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"@openzeppelin/contracts/utils/StorageSlot.sol\";\n\nabstract contract HasProxyAdmin {\n // bytes32(uint256(keccak256(\"eip1967.proxy.admin\")) - 1));\n bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;\n\n modifier onlyAdmin() {\n require(msg.sender == _getAdmin(), \"HasProxyAdmin: unauthorized sender\");\n _;\n }\n\n /**\n * @dev Returns proxy admin.\n */\n function _getAdmin() internal view returns (address) {\n return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;\n }\n}\n"},"@openzeppelin/contracts/utils/introspection/IERC165.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC165 standard, as defined in the\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\n *\n * Implementers can declare support of contract interfaces, which can then be\n * queried by others ({ERC165Checker}).\n *\n * For an implementation, see {ERC165}.\n */\ninterface IERC165 {\n /**\n * @dev Returns true if this contract implements the interface defined by\n * `interfaceId`. See the corresponding\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\n * to learn more about how these ids are created.\n *\n * This function call must use less than 30 000 gas.\n */\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n"},"contracts/v0.8/interfaces/SignatureConsumer.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface SignatureConsumer {\n struct Signature {\n uint8 v;\n bytes32 r;\n bytes32 s;\n }\n}\n"},"@openzeppelin/contracts/utils/Strings.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev String operations.\n */\nlibrary Strings {\n bytes16 private constant _HEX_SYMBOLS = \"0123456789abcdef\";\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\n */\n function toString(uint256 value) internal pure returns (string memory) {\n // Inspired by OraclizeAPI's implementation - MIT licence\n // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol\n\n if (value == 0) {\n return \"0\";\n }\n uint256 temp = value;\n uint256 digits;\n while (temp != 0) {\n digits++;\n temp /= 10;\n }\n bytes memory buffer = new bytes(digits);\n while (value != 0) {\n digits -= 1;\n buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\n value /= 10;\n }\n return string(buffer);\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\n */\n function toHexString(uint256 value) internal pure returns (string memory) {\n if (value == 0) {\n return \"0x00\";\n }\n uint256 temp = value;\n uint256 length = 0;\n while (temp != 0) {\n length++;\n temp >>= 8;\n }\n return toHexString(value, length);\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\n */\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\n bytes memory buffer = new bytes(2 * length + 2);\n buffer[0] = \"0\";\n buffer[1] = \"x\";\n for (uint256 i = 2 * length + 1; i > 1; --i) {\n buffer[i] = _HEX_SYMBOLS[value & 0xf];\n value >>= 4;\n }\n require(value == 0, \"Strings: hex length insufficient\");\n return string(buffer);\n }\n}\n"},"@openzeppelin/contracts/utils/introspection/ERC165.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IERC165.sol\";\n\n/**\n * @dev Implementation of the {IERC165} interface.\n *\n * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check\n * for the additional interface id that will be supported. For example:\n *\n * ```solidity\n * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n * return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);\n * }\n * ```\n *\n * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.\n */\nabstract contract ERC165 is IERC165 {\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IERC165).interfaceId;\n }\n}\n"},"@openzeppelin/contracts/access/AccessControlEnumerable.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControlEnumerable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IAccessControlEnumerable.sol\";\nimport \"./AccessControl.sol\";\nimport \"../utils/structs/EnumerableSet.sol\";\n\n/**\n * @dev Extension of {AccessControl} that allows enumerating the members of each role.\n */\nabstract contract AccessControlEnumerable is IAccessControlEnumerable, AccessControl {\n using EnumerableSet for EnumerableSet.AddressSet;\n\n mapping(bytes32 => EnumerableSet.AddressSet) private _roleMembers;\n\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IAccessControlEnumerable).interfaceId || super.supportsInterface(interfaceId);\n }\n\n /**\n * @dev Returns one of the accounts that have `role`. `index` must be a\n * value between 0 and {getRoleMemberCount}, non-inclusive.\n *\n * Role bearers are not sorted in any particular way, and their ordering may\n * change at any point.\n *\n * WARNING: When using {getRoleMember} and {getRoleMemberCount}, make sure\n * you perform all queries on the same block. See the following\n * https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296[forum post]\n * for more information.\n */\n function getRoleMember(bytes32 role, uint256 index) public view virtual override returns (address) {\n return _roleMembers[role].at(index);\n }\n\n /**\n * @dev Returns the number of accounts that have `role`. Can be used\n * together with {getRoleMember} to enumerate all bearers of a role.\n */\n function getRoleMemberCount(bytes32 role) public view virtual override returns (uint256) {\n return _roleMembers[role].length();\n }\n\n /**\n * @dev Overload {_grantRole} to track enumerable memberships\n */\n function _grantRole(bytes32 role, address account) internal virtual override {\n super._grantRole(role, account);\n _roleMembers[role].add(account);\n }\n\n /**\n * @dev Overload {_revokeRole} to track enumerable memberships\n */\n function _revokeRole(bytes32 role, address account) internal virtual override {\n super._revokeRole(role, account);\n _roleMembers[role].remove(account);\n }\n}\n"},"contracts/v0.8/library/Transfer.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport \"@openzeppelin/contracts/utils/cryptography/ECDSA.sol\";\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport \"@openzeppelin/contracts/utils/Strings.sol\";\nimport \"./Token.sol\";\n\nlibrary Transfer {\n using ECDSA for bytes32;\n\n enum Kind {\n Deposit,\n Withdrawal\n }\n\n struct Request {\n // For deposit request: Recipient address on Ronin network\n // For withdrawal request: Recipient address on mainchain network\n address recipientAddr;\n // Token address to deposit/withdraw\n // Value 0: native token\n address tokenAddr;\n Token.Info info;\n }\n\n /**\n * @dev Converts the transfer request into the deposit receipt.\n */\n function into_deposit_receipt(\n Request memory _request,\n address _requester,\n uint256 _id,\n address _roninTokenAddr,\n uint256 _roninChainId\n ) internal view returns (Receipt memory _receipt) {\n _receipt.id = _id;\n _receipt.kind = Kind.Deposit;\n _receipt.mainchain.addr = _requester;\n _receipt.mainchain.tokenAddr = _request.tokenAddr;\n _receipt.mainchain.chainId = block.chainid;\n _receipt.ronin.addr = _request.recipientAddr;\n _receipt.ronin.tokenAddr = _roninTokenAddr;\n _receipt.ronin.chainId = _roninChainId;\n _receipt.info = _request.info;\n }\n\n /**\n * @dev Converts the transfer request into the withdrawal receipt.\n */\n function into_withdrawal_receipt(\n Request memory _request,\n address _requester,\n uint256 _id,\n address _mainchainTokenAddr,\n uint256 _mainchainId\n ) internal view returns (Receipt memory _receipt) {\n _receipt.id = _id;\n _receipt.kind = Kind.Withdrawal;\n _receipt.ronin.addr = _requester;\n _receipt.ronin.tokenAddr = _request.tokenAddr;\n _receipt.ronin.chainId = block.chainid;\n _receipt.mainchain.addr = _request.recipientAddr;\n _receipt.mainchain.tokenAddr = _mainchainTokenAddr;\n _receipt.mainchain.chainId = _mainchainId;\n _receipt.info = _request.info;\n }\n\n struct Receipt {\n uint256 id;\n Kind kind;\n Token.Owner mainchain;\n Token.Owner ronin;\n Token.Info info;\n }\n\n // keccak256(\"Receipt(uint256 id,uint8 kind,TokenOwner mainchain,TokenOwner ronin,TokenInfo info)TokenInfo(uint8 erc,uint256 id,uint256 quantity)TokenOwner(address addr,address tokenAddr,uint256 chainId)\");\n bytes32 public constant TYPE_HASH = 0xb9d1fe7c9deeec5dc90a2f47ff1684239519f2545b2228d3d91fb27df3189eea;\n\n /**\n * @dev Returns token info struct hash.\n */\n function hash(Receipt memory _receipt) internal pure returns (bytes32) {\n return\n keccak256(\n abi.encode(\n TYPE_HASH,\n _receipt.id,\n _receipt.kind,\n Token.hash(_receipt.mainchain),\n Token.hash(_receipt.ronin),\n Token.hash(_receipt.info)\n )\n );\n }\n\n /**\n * @dev Returns the receipt digest.\n */\n function receiptDigest(bytes32 _domainSeparator, bytes32 _receiptHash) internal pure returns (bytes32) {\n return _domainSeparator.toTypedDataHash(_receiptHash);\n }\n}\n"},"@openzeppelin/contracts/utils/Context.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n}\n"},"@openzeppelin/contracts/security/Pausable.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (security/Pausable.sol)\n\npragma solidity ^0.8.0;\n\nimport \"../utils/Context.sol\";\n\n/**\n * @dev Contract module which allows children to implement an emergency stop\n * mechanism that can be triggered by an authorized account.\n *\n * This module is used through inheritance. It will make available the\n * modifiers `whenNotPaused` and `whenPaused`, which can be applied to\n * the functions of your contract. Note that they will not be pausable by\n * simply including this module, only once the modifiers are put in place.\n */\nabstract contract Pausable is Context {\n /**\n * @dev Emitted when the pause is triggered by `account`.\n */\n event Paused(address account);\n\n /**\n * @dev Emitted when the pause is lifted by `account`.\n */\n event Unpaused(address account);\n\n bool private _paused;\n\n /**\n * @dev Initializes the contract in unpaused state.\n */\n constructor() {\n _paused = false;\n }\n\n /**\n * @dev Returns true if the contract is paused, and false otherwise.\n */\n function paused() public view virtual returns (bool) {\n return _paused;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is not paused.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n modifier whenNotPaused() {\n require(!paused(), \"Pausable: paused\");\n _;\n }\n\n /**\n * @dev Modifier to make a function callable only when the contract is paused.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n modifier whenPaused() {\n require(paused(), \"Pausable: not paused\");\n _;\n }\n\n /**\n * @dev Triggers stopped state.\n *\n * Requirements:\n *\n * - The contract must not be paused.\n */\n function _pause() internal virtual whenNotPaused {\n _paused = true;\n emit Paused(_msgSender());\n }\n\n /**\n * @dev Returns to normal state.\n *\n * Requirements:\n *\n * - The contract must be paused.\n */\n function _unpause() internal virtual whenPaused {\n _paused = false;\n emit Unpaused(_msgSender());\n }\n}\n"},"@openzeppelin/contracts/utils/structs/EnumerableSet.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Library for managing\n * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive\n * types.\n *\n * Sets have the following properties:\n *\n * - Elements are added, removed, and checked for existence in constant time\n * (O(1)).\n * - Elements are enumerated in O(n). No guarantees are made on the ordering.\n *\n * ```\n * contract Example {\n * // Add the library methods\n * using EnumerableSet for EnumerableSet.AddressSet;\n *\n * // Declare a set state variable\n * EnumerableSet.AddressSet private mySet;\n * }\n * ```\n *\n * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)\n * and `uint256` (`UintSet`) are supported.\n */\nlibrary EnumerableSet {\n // To implement this library for multiple types with as little code\n // repetition as possible, we write it in terms of a generic Set type with\n // bytes32 values.\n // The Set implementation uses private functions, and user-facing\n // implementations (such as AddressSet) are just wrappers around the\n // underlying Set.\n // This means that we can only create new EnumerableSets for types that fit\n // in bytes32.\n\n struct Set {\n // Storage of set values\n bytes32[] _values;\n // Position of the value in the `values` array, plus 1 because index 0\n // means a value is not in the set.\n mapping(bytes32 => uint256) _indexes;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function _add(Set storage set, bytes32 value) private returns (bool) {\n if (!_contains(set, value)) {\n set._values.push(value);\n // The value is stored at length-1, but we add 1 to all indexes\n // and use 0 as a sentinel value\n set._indexes[value] = set._values.length;\n return true;\n } else {\n return false;\n }\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function _remove(Set storage set, bytes32 value) private returns (bool) {\n // We read and store the value's index to prevent multiple reads from the same storage slot\n uint256 valueIndex = set._indexes[value];\n\n if (valueIndex != 0) {\n // Equivalent to contains(set, value)\n // To delete an element from the _values array in O(1), we swap the element to delete with the last one in\n // the array, and then remove the last element (sometimes called as 'swap and pop').\n // This modifies the order of the array, as noted in {at}.\n\n uint256 toDeleteIndex = valueIndex - 1;\n uint256 lastIndex = set._values.length - 1;\n\n if (lastIndex != toDeleteIndex) {\n bytes32 lastvalue = set._values[lastIndex];\n\n // Move the last value to the index where the value to delete is\n set._values[toDeleteIndex] = lastvalue;\n // Update the index for the moved value\n set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex\n }\n\n // Delete the slot where the moved value was stored\n set._values.pop();\n\n // Delete the index for the deleted slot\n delete set._indexes[value];\n\n return true;\n } else {\n return false;\n }\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function _contains(Set storage set, bytes32 value) private view returns (bool) {\n return set._indexes[value] != 0;\n }\n\n /**\n * @dev Returns the number of values on the set. O(1).\n */\n function _length(Set storage set) private view returns (uint256) {\n return set._values.length;\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function _at(Set storage set, uint256 index) private view returns (bytes32) {\n return set._values[index];\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function _values(Set storage set) private view returns (bytes32[] memory) {\n return set._values;\n }\n\n // Bytes32Set\n\n struct Bytes32Set {\n Set _inner;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {\n return _add(set._inner, value);\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {\n return _remove(set._inner, value);\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {\n return _contains(set._inner, value);\n }\n\n /**\n * @dev Returns the number of values in the set. O(1).\n */\n function length(Bytes32Set storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {\n return _at(set._inner, index);\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {\n return _values(set._inner);\n }\n\n // AddressSet\n\n struct AddressSet {\n Set _inner;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function add(AddressSet storage set, address value) internal returns (bool) {\n return _add(set._inner, bytes32(uint256(uint160(value))));\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function remove(AddressSet storage set, address value) internal returns (bool) {\n return _remove(set._inner, bytes32(uint256(uint160(value))));\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function contains(AddressSet storage set, address value) internal view returns (bool) {\n return _contains(set._inner, bytes32(uint256(uint160(value))));\n }\n\n /**\n * @dev Returns the number of values in the set. O(1).\n */\n function length(AddressSet storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function at(AddressSet storage set, uint256 index) internal view returns (address) {\n return address(uint160(uint256(_at(set._inner, index))));\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function values(AddressSet storage set) internal view returns (address[] memory) {\n bytes32[] memory store = _values(set._inner);\n address[] memory result;\n\n assembly {\n result := store\n }\n\n return result;\n }\n\n // UintSet\n\n struct UintSet {\n Set _inner;\n }\n\n /**\n * @dev Add a value to a set. O(1).\n *\n * Returns true if the value was added to the set, that is if it was not\n * already present.\n */\n function add(UintSet storage set, uint256 value) internal returns (bool) {\n return _add(set._inner, bytes32(value));\n }\n\n /**\n * @dev Removes a value from a set. O(1).\n *\n * Returns true if the value was removed from the set, that is if it was\n * present.\n */\n function remove(UintSet storage set, uint256 value) internal returns (bool) {\n return _remove(set._inner, bytes32(value));\n }\n\n /**\n * @dev Returns true if the value is in the set. O(1).\n */\n function contains(UintSet storage set, uint256 value) internal view returns (bool) {\n return _contains(set._inner, bytes32(value));\n }\n\n /**\n * @dev Returns the number of values on the set. O(1).\n */\n function length(UintSet storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n /**\n * @dev Returns the value stored at position `index` in the set. O(1).\n *\n * Note that there are no guarantees on the ordering of values inside the\n * array, and it may change when more values are added or removed.\n *\n * Requirements:\n *\n * - `index` must be strictly less than {length}.\n */\n function at(UintSet storage set, uint256 index) internal view returns (uint256) {\n return uint256(_at(set._inner, index));\n }\n\n /**\n * @dev Return the entire set in an array\n *\n * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed\n * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that\n * this function has an unbounded cost, and using it as part of a state-changing function may render the function\n * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.\n */\n function values(UintSet storage set) internal view returns (uint256[] memory) {\n bytes32[] memory store = _values(set._inner);\n uint256[] memory result;\n\n assembly {\n result := store\n }\n\n return result;\n }\n}\n"},"contracts/v0.8/interfaces/IQuorum.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IQuorum {\n /// @dev Emitted when the threshold is updated\n event ThresholdUpdated(\n uint256 indexed nonce,\n uint256 indexed numerator,\n uint256 indexed denominator,\n uint256 previousNumerator,\n uint256 previousDenominator\n );\n\n /**\n * @dev Returns the threshold.\n */\n function getThreshold() external view returns (uint256 _num, uint256 _denom);\n\n /**\n * @dev Checks whether the `_voteWeight` passes the threshold.\n */\n function checkThreshold(uint256 _voteWeight) external view returns (bool);\n\n /**\n * @dev Returns the minimum vote weight to pass the threshold.\n */\n function minimumVoteWeight() external view returns (uint256);\n\n /**\n * @dev Sets the threshold.\n *\n * Requirements:\n * - The method caller is admin.\n *\n * Emits the `ThresholdUpdated` event.\n *\n */\n function setThreshold(uint256 _numerator, uint256 _denominator)\n external\n returns (uint256 _previousNum, uint256 _previousDenom);\n}\n"},"contracts/v0.8/interfaces/IWETH.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IWETH {\n function deposit() external payable;\n\n function withdraw(uint256 _wad) external;\n\n function balanceOf(address) external view returns (uint256);\n}\n"},"@openzeppelin/contracts/access/AccessControl.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0) (access/AccessControl.sol)\n\npragma solidity ^0.8.0;\n\nimport \"./IAccessControl.sol\";\nimport \"../utils/Context.sol\";\nimport \"../utils/Strings.sol\";\nimport \"../utils/introspection/ERC165.sol\";\n\n/**\n * @dev Contract module that allows children to implement role-based access\n * control mechanisms. This is a lightweight version that doesn't allow enumerating role\n * members except through off-chain means by accessing the contract event logs. Some\n * applications may benefit from on-chain enumerability, for those cases see\n * {AccessControlEnumerable}.\n *\n * Roles are referred to by their `bytes32` identifier. These should be exposed\n * in the external API and be unique. The best way to achieve this is by\n * using `public constant` hash digests:\n *\n * ```\n * bytes32 public constant MY_ROLE = keccak256(\"MY_ROLE\");\n * ```\n *\n * Roles can be used to represent a set of permissions. To restrict access to a\n * function call, use {hasRole}:\n *\n * ```\n * function foo() public {\n * require(hasRole(MY_ROLE, msg.sender));\n * ...\n * }\n * ```\n *\n * Roles can be granted and revoked dynamically via the {grantRole} and\n * {revokeRole} functions. Each role has an associated admin role, and only\n * accounts that have a role's admin role can call {grantRole} and {revokeRole}.\n *\n * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means\n * that only accounts with this role will be able to grant or revoke other\n * roles. More complex role relationships can be created by using\n * {_setRoleAdmin}.\n *\n * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to\n * grant and revoke this role. Extra precautions should be taken to secure\n * accounts that have been granted it.\n */\nabstract contract AccessControl is Context, IAccessControl, ERC165 {\n struct RoleData {\n mapping(address => bool) members;\n bytes32 adminRole;\n }\n\n mapping(bytes32 => RoleData) private _roles;\n\n bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;\n\n /**\n * @dev Modifier that checks that an account has a specific role. Reverts\n * with a standardized message including the required role.\n *\n * The format of the revert reason is given by the following regular expression:\n *\n * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/\n *\n * _Available since v4.1._\n */\n modifier onlyRole(bytes32 role) {\n _checkRole(role, _msgSender());\n _;\n }\n\n /**\n * @dev See {IERC165-supportsInterface}.\n */\n function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {\n return interfaceId == type(IAccessControl).interfaceId || super.supportsInterface(interfaceId);\n }\n\n /**\n * @dev Returns `true` if `account` has been granted `role`.\n */\n function hasRole(bytes32 role, address account) public view virtual override returns (bool) {\n return _roles[role].members[account];\n }\n\n /**\n * @dev Revert with a standard message if `account` is missing `role`.\n *\n * The format of the revert reason is given by the following regular expression:\n *\n * /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/\n */\n function _checkRole(bytes32 role, address account) internal view virtual {\n if (!hasRole(role, account)) {\n revert(\n string(\n abi.encodePacked(\n \"AccessControl: account \",\n Strings.toHexString(uint160(account), 20),\n \" is missing role \",\n Strings.toHexString(uint256(role), 32)\n )\n )\n );\n }\n }\n\n /**\n * @dev Returns the admin role that controls `role`. See {grantRole} and\n * {revokeRole}.\n *\n * To change a role's admin, use {_setRoleAdmin}.\n */\n function getRoleAdmin(bytes32 role) public view virtual override returns (bytes32) {\n return _roles[role].adminRole;\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {\n _grantRole(role, account);\n }\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * If `account` had been granted `role`, emits a {RoleRevoked} event.\n *\n * Requirements:\n *\n * - the caller must have ``role``'s admin role.\n */\n function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {\n _revokeRole(role, account);\n }\n\n /**\n * @dev Revokes `role` from the calling account.\n *\n * Roles are often managed via {grantRole} and {revokeRole}: this function's\n * purpose is to provide a mechanism for accounts to lose their privileges\n * if they are compromised (such as when a trusted device is misplaced).\n *\n * If the calling account had been revoked `role`, emits a {RoleRevoked}\n * event.\n *\n * Requirements:\n *\n * - the caller must be `account`.\n */\n function renounceRole(bytes32 role, address account) public virtual override {\n require(account == _msgSender(), \"AccessControl: can only renounce roles for self\");\n\n _revokeRole(role, account);\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * If `account` had not been already granted `role`, emits a {RoleGranted}\n * event. Note that unlike {grantRole}, this function doesn't perform any\n * checks on the calling account.\n *\n * [WARNING]\n * ====\n * This function should only be called from the constructor when setting\n * up the initial roles for the system.\n *\n * Using this function in any other way is effectively circumventing the admin\n * system imposed by {AccessControl}.\n * ====\n *\n * NOTE: This function is deprecated in favor of {_grantRole}.\n */\n function _setupRole(bytes32 role, address account) internal virtual {\n _grantRole(role, account);\n }\n\n /**\n * @dev Sets `adminRole` as ``role``'s admin role.\n *\n * Emits a {RoleAdminChanged} event.\n */\n function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {\n bytes32 previousAdminRole = getRoleAdmin(role);\n _roles[role].adminRole = adminRole;\n emit RoleAdminChanged(role, previousAdminRole, adminRole);\n }\n\n /**\n * @dev Grants `role` to `account`.\n *\n * Internal function without access restriction.\n */\n function _grantRole(bytes32 role, address account) internal virtual {\n if (!hasRole(role, account)) {\n _roles[role].members[account] = true;\n emit RoleGranted(role, account, _msgSender());\n }\n }\n\n /**\n * @dev Revokes `role` from `account`.\n *\n * Internal function without access restriction.\n */\n function _revokeRole(bytes32 role, address account) internal virtual {\n if (hasRole(role, account)) {\n _roles[role].members[account] = false;\n emit RoleRevoked(role, account, _msgSender());\n }\n }\n}\n"},"@openzeppelin/contracts/utils/Address.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)\n\npragma solidity ^0.8.1;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n * ====\n *\n * [IMPORTANT]\n * ====\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\n *\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\n * constructor.\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize/address.code.length, which returns 0\n // for contracts in construction, since the code is only stored at the end\n // of the constructor execution.\n\n return account.code.length > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCall(target, data, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n require(isContract(target), \"Address: call to non-contract\");\n\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n require(isContract(target), \"Address: static call to non-contract\");\n\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(isContract(target), \"Address: delegate call to non-contract\");\n\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n }\n}\n"},"@openzeppelin/contracts/token/ERC20/IERC20.sol":{"content":"// SPDX-License-Identifier: MIT\n// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/IERC20.sol)\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `from` to `to` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address from,\n address to,\n uint256 amount\n ) external returns (bool);\n\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n}\n"}},"settings":{"evmVersion":"london","libraries":{},"metadata":{"bytecodeHash":"ipfs","useLiteralContent":true},"optimizer":{"enabled":true,"runs":1000},"remappings":[],"outputSelection":{"*":{"*":["evm.bytecode","evm.deployedBytecode","devdoc","userdoc","metadata","abi"]}}}},"ABI":"[{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"tokens\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint256[]\",\"name\":\"limits\",\"type\":\"uint256[]\"}],\"name\":\"DailyWithdrawalLimitsUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"receiptHash\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"enum Transfer.Kind\",\"name\":\"kind\",\"type\":\"uint8\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"mainchain\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"ronin\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enum Token.Standard\",\"name\":\"erc\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"quantity\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Info\",\"name\":\"info\",\"type\":\"tuple\"}],\"indexed\":false,\"internalType\":\"struct Transfer.Receipt\",\"name\":\"receipt\",\"type\":\"tuple\"}],\"name\":\"DepositRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"tokens\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint256[]\",\"name\":\"thresholds\",\"type\":\"uint256[]\"}],\"name\":\"HighTierThresholdsUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"numerator\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"denominator\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"previousNumerator\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"previousDenominator\",\"type\":\"uint256\"}],\"name\":\"HighTierVoteWeightThresholdUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"tokens\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint256[]\",\"name\":\"thresholds\",\"type\":\"uint256[]\"}],\"name\":\"LockedThresholdsUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"Paused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"previousAdminRole\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"newAdminRole\",\"type\":\"bytes32\"}],\"name\":\"RoleAdminChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleGranted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"RoleRevoked\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"numerator\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"denominator\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"previousNumerator\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"previousDenominator\",\"type\":\"uint256\"}],\"name\":\"ThresholdUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"mainchainTokens\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"roninTokens\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"enum Token.Standard[]\",\"name\":\"standards\",\"type\":\"uint8[]\"}],\"name\":\"TokenMapped\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"tokens\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint256[]\",\"name\":\"percentages\",\"type\":\"uint256[]\"}],\"name\":\"UnlockFeePercentagesUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"Unpaused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IWeightedValidator\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"ValidatorContractUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"receiptHash\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"enum Transfer.Kind\",\"name\":\"kind\",\"type\":\"uint8\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"mainchain\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"ronin\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enum Token.Standard\",\"name\":\"erc\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"quantity\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Info\",\"name\":\"info\",\"type\":\"tuple\"}],\"indexed\":false,\"internalType\":\"struct Transfer.Receipt\",\"name\":\"receipt\",\"type\":\"tuple\"}],\"name\":\"WithdrawalLocked\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"receiptHash\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"enum Transfer.Kind\",\"name\":\"kind\",\"type\":\"uint8\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"mainchain\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"ronin\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enum Token.Standard\",\"name\":\"erc\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"quantity\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Info\",\"name\":\"info\",\"type\":\"tuple\"}],\"indexed\":false,\"internalType\":\"struct Transfer.Receipt\",\"name\":\"receipt\",\"type\":\"tuple\"}],\"name\":\"WithdrawalUnlocked\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"receiptHash\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"enum Transfer.Kind\",\"name\":\"kind\",\"type\":\"uint8\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"mainchain\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"ronin\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enum Token.Standard\",\"name\":\"erc\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"quantity\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Info\",\"name\":\"info\",\"type\":\"tuple\"}],\"indexed\":false,\"internalType\":\"struct Transfer.Receipt\",\"name\":\"receipt\",\"type\":\"tuple\"}],\"name\":\"Withdrew\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IWETH\",\"name\":\"weth\",\"type\":\"address\"}],\"name\":\"WrappedNativeTokenContractUpdated\",\"type\":\"event\"},{\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"inputs\":[],\"name\":\"DEFAULT_ADMIN_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DOMAIN_SEPARATOR\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"WITHDRAWAL_UNLOCKER_ROLE\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"_MAX_PERCENTAGE\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_voteWeight\",\"type\":\"uint256\"}],\"name\":\"checkHighTierVoteWeightThreshold\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_voteWeight\",\"type\":\"uint256\"}],\"name\":\"checkThreshold\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"dailyWithdrawalLimit\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"depositCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getHighTierVoteWeightThreshold\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"}],\"name\":\"getRoleAdmin\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"index\",\"type\":\"uint256\"}],\"name\":\"getRoleMember\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"}],\"name\":\"getRoleMemberCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_mainchainToken\",\"type\":\"address\"}],\"name\":\"getRoninToken\",\"outputs\":[{\"components\":[{\"internalType\":\"enum Token.Standard\",\"name\":\"erc\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"}],\"internalType\":\"struct MappedTokenConsumer.MappedToken\",\"name\":\"_token\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getThreshold\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"grantRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"hasRole\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"highTierThreshold\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_roleSetter\",\"type\":\"address\"},{\"internalType\":\"contract IWETH\",\"name\":\"_wrappedToken\",\"type\":\"address\"},{\"internalType\":\"contract IWeightedValidator\",\"name\":\"_validatorContract\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_roninChainId\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_numerator\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_highTierVWNumerator\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_denominator\",\"type\":\"uint256\"},{\"internalType\":\"address[][3]\",\"name\":\"_addresses\",\"type\":\"address[][3]\"},{\"internalType\":\"uint256[][4]\",\"name\":\"_thresholds\",\"type\":\"uint256[][4]\"},{\"internalType\":\"enum Token.Standard[]\",\"name\":\"_standards\",\"type\":\"uint8[]\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"lastDateSynced\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"lastSyncedWithdrawal\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"lockedThreshold\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_mainchainTokens\",\"type\":\"address[]\"},{\"internalType\":\"address[]\",\"name\":\"_roninTokens\",\"type\":\"address[]\"},{\"internalType\":\"enum Token.Standard[]\",\"name\":\"_standards\",\"type\":\"uint8[]\"}],\"name\":\"mapTokens\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_mainchainTokens\",\"type\":\"address[]\"},{\"internalType\":\"address[]\",\"name\":\"_roninTokens\",\"type\":\"address[]\"},{\"internalType\":\"enum Token.Standard[]\",\"name\":\"_standards\",\"type\":\"uint8[]\"},{\"internalType\":\"uint256[][4]\",\"name\":\"_thresholds\",\"type\":\"uint256[][4]\"}],\"name\":\"mapTokensAndThresholds\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"minimumVoteWeight\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"nonce\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pause\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"paused\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_quantity\",\"type\":\"uint256\"}],\"name\":\"reachedWithdrawalLimit\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"receiveEther\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"renounceRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"recipientAddr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"enum Token.Standard\",\"name\":\"erc\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"quantity\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Info\",\"name\":\"info\",\"type\":\"tuple\"}],\"internalType\":\"struct Transfer.Request\",\"name\":\"_request\",\"type\":\"tuple\"}],\"name\":\"requestDepositFor\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"role\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"revokeRole\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"roninChainId\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_tokens\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"_limits\",\"type\":\"uint256[]\"}],\"name\":\"setDailyWithdrawalLimits\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_tokens\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"_thresholds\",\"type\":\"uint256[]\"}],\"name\":\"setHighTierThresholds\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_numerator\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_denominator\",\"type\":\"uint256\"}],\"name\":\"setHighTierVoteWeightThreshold\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"_previousNum\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_previousDenom\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_tokens\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"_thresholds\",\"type\":\"uint256[]\"}],\"name\":\"setLockedThresholds\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_numerator\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_denominator\",\"type\":\"uint256\"}],\"name\":\"setThreshold\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"_previousNum\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_previousDenom\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"_tokens\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"_percentages\",\"type\":\"uint256[]\"}],\"name\":\"setUnlockFeePercentages\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IWeightedValidator\",\"name\":\"_validatorContract\",\"type\":\"address\"}],\"name\":\"setValidatorContract\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IWETH\",\"name\":\"_wrappedToken\",\"type\":\"address\"}],\"name\":\"setWrappedNativeTokenContract\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"enum Transfer.Kind\",\"name\":\"kind\",\"type\":\"uint8\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"mainchain\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"ronin\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enum Token.Standard\",\"name\":\"erc\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"quantity\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Info\",\"name\":\"info\",\"type\":\"tuple\"}],\"internalType\":\"struct Transfer.Receipt\",\"name\":\"_receipt\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"internalType\":\"struct SignatureConsumer.Signature[]\",\"name\":\"_signatures\",\"type\":\"tuple[]\"}],\"name\":\"submitWithdrawal\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"_locked\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"unlockFeePercentages\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"enum Transfer.Kind\",\"name\":\"kind\",\"type\":\"uint8\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"mainchain\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Owner\",\"name\":\"ronin\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"enum Token.Standard\",\"name\":\"erc\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"quantity\",\"type\":\"uint256\"}],\"internalType\":\"struct Token.Info\",\"name\":\"info\",\"type\":\"tuple\"}],\"internalType\":\"struct Transfer.Receipt\",\"name\":\"_receipt\",\"type\":\"tuple\"}],\"name\":\"unlockWithdrawal\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"unpause\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"validatorContract\",\"outputs\":[{\"internalType\":\"contract IWeightedValidator\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"withdrawalHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"withdrawalLocked\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"wrappedNativeToken\",\"outputs\":[{\"internalType\":\"contract IWETH\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]","ContractName":"MainchainGatewayV2","CompilerVersion":"v0.8.9+commit.e5eed63a","OptimizationUsed":1,"Runs":1000,"ConstructorArguments":"0x","EVMVersion":"Default","Library":"","LicenseType":"MIT","Proxy":0,"SwarmSource":""}] \ No newline at end of file diff --git a/testdata/etherscan/0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545/creation_data.json b/testdata/etherscan/0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545/creation_data.json new file mode 100644 index 000000000..f4dfea9e7 --- /dev/null +++ b/testdata/etherscan/0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545/creation_data.json @@ -0,0 +1 @@ +{"contractAddress":"0x8b3d32cf2bb4d0d16656f4c0b04fa546274f1545","contractCreator":"0x958892b4a0512b28aaac890fc938868bbd42f064","txHash":"0x79820495643caf5a1e7e96578361c9ddba0e0735cd684ada7450254f6fd58f51"} \ No newline at end of file diff --git a/testdata/etherscan/0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545/metadata.json b/testdata/etherscan/0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545/metadata.json new file mode 100644 index 000000000..1fd95fa4f --- /dev/null +++ b/testdata/etherscan/0x8B3D32cf2bb4d0D16656f4c0b04Fa546274f1545/metadata.json @@ -0,0 +1 @@ +[{"SourceCode":{"language":"Solidity","sources":{"contracts/governance/governor/GovernorStorage.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\nimport \"./IIpt.sol\";\nimport \"./Structs.sol\";\n\ncontract GovernorCharlieDelegatorStorage {\n /// @notice Active brains of Governor\n address public implementation;\n}\n\n/**\n * @title Storage for Governor Charlie Delegate\n * @notice For future upgrades, do not change GovernorCharlieDelegateStorage. Create a new\n * contract which implements GovernorCharlieDelegateStorage and following the naming convention\n * GovernorCharlieDelegateStorageVX.\n */\n//solhint-disable-next-line max-states-count\ncontract GovernorCharlieDelegateStorage is GovernorCharlieDelegatorStorage {\n /// @notice The number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed\n uint256 public quorumVotes;\n\n /// @notice The number of votes in support of a proposal required in order for an emergency quorum to be reached and for a vote to succeed\n uint256 public emergencyQuorumVotes;\n\n /// @notice The delay before voting on a proposal may take place, once proposed, in blocks\n uint256 public votingDelay;\n\n /// @notice The duration of voting on a proposal, in blocks\n uint256 public votingPeriod;\n\n /// @notice The number of votes required in order for a voter to become a proposer\n uint256 public proposalThreshold;\n\n /// @notice Initial proposal id set at become\n uint256 public initialProposalId;\n\n /// @notice The total number of proposals\n uint256 public proposalCount;\n\n /// @notice The address of the Interest Protocol governance token\n IIpt public ipt;\n\n /// @notice The official record of all proposals ever proposed\n mapping(uint256 => Proposal) public proposals;\n\n /// @notice The latest proposal for each proposer\n mapping(address => uint256) public latestProposalIds;\n\n /// @notice The latest proposal for each proposer\n mapping(bytes32 => bool) public queuedTransactions;\n\n /// @notice The proposal holding period\n uint256 public proposalTimelockDelay;\n\n /// @notice Stores the expiration of account whitelist status as a timestamp\n mapping(address => uint256) public whitelistAccountExpirations;\n\n /// @notice Address which manages whitelisted proposals and whitelist accounts\n address public whitelistGuardian;\n\n /// @notice The duration of the voting on a emergency proposal, in blocks\n uint256 public emergencyVotingPeriod;\n\n /// @notice The emergency proposal holding period\n uint256 public emergencyTimelockDelay;\n\n /// all receipts for proposal\n mapping(uint256 => mapping(address => Receipt)) public proposalReceipts;\n\n /// @notice The emergency proposal holding period\n bool public initialized;\n\n /// @notice The number of votes to reject an optimistic proposal\n uint256 public optimisticQuorumVotes; \n\n /// @notice The delay period before voting begins\n uint256 public optimisticVotingDelay; \n\n /// @notice The maximum number of seconds an address can be whitelisted for\n uint256 public maxWhitelistPeriod; \n}\n"},"hardhat/console.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity >= 0.4.22 <0.9.0;\n\nlibrary console {\n\taddress constant CONSOLE_ADDRESS = address(0x000000000000000000636F6e736F6c652e6c6f67);\n\n\tfunction _sendLogPayload(bytes memory payload) private view {\n\t\tuint256 payloadLength = payload.length;\n\t\taddress consoleAddress = CONSOLE_ADDRESS;\n\t\tassembly {\n\t\t\tlet payloadStart := add(payload, 32)\n\t\t\tlet r := staticcall(gas(), consoleAddress, payloadStart, payloadLength, 0, 0)\n\t\t}\n\t}\n\n\tfunction log() internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log()\"));\n\t}\n\n\tfunction logInt(int p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(int)\", p0));\n\t}\n\n\tfunction logUint(uint p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint)\", p0));\n\t}\n\n\tfunction logString(string memory p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string)\", p0));\n\t}\n\n\tfunction logBool(bool p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool)\", p0));\n\t}\n\n\tfunction logAddress(address p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address)\", p0));\n\t}\n\n\tfunction logBytes(bytes memory p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes)\", p0));\n\t}\n\n\tfunction logBytes1(bytes1 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes1)\", p0));\n\t}\n\n\tfunction logBytes2(bytes2 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes2)\", p0));\n\t}\n\n\tfunction logBytes3(bytes3 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes3)\", p0));\n\t}\n\n\tfunction logBytes4(bytes4 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes4)\", p0));\n\t}\n\n\tfunction logBytes5(bytes5 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes5)\", p0));\n\t}\n\n\tfunction logBytes6(bytes6 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes6)\", p0));\n\t}\n\n\tfunction logBytes7(bytes7 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes7)\", p0));\n\t}\n\n\tfunction logBytes8(bytes8 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes8)\", p0));\n\t}\n\n\tfunction logBytes9(bytes9 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes9)\", p0));\n\t}\n\n\tfunction logBytes10(bytes10 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes10)\", p0));\n\t}\n\n\tfunction logBytes11(bytes11 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes11)\", p0));\n\t}\n\n\tfunction logBytes12(bytes12 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes12)\", p0));\n\t}\n\n\tfunction logBytes13(bytes13 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes13)\", p0));\n\t}\n\n\tfunction logBytes14(bytes14 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes14)\", p0));\n\t}\n\n\tfunction logBytes15(bytes15 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes15)\", p0));\n\t}\n\n\tfunction logBytes16(bytes16 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes16)\", p0));\n\t}\n\n\tfunction logBytes17(bytes17 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes17)\", p0));\n\t}\n\n\tfunction logBytes18(bytes18 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes18)\", p0));\n\t}\n\n\tfunction logBytes19(bytes19 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes19)\", p0));\n\t}\n\n\tfunction logBytes20(bytes20 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes20)\", p0));\n\t}\n\n\tfunction logBytes21(bytes21 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes21)\", p0));\n\t}\n\n\tfunction logBytes22(bytes22 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes22)\", p0));\n\t}\n\n\tfunction logBytes23(bytes23 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes23)\", p0));\n\t}\n\n\tfunction logBytes24(bytes24 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes24)\", p0));\n\t}\n\n\tfunction logBytes25(bytes25 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes25)\", p0));\n\t}\n\n\tfunction logBytes26(bytes26 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes26)\", p0));\n\t}\n\n\tfunction logBytes27(bytes27 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes27)\", p0));\n\t}\n\n\tfunction logBytes28(bytes28 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes28)\", p0));\n\t}\n\n\tfunction logBytes29(bytes29 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes29)\", p0));\n\t}\n\n\tfunction logBytes30(bytes30 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes30)\", p0));\n\t}\n\n\tfunction logBytes31(bytes31 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes31)\", p0));\n\t}\n\n\tfunction logBytes32(bytes32 p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bytes32)\", p0));\n\t}\n\n\tfunction log(uint p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint)\", p0));\n\t}\n\n\tfunction log(string memory p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string)\", p0));\n\t}\n\n\tfunction log(bool p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool)\", p0));\n\t}\n\n\tfunction log(address p0) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address)\", p0));\n\t}\n\n\tfunction log(uint p0, uint p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint)\", p0, p1));\n\t}\n\n\tfunction log(uint p0, string memory p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string)\", p0, p1));\n\t}\n\n\tfunction log(uint p0, bool p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool)\", p0, p1));\n\t}\n\n\tfunction log(uint p0, address p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address)\", p0, p1));\n\t}\n\n\tfunction log(string memory p0, uint p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint)\", p0, p1));\n\t}\n\n\tfunction log(string memory p0, string memory p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string)\", p0, p1));\n\t}\n\n\tfunction log(string memory p0, bool p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool)\", p0, p1));\n\t}\n\n\tfunction log(string memory p0, address p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address)\", p0, p1));\n\t}\n\n\tfunction log(bool p0, uint p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint)\", p0, p1));\n\t}\n\n\tfunction log(bool p0, string memory p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string)\", p0, p1));\n\t}\n\n\tfunction log(bool p0, bool p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool)\", p0, p1));\n\t}\n\n\tfunction log(bool p0, address p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address)\", p0, p1));\n\t}\n\n\tfunction log(address p0, uint p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint)\", p0, p1));\n\t}\n\n\tfunction log(address p0, string memory p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string)\", p0, p1));\n\t}\n\n\tfunction log(address p0, bool p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool)\", p0, p1));\n\t}\n\n\tfunction log(address p0, address p1) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address)\", p0, p1));\n\t}\n\n\tfunction log(uint p0, uint p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, uint p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, uint p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, uint p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, string memory p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, string memory p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, string memory p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, string memory p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, bool p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, bool p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, bool p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, bool p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, address p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, address p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, address p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, address p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, uint p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, uint p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, uint p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, uint p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, string memory p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, string memory p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, string memory p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, string memory p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, bool p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, bool p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, bool p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, bool p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, address p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, address p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, address p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(string memory p0, address p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, uint p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, uint p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, uint p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, uint p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, string memory p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, string memory p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, string memory p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, string memory p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, bool p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, bool p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, bool p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, bool p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, address p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, address p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, address p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(bool p0, address p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, uint p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, uint p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, uint p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, uint p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, string memory p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, string memory p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, string memory p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, string memory p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, bool p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, bool p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, bool p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, bool p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, address p1, uint p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,uint)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, address p1, string memory p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,string)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, address p1, bool p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,bool)\", p0, p1, p2));\n\t}\n\n\tfunction log(address p0, address p1, address p2) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,address)\", p0, p1, p2));\n\t}\n\n\tfunction log(uint p0, uint p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, uint p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,uint,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, string memory p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,string,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, bool p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,bool,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(uint p0, address p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(uint,address,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, uint p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,uint,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, string memory p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,string,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, bool p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,bool,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(string memory p0, address p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(string,address,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, uint p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,uint,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, string memory p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,string,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, bool p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,bool,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(bool p0, address p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(bool,address,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, uint p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,uint,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, string memory p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,string,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, bool p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,bool,address,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, uint p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,uint,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, uint p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,uint,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, uint p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,uint,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, uint p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,uint,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, string memory p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,string,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, string memory p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,string,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, string memory p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,string,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, string memory p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,string,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, bool p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,bool,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, bool p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,bool,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, bool p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,bool,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, bool p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,bool,address)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, address p2, uint p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,address,uint)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, address p2, string memory p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,address,string)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, address p2, bool p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,address,bool)\", p0, p1, p2, p3));\n\t}\n\n\tfunction log(address p0, address p1, address p2, address p3) internal view {\n\t\t_sendLogPayload(abi.encodeWithSignature(\"log(address,address,address,address)\", p0, p1, p2, p3));\n\t}\n\n}\n"},"contracts/governance/governor/IGovernor.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\nimport \"./Structs.sol\";\n\n/// @title interface to interact with TokenDelgator\ninterface IGovernorCharlieDelegator {\n function _setImplementation(address implementation_) external;\n\n fallback() external payable;\n\n receive() external payable;\n}\n\n/// @title interface to interact with TokenDelgate\ninterface IGovernorCharlieDelegate {\n function initialize(\n address ipt_\n ) external;\n\n function propose(\n address[] memory targets,\n uint256[] memory values,\n string[] memory signatures,\n bytes[] memory calldatas,\n string memory description,\n bool emergency\n ) external returns (uint256);\n\n function queue(uint256 proposalId) external;\n\n function execute(uint256 proposalId) external payable;\n\n function executeTransaction(\n address target,\n uint256 value,\n string memory signature,\n bytes memory data,\n uint256 eta\n ) external payable;\n\n function cancel(uint256 proposalId) external;\n\n function getActions(uint256 proposalId)\n external\n view\n returns (\n address[] memory targets,\n uint256[] memory values,\n string[] memory signatures,\n bytes[] memory calldatas\n );\n\n function getReceipt(uint256 proposalId, address voter) external view returns (Receipt memory);\n\n function state(uint256 proposalId) external view returns (ProposalState);\n\n function castVote(uint256 proposalId, uint8 support) external;\n\n function castVoteWithReason(\n uint256 proposalId,\n uint8 support,\n string calldata reason\n ) external;\n\n function castVoteBySig(\n uint256 proposalId,\n uint8 support,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external;\n\n function isWhitelisted(address account) external view returns (bool);\n\n function _setDelay(uint256 proposalTimelockDelay_) external;\n\n function _setEmergencyDelay(uint256 emergencyTimelockDelay_) external;\n\n function _setVotingDelay(uint256 newVotingDelay) external;\n\n function _setVotingPeriod(uint256 newVotingPeriod) external;\n\n function _setEmergencyVotingPeriod(uint256 newEmergencyVotingPeriod) external;\n\n function _setProposalThreshold(uint256 newProposalThreshold) external;\n\n function _setQuorumVotes(uint256 newQuorumVotes) external;\n\n function _setEmergencyQuorumVotes(uint256 newEmergencyQuorumVotes) external;\n\n function _setWhitelistAccountExpiration(address account, uint256 expiration) external;\n\n function _setWhitelistGuardian(address account) external;\n\n function _setOptimisticDelay(uint256 newOptimisticVotingDelay) external;\n\n function _setOptimisticQuorumVotes(uint256 newOptimisticQuorumVotes) external;\n}\n\n/// @title interface which contains all events emitted by delegator & delegate\ninterface GovernorCharlieEvents {\n /// @notice An event emitted when a new proposal is created\n event ProposalCreated(\n uint256 indexed id,\n address indexed proposer,\n address[] targets,\n uint256[] values,\n string[] signatures,\n bytes[] calldatas,\n uint256 indexed startBlock,\n uint256 endBlock,\n string description\n );\n\n /// @notice An event emitted when a vote has been cast on a proposal\n /// @param voter The address which casted a vote\n /// @param proposalId The proposal id which was voted on\n /// @param support Support value for the vote. 0=against, 1=for, 2=abstain\n /// @param votes Number of votes which were cast by the voter\n /// @param reason The reason given for the vote by the voter\n event VoteCast(address indexed voter, uint256 indexed proposalId, uint8 support, uint256 votes, string reason);\n\n /// @notice An event emitted when a proposal has been canceled\n event ProposalCanceled(uint256 indexed id);\n\n /// @notice An event emitted when a proposal has been queued in the Timelock\n event ProposalQueued(uint256 indexed id, uint256 eta);\n\n /// @notice An event emitted when a proposal has been executed in the Timelock\n event ProposalExecuted(uint256 indexed id);\n\n /// @notice An event emitted when the voting delay is set\n event VotingDelaySet(uint256 oldVotingDelay, uint256 newVotingDelay);\n\n /// @notice An event emitted when the voting period is set\n event VotingPeriodSet(uint256 oldVotingPeriod, uint256 newVotingPeriod);\n\n /// @notice An event emitted when the emergency voting period is set\n event EmergencyVotingPeriodSet(uint256 oldEmergencyVotingPeriod, uint256 emergencyVotingPeriod);\n\n /// @notice Emitted when implementation is changed\n event NewImplementation(address oldImplementation, address newImplementation);\n\n /// @notice Emitted when proposal threshold is set\n event ProposalThresholdSet(uint256 oldProposalThreshold, uint256 newProposalThreshold);\n\n /// @notice Emitted when pendingAdmin is changed\n event NewPendingAdmin(address oldPendingAdmin, address newPendingAdmin);\n\n /// @notice Emitted when pendingAdmin is accepted, which means admin is updated\n event NewAdmin(address oldAdmin, address newAdmin);\n\n /// @notice Emitted when whitelist account expiration is set\n event WhitelistAccountExpirationSet(address account, uint256 expiration);\n\n /// @notice Emitted when the whitelistGuardian is set\n event WhitelistGuardianSet(address oldGuardian, address newGuardian);\n\n /// @notice Emitted when the a new delay is set\n event NewDelay(uint256 oldTimelockDelay, uint256 proposalTimelockDelay);\n\n /// @notice Emitted when the a new emergency delay is set\n event NewEmergencyDelay(uint256 oldEmergencyTimelockDelay, uint256 emergencyTimelockDelay);\n\n /// @notice Emitted when the quorum is updated\n event NewQuorum(uint256 oldQuorumVotes, uint256 quorumVotes);\n\n /// @notice Emitted when the emergency quorum is updated\n event NewEmergencyQuorum(uint256 oldEmergencyQuorumVotes, uint256 emergencyQuorumVotes);\n\n /// @notice An event emitted when the optimistic voting delay is set\n event OptimisticVotingDelaySet(uint256 oldOptimisticVotingDelay, uint256 optimisticVotingDelay);\n\n /// @notice Emitted when the optimistic quorum is updated\n event OptimisticQuorumVotesSet(uint256 oldOptimisticQuorumVotes, uint256 optimisticQuorumVotes);\n\n /// @notice Emitted when a transaction is canceled\n event CancelTransaction(\n bytes32 indexed txHash,\n address indexed target,\n uint256 value,\n string signature,\n bytes data,\n uint256 eta\n );\n\n /// @notice Emitted when a transaction is executed\n event ExecuteTransaction(\n bytes32 indexed txHash,\n address indexed target,\n uint256 value,\n string signature,\n bytes data,\n uint256 eta\n );\n\n /// @notice Emitted when a transaction is queued\n event QueueTransaction(\n bytes32 indexed txHash,\n address indexed target,\n uint256 value,\n string signature,\n bytes data,\n uint256 eta\n );\n}\n"},"contracts/governance/governor/Structs.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\nstruct Proposal {\n /// @notice Unique id for looking up a proposal\n uint256 id;\n /// @notice Creator of the proposal\n address proposer;\n /// @notice The timestamp that the proposal will be available for execution, set once the vote succeeds\n uint256 eta;\n /// @notice the ordered list of target addresses for calls to be made\n address[] targets;\n /// @notice The ordered list of values (i.e. msg.value) to be passed to the calls to be made\n uint256[] values;\n /// @notice The ordered list of function signatures to be called\n string[] signatures;\n /// @notice The ordered list of calldata to be passed to each call\n bytes[] calldatas;\n /// @notice The block at which voting begins: holders must delegate their votes prior to this block\n uint256 startBlock;\n /// @notice The block at which voting ends: votes must be cast prior to this block\n uint256 endBlock;\n /// @notice Current number of votes in favor of this proposal\n uint256 forVotes;\n /// @notice Current number of votes in opposition to this proposal\n uint256 againstVotes;\n /// @notice Current number of votes for abstaining for this proposal\n uint256 abstainVotes;\n /// @notice Flag marking whether the proposal has been canceled\n bool canceled;\n /// @notice Flag marking whether the proposal has been executed\n bool executed;\n /// @notice Whether the proposal is an emergency proposal\n bool emergency;\n /// @notice quorum votes requires\n uint256 quorumVotes;\n /// @notice time delay\n uint256 delay;\n}\n\n/// @notice Ballot receipt record for a voter\nstruct Receipt {\n /// @notice Whether or not a vote has been cast\n bool hasVoted;\n /// @notice Whether or not the voter supports the proposal or abstains\n uint8 support;\n /// @notice The number of votes the voter had, which were cast\n uint96 votes;\n}\n\n/// @notice Possible states that a proposal may be in\nenum ProposalState {\n Pending,\n Active,\n Canceled,\n Defeated,\n Succeeded,\n Queued,\n Expired,\n Executed\n}\n"},"contracts/governance/governor/IIpt.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\ninterface IIpt {\n function getPriorVotes(address account, uint256 blockNumber) external view returns (uint96);\n}\n"},"contracts/governance/governor/GovernorDelegate.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\npragma experimental ABIEncoderV2;\nimport \"hardhat/console.sol\";\n\nimport \"./IGovernor.sol\";\nimport \"./GovernorStorage.sol\";\n\ncontract GovernorCharlieDelegate is GovernorCharlieDelegateStorage, GovernorCharlieEvents, IGovernorCharlieDelegate {\n /// @notice The name of this contract\n string public constant name = \"Interest Protocol Governor\";\n\n /// @notice The maximum number of actions that can be included in a proposal\n uint256 public constant proposalMaxOperations = 10;\n\n /// @notice The EIP-712 typehash for the contract's domain\n bytes32 public constant DOMAIN_TYPEHASH =\n keccak256(\"EIP712Domain(string name,uint256 chainId,address verifyingContract)\");\n\n /// @notice The EIP-712 typehash for the ballot struct used by the contract\n bytes32 public constant BALLOT_TYPEHASH = keccak256(\"Ballot(uint256 proposalId,uint8 support)\");\n\n /// @notice The time for a proposal to be executed after passing\n uint256 public constant GRACE_PERIOD = 14 days;\n\n /**\n * @notice Used to initialize the contract during delegator contructor\n * @param ipt_ The address of the IPT token\n */\n function initialize(\n address ipt_\n ) external override {\n require(!initialized, \"already been initialized\");\n ipt = IIpt(ipt_);\n votingPeriod = 40320;\n votingDelay = 13140;\n proposalThreshold = 1000000000000000000000000;\n proposalTimelockDelay = 172800;\n proposalCount = 0;\n quorumVotes = 10000000000000000000000000;\n emergencyQuorumVotes = 40000000000000000000000000;\n emergencyVotingPeriod = 6570;\n emergencyTimelockDelay = 43200;\n optimisticQuorumVotes = 2000000000000000000000000;\n optimisticVotingDelay = 18000;\n maxWhitelistPeriod = 31536000;\n\n initialized = true;\n }\n\n /// @notice any function with this modifier will call the pay_interest() function before\n modifier onlyGov() {\n require(_msgSender() == address(this), \"must come from the gov.\");\n _;\n }\n\n /**\n * @notice Function used to propose a new proposal. Sender must have delegates above the proposal threshold\n * @param targets Target addresses for proposal calls\n * @param values Eth values for proposal calls\n * @param signatures Function signatures for proposal calls\n * @param calldatas Calldatas for proposal calls\n * @param description String description of the proposal\n * @return Proposal id of new proposal\n */\n function propose(\n address[] memory targets,\n uint256[] memory values,\n string[] memory signatures,\n bytes[] memory calldatas,\n string memory description,\n bool emergency\n ) public override returns (uint256) {\n // Reject proposals before initiating as Governor\n require(quorumVotes != 0, \"Charlie not active\");\n // Allow addresses above proposal threshold and whitelisted addresses to propose\n require(\n ipt.getPriorVotes(_msgSender(), (block.number - 1)) >= proposalThreshold || isWhitelisted(_msgSender()),\n \"votes below proposal threshold\"\n );\n require(\n targets.length == values.length && targets.length == signatures.length && targets.length == calldatas.length,\n \"information arity mismatch\"\n );\n require(targets.length != 0, \"must provide actions\");\n require(targets.length <= proposalMaxOperations, \"too many actions\");\n\n uint256 latestProposalId = latestProposalIds[_msgSender()];\n if (latestProposalId != 0) {\n ProposalState proposersLatestProposalState = state(latestProposalId);\n require(proposersLatestProposalState != ProposalState.Active, \"one live proposal per proposer\");\n require(proposersLatestProposalState != ProposalState.Pending, \"one live proposal per proposer\");\n }\n\n proposalCount++;\n Proposal memory newProposal = Proposal({\n id: proposalCount,\n proposer: _msgSender(),\n eta: 0,\n targets: targets,\n values: values,\n signatures: signatures,\n calldatas: calldatas,\n startBlock: block.number + votingDelay,\n endBlock: block.number + votingDelay + votingPeriod,\n forVotes: 0,\n againstVotes: 0,\n abstainVotes: 0,\n canceled: false,\n executed: false,\n emergency: emergency,\n quorumVotes: quorumVotes,\n delay: proposalTimelockDelay\n });\n\n //whitelist can't make emergency\n if (emergency && !isWhitelisted(_msgSender())) {\n newProposal.startBlock = block.number;\n newProposal.endBlock = block.number + emergencyVotingPeriod;\n newProposal.quorumVotes = emergencyQuorumVotes;\n newProposal.delay = emergencyTimelockDelay;\n }\n\n //whitelist can only make optimistic proposals\n if (isWhitelisted(_msgSender())) {\n newProposal.quorumVotes = optimisticQuorumVotes;\n newProposal.startBlock = block.number + optimisticVotingDelay;\n newProposal.endBlock = block.number + optimisticVotingDelay + votingPeriod;\n }\n\n proposals[newProposal.id] = newProposal;\n latestProposalIds[newProposal.proposer] = newProposal.id;\n\n emit ProposalCreated(\n newProposal.id,\n _msgSender(),\n targets,\n values,\n signatures,\n calldatas,\n newProposal.startBlock,\n newProposal.endBlock,\n description\n );\n return newProposal.id;\n }\n\n /**\n * @notice Queues a proposal of state succeeded\n * @param proposalId The id of the proposal to queue\n */\n function queue(uint256 proposalId) external override {\n require(state(proposalId) == ProposalState.Succeeded, \"can only be queued if succeeded\");\n Proposal storage proposal = proposals[proposalId];\n uint256 eta = block.timestamp + proposal.delay;\n for (uint256 i = 0; i < proposal.targets.length; i++) {\n require(\n !queuedTransactions[\n keccak256(\n abi.encode(proposal.targets[i], proposal.values[i], proposal.signatures[i], proposal.calldatas[i], eta)\n )\n ],\n \"proposal already queued\"\n );\n queueTransaction(\n proposal.targets[i],\n proposal.values[i],\n proposal.signatures[i],\n proposal.calldatas[i],\n eta,\n proposal.delay\n );\n }\n proposal.eta = eta;\n emit ProposalQueued(proposalId, eta);\n }\n\n function queueTransaction(\n address target,\n uint256 value,\n string memory signature,\n bytes memory data,\n uint256 eta,\n uint256 delay\n ) internal returns (bytes32) {\n require(eta >= (getBlockTimestamp() + delay), \"must satisfy delay.\");\n\n bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));\n queuedTransactions[txHash] = true;\n\n emit QueueTransaction(txHash, target, value, signature, data, eta);\n return txHash;\n }\n\n /**\n * @notice Executes a queued proposal if eta has passed\n * @param proposalId The id of the proposal to execute\n */\n function execute(uint256 proposalId) external payable override {\n require(state(proposalId) == ProposalState.Queued, \"can only be exec'd if queued\");\n Proposal storage proposal = proposals[proposalId];\n proposal.executed = true;\n for (uint256 i = 0; i < proposal.targets.length; i++) {\n this.executeTransaction{value: proposal.values[i]}(\n proposal.targets[i],\n proposal.values[i],\n proposal.signatures[i],\n proposal.calldatas[i],\n proposal.eta\n );\n }\n emit ProposalExecuted(proposalId);\n }\n\n function executeTransaction(\n address target,\n uint256 value,\n string memory signature,\n bytes memory data,\n uint256 eta\n ) external payable override {\n bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));\n require(queuedTransactions[txHash], \"tx hasn't been queued.\");\n require(getBlockTimestamp() >= eta, \"tx hasn't surpassed timelock.\");\n require(getBlockTimestamp() <= eta + GRACE_PERIOD, \"tx is stale.\");\n\n queuedTransactions[txHash] = false;\n\n bytes memory callData;\n\n if (bytes(signature).length == 0) {\n callData = data;\n } else {\n callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data);\n }\n\n // solhint-disable-next-line avoid-low-level-calls\n (\n bool success, /*bytes memory returnData*/\n\n ) = target.call{value: value}(callData);\n require(success, \"tx execution reverted.\");\n\n emit ExecuteTransaction(txHash, target, value, signature, data, eta);\n }\n\n /**\n * @notice Cancels a proposal only if sender is the proposer, or proposer delegates dropped below proposal threshold\n * @param proposalId The id of the proposal to cancel\n */\n function cancel(uint256 proposalId) external override {\n require(state(proposalId) != ProposalState.Executed, \"cant cancel executed proposal\");\n\n Proposal storage proposal = proposals[proposalId];\n\n // Proposer can cancel\n if (_msgSender() != proposal.proposer) {\n // Whitelisted proposers can't be canceled for falling below proposal threshold\n if (isWhitelisted(proposal.proposer)) {\n require(\n (ipt.getPriorVotes(proposal.proposer, (block.number - 1)) < proposalThreshold) &&\n _msgSender() == whitelistGuardian,\n \"cancel: whitelisted proposer\"\n );\n } else {\n require(\n (ipt.getPriorVotes(proposal.proposer, (block.number - 1)) < proposalThreshold),\n \"cancel: proposer above threshold\"\n );\n }\n }\n\n proposal.canceled = true;\n for (uint256 i = 0; i < proposal.targets.length; i++) {\n cancelTransaction(\n proposal.targets[i],\n proposal.values[i],\n proposal.signatures[i],\n proposal.calldatas[i],\n proposal.eta\n );\n }\n\n emit ProposalCanceled(proposalId);\n }\n\n function cancelTransaction(\n address target,\n uint256 value,\n string memory signature,\n bytes memory data,\n uint256 eta\n ) internal {\n bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta));\n queuedTransactions[txHash] = false;\n\n emit CancelTransaction(txHash, target, value, signature, data, eta);\n }\n\n /**\n * @notice Gets actions of a proposal\n * @param proposalId the id of the proposal\n * @return targets proposal targets\n * @return values proposal values\n * @return signatures proposal signatures\n * @return calldatas proposal calldatae\n */\n function getActions(uint256 proposalId)\n external\n view\n override\n returns (\n address[] memory targets,\n uint256[] memory values,\n string[] memory signatures,\n bytes[] memory calldatas\n )\n {\n Proposal storage p = proposals[proposalId];\n return (p.targets, p.values, p.signatures, p.calldatas);\n }\n\n /**\n * @notice Gets the receipt for a voter on a given proposal\n * @param proposalId the id of proposal\n * @param voter The address of the voter\n * @return The voting receipt\n */\n function getReceipt(uint256 proposalId, address voter) external view override returns (Receipt memory) {\n return proposalReceipts[proposalId][voter];\n }\n\n /**\n * @notice Gets the state of a proposal\n * @param proposalId The id of the proposal\n * @return Proposal state\n */\n // solhint-disable-next-line code-complexity\n function state(uint256 proposalId) public view override returns (ProposalState) {\n require(proposalCount >= proposalId && proposalId > initialProposalId, \"state: invalid proposal id\");\n Proposal storage proposal = proposals[proposalId];\n bool whitelisted = isWhitelisted(proposal.proposer);\n if (proposal.canceled) {\n return ProposalState.Canceled;\n } else if (block.number <= proposal.startBlock) {\n return ProposalState.Pending;\n } else if (block.number <= proposal.endBlock) {\n return ProposalState.Active;\n } else if (\n (whitelisted && proposal.againstVotes > proposal.quorumVotes) ||\n (!whitelisted && proposal.forVotes <= proposal.againstVotes) ||\n (!whitelisted && proposal.forVotes < proposal.quorumVotes)\n ) {\n return ProposalState.Defeated;\n } else if (proposal.eta == 0) {\n return ProposalState.Succeeded;\n } else if (proposal.executed) {\n return ProposalState.Executed;\n } else if (block.timestamp >= (proposal.eta + GRACE_PERIOD)) {\n return ProposalState.Expired;\n }\n return ProposalState.Queued;\n }\n\n /**\n * @notice Cast a vote for a proposal\n * @param proposalId The id of the proposal to vote on\n * @param support The support value for the vote. 0=against, 1=for, 2=abstain\n */\n function castVote(uint256 proposalId, uint8 support) external override {\n emit VoteCast(_msgSender(), proposalId, support, castVoteInternal(_msgSender(), proposalId, support), \"\");\n }\n\n /**\n * @notice Cast a vote for a proposal with a reason\n * @param proposalId The id of the proposal to vote on\n * @param support The support value for the vote. 0=against, 1=for, 2=abstain\n * @param reason The reason given for the vote by the voter\n */\n function castVoteWithReason(\n uint256 proposalId,\n uint8 support,\n string calldata reason\n ) external override {\n emit VoteCast(_msgSender(), proposalId, support, castVoteInternal(_msgSender(), proposalId, support), reason);\n }\n\n /**\n * @notice Cast a vote for a proposal by signature\n * @dev external override function that accepts EIP-712 signatures for voting on proposals.\n */\n function castVoteBySig(\n uint256 proposalId,\n uint8 support,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external override {\n bytes32 domainSeparator = keccak256(\n abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), getChainIdInternal(), address(this))\n );\n bytes32 structHash = keccak256(abi.encode(BALLOT_TYPEHASH, proposalId, support));\n bytes32 digest = keccak256(abi.encodePacked(\"\\x19\\x01\", domainSeparator, structHash));\n address signatory = ecrecover(digest, v, r, s);\n require(signatory != address(0), \"castVoteBySig: invalid signature\");\n emit VoteCast(signatory, proposalId, support, castVoteInternal(signatory, proposalId, support), \"\");\n }\n\n /**\n * @notice Internal function that caries out voting logic\n * @param voter The voter that is casting their vote\n * @param proposalId The id of the proposal to vote on\n * @param support The support value for the vote. 0=against, 1=for, 2=abstain\n * @return The number of votes cast\n */\n function castVoteInternal(\n address voter,\n uint256 proposalId,\n uint8 support\n ) internal returns (uint96) {\n require(state(proposalId) == ProposalState.Active, \"voting is closed\");\n require(support <= 2, \"invalid vote type\");\n Proposal storage proposal = proposals[proposalId];\n Receipt storage receipt = proposalReceipts[proposalId][voter];\n require(receipt.hasVoted == false, \"voter already voted\");\n uint96 votes = ipt.getPriorVotes(voter, proposal.startBlock);\n\n if (support == 0) {\n proposal.againstVotes = proposal.againstVotes + votes;\n } else if (support == 1) {\n proposal.forVotes = proposal.forVotes + votes;\n } else if (support == 2) {\n proposal.abstainVotes = proposal.abstainVotes + votes;\n }\n\n receipt.hasVoted = true;\n receipt.support = support;\n receipt.votes = votes;\n\n return votes;\n }\n\n /**\n * @notice View function which returns if an account is whitelisted\n * @param account Account to check white list status of\n * @return If the account is whitelisted\n */\n function isWhitelisted(address account) public view override returns (bool) {\n return (whitelistAccountExpirations[account] > block.timestamp);\n }\n\n /**\n * @notice Governance function for setting the governance token\n * @param token_ new token addr\n */\n function _setNewToken(address token_) external onlyGov {\n ipt = IIpt(token_);\n }\n\n /**\n * @notice Used to update the timelock period\n * @param proposalTimelockDelay_ The proposal holding period\n */\n function _setDelay(uint256 proposalTimelockDelay_) public override onlyGov {\n uint256 oldTimelockDelay = proposalTimelockDelay;\n proposalTimelockDelay = proposalTimelockDelay_;\n\n emit NewDelay(oldTimelockDelay, proposalTimelockDelay);\n }\n\n /**\n * @notice Used to update the emergency timelock period\n * @param emergencyTimelockDelay_ The proposal holding period\n */\n function _setEmergencyDelay(uint256 emergencyTimelockDelay_) public override onlyGov {\n uint256 oldEmergencyTimelockDelay = emergencyTimelockDelay;\n emergencyTimelockDelay = emergencyTimelockDelay_;\n\n emit NewEmergencyDelay(oldEmergencyTimelockDelay, emergencyTimelockDelay);\n }\n\n /**\n * @notice Governance function for setting the voting delay\n * @param newVotingDelay new voting delay, in blocks\n */\n function _setVotingDelay(uint256 newVotingDelay) external override onlyGov {\n uint256 oldVotingDelay = votingDelay;\n votingDelay = newVotingDelay;\n\n emit VotingDelaySet(oldVotingDelay, votingDelay);\n }\n\n /**\n * @notice Governance function for setting the voting period\n * @param newVotingPeriod new voting period, in blocks\n */\n function _setVotingPeriod(uint256 newVotingPeriod) external override onlyGov {\n uint256 oldVotingPeriod = votingPeriod;\n votingPeriod = newVotingPeriod;\n\n emit VotingPeriodSet(oldVotingPeriod, votingPeriod);\n }\n\n /**\n * @notice Governance function for setting the emergency voting period\n * @param newEmergencyVotingPeriod new voting period, in blocks\n */\n function _setEmergencyVotingPeriod(uint256 newEmergencyVotingPeriod) external override onlyGov {\n uint256 oldEmergencyVotingPeriod = emergencyVotingPeriod;\n emergencyVotingPeriod = newEmergencyVotingPeriod;\n\n emit EmergencyVotingPeriodSet(oldEmergencyVotingPeriod, emergencyVotingPeriod);\n }\n\n /**\n * @notice Governance function for setting the proposal threshold\n * @param newProposalThreshold new proposal threshold\n */\n function _setProposalThreshold(uint256 newProposalThreshold) external override onlyGov {\n uint256 oldProposalThreshold = proposalThreshold;\n proposalThreshold = newProposalThreshold;\n\n emit ProposalThresholdSet(oldProposalThreshold, proposalThreshold);\n }\n\n /**\n * @notice Governance function for setting the quorum\n * @param newQuorumVotes new proposal quorum\n */\n function _setQuorumVotes(uint256 newQuorumVotes) external override onlyGov {\n uint256 oldQuorumVotes = quorumVotes;\n quorumVotes = newQuorumVotes;\n\n emit NewQuorum(oldQuorumVotes, quorumVotes);\n }\n\n /**\n * @notice Governance function for setting the emergency quorum\n * @param newEmergencyQuorumVotes new proposal quorum\n */\n function _setEmergencyQuorumVotes(uint256 newEmergencyQuorumVotes) external override onlyGov {\n uint256 oldEmergencyQuorumVotes = emergencyQuorumVotes;\n emergencyQuorumVotes = newEmergencyQuorumVotes;\n\n emit NewEmergencyQuorum(oldEmergencyQuorumVotes, emergencyQuorumVotes);\n }\n\n /**\n * @notice Governance function for setting the whitelist expiration as a timestamp\n * for an account. Whitelist status allows accounts to propose without meeting threshold\n * @param account Account address to set whitelist expiration for\n * @param expiration Expiration for account whitelist status as timestamp (if now < expiration, whitelisted)\n */\n function _setWhitelistAccountExpiration(address account, uint256 expiration) external override onlyGov {\n require (expiration < (maxWhitelistPeriod + block.timestamp), \"expiration exceeds max\");\n whitelistAccountExpirations[account] = expiration;\n\n emit WhitelistAccountExpirationSet(account, expiration);\n }\n\n /**\n * @notice Governance function for setting the whitelistGuardian. WhitelistGuardian can cancel proposals from whitelisted addresses\n * @param account Account to set whitelistGuardian to (0x0 to remove whitelistGuardian)\n */\n function _setWhitelistGuardian(address account) external override onlyGov {\n address oldGuardian = whitelistGuardian;\n whitelistGuardian = account;\n\n emit WhitelistGuardianSet(oldGuardian, whitelistGuardian);\n }\n\n /**\n * @notice Governance function for setting the optimistic voting delay\n * @param newOptimisticVotingDelay new optimistic voting delay, in blocks\n */\n function _setOptimisticDelay(uint256 newOptimisticVotingDelay) external override onlyGov {\n uint256 oldOptimisticVotingDelay = optimisticVotingDelay;\n optimisticVotingDelay = newOptimisticVotingDelay;\n\n emit OptimisticVotingDelaySet(oldOptimisticVotingDelay, optimisticVotingDelay);\n }\n\n /**\n * @notice Governance function for setting the optimistic quorum\n * @param newOptimisticQuorumVotes new optimistic quorum votes, in blocks\n */\n function _setOptimisticQuorumVotes(uint256 newOptimisticQuorumVotes) external override onlyGov {\n uint256 oldOptimisticQuorumVotes = optimisticQuorumVotes;\n optimisticQuorumVotes = newOptimisticQuorumVotes;\n\n emit OptimisticQuorumVotesSet(oldOptimisticQuorumVotes, optimisticQuorumVotes);\n }\n\n function getChainIdInternal() internal view returns (uint256) {\n return block.chainid;\n }\n\n function getBlockTimestamp() internal view returns (uint256) {\n // solium-disable-next-line security/no-block-members\n return block.timestamp;\n }\n\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n}\n"}},"settings":{"optimizer":{"enabled":true,"runs":200,"details":{"orderLiterals":true,"deduplicate":true,"cse":true,"yul":true}},"outputSelection":{"*":{"*":["evm.bytecode","evm.deployedBytecode","devdoc","userdoc","metadata","abi"]}},"libraries":{}}},"ABI":"[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"txHash\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"signature\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"eta\",\"type\":\"uint256\"}],\"name\":\"CancelTransaction\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldEmergencyVotingPeriod\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"emergencyVotingPeriod\",\"type\":\"uint256\"}],\"name\":\"EmergencyVotingPeriodSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"txHash\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"signature\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"eta\",\"type\":\"uint256\"}],\"name\":\"ExecuteTransaction\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"oldAdmin\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newAdmin\",\"type\":\"address\"}],\"name\":\"NewAdmin\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldTimelockDelay\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"proposalTimelockDelay\",\"type\":\"uint256\"}],\"name\":\"NewDelay\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldEmergencyTimelockDelay\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"emergencyTimelockDelay\",\"type\":\"uint256\"}],\"name\":\"NewEmergencyDelay\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldEmergencyQuorumVotes\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"emergencyQuorumVotes\",\"type\":\"uint256\"}],\"name\":\"NewEmergencyQuorum\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"oldImplementation\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newImplementation\",\"type\":\"address\"}],\"name\":\"NewImplementation\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"oldPendingAdmin\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newPendingAdmin\",\"type\":\"address\"}],\"name\":\"NewPendingAdmin\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldQuorumVotes\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"quorumVotes\",\"type\":\"uint256\"}],\"name\":\"NewQuorum\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldOptimisticQuorumVotes\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"optimisticQuorumVotes\",\"type\":\"uint256\"}],\"name\":\"OptimisticQuorumVotesSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldOptimisticVotingDelay\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"optimisticVotingDelay\",\"type\":\"uint256\"}],\"name\":\"OptimisticVotingDelaySet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"ProposalCanceled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"proposer\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address[]\",\"name\":\"targets\",\"type\":\"address[]\"},{\"indexed\":false,\"internalType\":\"uint256[]\",\"name\":\"values\",\"type\":\"uint256[]\"},{\"indexed\":false,\"internalType\":\"string[]\",\"name\":\"signatures\",\"type\":\"string[]\"},{\"indexed\":false,\"internalType\":\"bytes[]\",\"name\":\"calldatas\",\"type\":\"bytes[]\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"startBlock\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"endBlock\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"description\",\"type\":\"string\"}],\"name\":\"ProposalCreated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"ProposalExecuted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"eta\",\"type\":\"uint256\"}],\"name\":\"ProposalQueued\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldProposalThreshold\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newProposalThreshold\",\"type\":\"uint256\"}],\"name\":\"ProposalThresholdSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"txHash\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"signature\",\"type\":\"string\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"eta\",\"type\":\"uint256\"}],\"name\":\"QueueTransaction\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"voter\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"support\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"votes\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"reason\",\"type\":\"string\"}],\"name\":\"VoteCast\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldVotingDelay\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newVotingDelay\",\"type\":\"uint256\"}],\"name\":\"VotingDelaySet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"oldVotingPeriod\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newVotingPeriod\",\"type\":\"uint256\"}],\"name\":\"VotingPeriodSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"expiration\",\"type\":\"uint256\"}],\"name\":\"WhitelistAccountExpirationSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"oldGuardian\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newGuardian\",\"type\":\"address\"}],\"name\":\"WhitelistGuardianSet\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"BALLOT_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DOMAIN_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"GRACE_PERIOD\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalTimelockDelay_\",\"type\":\"uint256\"}],\"name\":\"_setDelay\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"emergencyTimelockDelay_\",\"type\":\"uint256\"}],\"name\":\"_setEmergencyDelay\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newEmergencyQuorumVotes\",\"type\":\"uint256\"}],\"name\":\"_setEmergencyQuorumVotes\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newEmergencyVotingPeriod\",\"type\":\"uint256\"}],\"name\":\"_setEmergencyVotingPeriod\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token_\",\"type\":\"address\"}],\"name\":\"_setNewToken\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newOptimisticVotingDelay\",\"type\":\"uint256\"}],\"name\":\"_setOptimisticDelay\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newOptimisticQuorumVotes\",\"type\":\"uint256\"}],\"name\":\"_setOptimisticQuorumVotes\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newProposalThreshold\",\"type\":\"uint256\"}],\"name\":\"_setProposalThreshold\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newQuorumVotes\",\"type\":\"uint256\"}],\"name\":\"_setQuorumVotes\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newVotingDelay\",\"type\":\"uint256\"}],\"name\":\"_setVotingDelay\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newVotingPeriod\",\"type\":\"uint256\"}],\"name\":\"_setVotingPeriod\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"expiration\",\"type\":\"uint256\"}],\"name\":\"_setWhitelistAccountExpiration\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"_setWhitelistGuardian\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"}],\"name\":\"cancel\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"support\",\"type\":\"uint8\"}],\"name\":\"castVote\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"support\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"castVoteBySig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"support\",\"type\":\"uint8\"},{\"internalType\":\"string\",\"name\":\"reason\",\"type\":\"string\"}],\"name\":\"castVoteWithReason\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"emergencyQuorumVotes\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"emergencyTimelockDelay\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"emergencyVotingPeriod\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"}],\"name\":\"execute\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"internalType\":\"string\",\"name\":\"signature\",\"type\":\"string\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"eta\",\"type\":\"uint256\"}],\"name\":\"executeTransaction\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"}],\"name\":\"getActions\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"targets\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"values\",\"type\":\"uint256[]\"},{\"internalType\":\"string[]\",\"name\":\"signatures\",\"type\":\"string[]\"},{\"internalType\":\"bytes[]\",\"name\":\"calldatas\",\"type\":\"bytes[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"voter\",\"type\":\"address\"}],\"name\":\"getReceipt\",\"outputs\":[{\"components\":[{\"internalType\":\"bool\",\"name\":\"hasVoted\",\"type\":\"bool\"},{\"internalType\":\"uint8\",\"name\":\"support\",\"type\":\"uint8\"},{\"internalType\":\"uint96\",\"name\":\"votes\",\"type\":\"uint96\"}],\"internalType\":\"struct Receipt\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"implementation\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"initialProposalId\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"ipt_\",\"type\":\"address\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"initialized\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"ipt\",\"outputs\":[{\"internalType\":\"contract IIpt\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"isWhitelisted\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"latestProposalIds\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"maxWhitelistPeriod\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"optimisticQuorumVotes\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"optimisticVotingDelay\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"proposalCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"proposalMaxOperations\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"proposalReceipts\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"hasVoted\",\"type\":\"bool\"},{\"internalType\":\"uint8\",\"name\":\"support\",\"type\":\"uint8\"},{\"internalType\":\"uint96\",\"name\":\"votes\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"proposalThreshold\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"proposalTimelockDelay\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"proposals\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"proposer\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"eta\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"startBlock\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"endBlock\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"forVotes\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"againstVotes\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"abstainVotes\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"canceled\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"executed\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"emergency\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"quorumVotes\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"delay\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"targets\",\"type\":\"address[]\"},{\"internalType\":\"uint256[]\",\"name\":\"values\",\"type\":\"uint256[]\"},{\"internalType\":\"string[]\",\"name\":\"signatures\",\"type\":\"string[]\"},{\"internalType\":\"bytes[]\",\"name\":\"calldatas\",\"type\":\"bytes[]\"},{\"internalType\":\"string\",\"name\":\"description\",\"type\":\"string\"},{\"internalType\":\"bool\",\"name\":\"emergency\",\"type\":\"bool\"}],\"name\":\"propose\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"}],\"name\":\"queue\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"queuedTransactions\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"quorumVotes\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"proposalId\",\"type\":\"uint256\"}],\"name\":\"state\",\"outputs\":[{\"internalType\":\"enum ProposalState\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"votingDelay\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"votingPeriod\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"whitelistAccountExpirations\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"whitelistGuardian\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}]","ContractName":"GovernorCharlieDelegate","CompilerVersion":"v0.8.9+commit.e5eed63a","OptimizationUsed":1,"Runs":200,"ConstructorArguments":"0x","EVMVersion":"Default","Library":"","LicenseType":"","Proxy":0,"SwarmSource":""}] \ No newline at end of file diff --git a/testdata/etherscan/0x9AB6b21cDF116f611110b048987E58894786C244/creation_data.json b/testdata/etherscan/0x9AB6b21cDF116f611110b048987E58894786C244/creation_data.json new file mode 100644 index 000000000..2378ed6aa --- /dev/null +++ b/testdata/etherscan/0x9AB6b21cDF116f611110b048987E58894786C244/creation_data.json @@ -0,0 +1 @@ +{"contractAddress":"0x9ab6b21cdf116f611110b048987e58894786c244","contractCreator":"0x603d50bad151da8becf405e51a8c4abc8ba1c95e","txHash":"0x72be611ae1ade09242d9fc9c950a73d076f6c23514564a7b9ac730400dbaf2c0"} \ No newline at end of file diff --git a/testdata/etherscan/0x9AB6b21cDF116f611110b048987E58894786C244/metadata.json b/testdata/etherscan/0x9AB6b21cDF116f611110b048987E58894786C244/metadata.json new file mode 100644 index 000000000..b0f893895 --- /dev/null +++ b/testdata/etherscan/0x9AB6b21cDF116f611110b048987E58894786C244/metadata.json @@ -0,0 +1 @@ +[{"SourceCode":{"language":"Solidity","sources":{"contracts/InterestRates/InterestRatePositionManager.f.sol":{"content":"// SPDX-License-Identifier: BUSL-1.1\npragma solidity 0.8.19;\n\n// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `to`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address to, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `from` to `to` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address from,\n address to,\n uint256 amount\n ) external returns (bool);\n}\n\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)\n\n/**\n * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in\n * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].\n *\n * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by\n * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't\n * need to send a transaction, and thus is not required to hold Ether at all.\n */\ninterface IERC20Permit {\n /**\n * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,\n * given ``owner``'s signed approval.\n *\n * IMPORTANT: The same issues {IERC20-approve} has related to transaction\n * ordering also apply here.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `deadline` must be a timestamp in the future.\n * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`\n * over the EIP712-formatted function arguments.\n * - the signature must use ``owner``'s current nonce (see {nonces}).\n *\n * For more information on the signature format, see the\n * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP\n * section].\n */\n function permit(\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) external;\n\n /**\n * @dev Returns the current nonce for `owner`. This value must be\n * included whenever a signature is generated for {permit}.\n *\n * Every successful call to {permit} increases ``owner``'s nonce by one. This\n * prevents a signature from being used multiple times.\n */\n function nonces(address owner) external view returns (uint256);\n\n /**\n * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.\n */\n // solhint-disable-next-line func-name-mixedcase\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n}\n\n/// Parameters for ERC20Permit.permit call\nstruct ERC20PermitSignature {\n IERC20Permit token;\n uint256 value;\n uint256 deadline;\n uint8 v;\n bytes32 r;\n bytes32 s;\n}\n\nlibrary PermitHelper {\n function applyPermit(\n ERC20PermitSignature calldata p,\n address owner,\n address spender\n ) internal {\n p.token.permit(owner, spender, p.value, p.deadline, p.v, p.r, p.s);\n }\n\n function applyPermits(\n ERC20PermitSignature[] calldata permits,\n address owner,\n address spender\n ) internal {\n for (uint256 i = 0; i < permits.length; i++) {\n applyPermit(permits[i], owner, spender);\n }\n }\n}\n\n// OpenZeppelin Contracts v4.4.1 (interfaces/IERC3156FlashLender.sol)\n\n// OpenZeppelin Contracts (last updated v4.7.0) (interfaces/IERC3156FlashBorrower.sol)\n\n/**\n * @dev Interface of the ERC3156 FlashBorrower, as defined in\n * https://eips.ethereum.org/EIPS/eip-3156[ERC-3156].\n *\n * _Available since v4.1._\n */\ninterface IERC3156FlashBorrower {\n /**\n * @dev Receive a flash loan.\n * @param initiator The initiator of the loan.\n * @param token The loan currency.\n * @param amount The amount of tokens lent.\n * @param fee The additional amount of tokens to repay.\n * @param data Arbitrary data structure, intended to contain user-defined parameters.\n * @return The keccak256 hash of \"IERC3156FlashBorrower.onFlashLoan\"\n */\n function onFlashLoan(\n address initiator,\n address token,\n uint256 amount,\n uint256 fee,\n bytes calldata data\n ) external returns (bytes32);\n}\n\n/**\n * @dev Interface of the ERC3156 FlashLender, as defined in\n * https://eips.ethereum.org/EIPS/eip-3156[ERC-3156].\n *\n * _Available since v4.1._\n */\ninterface IERC3156FlashLender {\n /**\n * @dev The amount of currency available to be lended.\n * @param token The loan currency.\n * @return The amount of `token` that can be borrowed.\n */\n function maxFlashLoan(address token) external view returns (uint256);\n\n /**\n * @dev The fee to be charged for a given loan.\n * @param token The loan currency.\n * @param amount The amount of tokens lent.\n * @return The amount of `token` to be charged for the loan, on top of the returned principal.\n */\n function flashFee(address token, uint256 amount) external view returns (uint256);\n\n /**\n * @dev Initiate a flash loan.\n * @param receiver The receiver of the tokens in the loan, and the receiver of the callback.\n * @param token The loan currency.\n * @param amount The amount of tokens lent.\n * @param data Arbitrary data structure, intended to contain user-defined parameters.\n */\n function flashLoan(\n IERC3156FlashBorrower receiver,\n address token,\n uint256 amount,\n bytes calldata data\n ) external returns (bool);\n}\n\n/// @dev Interface to be used by contracts that collect fees. Contains fee recipient that can be changed by owner.\ninterface IFeeCollector {\n // --- Events ---\n\n /// @dev Fee Recipient is changed to @param feeRecipient address.\n /// @param feeRecipient New fee recipient address.\n event FeeRecipientChanged(address feeRecipient);\n\n // --- Errors ---\n\n /// @dev Invalid fee recipient.\n error InvalidFeeRecipient();\n\n // --- Functions ---\n\n /// @return Address of the current fee recipient.\n function feeRecipient() external view returns (address);\n\n /// @dev Sets new fee recipient address\n /// @param newFeeRecipient Address of the new fee recipient.\n function setFeeRecipient(address newFeeRecipient) external;\n}\n\ninterface IPositionManagerDependent {\n // --- Errors ---\n\n /// @dev Position Manager cannot be zero.\n error PositionManagerCannotBeZero();\n\n /// @dev Caller is not Position Manager.\n error CallerIsNotPositionManager(address caller);\n\n // --- Functions ---\n\n /// @dev Returns address of the PositionManager contract.\n function positionManager() external view returns (address);\n}\n\n/// @dev Interface of R stablecoin token. Implements some standards like IERC20, IERC20Permit, and IERC3156FlashLender.\n/// Raft's specific implementation contains IFeeCollector and IPositionManagerDependent.\n/// PositionManager can mint and burn R when particular actions happen with user's position.\ninterface IRToken is IERC20, IERC20Permit, IERC3156FlashLender, IFeeCollector, IPositionManagerDependent {\n // --- Events ---\n\n /// @dev New R token is deployed\n /// @param positionManager Address of the PositionManager contract that is authorized to mint and burn new tokens.\n /// @param flashMintFeeRecipient Address of flash mint fee recipient.\n event RDeployed(address positionManager, address flashMintFeeRecipient);\n\n /// @dev The Flash Mint Fee Percentage has been changed.\n /// @param flashMintFeePercentage The new Flash Mint Fee Percentage value.\n event FlashMintFeePercentageChanged(uint256 flashMintFeePercentage);\n\n /// --- Errors ---\n\n /// @dev Proposed flash mint fee percentage is too big.\n /// @param feePercentage Proposed flash mint fee percentage.\n error FlashFeePercentageTooBig(uint256 feePercentage);\n\n // --- Functions ---\n\n /// @return Number representing 100 percentage.\n function PERCENTAGE_BASE() external view returns (uint256);\n\n /// @dev Mints new tokens. Callable only by PositionManager contract.\n /// @param to Address that will receive newly minted tokens.\n /// @param amount Amount of tokens to mint.\n function mint(address to, uint256 amount) external;\n\n /// @dev Mints new tokens. Callable only by PositionManager contract.\n /// @param from Address of user whose tokens are burnt.\n /// @param amount Amount of tokens to burn.\n function burn(address from, uint256 amount) external;\n\n /// @return Maximum flash mint fee percentage that can be set by owner.\n function MAX_FLASH_MINT_FEE_PERCENTAGE() external view returns (uint256);\n\n /// @return Current flash mint fee percentage.\n function flashMintFeePercentage() external view returns (uint256);\n\n /// @dev Sets new flash mint fee percentage. Callable only by owner.\n /// @notice The proposed flash mint fee percentage cannot exceed `MAX_FLASH_MINT_FEE_PERCENTAGE`.\n /// @param feePercentage New flash fee percentage.\n function setFlashMintFeePercentage(uint256 feePercentage) external;\n}\n\ninterface IPriceOracle {\n // --- Errors ---\n\n /// @dev Contract initialized with an invalid deviation parameter.\n error InvalidDeviation();\n\n // --- Types ---\n\n struct PriceOracleResponse {\n bool isBrokenOrFrozen;\n bool priceChangeAboveMax;\n uint256 price;\n }\n\n // --- Functions ---\n\n /// @dev Return price oracle response which consists the following information: oracle is broken or frozen, the\n /// price change between two rounds is more than max, and the price.\n function getPriceOracleResponse() external returns (PriceOracleResponse memory);\n\n /// @dev Maximum time period allowed since oracle latest round data timestamp, beyond which oracle is considered\n /// frozen.\n function timeout() external view returns (uint256);\n\n /// @dev Used to convert a price answer to an 18-digit precision uint.\n function TARGET_DIGITS() external view returns (uint256);\n\n /// @dev price deviation for the oracle in percentage.\n function DEVIATION() external view returns (uint256);\n}\n\ninterface IPriceFeed {\n // --- Events ---\n\n /// @dev Last good price has been updated.\n event LastGoodPriceUpdated(uint256 lastGoodPrice);\n\n /// @dev Price difference between oracles has been updated.\n /// @param priceDifferenceBetweenOracles New price difference between oracles.\n event PriceDifferenceBetweenOraclesUpdated(uint256 priceDifferenceBetweenOracles);\n\n /// @dev Primary oracle has been updated.\n /// @param primaryOracle New primary oracle.\n event PrimaryOracleUpdated(IPriceOracle primaryOracle);\n\n /// @dev Secondary oracle has been updated.\n /// @param secondaryOracle New secondary oracle.\n event SecondaryOracleUpdated(IPriceOracle secondaryOracle);\n\n // --- Errors ---\n\n /// @dev Invalid primary oracle.\n error InvalidPrimaryOracle();\n\n /// @dev Invalid secondary oracle.\n error InvalidSecondaryOracle();\n\n /// @dev Primary oracle is broken or frozen or has bad result.\n error PrimaryOracleBrokenOrFrozenOrBadResult();\n\n /// @dev Invalid price difference between oracles.\n error InvalidPriceDifferenceBetweenOracles();\n\n // --- Functions ---\n\n /// @dev Return primary oracle address.\n function primaryOracle() external returns (IPriceOracle);\n\n /// @dev Return secondary oracle address\n function secondaryOracle() external returns (IPriceOracle);\n\n /// @dev The last good price seen from an oracle by Raft.\n function lastGoodPrice() external returns (uint256);\n\n /// @dev The maximum relative price difference between two oracle responses.\n function priceDifferenceBetweenOracles() external returns (uint256);\n\n /// @dev Set primary oracle address.\n /// @param newPrimaryOracle Primary oracle address.\n function setPrimaryOracle(IPriceOracle newPrimaryOracle) external;\n\n /// @dev Set secondary oracle address.\n /// @param newSecondaryOracle Secondary oracle address.\n function setSecondaryOracle(IPriceOracle newSecondaryOracle) external;\n\n /// @dev Set the maximum relative price difference between two oracle responses.\n /// @param newPriceDifferenceBetweenOracles The maximum relative price difference between two oracle responses.\n function setPriceDifferenceBetweenOracles(uint256 newPriceDifferenceBetweenOracles) external;\n\n /// @dev Returns the latest price obtained from the Oracle. Called by Raft functions that require a current price.\n ///\n /// Also callable by anyone externally.\n /// Non-view function - it stores the last good price seen by Raft.\n ///\n /// Uses a primary oracle and a fallback oracle in case primary fails. If both fail,\n /// it uses the last good price seen by Raft.\n ///\n /// @return currentPrice Returned price.\n /// @return deviation Deviation of the reported price in percentage.\n /// @notice Actual returned price is in range `currentPrice` +/- `currentPrice * deviation / ONE`\n function fetchPrice() external returns (uint256 currentPrice, uint256 deviation);\n}\n\ninterface IERC20Indexable is IERC20, IPositionManagerDependent {\n // --- Events ---\n\n /// @dev New token is deployed.\n /// @param positionManager Address of the PositionManager contract that is authorized to mint and burn new tokens.\n event ERC20IndexableDeployed(address positionManager);\n\n /// @dev New index has been set.\n /// @param newIndex Value of the new index.\n event IndexUpdated(uint256 newIndex);\n\n // --- Errors ---\n\n /// @dev Unsupported action for ERC20Indexable contract.\n error NotSupported();\n\n // --- Functions ---\n\n /// @return Precision for token index. Represents index that is equal to 1.\n function INDEX_PRECISION() external view returns (uint256);\n\n /// @return Current index value.\n function currentIndex() external view returns (uint256);\n\n /// @dev Sets new token index. Callable only by PositionManager contract.\n /// @param backingAmount Amount of backing token that is covered by total supply.\n function setIndex(uint256 backingAmount) external;\n\n /// @dev Mints new tokens. Callable only by PositionManager contract.\n /// @param to Address that will receive newly minted tokens.\n /// @param amount Amount of tokens to mint.\n function mint(address to, uint256 amount) external;\n\n /// @dev Mints new tokens. Callable only by PositionManager contract.\n /// @param from Address of user whose tokens are burnt.\n /// @param amount Amount of tokens to burn.\n function burn(address from, uint256 amount) external;\n}\n\ninterface ISplitLiquidationCollateral {\n // --- Functions ---\n\n /// @dev Returns lowest total debt that will be split.\n function LOW_TOTAL_DEBT() external view returns (uint256);\n\n /// @dev Minimum collateralization ratio for position\n function MCR() external view returns (uint256);\n\n /// @dev Splits collateral between protocol and liquidator.\n /// @param totalCollateral Amount of collateral to split.\n /// @param totalDebt Amount of debt to split.\n /// @param price Price of collateral.\n /// @param isRedistribution True if this is a redistribution.\n /// @return collateralToSendToProtocol Amount of collateral to send to protocol.\n /// @return collateralToSentToLiquidator Amount of collateral to send to liquidator.\n function split(\n uint256 totalCollateral,\n uint256 totalDebt,\n uint256 price,\n bool isRedistribution\n )\n external\n view\n returns (uint256 collateralToSendToProtocol, uint256 collateralToSentToLiquidator);\n}\n\n/// @dev Common interface for the Position Manager.\ninterface IPositionManager is IFeeCollector {\n // --- Types ---\n\n /// @dev Information for a Raft indexable collateral token.\n /// @param collateralToken The Raft indexable collateral token.\n /// @param debtToken Corresponding Raft indexable debt token.\n /// @param priceFeed The contract that provides a price for the collateral token.\n /// @param splitLiquidation The contract that calculates collateral split in case of liquidation.\n /// @param isEnabled Whether the token can be used as collateral or not.\n /// @param lastFeeOperationTime Timestamp of the last operation for the collateral token.\n /// @param borrowingSpread The current borrowing spread.\n /// @param baseRate The current base rate.\n /// @param redemptionSpread The current redemption spread.\n /// @param redemptionRebate Percentage of the redemption fee returned to redeemed positions.\n struct CollateralTokenInfo {\n IERC20Indexable collateralToken;\n IERC20Indexable debtToken;\n IPriceFeed priceFeed;\n ISplitLiquidationCollateral splitLiquidation;\n bool isEnabled;\n uint256 lastFeeOperationTime;\n uint256 borrowingSpread;\n uint256 baseRate;\n uint256 redemptionSpread;\n uint256 redemptionRebate;\n }\n\n // --- Events ---\n\n /// @dev New position manager has been token deployed.\n /// @param rToken The R token used by the position manager.\n /// @param feeRecipient The address of fee recipient.\n event PositionManagerDeployed(IRToken rToken, address feeRecipient);\n\n /// @dev New collateral token has been added added to the system.\n /// @param collateralToken The token used as collateral.\n /// @param raftCollateralToken The Raft indexable collateral token for the given collateral token.\n /// @param raftDebtToken The Raft indexable debt token for given collateral token.\n /// @param priceFeed The contract that provides price for the collateral token.\n event CollateralTokenAdded(\n IERC20 collateralToken,\n IERC20Indexable raftCollateralToken,\n IERC20Indexable raftDebtToken,\n IPriceFeed priceFeed\n );\n\n /// @dev Collateral token has been enabled or disabled.\n /// @param collateralToken The token used as collateral.\n /// @param isEnabled True if the token is enabled, false otherwise.\n event CollateralTokenModified(IERC20 collateralToken, bool isEnabled);\n\n /// @dev A delegate has been whitelisted for a certain position.\n /// @param position The position for which the delegate was whitelisted.\n /// @param delegate The delegate which was whitelisted.\n /// @param whitelisted Specifies whether the delegate whitelisting has been enabled (true) or disabled (false).\n event DelegateWhitelisted(address indexed position, address indexed delegate, bool whitelisted);\n\n /// @dev New position has been created.\n /// @param position The address of the user opening new position.\n /// @param collateralToken The token used as collateral for the created position.\n event PositionCreated(address indexed position, IERC20 indexed collateralToken);\n\n /// @dev The position has been closed by either repayment, liquidation, or redemption.\n /// @param position The address of the user whose position is closed.\n event PositionClosed(address indexed position);\n\n /// @dev Collateral amount for the position has been changed.\n /// @param position The address of the user that has opened the position.\n /// @param collateralToken The address of the collateral token being added to position.\n /// @param collateralAmount The amount of collateral added or removed.\n /// @param isCollateralIncrease Whether the collateral is added to the position or removed from it.\n event CollateralChanged(\n address indexed position, IERC20 indexed collateralToken, uint256 collateralAmount, bool isCollateralIncrease\n );\n\n /// @dev Debt amount for position has been changed.\n /// @param position The address of the user that has opened the position.\n /// @param collateralToken The address of the collateral token backing the debt.\n /// @param debtAmount The amount of debt added or removed.\n /// @param isDebtIncrease Whether the debt is added to the position or removed from it.\n event DebtChanged(\n address indexed position, IERC20 indexed collateralToken, uint256 debtAmount, bool isDebtIncrease\n );\n\n /// @dev Borrowing fee has been paid. Emitted only if the actual fee was paid - doesn't happen with no fees are\n /// paid.\n /// @param collateralToken Collateral token used to mint R.\n /// @param position The address of position's owner that triggered the fee payment.\n /// @param feeAmount The amount of tokens paid as the borrowing fee.\n event RBorrowingFeePaid(IERC20 collateralToken, address indexed position, uint256 feeAmount);\n\n /// @dev Liquidation has been executed.\n /// @param liquidator The liquidator that executed the liquidation.\n /// @param position The address of position's owner whose position was liquidated.\n /// @param collateralToken The collateral token used for the liquidation.\n /// @param debtLiquidated The total debt that was liquidated or redistributed.\n /// @param collateralLiquidated The total collateral liquidated.\n /// @param collateralSentToLiquidator The collateral amount sent to the liquidator.\n /// @param collateralLiquidationFeePaid The total collateral paid as the liquidation fee to the fee recipient.\n /// @param isRedistribution Whether the executed liquidation was redistribution or not.\n event Liquidation(\n address indexed liquidator,\n address indexed position,\n IERC20 indexed collateralToken,\n uint256 debtLiquidated,\n uint256 collateralLiquidated,\n uint256 collateralSentToLiquidator,\n uint256 collateralLiquidationFeePaid,\n bool isRedistribution\n );\n\n /// @dev Redemption has been executed.\n /// @param redeemer User that redeemed R.\n /// @param amount Amount of R that was redeemed.\n /// @param collateralSent The amount of collateral sent to the redeemer.\n /// @param fee The amount of fee paid to the fee recipient.\n /// @param rebate Redemption rebate amount.\n event Redemption(address indexed redeemer, uint256 amount, uint256 collateralSent, uint256 fee, uint256 rebate);\n\n /// @dev Borrowing spread has been updated.\n /// @param borrowingSpread The new borrowing spread.\n event BorrowingSpreadUpdated(uint256 borrowingSpread);\n\n /// @dev Redemption rebate has been updated.\n /// @param redemptionRebate The new redemption rebate.\n event RedemptionRebateUpdated(uint256 redemptionRebate);\n\n /// @dev Redemption spread has been updated.\n /// @param collateralToken Collateral token that the spread was set for.\n /// @param redemptionSpread The new redemption spread.\n event RedemptionSpreadUpdated(IERC20 collateralToken, uint256 redemptionSpread);\n\n /// @dev Base rate has been updated.\n /// @param collateralToken Collateral token that the baser rate was updated for.\n /// @param baseRate The new base rate.\n event BaseRateUpdated(IERC20 collateralToken, uint256 baseRate);\n\n /// @dev Last fee operation time has been updated.\n /// @param collateralToken Collateral token that the baser rate was updated for.\n /// @param lastFeeOpTime The new operation time.\n event LastFeeOpTimeUpdated(IERC20 collateralToken, uint256 lastFeeOpTime);\n\n /// @dev Split liquidation collateral has been changed.\n /// @param collateralToken Collateral token whose split liquidation collateral contract is set.\n /// @param newSplitLiquidationCollateral New value that was set to be split liquidation collateral.\n event SplitLiquidationCollateralChanged(\n IERC20 collateralToken, ISplitLiquidationCollateral indexed newSplitLiquidationCollateral\n );\n\n // --- Errors ---\n\n /// @dev Max fee percentage must be between borrowing spread and 100%.\n error InvalidMaxFeePercentage();\n\n /// @dev Max fee percentage must be between 0.5% and 100%.\n error MaxFeePercentageOutOfRange();\n\n /// @dev Amount is zero.\n error AmountIsZero();\n\n /// @dev Nothing to liquidate.\n error NothingToLiquidate();\n\n /// @dev Cannot liquidate last position.\n error CannotLiquidateLastPosition();\n\n /// @dev Cannot redeem collateral below minimum debt threshold.\n /// @param collateralToken Collateral token used to redeem.\n /// @param newTotalDebt New total debt backed by collateral, which is lower than minimum debt.\n error TotalDebtCannotBeLowerThanMinDebt(IERC20 collateralToken, uint256 newTotalDebt);\n\n /// @dev Cannot redeem collateral\n /// @param collateralToken Collateral token used to redeem.\n /// @param newTotalCollateral New total collateral, which is lower than minimum collateral.\n /// @param minimumCollateral Minimum collateral required to complete redeem\n error TotalCollateralCannotBeLowerThanMinCollateral(\n IERC20 collateralToken, uint256 newTotalCollateral, uint256 minimumCollateral\n );\n\n /// @dev Fee would eat up all returned collateral.\n error FeeEatsUpAllReturnedCollateral();\n\n /// @dev Borrowing spread exceeds maximum.\n error BorrowingSpreadExceedsMaximum();\n\n /// @dev Redemption rebate exceeds maximum.\n error RedemptionRebateExceedsMaximum();\n\n /// @dev Redemption spread is out of allowed range.\n error RedemptionSpreadOutOfRange();\n\n /// @dev There must be either a collateral change or a debt change.\n error NoCollateralOrDebtChange();\n\n /// @dev There is some collateral for position that doesn't have debt.\n error InvalidPosition();\n\n /// @dev An operation that would result in ICR < MCR is not permitted.\n /// @param newICR Resulting ICR that is below MCR.\n error NewICRLowerThanMCR(uint256 newICR);\n\n /// @dev Position's net debt must be greater than minimum.\n /// @param netDebt Net debt amount that is below minimum.\n error NetDebtBelowMinimum(uint256 netDebt);\n\n /// @dev The provided delegate address is invalid.\n error InvalidDelegateAddress();\n\n /// @dev A non-whitelisted delegate cannot adjust positions.\n error DelegateNotWhitelisted();\n\n /// @dev Fee exceeded provided maximum fee percentage.\n /// @param fee The fee amount.\n /// @param amount The amount of debt or collateral.\n /// @param maxFeePercentage The maximum fee percentage.\n error FeeExceedsMaxFee(uint256 fee, uint256 amount, uint256 maxFeePercentage);\n\n /// @dev Borrower uses a different collateral token already.\n error PositionCollateralTokenMismatch();\n\n /// @dev Collateral token address cannot be zero.\n error CollateralTokenAddressCannotBeZero();\n\n /// @dev Price feed address cannot be zero.\n error PriceFeedAddressCannotBeZero();\n\n /// @dev Collateral token already added.\n error CollateralTokenAlreadyAdded();\n\n /// @dev Collateral token is not added.\n error CollateralTokenNotAdded();\n\n /// @dev Collateral token is not enabled.\n error CollateralTokenDisabled();\n\n /// @dev Split liquidation collateral cannot be zero.\n error SplitLiquidationCollateralCannotBeZero();\n\n /// @dev Cannot change collateral in case of repaying the whole debt.\n error WrongCollateralParamsForFullRepayment();\n\n // --- Functions ---\n\n /// @return The R token used by position manager.\n function rToken() external view returns (IRToken);\n\n /// @dev Retrieves information about certain collateral type.\n /// @param collateralToken The token used as collateral.\n /// @return raftCollateralToken The Raft indexable collateral token.\n /// @return raftDebtToken The Raft indexable debt token.\n /// @return priceFeed The contract that provides a price for the collateral token.\n /// @return splitLiquidation The contract that calculates collateral split in case of liquidation.\n /// @return isEnabled Whether the collateral token can be used as collateral or not.\n /// @return lastFeeOperationTime Timestamp of the last operation for the collateral token.\n /// @return borrowingSpread The current borrowing spread.\n /// @return baseRate The current base rate.\n /// @return redemptionSpread The current redemption spread.\n /// @return redemptionRebate Percentage of the redemption fee returned to redeemed positions.\n function collateralInfo(IERC20 collateralToken)\n external\n view\n returns (\n IERC20Indexable raftCollateralToken,\n IERC20Indexable raftDebtToken,\n IPriceFeed priceFeed,\n ISplitLiquidationCollateral splitLiquidation,\n bool isEnabled,\n uint256 lastFeeOperationTime,\n uint256 borrowingSpread,\n uint256 baseRate,\n uint256 redemptionSpread,\n uint256 redemptionRebate\n );\n\n /// @param collateralToken Collateral token whose raft collateral indexable token is being queried.\n /// @return Raft collateral token address for given collateral token.\n function raftCollateralToken(IERC20 collateralToken) external view returns (IERC20Indexable);\n\n /// @param collateralToken Collateral token whose raft collateral indexable token is being queried.\n /// @return Raft debt token address for given collateral token.\n function raftDebtToken(IERC20 collateralToken) external view returns (IERC20Indexable);\n\n /// @param collateralToken Collateral token whose price feed contract is being queried.\n /// @return Price feed contract address for given collateral token.\n function priceFeed(IERC20 collateralToken) external view returns (IPriceFeed);\n\n /// @param collateralToken Collateral token whose split liquidation collateral is being queried.\n /// @return Returns address of the split liquidation collateral contract.\n function splitLiquidationCollateral(IERC20 collateralToken) external view returns (ISplitLiquidationCollateral);\n\n /// @param collateralToken Collateral token whose split liquidation collateral is being queried.\n /// @return Returns whether collateral is enabled or nor.\n function collateralEnabled(IERC20 collateralToken) external view returns (bool);\n\n /// @param collateralToken Collateral token we query last operation time fee for.\n /// @return The timestamp of the latest fee operation (redemption or new R issuance).\n function lastFeeOperationTime(IERC20 collateralToken) external view returns (uint256);\n\n /// @param collateralToken Collateral token we query borrowing spread for.\n /// @return The current borrowing spread.\n function borrowingSpread(IERC20 collateralToken) external view returns (uint256);\n\n /// @param collateralToken Collateral token we query base rate for.\n /// @return rate The base rate.\n function baseRate(IERC20 collateralToken) external view returns (uint256 rate);\n\n /// @param collateralToken Collateral token we query redemption spread for.\n /// @return The current redemption spread for collateral token.\n function redemptionSpread(IERC20 collateralToken) external view returns (uint256);\n\n /// @param collateralToken Collateral token we query redemption rebate for.\n /// @return rebate Percentage of the redemption fee returned to redeemed positions.\n function redemptionRebate(IERC20 collateralToken) external view returns (uint256);\n\n /// @param collateralToken Collateral token we query redemption rate for.\n /// @return rate The current redemption rate for collateral token.\n function getRedemptionRate(IERC20 collateralToken) external view returns (uint256 rate);\n\n /// @dev Returns the collateral token that a given position used for their position.\n /// @param position The address of the borrower.\n /// @return collateralToken The collateral token of the borrower's position.\n function collateralTokenForPosition(address position) external view returns (IERC20 collateralToken);\n\n /// @dev Adds a new collateral token to the protocol.\n /// @param collateralToken The new collateral token.\n /// @param priceFeed The price feed for the collateral token.\n /// @param newSplitLiquidationCollateral split liquidation collateral contract address.\n function addCollateralToken(\n IERC20 collateralToken,\n IPriceFeed priceFeed,\n ISplitLiquidationCollateral newSplitLiquidationCollateral\n )\n external;\n\n /// @dev Adds a new collateral token to the protocol.\n /// @param collateralToken The new collateral token.\n /// @param priceFeed The price feed for the collateral token.\n /// @param newSplitLiquidationCollateral split liquidation collateral contract address.\n /// @param raftCollateralToken_ Address of raft collateral token.\n /// @param raftDebtToken_ Address of raft debt token.\n function addCollateralToken(\n IERC20 collateralToken,\n IPriceFeed priceFeed,\n ISplitLiquidationCollateral newSplitLiquidationCollateral,\n IERC20Indexable raftCollateralToken_,\n IERC20Indexable raftDebtToken_\n )\n external;\n\n /// @dev Enables or disables a collateral token. Reverts if the collateral token has not been added.\n /// @param collateralToken The collateral token.\n /// @param isEnabled Whether the collateral token can be used as collateral or not.\n function setCollateralEnabled(IERC20 collateralToken, bool isEnabled) external;\n\n /// @dev Sets the new split liquidation collateral contract.\n /// @param collateralToken Collateral token whose split liquidation collateral is being set.\n /// @param newSplitLiquidationCollateral New split liquidation collateral contract address.\n function setSplitLiquidationCollateral(\n IERC20 collateralToken,\n ISplitLiquidationCollateral newSplitLiquidationCollateral\n )\n external;\n\n /// @dev Liquidates the borrower if its position's ICR is lower than the minimum collateral ratio.\n /// @param position The address of the borrower.\n function liquidate(address position) external;\n\n /// @dev Redeems the collateral token for a given debt amount. It sends @param debtAmount R to the system and\n /// redeems the corresponding amount of collateral from as many positions as are needed to fill the redemption\n /// request.\n /// @param collateralToken The token used as collateral.\n /// @param debtAmount The amount of debt to be redeemed. Must be greater than zero.\n /// @param maxFeePercentage The maximum fee percentage to pay for the redemption.\n function redeemCollateral(IERC20 collateralToken, uint256 debtAmount, uint256 maxFeePercentage) external;\n\n /// @dev Manages the position on behalf of a given borrower.\n /// @param collateralToken The token the borrower used as collateral.\n /// @param position The address of the borrower.\n /// @param collateralChange The amount of collateral to add or remove.\n /// @param isCollateralIncrease True if the collateral is being increased, false otherwise.\n /// @param debtChange The amount of R to add or remove. In case of repayment (isDebtIncrease = false)\n /// `type(uint256).max` value can be used to repay the whole outstanding loan.\n /// @param isDebtIncrease True if the debt is being increased, false otherwise.\n /// @param maxFeePercentage The maximum fee percentage to pay for the position management.\n /// @param permitSignature Optional permit signature for tokens that support IERC20Permit interface.\n /// @notice `permitSignature` it is ignored if permit signature is not for `collateralToken`.\n /// @notice In case of full debt repayment, `isCollateralIncrease` is ignored and `collateralChange` must be 0.\n /// These values are set to `false`(collateral decrease), and the whole collateral balance of the user.\n /// @return actualCollateralChange Actual amount of collateral added/removed.\n /// Can be different to `collateralChange` in case of full repayment.\n /// @return actualDebtChange Actual amount of debt added/removed.\n /// Can be different to `debtChange` in case of passing type(uint256).max as `debtChange`.\n function managePosition(\n IERC20 collateralToken,\n address position,\n uint256 collateralChange,\n bool isCollateralIncrease,\n uint256 debtChange,\n bool isDebtIncrease,\n uint256 maxFeePercentage,\n ERC20PermitSignature calldata permitSignature\n )\n external\n returns (uint256 actualCollateralChange, uint256 actualDebtChange);\n\n /// @return The max borrowing spread.\n function MAX_BORROWING_SPREAD() external view returns (uint256);\n\n /// @return The max borrowing rate.\n function MAX_BORROWING_RATE() external view returns (uint256);\n\n /// @dev Sets the new borrowing spread.\n /// @param collateralToken Collateral token we set borrowing spread for.\n /// @param newBorrowingSpread New borrowing spread to be used.\n function setBorrowingSpread(IERC20 collateralToken, uint256 newBorrowingSpread) external;\n\n /// @param collateralToken Collateral token we query borrowing rate for.\n /// @return The current borrowing rate.\n function getBorrowingRate(IERC20 collateralToken) external view returns (uint256);\n\n /// @param collateralToken Collateral token we query borrowing rate with decay for.\n /// @return The current borrowing rate with decay.\n function getBorrowingRateWithDecay(IERC20 collateralToken) external view returns (uint256);\n\n /// @dev Returns the borrowing fee for a given debt amount.\n /// @param collateralToken Collateral token we query borrowing fee for.\n /// @param debtAmount The amount of debt.\n /// @return The borrowing fee.\n function getBorrowingFee(IERC20 collateralToken, uint256 debtAmount) external view returns (uint256);\n\n /// @dev Sets the new redemption spread.\n /// @param newRedemptionSpread New redemption spread to be used.\n function setRedemptionSpread(IERC20 collateralToken, uint256 newRedemptionSpread) external;\n\n /// @dev Sets new redemption rebate percentage.\n /// @param newRedemptionRebate Value that is being set as a redemption rebate percentage.\n function setRedemptionRebate(IERC20 collateralToken, uint256 newRedemptionRebate) external;\n\n /// @param collateralToken Collateral token we query redemption rate with decay for.\n /// @return The current redemption rate with decay.\n function getRedemptionRateWithDecay(IERC20 collateralToken) external view returns (uint256);\n\n /// @dev Returns the redemption fee for a given collateral amount.\n /// @param collateralToken Collateral token we query redemption fee for.\n /// @param collateralAmount The amount of collateral.\n /// @param priceDeviation Deviation for the reported price by oracle in percentage.\n /// @return The redemption fee.\n function getRedemptionFee(\n IERC20 collateralToken,\n uint256 collateralAmount,\n uint256 priceDeviation\n )\n external\n view\n returns (uint256);\n\n /// @dev Returns the redemption fee with decay for a given collateral amount.\n /// @param collateralToken Collateral token we query redemption fee with decay for.\n /// @param collateralAmount The amount of collateral.\n /// @return The redemption fee with decay.\n function getRedemptionFeeWithDecay(\n IERC20 collateralToken,\n uint256 collateralAmount\n )\n external\n view\n returns (uint256);\n\n /// @return Half-life of 12h (720 min).\n /// @dev (1/2) = d^720 => d = (1/2)^(1/720)\n function MINUTE_DECAY_FACTOR() external view returns (uint256);\n\n /// @dev Returns if a given delegate is whitelisted for a given borrower.\n /// @param position The address of the borrower.\n /// @param delegate The address of the delegate.\n /// @return isWhitelisted True if the delegate is whitelisted for a given borrower, false otherwise.\n function isDelegateWhitelisted(address position, address delegate) external view returns (bool isWhitelisted);\n\n /// @dev Whitelists a delegate.\n /// @param delegate The address of the delegate.\n /// @param whitelisted True if delegate is being whitelisted, false otherwise.\n function whitelistDelegate(address delegate, bool whitelisted) external;\n\n /// @return Parameter by which to divide the redeemed fraction, in order to calc the new base rate from a\n /// redemption. Corresponds to (1 / ALPHA) in the white paper.\n function BETA() external view returns (uint256);\n}\n\n/// @dev Common interface for the Position Manager.\ninterface IInterestRatePositionManager is IPositionManager {\n // --- Events ---\n\n /// @dev Fees coming from accrued interest are minted.\n /// @param collateralToken Collateral token that fees are paid for.\n /// @param amount Amount of R minted.\n event MintedFees(IERC20 collateralToken, uint256 amount);\n\n // --- Errors ---\n\n /// @dev Only registered debt token can be caller.\n /// @param sender Actual caller.\n error InvalidDebtToken(address sender);\n\n // --- Functions ---\n\n /// @dev Mints fees coming from accrued interest. Can be called only from matching debt token.\n /// @param collateralToken Collateral token to mint fees for.\n /// @param amount Amount of R to mint.\n function mintFees(IERC20 collateralToken, uint256 amount) external;\n}\n\n// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)\n\n/**\n * @dev Standard math utilities missing in the Solidity language.\n */\nlibrary Math {\n enum Rounding {\n Down, // Toward negative infinity\n Up, // Toward infinity\n Zero // Toward zero\n }\n\n /**\n * @dev Returns the largest of two numbers.\n */\n function max(uint256 a, uint256 b) internal pure returns (uint256) {\n return a > b ? a : b;\n }\n\n /**\n * @dev Returns the smallest of two numbers.\n */\n function min(uint256 a, uint256 b) internal pure returns (uint256) {\n return a < b ? a : b;\n }\n\n /**\n * @dev Returns the average of two numbers. The result is rounded towards\n * zero.\n */\n function average(uint256 a, uint256 b) internal pure returns (uint256) {\n // (a + b) / 2 can overflow.\n return (a & b) + (a ^ b) / 2;\n }\n\n /**\n * @dev Returns the ceiling of the division of two numbers.\n *\n * This differs from standard division with `/` in that it rounds up instead\n * of rounding down.\n */\n function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {\n // (a + b - 1) / b can overflow on addition, so we distribute.\n return a == 0 ? 0 : (a - 1) / b + 1;\n }\n\n /**\n * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0\n * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)\n * with further edits by Uniswap Labs also under MIT license.\n */\n function mulDiv(\n uint256 x,\n uint256 y,\n uint256 denominator\n ) internal pure returns (uint256 result) {\n unchecked {\n // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use\n // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256\n // variables such that product = prod1 * 2^256 + prod0.\n uint256 prod0; // Least significant 256 bits of the product\n uint256 prod1; // Most significant 256 bits of the product\n assembly {\n let mm := mulmod(x, y, not(0))\n prod0 := mul(x, y)\n prod1 := sub(sub(mm, prod0), lt(mm, prod0))\n }\n\n // Handle non-overflow cases, 256 by 256 division.\n if (prod1 == 0) {\n return prod0 / denominator;\n }\n\n // Make sure the result is less than 2^256. Also prevents denominator == 0.\n require(denominator > prod1);\n\n ///////////////////////////////////////////////\n // 512 by 256 division.\n ///////////////////////////////////////////////\n\n // Make division exact by subtracting the remainder from [prod1 prod0].\n uint256 remainder;\n assembly {\n // Compute remainder using mulmod.\n remainder := mulmod(x, y, denominator)\n\n // Subtract 256 bit number from 512 bit number.\n prod1 := sub(prod1, gt(remainder, prod0))\n prod0 := sub(prod0, remainder)\n }\n\n // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.\n // See https://cs.stackexchange.com/q/138556/92363.\n\n // Does not overflow because the denominator cannot be zero at this stage in the function.\n uint256 twos = denominator & (~denominator + 1);\n assembly {\n // Divide denominator by twos.\n denominator := div(denominator, twos)\n\n // Divide [prod1 prod0] by twos.\n prod0 := div(prod0, twos)\n\n // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.\n twos := add(div(sub(0, twos), twos), 1)\n }\n\n // Shift in bits from prod1 into prod0.\n prod0 |= prod1 * twos;\n\n // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such\n // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for\n // four bits. That is, denominator * inv = 1 mod 2^4.\n uint256 inverse = (3 * denominator) ^ 2;\n\n // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works\n // in modular arithmetic, doubling the correct bits in each step.\n inverse *= 2 - denominator * inverse; // inverse mod 2^8\n inverse *= 2 - denominator * inverse; // inverse mod 2^16\n inverse *= 2 - denominator * inverse; // inverse mod 2^32\n inverse *= 2 - denominator * inverse; // inverse mod 2^64\n inverse *= 2 - denominator * inverse; // inverse mod 2^128\n inverse *= 2 - denominator * inverse; // inverse mod 2^256\n\n // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.\n // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is\n // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1\n // is no longer required.\n result = prod0 * inverse;\n return result;\n }\n }\n\n /**\n * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.\n */\n function mulDiv(\n uint256 x,\n uint256 y,\n uint256 denominator,\n Rounding rounding\n ) internal pure returns (uint256) {\n uint256 result = mulDiv(x, y, denominator);\n if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {\n result += 1;\n }\n return result;\n }\n\n /**\n * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.\n *\n * Inspired by Henry S. Warren, Jr.'s \"Hacker's Delight\" (Chapter 11).\n */\n function sqrt(uint256 a) internal pure returns (uint256) {\n if (a == 0) {\n return 0;\n }\n\n // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.\n //\n // We know that the \"msb\" (most significant bit) of our target number `a` is a power of 2 such that we have\n // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.\n //\n // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`\n // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`\n // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`\n //\n // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.\n uint256 result = 1 << (log2(a) >> 1);\n\n // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,\n // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at\n // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision\n // into the expected uint128 result.\n unchecked {\n result = (result + a / result) >> 1;\n result = (result + a / result) >> 1;\n result = (result + a / result) >> 1;\n result = (result + a / result) >> 1;\n result = (result + a / result) >> 1;\n result = (result + a / result) >> 1;\n result = (result + a / result) >> 1;\n return min(result, a / result);\n }\n }\n\n /**\n * @notice Calculates sqrt(a), following the selected rounding direction.\n */\n function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = sqrt(a);\n return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);\n }\n }\n\n /**\n * @dev Return the log in base 2, rounded down, of a positive value.\n * Returns 0 if given 0.\n */\n function log2(uint256 value) internal pure returns (uint256) {\n uint256 result = 0;\n unchecked {\n if (value >> 128 > 0) {\n value >>= 128;\n result += 128;\n }\n if (value >> 64 > 0) {\n value >>= 64;\n result += 64;\n }\n if (value >> 32 > 0) {\n value >>= 32;\n result += 32;\n }\n if (value >> 16 > 0) {\n value >>= 16;\n result += 16;\n }\n if (value >> 8 > 0) {\n value >>= 8;\n result += 8;\n }\n if (value >> 4 > 0) {\n value >>= 4;\n result += 4;\n }\n if (value >> 2 > 0) {\n value >>= 2;\n result += 2;\n }\n if (value >> 1 > 0) {\n result += 1;\n }\n }\n return result;\n }\n\n /**\n * @dev Return the log in base 2, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log2(value);\n return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);\n }\n }\n\n /**\n * @dev Return the log in base 10, rounded down, of a positive value.\n * Returns 0 if given 0.\n */\n function log10(uint256 value) internal pure returns (uint256) {\n uint256 result = 0;\n unchecked {\n if (value >= 10**64) {\n value /= 10**64;\n result += 64;\n }\n if (value >= 10**32) {\n value /= 10**32;\n result += 32;\n }\n if (value >= 10**16) {\n value /= 10**16;\n result += 16;\n }\n if (value >= 10**8) {\n value /= 10**8;\n result += 8;\n }\n if (value >= 10**4) {\n value /= 10**4;\n result += 4;\n }\n if (value >= 10**2) {\n value /= 10**2;\n result += 2;\n }\n if (value >= 10**1) {\n result += 1;\n }\n }\n return result;\n }\n\n /**\n * @dev Return the log in base 10, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log10(value);\n return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);\n }\n }\n\n /**\n * @dev Return the log in base 256, rounded down, of a positive value.\n * Returns 0 if given 0.\n *\n * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.\n */\n function log256(uint256 value) internal pure returns (uint256) {\n uint256 result = 0;\n unchecked {\n if (value >> 128 > 0) {\n value >>= 128;\n result += 16;\n }\n if (value >> 64 > 0) {\n value >>= 64;\n result += 8;\n }\n if (value >> 32 > 0) {\n value >>= 32;\n result += 4;\n }\n if (value >> 16 > 0) {\n value >>= 16;\n result += 2;\n }\n if (value >> 8 > 0) {\n result += 1;\n }\n }\n return result;\n }\n\n /**\n * @dev Return the log in base 10, following the selected rounding direction, of a positive value.\n * Returns 0 if given 0.\n */\n function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {\n unchecked {\n uint256 result = log256(value);\n return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);\n }\n }\n}\n\n// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)\n\n/**\n * @dev Interface for the optional metadata functions from the ERC20 standard.\n *\n * _Available since v4.1._\n */\ninterface IERC20Metadata is IERC20 {\n /**\n * @dev Returns the name of the token.\n */\n function name() external view returns (string memory);\n\n /**\n * @dev Returns the symbol of the token.\n */\n function symbol() external view returns (string memory);\n\n /**\n * @dev Returns the decimals places of the token.\n */\n function decimals() external view returns (uint8);\n}\n\n// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol)\n\n// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n * ====\n *\n * [IMPORTANT]\n * ====\n * You shouldn't rely on `isContract` to protect against flash loan attacks!\n *\n * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets\n * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract\n * constructor.\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize/address.code.length, which returns 0\n // for contracts in construction, since the code is only stored at the end\n // of the constructor execution.\n\n return account.code.length > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResultFromTarget(target, success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling\n * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.\n *\n * _Available since v4.8._\n */\n function verifyCallResultFromTarget(\n address target,\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n if (success) {\n if (returndata.length == 0) {\n // only check isContract if the call was successful and the return data is empty\n // otherwise we already know that it was a contract\n require(isContract(target), \"Address: call to non-contract\");\n }\n return returndata;\n } else {\n _revert(returndata, errorMessage);\n }\n }\n\n /**\n * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason or using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n _revert(returndata, errorMessage);\n }\n }\n\n function _revert(bytes memory returndata, string memory errorMessage) private pure {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n /// @solidity memory-safe-assembly\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n}\n\n/**\n * @title SafeERC20\n * @dev Wrappers around ERC20 operations that throw on failure (when the token\n * contract returns false). Tokens that return no value (and instead revert or\n * throw on failure) are also supported, non-reverting calls are assumed to be\n * successful.\n * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\n */\nlibrary SafeERC20 {\n using Address for address;\n\n function safeTransfer(\n IERC20 token,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\n }\n\n function safeTransferFrom(\n IERC20 token,\n address from,\n address to,\n uint256 value\n ) internal {\n _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\n }\n\n /**\n * @dev Deprecated. This function has issues similar to the ones found in\n * {IERC20-approve}, and its usage is discouraged.\n *\n * Whenever possible, use {safeIncreaseAllowance} and\n * {safeDecreaseAllowance} instead.\n */\n function safeApprove(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n // safeApprove should only be called when setting an initial allowance,\n // or when resetting it to zero. To increase and decrease it, use\n // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'\n require(\n (value == 0) || (token.allowance(address(this), spender) == 0),\n \"SafeERC20: approve from non-zero to non-zero allowance\"\n );\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\n }\n\n function safeIncreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n uint256 newAllowance = token.allowance(address(this), spender) + value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n\n function safeDecreaseAllowance(\n IERC20 token,\n address spender,\n uint256 value\n ) internal {\n unchecked {\n uint256 oldAllowance = token.allowance(address(this), spender);\n require(oldAllowance >= value, \"SafeERC20: decreased allowance below zero\");\n uint256 newAllowance = oldAllowance - value;\n _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\n }\n }\n\n function safePermit(\n IERC20Permit token,\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal {\n uint256 nonceBefore = token.nonces(owner);\n token.permit(owner, spender, value, deadline, v, r, s);\n uint256 nonceAfter = token.nonces(owner);\n require(nonceAfter == nonceBefore + 1, \"SafeERC20: permit did not succeed\");\n }\n\n /**\n * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement\n * on the return value: the return value is optional (but if data is returned, it must not be false).\n * @param token The token targeted by the call.\n * @param data The call data (encoded using abi.encode or one of its variants).\n */\n function _callOptionalReturn(IERC20 token, bytes memory data) private {\n // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since\n // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that\n // the target address contains contract code and also asserts for success in the low-level call.\n\n bytes memory returndata = address(token).functionCall(data, \"SafeERC20: low-level call failed\");\n if (returndata.length > 0) {\n // Return data is optional\n require(abi.decode(returndata, (bool)), \"SafeERC20: ERC20 operation did not succeed\");\n }\n }\n}\n\nlibrary Fixed256x18 {\n uint256 internal constant ONE = 1e18; // 18 decimal places\n\n function mulDown(uint256 a, uint256 b) internal pure returns (uint256) {\n return (a * b) / ONE;\n }\n\n function mulUp(uint256 a, uint256 b) internal pure returns (uint256) {\n uint256 product = a * b;\n\n if (product == 0) {\n return 0;\n } else {\n return ((product - 1) / ONE) + 1;\n }\n }\n\n function divDown(uint256 a, uint256 b) internal pure returns (uint256) {\n return (a * ONE) / b;\n }\n\n function divUp(uint256 a, uint256 b) internal pure returns (uint256) {\n if (a == 0) {\n return 0;\n } else {\n return (((a * ONE) - 1) / b) + 1;\n }\n }\n\n function complement(uint256 x) internal pure returns (uint256) {\n return (x < ONE) ? (ONE - x) : 0;\n }\n}\n\nlibrary MathUtils {\n // --- Constants ---\n\n /// @notice Represents 100%.\n /// @dev 1e18 is the scaling factor (100% == 1e18).\n uint256 public constant _100_PERCENT = Fixed256x18.ONE;\n\n /// @notice Precision for Nominal ICR (independent of price).\n /// @dev Rationale for the value:\n /// - Making it “too high” could lead to overflows.\n /// - Making it “too low” could lead to an ICR equal to zero, due to truncation from floor division.\n ///\n /// This value of 1e20 is chosen for safety: the NICR will only overflow for numerator > ~1e39 collateralToken,\n /// and will only truncate to 0 if the denominator is at least 1e20 times greater than the numerator.\n uint256 internal constant _NICR_PRECISION = 1e20;\n\n /// @notice Number of minutes in 1000 years.\n uint256 internal constant _MINUTES_IN_1000_YEARS = 1000 * 365 days / 1 minutes;\n\n // --- Functions ---\n\n /// @notice Multiplies two decimal numbers and use normal rounding rules:\n /// - round product up if 19'th mantissa digit >= 5\n /// - round product down if 19'th mantissa digit < 5.\n /// @param x First number.\n /// @param y Second number.\n function _decMul(uint256 x, uint256 y) internal pure returns (uint256 decProd) {\n decProd = (x * y + Fixed256x18.ONE / 2) / Fixed256x18.ONE;\n }\n\n /// @notice Exponentiation function for 18-digit decimal base, and integer exponent n.\n ///\n /// @dev Uses the efficient \"exponentiation by squaring\" algorithm. O(log(n)) complexity. The exponent is capped to\n /// avoid reverting due to overflow.\n ///\n /// If a period of > 1000 years is ever used as an exponent in either of the above functions, the result will be\n /// negligibly different from just passing the cap, since the decayed base rate will be 0 for 1000 years or > 1000\n /// years.\n /// @param base The decimal base.\n /// @param exponent The exponent.\n /// @return The result of the exponentiation.\n function _decPow(uint256 base, uint256 exponent) internal pure returns (uint256) {\n if (exponent == 0) {\n return Fixed256x18.ONE;\n }\n\n uint256 y = Fixed256x18.ONE;\n uint256 x = base;\n uint256 n = Math.min(exponent, _MINUTES_IN_1000_YEARS); // cap to avoid overflow\n\n // Exponentiation-by-squaring\n while (n > 1) {\n if (n % 2 != 0) {\n y = _decMul(x, y);\n }\n x = _decMul(x, x);\n n /= 2;\n }\n\n return _decMul(x, y);\n }\n\n /// @notice Computes the Nominal Individual Collateral Ratio (NICR) for given collateral and debt. If debt is zero,\n /// it returns the maximal value for uint256 (represents \"infinite\" CR).\n /// @param collateral Collateral amount.\n /// @param debt Debt amount.\n /// @return NICR.\n function _computeNominalCR(uint256 collateral, uint256 debt) internal pure returns (uint256) {\n return debt > 0 ? collateral * _NICR_PRECISION / debt : type(uint256).max;\n }\n\n /// @notice Computes the Collateral Ratio for given collateral, debt and price. If debt is zero, it returns the\n /// maximal value for uint256 (represents \"infinite\" CR).\n /// @param collateral Collateral amount.\n /// @param debt Debt amount.\n /// @param price Collateral price.\n /// @return Collateral ratio.\n function _computeCR(uint256 collateral, uint256 debt, uint256 price) internal pure returns (uint256) {\n return debt > 0 ? collateral * price / debt : type(uint256).max;\n }\n}\n\n// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/ERC20.sol)\n\n// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)\n\n/**\n * @dev Provides information about the current execution context, including the\n * sender of the transaction and its data. While these are generally available\n * via msg.sender and msg.data, they should not be accessed in such a direct\n * manner, since when dealing with meta-transactions the account sending and\n * paying for execution may not be the actual sender (as far as an application\n * is concerned).\n *\n * This contract is only required for intermediate, library-like contracts.\n */\nabstract contract Context {\n function _msgSender() internal view virtual returns (address) {\n return msg.sender;\n }\n\n function _msgData() internal view virtual returns (bytes calldata) {\n return msg.data;\n }\n}\n\n/**\n * @dev Implementation of the {IERC20} interface.\n *\n * This implementation is agnostic to the way tokens are created. This means\n * that a supply mechanism has to be added in a derived contract using {_mint}.\n * For a generic mechanism see {ERC20PresetMinterPauser}.\n *\n * TIP: For a detailed writeup see our guide\n * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How\n * to implement supply mechanisms].\n *\n * We have followed general OpenZeppelin Contracts guidelines: functions revert\n * instead returning `false` on failure. This behavior is nonetheless\n * conventional and does not conflict with the expectations of ERC20\n * applications.\n *\n * Additionally, an {Approval} event is emitted on calls to {transferFrom}.\n * This allows applications to reconstruct the allowance for all accounts just\n * by listening to said events. Other implementations of the EIP may not emit\n * these events, as it isn't required by the specification.\n *\n * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}\n * functions have been added to mitigate the well-known issues around setting\n * allowances. See {IERC20-approve}.\n */\ncontract ERC20 is Context, IERC20, IERC20Metadata {\n mapping(address => uint256) private _balances;\n\n mapping(address => mapping(address => uint256)) private _allowances;\n\n uint256 private _totalSupply;\n\n string private _name;\n string private _symbol;\n\n /**\n * @dev Sets the values for {name} and {symbol}.\n *\n * The default value of {decimals} is 18. To select a different value for\n * {decimals} you should overload it.\n *\n * All two of these values are immutable: they can only be set once during\n * construction.\n */\n constructor(string memory name_, string memory symbol_) {\n _name = name_;\n _symbol = symbol_;\n }\n\n /**\n * @dev Returns the name of the token.\n */\n function name() public view virtual override returns (string memory) {\n return _name;\n }\n\n /**\n * @dev Returns the symbol of the token, usually a shorter version of the\n * name.\n */\n function symbol() public view virtual override returns (string memory) {\n return _symbol;\n }\n\n /**\n * @dev Returns the number of decimals used to get its user representation.\n * For example, if `decimals` equals `2`, a balance of `505` tokens should\n * be displayed to a user as `5.05` (`505 / 10 ** 2`).\n *\n * Tokens usually opt for a value of 18, imitating the relationship between\n * Ether and Wei. This is the value {ERC20} uses, unless this function is\n * overridden;\n *\n * NOTE: This information is only used for _display_ purposes: it in\n * no way affects any of the arithmetic of the contract, including\n * {IERC20-balanceOf} and {IERC20-transfer}.\n */\n function decimals() public view virtual override returns (uint8) {\n return 18;\n }\n\n /**\n * @dev See {IERC20-totalSupply}.\n */\n function totalSupply() public view virtual override returns (uint256) {\n return _totalSupply;\n }\n\n /**\n * @dev See {IERC20-balanceOf}.\n */\n function balanceOf(address account) public view virtual override returns (uint256) {\n return _balances[account];\n }\n\n /**\n * @dev See {IERC20-transfer}.\n *\n * Requirements:\n *\n * - `to` cannot be the zero address.\n * - the caller must have a balance of at least `amount`.\n */\n function transfer(address to, uint256 amount) public virtual override returns (bool) {\n address owner = _msgSender();\n _transfer(owner, to, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-allowance}.\n */\n function allowance(address owner, address spender) public view virtual override returns (uint256) {\n return _allowances[owner][spender];\n }\n\n /**\n * @dev See {IERC20-approve}.\n *\n * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on\n * `transferFrom`. This is semantically equivalent to an infinite approval.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function approve(address spender, uint256 amount) public virtual override returns (bool) {\n address owner = _msgSender();\n _approve(owner, spender, amount);\n return true;\n }\n\n /**\n * @dev See {IERC20-transferFrom}.\n *\n * Emits an {Approval} event indicating the updated allowance. This is not\n * required by the EIP. See the note at the beginning of {ERC20}.\n *\n * NOTE: Does not update the allowance if the current allowance\n * is the maximum `uint256`.\n *\n * Requirements:\n *\n * - `from` and `to` cannot be the zero address.\n * - `from` must have a balance of at least `amount`.\n * - the caller must have allowance for ``from``'s tokens of at least\n * `amount`.\n */\n function transferFrom(\n address from,\n address to,\n uint256 amount\n ) public virtual override returns (bool) {\n address spender = _msgSender();\n _spendAllowance(from, spender, amount);\n _transfer(from, to, amount);\n return true;\n }\n\n /**\n * @dev Atomically increases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n */\n function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {\n address owner = _msgSender();\n _approve(owner, spender, allowance(owner, spender) + addedValue);\n return true;\n }\n\n /**\n * @dev Atomically decreases the allowance granted to `spender` by the caller.\n *\n * This is an alternative to {approve} that can be used as a mitigation for\n * problems described in {IERC20-approve}.\n *\n * Emits an {Approval} event indicating the updated allowance.\n *\n * Requirements:\n *\n * - `spender` cannot be the zero address.\n * - `spender` must have allowance for the caller of at least\n * `subtractedValue`.\n */\n function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {\n address owner = _msgSender();\n uint256 currentAllowance = allowance(owner, spender);\n require(currentAllowance >= subtractedValue, \"ERC20: decreased allowance below zero\");\n unchecked {\n _approve(owner, spender, currentAllowance - subtractedValue);\n }\n\n return true;\n }\n\n /**\n * @dev Moves `amount` of tokens from `from` to `to`.\n *\n * This internal function is equivalent to {transfer}, and can be used to\n * e.g. implement automatic token fees, slashing mechanisms, etc.\n *\n * Emits a {Transfer} event.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `from` must have a balance of at least `amount`.\n */\n function _transfer(\n address from,\n address to,\n uint256 amount\n ) internal virtual {\n require(from != address(0), \"ERC20: transfer from the zero address\");\n require(to != address(0), \"ERC20: transfer to the zero address\");\n\n _beforeTokenTransfer(from, to, amount);\n\n uint256 fromBalance = _balances[from];\n require(fromBalance >= amount, \"ERC20: transfer amount exceeds balance\");\n unchecked {\n _balances[from] = fromBalance - amount;\n // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by\n // decrementing then incrementing.\n _balances[to] += amount;\n }\n\n emit Transfer(from, to, amount);\n\n _afterTokenTransfer(from, to, amount);\n }\n\n /** @dev Creates `amount` tokens and assigns them to `account`, increasing\n * the total supply.\n *\n * Emits a {Transfer} event with `from` set to the zero address.\n *\n * Requirements:\n *\n * - `account` cannot be the zero address.\n */\n function _mint(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: mint to the zero address\");\n\n _beforeTokenTransfer(address(0), account, amount);\n\n _totalSupply += amount;\n unchecked {\n // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.\n _balances[account] += amount;\n }\n emit Transfer(address(0), account, amount);\n\n _afterTokenTransfer(address(0), account, amount);\n }\n\n /**\n * @dev Destroys `amount` tokens from `account`, reducing the\n * total supply.\n *\n * Emits a {Transfer} event with `to` set to the zero address.\n *\n * Requirements:\n *\n * - `account` cannot be the zero address.\n * - `account` must have at least `amount` tokens.\n */\n function _burn(address account, uint256 amount) internal virtual {\n require(account != address(0), \"ERC20: burn from the zero address\");\n\n _beforeTokenTransfer(account, address(0), amount);\n\n uint256 accountBalance = _balances[account];\n require(accountBalance >= amount, \"ERC20: burn amount exceeds balance\");\n unchecked {\n _balances[account] = accountBalance - amount;\n // Overflow not possible: amount <= accountBalance <= totalSupply.\n _totalSupply -= amount;\n }\n\n emit Transfer(account, address(0), amount);\n\n _afterTokenTransfer(account, address(0), amount);\n }\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.\n *\n * This internal function is equivalent to `approve`, and can be used to\n * e.g. set automatic allowances for certain subsystems, etc.\n *\n * Emits an {Approval} event.\n *\n * Requirements:\n *\n * - `owner` cannot be the zero address.\n * - `spender` cannot be the zero address.\n */\n function _approve(\n address owner,\n address spender,\n uint256 amount\n ) internal virtual {\n require(owner != address(0), \"ERC20: approve from the zero address\");\n require(spender != address(0), \"ERC20: approve to the zero address\");\n\n _allowances[owner][spender] = amount;\n emit Approval(owner, spender, amount);\n }\n\n /**\n * @dev Updates `owner` s allowance for `spender` based on spent `amount`.\n *\n * Does not update the allowance amount in case of infinite allowance.\n * Revert if not enough allowance is available.\n *\n * Might emit an {Approval} event.\n */\n function _spendAllowance(\n address owner,\n address spender,\n uint256 amount\n ) internal virtual {\n uint256 currentAllowance = allowance(owner, spender);\n if (currentAllowance != type(uint256).max) {\n require(currentAllowance >= amount, \"ERC20: insufficient allowance\");\n unchecked {\n _approve(owner, spender, currentAllowance - amount);\n }\n }\n }\n\n /**\n * @dev Hook that is called before any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * will be transferred to `to`.\n * - when `from` is zero, `amount` tokens will be minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens will be burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _beforeTokenTransfer(\n address from,\n address to,\n uint256 amount\n ) internal virtual {}\n\n /**\n * @dev Hook that is called after any transfer of tokens. This includes\n * minting and burning.\n *\n * Calling conditions:\n *\n * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens\n * has been transferred to `to`.\n * - when `from` is zero, `amount` tokens have been minted for `to`.\n * - when `to` is zero, `amount` of ``from``'s tokens have been burned.\n * - `from` and `to` are never both zero.\n *\n * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].\n */\n function _afterTokenTransfer(\n address from,\n address to,\n uint256 amount\n ) internal virtual {}\n}\n\n// OpenZeppelin Contracts (last updated v4.8.0) (access/Ownable2Step.sol)\n\n// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)\n\n/**\n * @dev Contract module which provides a basic access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership}.\n *\n * This module is used through inheritance. It will make available the modifier\n * `onlyOwner`, which can be applied to your functions to restrict their use to\n * the owner.\n */\nabstract contract Ownable is Context {\n address private _owner;\n\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Initializes the contract setting the deployer as the initial owner.\n */\n constructor() {\n _transferOwnership(_msgSender());\n }\n\n /**\n * @dev Throws if called by any account other than the owner.\n */\n modifier onlyOwner() {\n _checkOwner();\n _;\n }\n\n /**\n * @dev Returns the address of the current owner.\n */\n function owner() public view virtual returns (address) {\n return _owner;\n }\n\n /**\n * @dev Throws if the sender is not the owner.\n */\n function _checkOwner() internal view virtual {\n require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\n }\n\n /**\n * @dev Leaves the contract without owner. It will not be possible to call\n * `onlyOwner` functions anymore. Can only be called by the current owner.\n *\n * NOTE: Renouncing ownership will leave the contract without an owner,\n * thereby removing any functionality that is only available to the owner.\n */\n function renounceOwnership() public virtual onlyOwner {\n _transferOwnership(address(0));\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual onlyOwner {\n require(newOwner != address(0), \"Ownable: new owner is the zero address\");\n _transferOwnership(newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual {\n address oldOwner = _owner;\n _owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n}\n\n/**\n * @dev Contract module which provides access control mechanism, where\n * there is an account (an owner) that can be granted exclusive access to\n * specific functions.\n *\n * By default, the owner account will be the one that deploys the contract. This\n * can later be changed with {transferOwnership} and {acceptOwnership}.\n *\n * This module is used through inheritance. It will make available all functions\n * from parent (Ownable).\n */\nabstract contract Ownable2Step is Ownable {\n address private _pendingOwner;\n\n event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);\n\n /**\n * @dev Returns the address of the pending owner.\n */\n function pendingOwner() public view virtual returns (address) {\n return _pendingOwner;\n }\n\n /**\n * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) public virtual override onlyOwner {\n _pendingOwner = newOwner;\n emit OwnershipTransferStarted(owner(), newOwner);\n }\n\n /**\n * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.\n * Internal function without access restriction.\n */\n function _transferOwnership(address newOwner) internal virtual override {\n delete _pendingOwner;\n super._transferOwnership(newOwner);\n }\n\n /**\n * @dev The new owner accepts the ownership transfer.\n */\n function acceptOwnership() external {\n address sender = _msgSender();\n require(pendingOwner() == sender, \"Ownable2Step: caller is not the new owner\");\n _transferOwnership(sender);\n }\n}\n\n/**\n * @dev Extension of {ERC20} that adds a cap to the supply of tokens.\n */\nabstract contract ERC20Capped is ERC20, Ownable2Step {\n uint256 public cap;\n\n /**\n * @dev Total supply cap has been exceeded.\n */\n error ERC20ExceededCap();\n\n /**\n * @dev The supplied cap is not a valid cap.\n */\n error ERC20InvalidCap(uint256 cap);\n\n constructor(uint256 cap_) {\n setCap(cap_);\n }\n\n /**\n * @dev Sets the value of the `cap`.\n */\n function setCap(uint256 cap_) public onlyOwner {\n if (cap_ == 0) {\n revert ERC20InvalidCap(0);\n }\n cap = cap_;\n }\n\n /**\n * @dev See {ERC20-_mint}.\n */\n function _mint(address account, uint256 amount) internal virtual override {\n if (totalSupply() + amount > cap) {\n revert ERC20ExceededCap();\n }\n super._mint(account, amount);\n }\n}\n\nabstract contract PositionManagerDependent is IPositionManagerDependent {\n // --- Immutable variables ---\n\n address public immutable override positionManager;\n\n // --- Modifiers ---\n\n modifier onlyPositionManager() {\n if (msg.sender != positionManager) {\n revert CallerIsNotPositionManager(msg.sender);\n }\n _;\n }\n\n // --- Constructor ---\n\n constructor(address positionManager_) {\n if (positionManager_ == address(0)) {\n revert PositionManagerCannotBeZero();\n }\n positionManager = positionManager_;\n }\n}\n\ncontract ERC20Indexable is IERC20Indexable, ERC20Capped, PositionManagerDependent {\n // --- Types ---\n\n using Fixed256x18 for uint256;\n\n // --- Constants ---\n\n uint256 public constant override INDEX_PRECISION = Fixed256x18.ONE;\n\n // --- Variables ---\n\n uint256 internal storedIndex;\n\n // --- Constructor ---\n\n constructor(\n address positionManager_,\n string memory name_,\n string memory symbol_,\n uint256 cap_\n )\n ERC20(name_, symbol_)\n ERC20Capped(cap_)\n PositionManagerDependent(positionManager_)\n {\n storedIndex = INDEX_PRECISION;\n emit ERC20IndexableDeployed(positionManager_);\n }\n\n // --- Functions ---\n\n function mint(address to, uint256 amount) public virtual override onlyPositionManager {\n _mint(to, amount.divUp(storedIndex));\n }\n\n function burn(address from, uint256 amount) public virtual override onlyPositionManager {\n _burn(from, amount == type(uint256).max ? ERC20.balanceOf(from) : amount.divUp(storedIndex));\n }\n\n function setIndex(uint256 backingAmount) external override onlyPositionManager {\n uint256 supply = ERC20.totalSupply();\n uint256 newIndex = (backingAmount == 0 && supply == 0) ? INDEX_PRECISION : backingAmount.divUp(supply);\n storedIndex = newIndex;\n emit IndexUpdated(newIndex);\n }\n\n function currentIndex() public view virtual override returns (uint256) {\n return storedIndex;\n }\n\n function totalSupply() public view virtual override(IERC20, ERC20) returns (uint256) {\n return ERC20.totalSupply().mulDown(currentIndex());\n }\n\n function balanceOf(address account) public view virtual override(IERC20, ERC20) returns (uint256) {\n return ERC20.balanceOf(account).mulDown(currentIndex());\n }\n\n function transfer(address, uint256) public virtual override(IERC20, ERC20) returns (bool) {\n revert NotSupported();\n }\n\n function allowance(address, address) public view virtual override(IERC20, ERC20) returns (uint256) {\n revert NotSupported();\n }\n\n function approve(address, uint256) public virtual override(IERC20, ERC20) returns (bool) {\n revert NotSupported();\n }\n\n function transferFrom(address, address, uint256) public virtual override(IERC20, ERC20) returns (bool) {\n revert NotSupported();\n }\n\n function increaseAllowance(address, uint256) public virtual override returns (bool) {\n revert NotSupported();\n }\n\n function decreaseAllowance(address, uint256) public virtual override returns (bool) {\n revert NotSupported();\n }\n}\n\nabstract contract FeeCollector is Ownable2Step, IFeeCollector {\n // --- Variables ---\n\n address public override feeRecipient;\n\n // --- Constructor ---\n\n /// @param feeRecipient_ Address of the fee recipient to initialize contract with.\n constructor(address feeRecipient_) {\n if (feeRecipient_ == address(0)) {\n revert InvalidFeeRecipient();\n }\n\n feeRecipient = feeRecipient_;\n }\n\n // --- Functions ---\n\n function setFeeRecipient(address newFeeRecipient) external onlyOwner {\n if (newFeeRecipient == address(0)) {\n revert InvalidFeeRecipient();\n }\n\n feeRecipient = newFeeRecipient;\n emit FeeRecipientChanged(newFeeRecipient);\n }\n}\n\n// OpenZeppelin Contracts (last updated v4.8.0) (security/ReentrancyGuard.sol)\n\n/**\n * @dev Contract module that helps prevent reentrant calls to a function.\n *\n * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier\n * available, which can be applied to functions to make sure there are no nested\n * (reentrant) calls to them.\n *\n * Note that because there is a single `nonReentrant` guard, functions marked as\n * `nonReentrant` may not call one another. This can be worked around by making\n * those functions `private`, and then adding `external` `nonReentrant` entry\n * points to them.\n *\n * TIP: If you would like to learn more about reentrancy and alternative ways\n * to protect against it, check out our blog post\n * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].\n */\nabstract contract ReentrancyGuard {\n // Booleans are more expensive than uint256 or any type that takes up a full\n // word because each write operation emits an extra SLOAD to first read the\n // slot's contents, replace the bits taken up by the boolean, and then write\n // back. This is the compiler's defense against contract upgrades and\n // pointer aliasing, and it cannot be disabled.\n\n // The values being non-zero value makes deployment a bit more expensive,\n // but in exchange the refund on every call to nonReentrant will be lower in\n // amount. Since refunds are capped to a percentage of the total\n // transaction's gas, it is best to keep them low in cases like this one, to\n // increase the likelihood of the full refund coming into effect.\n uint256 private constant _NOT_ENTERED = 1;\n uint256 private constant _ENTERED = 2;\n\n uint256 private _status;\n\n constructor() {\n _status = _NOT_ENTERED;\n }\n\n /**\n * @dev Prevents a contract from calling itself, directly or indirectly.\n * Calling a `nonReentrant` function from another `nonReentrant`\n * function is not supported. It is possible to prevent this from happening\n * by making the `nonReentrant` function external, and making it call a\n * `private` function that does the actual work.\n */\n modifier nonReentrant() {\n _nonReentrantBefore();\n _;\n _nonReentrantAfter();\n }\n\n function _nonReentrantBefore() private {\n // On the first call to nonReentrant, _status will be _NOT_ENTERED\n require(_status != _ENTERED, \"ReentrancyGuard: reentrant call\");\n\n // Any calls to nonReentrant after this point will fail\n _status = _ENTERED;\n }\n\n function _nonReentrantAfter() private {\n // By storing the original value once again, a refund is triggered (see\n // https://eips.ethereum.org/EIPS/eip-2200)\n _status = _NOT_ENTERED;\n }\n}\n\n// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/extensions/draft-ERC20Permit.sol)\n\n// OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/ECDSA.sol)\n\n// OpenZeppelin Contracts (last updated v4.8.0) (utils/Strings.sol)\n\n/**\n * @dev String operations.\n */\nlibrary Strings {\n bytes16 private constant _SYMBOLS = \"0123456789abcdef\";\n uint8 private constant _ADDRESS_LENGTH = 20;\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\n */\n function toString(uint256 value) internal pure returns (string memory) {\n unchecked {\n uint256 length = Math.log10(value) + 1;\n string memory buffer = new string(length);\n uint256 ptr;\n /// @solidity memory-safe-assembly\n assembly {\n ptr := add(buffer, add(32, length))\n }\n while (true) {\n ptr--;\n /// @solidity memory-safe-assembly\n assembly {\n mstore8(ptr, byte(mod(value, 10), _SYMBOLS))\n }\n value /= 10;\n if (value == 0) break;\n }\n return buffer;\n }\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\n */\n function toHexString(uint256 value) internal pure returns (string memory) {\n unchecked {\n return toHexString(value, Math.log256(value) + 1);\n }\n }\n\n /**\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\n */\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\n bytes memory buffer = new bytes(2 * length + 2);\n buffer[0] = \"0\";\n buffer[1] = \"x\";\n for (uint256 i = 2 * length + 1; i > 1; --i) {\n buffer[i] = _SYMBOLS[value & 0xf];\n value >>= 4;\n }\n require(value == 0, \"Strings: hex length insufficient\");\n return string(buffer);\n }\n\n /**\n * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.\n */\n function toHexString(address addr) internal pure returns (string memory) {\n return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);\n }\n}\n\n/**\n * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.\n *\n * These functions can be used to verify that a message was signed by the holder\n * of the private keys of a given address.\n */\nlibrary ECDSA {\n enum RecoverError {\n NoError,\n InvalidSignature,\n InvalidSignatureLength,\n InvalidSignatureS,\n InvalidSignatureV // Deprecated in v4.8\n }\n\n function _throwError(RecoverError error) private pure {\n if (error == RecoverError.NoError) {\n return; // no error: do nothing\n } else if (error == RecoverError.InvalidSignature) {\n revert(\"ECDSA: invalid signature\");\n } else if (error == RecoverError.InvalidSignatureLength) {\n revert(\"ECDSA: invalid signature length\");\n } else if (error == RecoverError.InvalidSignatureS) {\n revert(\"ECDSA: invalid signature 's' value\");\n }\n }\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with\n * `signature` or error string. This address can then be used for verification purposes.\n *\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {toEthSignedMessageHash} on it.\n *\n * Documentation for signature generation:\n * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]\n * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]\n *\n * _Available since v4.3._\n */\n function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {\n if (signature.length == 65) {\n bytes32 r;\n bytes32 s;\n uint8 v;\n // ecrecover takes the signature parameters, and the only way to get them\n // currently is to use assembly.\n /// @solidity memory-safe-assembly\n assembly {\n r := mload(add(signature, 0x20))\n s := mload(add(signature, 0x40))\n v := byte(0, mload(add(signature, 0x60)))\n }\n return tryRecover(hash, v, r, s);\n } else {\n return (address(0), RecoverError.InvalidSignatureLength);\n }\n }\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with\n * `signature`. This address can then be used for verification purposes.\n *\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {toEthSignedMessageHash} on it.\n */\n function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, signature);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.\n *\n * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]\n *\n * _Available since v4.3._\n */\n function tryRecover(\n bytes32 hash,\n bytes32 r,\n bytes32 vs\n ) internal pure returns (address, RecoverError) {\n bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);\n uint8 v = uint8((uint256(vs) >> 255) + 27);\n return tryRecover(hash, v, r, s);\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.\n *\n * _Available since v4.2._\n */\n function recover(\n bytes32 hash,\n bytes32 r,\n bytes32 vs\n ) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, r, vs);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `v`,\n * `r` and `s` signature fields separately.\n *\n * _Available since v4.3._\n */\n function tryRecover(\n bytes32 hash,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal pure returns (address, RecoverError) {\n // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature\n // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines\n // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most\n // signatures from current libraries generate a unique signature with an s-value in the lower half order.\n //\n // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value\n // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or\n // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept\n // these malleable signatures as well.\n if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {\n return (address(0), RecoverError.InvalidSignatureS);\n }\n\n // If the signature is valid (and not malleable), return the signer address\n address signer = ecrecover(hash, v, r, s);\n if (signer == address(0)) {\n return (address(0), RecoverError.InvalidSignature);\n }\n\n return (signer, RecoverError.NoError);\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `v`,\n * `r` and `s` signature fields separately.\n */\n function recover(\n bytes32 hash,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, v, r, s);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Returns an Ethereum Signed Message, created from a `hash`. This\n * produces hash corresponding to the one signed with the\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\n * JSON-RPC method as part of EIP-191.\n *\n * See {recover}.\n */\n function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {\n // 32 is the length in bytes of hash,\n // enforced by the type signature above\n return keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n32\", hash));\n }\n\n /**\n * @dev Returns an Ethereum Signed Message, created from `s`. This\n * produces hash corresponding to the one signed with the\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\n * JSON-RPC method as part of EIP-191.\n *\n * See {recover}.\n */\n function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {\n return keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n\", Strings.toString(s.length), s));\n }\n\n /**\n * @dev Returns an Ethereum Signed Typed Data, created from a\n * `domainSeparator` and a `structHash`. This produces hash corresponding\n * to the one signed with the\n * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]\n * JSON-RPC method as part of EIP-712.\n *\n * See {recover}.\n */\n function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {\n return keccak256(abi.encodePacked(\"\\x19\\x01\", domainSeparator, structHash));\n }\n}\n\n// OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/EIP712.sol)\n\n/**\n * @dev https://eips.ethereum.org/EIPS/eip-712[EIP 712] is a standard for hashing and signing of typed structured data.\n *\n * The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible,\n * thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding\n * they need in their contracts using a combination of `abi.encode` and `keccak256`.\n *\n * This contract implements the EIP 712 domain separator ({_domainSeparatorV4}) that is used as part of the encoding\n * scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA\n * ({_hashTypedDataV4}).\n *\n * The implementation of the domain separator was designed to be as efficient as possible while still properly updating\n * the chain id to protect against replay attacks on an eventual fork of the chain.\n *\n * NOTE: This contract implements the version of the encoding known as \"v4\", as implemented by the JSON RPC method\n * https://docs.metamask.io/guide/signing-data.html[`eth_signTypedDataV4` in MetaMask].\n *\n * _Available since v3.4._\n */\nabstract contract EIP712 {\n /* solhint-disable var-name-mixedcase */\n // Cache the domain separator as an immutable value, but also store the chain id that it corresponds to, in order to\n // invalidate the cached domain separator if the chain id changes.\n bytes32 private immutable _CACHED_DOMAIN_SEPARATOR;\n uint256 private immutable _CACHED_CHAIN_ID;\n address private immutable _CACHED_THIS;\n\n bytes32 private immutable _HASHED_NAME;\n bytes32 private immutable _HASHED_VERSION;\n bytes32 private immutable _TYPE_HASH;\n\n /* solhint-enable var-name-mixedcase */\n\n /**\n * @dev Initializes the domain separator and parameter caches.\n *\n * The meaning of `name` and `version` is specified in\n * https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator[EIP 712]:\n *\n * - `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol.\n * - `version`: the current major version of the signing domain.\n *\n * NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart\n * contract upgrade].\n */\n constructor(string memory name, string memory version) {\n bytes32 hashedName = keccak256(bytes(name));\n bytes32 hashedVersion = keccak256(bytes(version));\n bytes32 typeHash = keccak256(\n \"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)\"\n );\n _HASHED_NAME = hashedName;\n _HASHED_VERSION = hashedVersion;\n _CACHED_CHAIN_ID = block.chainid;\n _CACHED_DOMAIN_SEPARATOR = _buildDomainSeparator(typeHash, hashedName, hashedVersion);\n _CACHED_THIS = address(this);\n _TYPE_HASH = typeHash;\n }\n\n /**\n * @dev Returns the domain separator for the current chain.\n */\n function _domainSeparatorV4() internal view returns (bytes32) {\n if (address(this) == _CACHED_THIS && block.chainid == _CACHED_CHAIN_ID) {\n return _CACHED_DOMAIN_SEPARATOR;\n } else {\n return _buildDomainSeparator(_TYPE_HASH, _HASHED_NAME, _HASHED_VERSION);\n }\n }\n\n function _buildDomainSeparator(\n bytes32 typeHash,\n bytes32 nameHash,\n bytes32 versionHash\n ) private view returns (bytes32) {\n return keccak256(abi.encode(typeHash, nameHash, versionHash, block.chainid, address(this)));\n }\n\n /**\n * @dev Given an already https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct[hashed struct], this\n * function returns the hash of the fully encoded EIP712 message for this domain.\n *\n * This hash can be used together with {ECDSA-recover} to obtain the signer of a message. For example:\n *\n * ```solidity\n * bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(\n * keccak256(\"Mail(address to,string contents)\"),\n * mailTo,\n * keccak256(bytes(mailContents))\n * )));\n * address signer = ECDSA.recover(digest, signature);\n * ```\n */\n function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {\n return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash);\n }\n}\n\n// OpenZeppelin Contracts v4.4.1 (utils/Counters.sol)\n\n/**\n * @title Counters\n * @author Matt Condon (@shrugs)\n * @dev Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number\n * of elements in a mapping, issuing ERC721 ids, or counting request ids.\n *\n * Include with `using Counters for Counters.Counter;`\n */\nlibrary Counters {\n struct Counter {\n // This variable should never be directly accessed by users of the library: interactions must be restricted to\n // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add\n // this feature: see https://github.com/ethereum/solidity/issues/4637\n uint256 _value; // default: 0\n }\n\n function current(Counter storage counter) internal view returns (uint256) {\n return counter._value;\n }\n\n function increment(Counter storage counter) internal {\n unchecked {\n counter._value += 1;\n }\n }\n\n function decrement(Counter storage counter) internal {\n uint256 value = counter._value;\n require(value > 0, \"Counter: decrement overflow\");\n unchecked {\n counter._value = value - 1;\n }\n }\n\n function reset(Counter storage counter) internal {\n counter._value = 0;\n }\n}\n\n/**\n * @dev Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in\n * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].\n *\n * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by\n * presenting a message signed by the account. By not relying on `{IERC20-approve}`, the token holder account doesn't\n * need to send a transaction, and thus is not required to hold Ether at all.\n *\n * _Available since v3.4._\n */\nabstract contract ERC20Permit is ERC20, IERC20Permit, EIP712 {\n using Counters for Counters.Counter;\n\n mapping(address => Counters.Counter) private _nonces;\n\n // solhint-disable-next-line var-name-mixedcase\n bytes32 private constant _PERMIT_TYPEHASH =\n keccak256(\"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)\");\n /**\n * @dev In previous versions `_PERMIT_TYPEHASH` was declared as `immutable`.\n * However, to ensure consistency with the upgradeable transpiler, we will continue\n * to reserve a slot.\n * @custom:oz-renamed-from _PERMIT_TYPEHASH\n */\n // solhint-disable-next-line var-name-mixedcase\n bytes32 private _PERMIT_TYPEHASH_DEPRECATED_SLOT;\n\n /**\n * @dev Initializes the {EIP712} domain separator using the `name` parameter, and setting `version` to `\"1\"`.\n *\n * It's a good idea to use the same `name` that is defined as the ERC20 token name.\n */\n constructor(string memory name) EIP712(name, \"1\") {}\n\n /**\n * @dev See {IERC20Permit-permit}.\n */\n function permit(\n address owner,\n address spender,\n uint256 value,\n uint256 deadline,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) public virtual override {\n require(block.timestamp <= deadline, \"ERC20Permit: expired deadline\");\n\n bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));\n\n bytes32 hash = _hashTypedDataV4(structHash);\n\n address signer = ECDSA.recover(hash, v, r, s);\n require(signer == owner, \"ERC20Permit: invalid signature\");\n\n _approve(owner, spender, value);\n }\n\n /**\n * @dev See {IERC20Permit-nonces}.\n */\n function nonces(address owner) public view virtual override returns (uint256) {\n return _nonces[owner].current();\n }\n\n /**\n * @dev See {IERC20Permit-DOMAIN_SEPARATOR}.\n */\n // solhint-disable-next-line func-name-mixedcase\n function DOMAIN_SEPARATOR() external view override returns (bytes32) {\n return _domainSeparatorV4();\n }\n\n /**\n * @dev \"Consume a nonce\": return the current value and increment.\n *\n * _Available since v4.1._\n */\n function _useNonce(address owner) internal virtual returns (uint256 current) {\n Counters.Counter storage nonce = _nonces[owner];\n current = nonce.current();\n nonce.increment();\n }\n}\n\n// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/extensions/ERC20FlashMint.sol)\n\n/**\n * @dev Implementation of the ERC3156 Flash loans extension, as defined in\n * https://eips.ethereum.org/EIPS/eip-3156[ERC-3156].\n *\n * Adds the {flashLoan} method, which provides flash loan support at the token\n * level. By default there is no fee, but this can be changed by overriding {flashFee}.\n *\n * _Available since v4.1._\n */\nabstract contract ERC20FlashMint is ERC20, IERC3156FlashLender {\n bytes32 private constant _RETURN_VALUE = keccak256(\"ERC3156FlashBorrower.onFlashLoan\");\n\n /**\n * @dev Returns the maximum amount of tokens available for loan.\n * @param token The address of the token that is requested.\n * @return The amount of token that can be loaned.\n */\n function maxFlashLoan(address token) public view virtual override returns (uint256) {\n return token == address(this) ? type(uint256).max - ERC20.totalSupply() : 0;\n }\n\n /**\n * @dev Returns the fee applied when doing flash loans. This function calls\n * the {_flashFee} function which returns the fee applied when doing flash\n * loans.\n * @param token The token to be flash loaned.\n * @param amount The amount of tokens to be loaned.\n * @return The fees applied to the corresponding flash loan.\n */\n function flashFee(address token, uint256 amount) public view virtual override returns (uint256) {\n require(token == address(this), \"ERC20FlashMint: wrong token\");\n return _flashFee(token, amount);\n }\n\n /**\n * @dev Returns the fee applied when doing flash loans. By default this\n * implementation has 0 fees. This function can be overloaded to make\n * the flash loan mechanism deflationary.\n * @param token The token to be flash loaned.\n * @param amount The amount of tokens to be loaned.\n * @return The fees applied to the corresponding flash loan.\n */\n function _flashFee(address token, uint256 amount) internal view virtual returns (uint256) {\n // silence warning about unused variable without the addition of bytecode.\n token;\n amount;\n return 0;\n }\n\n /**\n * @dev Returns the receiver address of the flash fee. By default this\n * implementation returns the address(0) which means the fee amount will be burnt.\n * This function can be overloaded to change the fee receiver.\n * @return The address for which the flash fee will be sent to.\n */\n function _flashFeeReceiver() internal view virtual returns (address) {\n return address(0);\n }\n\n /**\n * @dev Performs a flash loan. New tokens are minted and sent to the\n * `receiver`, who is required to implement the {IERC3156FlashBorrower}\n * interface. By the end of the flash loan, the receiver is expected to own\n * amount + fee tokens and have them approved back to the token contract itself so\n * they can be burned.\n * @param receiver The receiver of the flash loan. Should implement the\n * {IERC3156FlashBorrower-onFlashLoan} interface.\n * @param token The token to be flash loaned. Only `address(this)` is\n * supported.\n * @param amount The amount of tokens to be loaned.\n * @param data An arbitrary datafield that is passed to the receiver.\n * @return `true` if the flash loan was successful.\n */\n // This function can reenter, but it doesn't pose a risk because it always preserves the property that the amount\n // minted at the beginning is always recovered and burned at the end, or else the entire function will revert.\n // slither-disable-next-line reentrancy-no-eth\n function flashLoan(\n IERC3156FlashBorrower receiver,\n address token,\n uint256 amount,\n bytes calldata data\n ) public virtual override returns (bool) {\n require(amount <= maxFlashLoan(token), \"ERC20FlashMint: amount exceeds maxFlashLoan\");\n uint256 fee = flashFee(token, amount);\n _mint(address(receiver), amount);\n require(\n receiver.onFlashLoan(msg.sender, token, amount, fee, data) == _RETURN_VALUE,\n \"ERC20FlashMint: invalid return value\"\n );\n address flashFeeReceiver = _flashFeeReceiver();\n _spendAllowance(address(receiver), address(this), amount + fee);\n if (fee == 0 || flashFeeReceiver == address(0)) {\n _burn(address(receiver), amount + fee);\n } else {\n _burn(address(receiver), amount);\n _transfer(address(receiver), flashFeeReceiver, fee);\n }\n return true;\n }\n}\n\ncontract RToken is ReentrancyGuard, ERC20Permit, ERC20FlashMint, PositionManagerDependent, FeeCollector, IRToken {\n // --- Constants ---\n\n uint256 public constant override PERCENTAGE_BASE = 10_000;\n uint256 public constant override MAX_FLASH_MINT_FEE_PERCENTAGE = 500;\n\n // --- Variables ---\n\n uint256 public override flashMintFeePercentage;\n\n // --- Constructor ---\n\n /// @dev Deploys new R token. Sets flash mint fee percentage to 0. Transfers ownership to @param feeRecipient_.\n /// @param positionManager_ Address of the PositionManager contract that is authorized to mint and burn new tokens.\n /// @param feeRecipient_ Address of flash mint fee recipient.\n constructor(\n address positionManager_,\n address feeRecipient_\n )\n ERC20Permit(\"R Stablecoin\")\n ERC20(\"R Stablecoin\", \"R\")\n PositionManagerDependent(positionManager_)\n FeeCollector(feeRecipient_)\n {\n setFlashMintFeePercentage(PERCENTAGE_BASE / 200); // 0.5%\n\n transferOwnership(feeRecipient_);\n\n emit RDeployed(positionManager_, feeRecipient_);\n }\n\n // --- Functions ---\n\n function mint(address to, uint256 amount) external override onlyPositionManager {\n _mint(to, amount);\n }\n\n function burn(address from, uint256 amount) external override onlyPositionManager {\n _burn(from, amount);\n }\n\n function setFlashMintFeePercentage(uint256 feePercentage) public override onlyOwner {\n if (feePercentage > MAX_FLASH_MINT_FEE_PERCENTAGE) {\n revert FlashFeePercentageTooBig(feePercentage);\n }\n\n flashMintFeePercentage = feePercentage;\n emit FlashMintFeePercentageChanged(flashMintFeePercentage);\n }\n\n function flashLoan(\n IERC3156FlashBorrower receiver,\n address token,\n uint256 amount,\n bytes calldata data\n )\n public\n override(ERC20FlashMint, IERC3156FlashLender)\n nonReentrant\n returns (bool)\n {\n return super.flashLoan(receiver, token, amount, data);\n }\n\n /// @dev Inherited from ERC20FlashMint. Defines maximum size of the flash mint.\n /// @param token Token to be flash minted. Returns 0 amount in case of token != address(this).\n function maxFlashLoan(address token)\n public\n view\n virtual\n override(ERC20FlashMint, IERC3156FlashLender)\n returns (uint256)\n {\n return token == address(this) ? Math.min(totalSupply() / 10, ERC20FlashMint.maxFlashLoan(address(this))) : 0;\n }\n\n /// @dev Inherited from ERC20FlashMint. Defines flash mint fee for the flash mint of @param amount tokens.\n /// @param token Token to be flash minted. Returns 0 fee in case of token != address(this).\n /// @param amount Size of the flash mint.\n function _flashFee(address token, uint256 amount) internal view virtual override returns (uint256) {\n return token == address(this) ? amount * flashMintFeePercentage / PERCENTAGE_BASE : 0;\n }\n\n /// @dev Inherited from ERC20FlashMint. Defines flash mint fee receiver.\n /// @return Address that will receive flash mint fees.\n function _flashFeeReceiver() internal view virtual override returns (address) {\n return feeRecipient;\n }\n}\n\n/// @dev Implementation of Position Manager. Current implementation does not support rebasing tokens as collateral.\ncontract PositionManager is FeeCollector, IPositionManager {\n // --- Types ---\n\n using SafeERC20 for IERC20;\n using Fixed256x18 for uint256;\n\n // --- Constants ---\n\n uint256 public constant override MINUTE_DECAY_FACTOR = 999_037_758_833_783_000;\n\n uint256 public constant override MAX_BORROWING_SPREAD = MathUtils._100_PERCENT / 100; // 1%\n uint256 public constant override MAX_BORROWING_RATE = MathUtils._100_PERCENT / 100 * 5; // 5%\n\n uint256 public constant override BETA = 2;\n\n // --- Immutables ---\n\n IRToken public immutable override rToken;\n\n // --- Variables ---\n\n mapping(address position => IERC20 collateralToken) public override collateralTokenForPosition;\n\n mapping(address position => mapping(address delegate => bool isWhitelisted)) public override isDelegateWhitelisted;\n\n mapping(IERC20 collateralToken => CollateralTokenInfo collateralTokenInfo) public override collateralInfo;\n\n // --- Modifiers ---\n\n /// @dev Checks if the collateral token has been added to the position manager, or reverts otherwise.\n /// @param collateralToken The collateral token to check.\n modifier collateralTokenExists(IERC20 collateralToken) {\n if (address(collateralInfo[collateralToken].collateralToken) == address(0)) {\n revert CollateralTokenNotAdded();\n }\n _;\n }\n\n /// @dev Checks if the collateral token has enabled, or reverts otherwise. When the condition is false, the check\n /// is skipped.\n /// @param collateralToken The collateral token to check.\n /// @param condition If true, the check will be performed.\n modifier onlyEnabledCollateralTokenWhen(IERC20 collateralToken, bool condition) {\n if (condition && !collateralInfo[collateralToken].isEnabled) {\n revert CollateralTokenDisabled();\n }\n _;\n }\n\n /// @dev Checks if the borrower has a position with the collateral token or doesn't have a position at all, or\n /// reverts otherwise.\n /// @param position The borrower to check.\n /// @param collateralToken The collateral token to check.\n modifier onlyDepositedCollateralTokenOrNew(address position, IERC20 collateralToken) {\n if (\n collateralTokenForPosition[position] != IERC20(address(0))\n && collateralTokenForPosition[position] != collateralToken\n ) {\n revert PositionCollateralTokenMismatch();\n }\n _;\n }\n\n /// @dev Checks if the max fee percentage is between the borrowing spread and 100%, or reverts otherwise. When the\n /// condition is false, the check is skipped.\n /// @param maxFeePercentage The max fee percentage to check.\n /// @param condition If true, the check will be performed.\n modifier validMaxFeePercentageWhen(uint256 maxFeePercentage, bool condition) {\n if (condition && maxFeePercentage > MathUtils._100_PERCENT) {\n revert InvalidMaxFeePercentage();\n }\n _;\n }\n\n // --- Constructor ---\n\n /// @dev Initializes the position manager.\n constructor(address rToken_) FeeCollector(msg.sender) {\n rToken = rToken_ == address(0) ? new RToken(address(this), msg.sender) : IRToken(rToken_);\n emit PositionManagerDeployed(rToken, msg.sender);\n }\n\n // --- External functions ---\n\n function managePosition(\n IERC20 collateralToken,\n address position,\n uint256 collateralChange,\n bool isCollateralIncrease,\n uint256 debtChange,\n bool isDebtIncrease,\n uint256 maxFeePercentage,\n ERC20PermitSignature calldata permitSignature\n )\n public\n virtual\n override\n collateralTokenExists(collateralToken)\n validMaxFeePercentageWhen(maxFeePercentage, isDebtIncrease)\n onlyDepositedCollateralTokenOrNew(position, collateralToken)\n onlyEnabledCollateralTokenWhen(collateralToken, isDebtIncrease && debtChange > 0)\n returns (uint256 actualCollateralChange, uint256 actualDebtChange)\n {\n if (position != msg.sender && !isDelegateWhitelisted[position][msg.sender]) {\n revert DelegateNotWhitelisted();\n }\n if (collateralChange == 0 && debtChange == 0) {\n revert NoCollateralOrDebtChange();\n }\n if (address(permitSignature.token) == address(collateralToken)) {\n PermitHelper.applyPermit(permitSignature, msg.sender, address(this));\n }\n\n CollateralTokenInfo storage collateralTokenInfo = collateralInfo[collateralToken];\n IERC20Indexable raftCollateralToken = collateralTokenInfo.collateralToken;\n IERC20Indexable raftDebtToken = collateralTokenInfo.debtToken;\n\n uint256 debtBefore = raftDebtToken.balanceOf(position);\n if (!isDebtIncrease && (debtChange == type(uint256).max || (debtBefore != 0 && debtChange == debtBefore))) {\n if (collateralChange != 0 || isCollateralIncrease) {\n revert WrongCollateralParamsForFullRepayment();\n }\n collateralChange = raftCollateralToken.balanceOf(position);\n debtChange = debtBefore;\n }\n\n _adjustDebt(position, collateralToken, raftDebtToken, debtChange, isDebtIncrease, maxFeePercentage);\n _adjustCollateral(collateralToken, raftCollateralToken, position, collateralChange, isCollateralIncrease);\n\n uint256 positionDebt = raftDebtToken.balanceOf(position);\n uint256 positionCollateral = raftCollateralToken.balanceOf(position);\n\n if (positionDebt == 0) {\n if (positionCollateral != 0) {\n revert InvalidPosition();\n }\n // position was closed, remove it\n _closePosition(raftCollateralToken, raftDebtToken, position, false);\n } else {\n _checkValidPosition(collateralToken, positionDebt, positionCollateral);\n\n if (debtBefore == 0) {\n collateralTokenForPosition[position] = collateralToken;\n emit PositionCreated(position, collateralToken);\n }\n }\n return (collateralChange, debtChange);\n }\n\n function liquidate(address position) external override {\n IERC20 collateralToken = collateralTokenForPosition[position];\n CollateralTokenInfo storage collateralTokenInfo = collateralInfo[collateralToken];\n IERC20Indexable raftCollateralToken = collateralTokenInfo.collateralToken;\n IERC20Indexable raftDebtToken = collateralTokenInfo.debtToken;\n ISplitLiquidationCollateral splitLiquidation = collateralTokenInfo.splitLiquidation;\n\n if (address(collateralToken) == address(0)) {\n revert NothingToLiquidate();\n }\n (uint256 price,) = collateralTokenInfo.priceFeed.fetchPrice();\n uint256 entireCollateral = raftCollateralToken.balanceOf(position);\n uint256 entireDebt = raftDebtToken.balanceOf(position);\n uint256 icr = MathUtils._computeCR(entireCollateral, entireDebt, price);\n\n if (icr >= splitLiquidation.MCR()) {\n revert NothingToLiquidate();\n }\n\n uint256 totalDebt = raftDebtToken.totalSupply();\n if (entireDebt == totalDebt) {\n revert CannotLiquidateLastPosition();\n }\n bool isRedistribution = icr <= MathUtils._100_PERCENT;\n\n // prettier: ignore\n (uint256 collateralLiquidationFee, uint256 collateralToSendToLiquidator) =\n splitLiquidation.split(entireCollateral, entireDebt, price, isRedistribution);\n\n if (!isRedistribution) {\n _burnRTokens(msg.sender, entireDebt);\n totalDebt -= entireDebt;\n\n // Collateral is sent to protocol as a fee only in case of liquidation\n collateralToken.safeTransfer(feeRecipient, collateralLiquidationFee);\n }\n\n collateralToken.safeTransfer(msg.sender, collateralToSendToLiquidator);\n\n _closePosition(raftCollateralToken, raftDebtToken, position, true);\n\n _updateDebtAndCollateralIndex(collateralToken, raftCollateralToken, raftDebtToken, totalDebt);\n\n emit Liquidation(\n msg.sender,\n position,\n collateralToken,\n entireDebt,\n entireCollateral,\n collateralToSendToLiquidator,\n collateralLiquidationFee,\n isRedistribution\n );\n }\n\n function redeemCollateral(\n IERC20 collateralToken,\n uint256 debtAmount,\n uint256 maxFeePercentage\n )\n public\n virtual\n override\n {\n if (maxFeePercentage > MathUtils._100_PERCENT) {\n revert MaxFeePercentageOutOfRange();\n }\n if (debtAmount == 0) {\n revert AmountIsZero();\n }\n IERC20Indexable raftDebtToken = collateralInfo[collateralToken].debtToken;\n\n uint256 newTotalDebt = raftDebtToken.totalSupply() - debtAmount;\n uint256 lowTotalDebt = collateralInfo[collateralToken].splitLiquidation.LOW_TOTAL_DEBT();\n if (newTotalDebt < lowTotalDebt) {\n revert TotalDebtCannotBeLowerThanMinDebt(collateralToken, newTotalDebt);\n }\n\n (uint256 price, uint256 deviation) = collateralInfo[collateralToken].priceFeed.fetchPrice();\n uint256 collateralToRedeem = debtAmount.divDown(price);\n uint256 totalCollateral = collateralToken.balanceOf(address(this));\n if (\n totalCollateral - collateralToRedeem == 0\n || totalCollateral - collateralToRedeem < lowTotalDebt.divDown(price)\n ) {\n revert TotalCollateralCannotBeLowerThanMinCollateral(\n collateralToken, totalCollateral - collateralToRedeem, lowTotalDebt.divDown(price)\n );\n }\n\n // Decay the baseRate due to time passed, and then increase it according to the size of this redemption.\n // Use the saved total R supply value, from before it was reduced by the redemption.\n _updateBaseRateFromRedemption(collateralToken, collateralToRedeem, price, rToken.totalSupply());\n\n // Calculate the redemption fee\n uint256 redemptionFee = getRedemptionFee(collateralToken, collateralToRedeem, deviation);\n uint256 rebate = redemptionFee.mulDown(collateralInfo[collateralToken].redemptionRebate);\n\n _checkValidFee(redemptionFee, collateralToRedeem, maxFeePercentage);\n\n // Send the redemption fee to the recipient\n collateralToken.safeTransfer(feeRecipient, redemptionFee - rebate);\n\n // Burn the total R that is cancelled with debt, and send the redeemed collateral to msg.sender\n _burnRTokens(msg.sender, debtAmount);\n\n // Send collateral to account\n collateralToken.safeTransfer(msg.sender, collateralToRedeem - redemptionFee);\n\n _updateDebtAndCollateralIndex(\n collateralToken, collateralInfo[collateralToken].collateralToken, raftDebtToken, newTotalDebt\n );\n\n emit Redemption(msg.sender, debtAmount, collateralToRedeem, redemptionFee, rebate);\n }\n\n function whitelistDelegate(address delegate, bool whitelisted) external override {\n if (delegate == address(0)) {\n revert InvalidDelegateAddress();\n }\n isDelegateWhitelisted[msg.sender][delegate] = whitelisted;\n\n emit DelegateWhitelisted(msg.sender, delegate, whitelisted);\n }\n\n function setBorrowingSpread(IERC20 collateralToken, uint256 newBorrowingSpread) external override onlyOwner {\n if (newBorrowingSpread > MAX_BORROWING_SPREAD) {\n revert BorrowingSpreadExceedsMaximum();\n }\n collateralInfo[collateralToken].borrowingSpread = newBorrowingSpread;\n emit BorrowingSpreadUpdated(newBorrowingSpread);\n }\n\n function setRedemptionRebate(IERC20 collateralToken, uint256 newRedemptionRebate) public override onlyOwner {\n if (newRedemptionRebate > MathUtils._100_PERCENT) {\n revert RedemptionRebateExceedsMaximum();\n }\n collateralInfo[collateralToken].redemptionRebate = newRedemptionRebate;\n emit RedemptionRebateUpdated(newRedemptionRebate);\n }\n\n function getRedemptionFeeWithDecay(\n IERC20 collateralToken,\n uint256 collateralAmount\n )\n external\n view\n override\n returns (uint256 redemptionFee)\n {\n redemptionFee = getRedemptionRateWithDecay(collateralToken).mulDown(collateralAmount);\n if (redemptionFee >= collateralAmount) {\n revert FeeEatsUpAllReturnedCollateral();\n }\n }\n\n // --- Public functions ---\n\n function addCollateralToken(\n IERC20 collateralToken,\n IPriceFeed priceFeed,\n ISplitLiquidationCollateral newSplitLiquidationCollateral\n )\n public\n virtual\n override\n {\n addCollateralToken(\n collateralToken,\n priceFeed,\n newSplitLiquidationCollateral,\n new ERC20Indexable(\n address(this),\n string(bytes.concat(\"Raft \", bytes(IERC20Metadata(address(collateralToken)).name()), \" collateral\")),\n string(bytes.concat(\"r\", bytes(IERC20Metadata(address(collateralToken)).symbol()), \"-c\")),\n type(uint256).max\n ),\n new ERC20Indexable(\n address(this),\n string(bytes.concat(\"Raft \", bytes(IERC20Metadata(address(collateralToken)).name()), \" debt\")),\n string(bytes.concat(\"r\", bytes(IERC20Metadata(address(collateralToken)).symbol()), \"-d\")),\n type(uint256).max\n )\n );\n }\n\n function addCollateralToken(\n IERC20 collateralToken,\n IPriceFeed priceFeed,\n ISplitLiquidationCollateral newSplitLiquidationCollateral,\n IERC20Indexable raftCollateralToken_,\n IERC20Indexable raftDebtToken_\n )\n public\n override\n onlyOwner\n {\n if (address(collateralToken) == address(0)) {\n revert CollateralTokenAddressCannotBeZero();\n }\n if (address(priceFeed) == address(0)) {\n revert PriceFeedAddressCannotBeZero();\n }\n if (address(collateralInfo[collateralToken].collateralToken) != address(0)) {\n revert CollateralTokenAlreadyAdded();\n }\n\n CollateralTokenInfo memory raftCollateralTokenInfo;\n raftCollateralTokenInfo.collateralToken = raftCollateralToken_;\n raftCollateralTokenInfo.debtToken = raftDebtToken_;\n raftCollateralTokenInfo.isEnabled = true;\n raftCollateralTokenInfo.priceFeed = priceFeed;\n\n collateralInfo[collateralToken] = raftCollateralTokenInfo;\n\n setRedemptionSpread(collateralToken, MathUtils._100_PERCENT);\n setRedemptionRebate(collateralToken, MathUtils._100_PERCENT);\n\n setSplitLiquidationCollateral(collateralToken, newSplitLiquidationCollateral);\n\n emit CollateralTokenAdded(\n collateralToken, raftCollateralTokenInfo.collateralToken, raftCollateralTokenInfo.debtToken, priceFeed\n );\n }\n\n function setCollateralEnabled(\n IERC20 collateralToken,\n bool isEnabled\n )\n public\n override\n onlyOwner\n collateralTokenExists(collateralToken)\n {\n bool previousIsEnabled = collateralInfo[collateralToken].isEnabled;\n collateralInfo[collateralToken].isEnabled = isEnabled;\n\n if (previousIsEnabled != isEnabled) {\n emit CollateralTokenModified(collateralToken, isEnabled);\n }\n }\n\n function setSplitLiquidationCollateral(\n IERC20 collateralToken,\n ISplitLiquidationCollateral newSplitLiquidationCollateral\n )\n public\n override\n onlyOwner\n {\n if (address(newSplitLiquidationCollateral) == address(0)) {\n revert SplitLiquidationCollateralCannotBeZero();\n }\n collateralInfo[collateralToken].splitLiquidation = newSplitLiquidationCollateral;\n emit SplitLiquidationCollateralChanged(collateralToken, newSplitLiquidationCollateral);\n }\n\n function setRedemptionSpread(IERC20 collateralToken, uint256 newRedemptionSpread) public override onlyOwner {\n if (newRedemptionSpread > MathUtils._100_PERCENT) {\n revert RedemptionSpreadOutOfRange();\n }\n collateralInfo[collateralToken].redemptionSpread = newRedemptionSpread;\n emit RedemptionSpreadUpdated(collateralToken, newRedemptionSpread);\n }\n\n function getRedemptionRateWithDecay(IERC20 collateralToken) public view override returns (uint256) {\n return _calcRedemptionRate(collateralToken, _calcDecayedBaseRate(collateralToken));\n }\n\n function raftCollateralToken(IERC20 collateralToken) external view override returns (IERC20Indexable) {\n return collateralInfo[collateralToken].collateralToken;\n }\n\n function raftDebtToken(IERC20 collateralToken) external view override returns (IERC20Indexable) {\n return collateralInfo[collateralToken].debtToken;\n }\n\n function priceFeed(IERC20 collateralToken) external view override returns (IPriceFeed) {\n return collateralInfo[collateralToken].priceFeed;\n }\n\n function splitLiquidationCollateral(IERC20 collateralToken) external view returns (ISplitLiquidationCollateral) {\n return collateralInfo[collateralToken].splitLiquidation;\n }\n\n function collateralEnabled(IERC20 collateralToken) external view override returns (bool) {\n return collateralInfo[collateralToken].isEnabled;\n }\n\n function lastFeeOperationTime(IERC20 collateralToken) external view override returns (uint256) {\n return collateralInfo[collateralToken].lastFeeOperationTime;\n }\n\n function borrowingSpread(IERC20 collateralToken) external view override returns (uint256) {\n return collateralInfo[collateralToken].borrowingSpread;\n }\n\n function baseRate(IERC20 collateralToken) external view override returns (uint256) {\n return collateralInfo[collateralToken].baseRate;\n }\n\n function redemptionSpread(IERC20 collateralToken) external view override returns (uint256) {\n return collateralInfo[collateralToken].redemptionSpread;\n }\n\n function redemptionRebate(IERC20 collateralToken) external view override returns (uint256) {\n return collateralInfo[collateralToken].redemptionRebate;\n }\n\n function getRedemptionRate(IERC20 collateralToken) public view override returns (uint256) {\n return _calcRedemptionRate(collateralToken, collateralInfo[collateralToken].baseRate);\n }\n\n function getRedemptionFee(\n IERC20 collateralToken,\n uint256 collateralAmount,\n uint256 priceDeviation\n )\n public\n view\n override\n returns (uint256)\n {\n return Math.min(getRedemptionRate(collateralToken) + priceDeviation, MathUtils._100_PERCENT).mulDown(\n collateralAmount\n );\n }\n\n function getBorrowingRate(IERC20 collateralToken) public view override returns (uint256) {\n return _calcBorrowingRate(collateralToken, collateralInfo[collateralToken].baseRate);\n }\n\n function getBorrowingRateWithDecay(IERC20 collateralToken) public view override returns (uint256) {\n return _calcBorrowingRate(collateralToken, _calcDecayedBaseRate(collateralToken));\n }\n\n function getBorrowingFee(IERC20 collateralToken, uint256 debtAmount) public view override returns (uint256) {\n return getBorrowingRate(collateralToken).mulDown(debtAmount);\n }\n\n // --- Helper functions ---\n\n /// @dev Adjusts the debt of a given borrower by burning or minting the corresponding amount of R and the Raft\n /// debt token. If the debt is being increased, the borrowing fee is also triggered.\n /// @param position The address of the borrower.\n /// @param debtChange The amount of R to add or remove. Must be positive.\n /// @param isDebtIncrease True if the debt is being increased, false otherwise.\n /// @param maxFeePercentage The maximum fee percentage.\n function _adjustDebt(\n address position,\n IERC20 collateralToken,\n IERC20Indexable raftDebtToken,\n uint256 debtChange,\n bool isDebtIncrease,\n uint256 maxFeePercentage\n )\n internal\n {\n if (debtChange == 0) {\n return;\n }\n\n if (isDebtIncrease) {\n uint256 totalDebtChange =\n debtChange + _triggerBorrowingFee(collateralToken, position, debtChange, maxFeePercentage);\n raftDebtToken.mint(position, totalDebtChange);\n _mintRTokens(msg.sender, debtChange);\n } else {\n raftDebtToken.burn(position, debtChange);\n _burnRTokens(msg.sender, debtChange);\n }\n\n emit DebtChanged(position, collateralToken, debtChange, isDebtIncrease);\n }\n\n /// @dev Mints R tokens\n function _mintRTokens(address to, uint256 amount) internal virtual {\n rToken.mint(to, amount);\n }\n\n /// @dev Burns R tokens\n function _burnRTokens(address from, uint256 amount) internal virtual {\n rToken.burn(from, amount);\n }\n\n /// @dev Adjusts the collateral of a given borrower by burning or minting the corresponding amount of Raft\n /// collateral token and transferring the corresponding amount of collateral token.\n /// @param collateralToken The token the borrower used as collateral.\n /// @param position The address of the borrower.\n /// @param collateralChange The amount of collateral to add or remove. Must be positive.\n /// @param isCollateralIncrease True if the collateral is being increased, false otherwise.\n function _adjustCollateral(\n IERC20 collateralToken,\n IERC20Indexable raftCollateralToken,\n address position,\n uint256 collateralChange,\n bool isCollateralIncrease\n )\n internal\n {\n if (collateralChange == 0) {\n return;\n }\n\n if (isCollateralIncrease) {\n raftCollateralToken.mint(position, collateralChange);\n collateralToken.safeTransferFrom(msg.sender, address(this), collateralChange);\n } else {\n raftCollateralToken.burn(position, collateralChange);\n collateralToken.safeTransfer(msg.sender, collateralChange);\n }\n\n emit CollateralChanged(position, collateralToken, collateralChange, isCollateralIncrease);\n }\n\n /// @dev Updates debt and collateral indexes for a given collateral token.\n /// @param collateralToken The collateral token for which to update the indexes.\n /// @param raftCollateralToken The raft collateral indexable token.\n /// @param raftDebtToken The raft debt indexable token.\n /// @param totalDebtForCollateral Totam amount of debt backed by collateral token.\n function _updateDebtAndCollateralIndex(\n IERC20 collateralToken,\n IERC20Indexable raftCollateralToken,\n IERC20Indexable raftDebtToken,\n uint256 totalDebtForCollateral\n )\n internal\n {\n raftDebtToken.setIndex(totalDebtForCollateral);\n raftCollateralToken.setIndex(collateralToken.balanceOf(address(this)));\n }\n\n function _closePosition(\n IERC20Indexable raftCollateralToken,\n IERC20Indexable raftDebtToken,\n address position,\n bool burnTokens\n )\n internal\n {\n collateralTokenForPosition[position] = IERC20(address(0));\n\n if (burnTokens) {\n raftDebtToken.burn(position, type(uint256).max);\n raftCollateralToken.burn(position, type(uint256).max);\n }\n emit PositionClosed(position);\n }\n\n // --- Borrowing & redemption fee helper functions ---\n\n /// @dev Updates the base rate from a redemption operation. Impacts on the base rate:\n /// 1. decays the base rate based on time passed since last redemption or R borrowing operation,\n /// 2. increases the base rate based on the amount redeemed, as a proportion of total supply.\n function _updateBaseRateFromRedemption(\n IERC20 collateralToken,\n uint256 collateralDrawn,\n uint256 price,\n uint256 totalDebtSupply\n )\n internal\n returns (uint256)\n {\n uint256 decayedBaseRate = _calcDecayedBaseRate(collateralToken);\n\n /* Convert the drawn collateral back to R at face value rate (1 R:1 USD), in order to get\n * the fraction of total supply that was redeemed at face value. */\n uint256 redeemedFraction = collateralDrawn * price / totalDebtSupply;\n\n uint256 newBaseRate = decayedBaseRate + redeemedFraction / BETA;\n newBaseRate = Math.min(newBaseRate, MathUtils._100_PERCENT); // cap baseRate at a maximum of 100%\n assert(newBaseRate > 0); // Base rate is always non-zero after redemption\n\n // Update the baseRate state variable\n collateralInfo[collateralToken].baseRate = newBaseRate;\n emit BaseRateUpdated(collateralToken, newBaseRate);\n\n _updateLastFeeOpTime(collateralToken);\n\n return newBaseRate;\n }\n\n function _calcRedemptionRate(IERC20 collateralToken, uint256 baseRate_) internal view returns (uint256) {\n return baseRate_ + collateralInfo[collateralToken].redemptionSpread;\n }\n\n function _calcBorrowingRate(IERC20 collateralToken, uint256 baseRate_) internal view returns (uint256) {\n return Math.min(collateralInfo[collateralToken].borrowingSpread + baseRate_, MAX_BORROWING_RATE);\n }\n\n /// @dev Updates the base rate based on time elapsed since the last redemption or R borrowing operation.\n function _decayBaseRateFromBorrowing(IERC20 collateralToken) internal {\n uint256 decayedBaseRate = _calcDecayedBaseRate(collateralToken);\n assert(decayedBaseRate <= MathUtils._100_PERCENT); // The baseRate can decay to 0\n\n collateralInfo[collateralToken].baseRate = decayedBaseRate;\n emit BaseRateUpdated(collateralToken, decayedBaseRate);\n\n _updateLastFeeOpTime(collateralToken);\n }\n\n /// @dev Update the last fee operation time only if time passed >= decay interval. This prevents base rate\n /// griefing.\n function _updateLastFeeOpTime(IERC20 collateralToken) internal {\n uint256 timePassed = block.timestamp - collateralInfo[collateralToken].lastFeeOperationTime;\n\n if (timePassed >= 1 minutes) {\n collateralInfo[collateralToken].lastFeeOperationTime = block.timestamp;\n emit LastFeeOpTimeUpdated(collateralToken, block.timestamp);\n }\n }\n\n function _calcDecayedBaseRate(IERC20 collateralToken) internal view returns (uint256) {\n uint256 minutesPassed = (block.timestamp - collateralInfo[collateralToken].lastFeeOperationTime) / 1 minutes;\n uint256 decayFactor = MathUtils._decPow(MINUTE_DECAY_FACTOR, minutesPassed);\n\n return collateralInfo[collateralToken].baseRate.mulDown(decayFactor);\n }\n\n function _triggerBorrowingFee(\n IERC20 collateralToken,\n address position,\n uint256 debtAmount,\n uint256 maxFeePercentage\n )\n internal\n virtual\n returns (uint256 borrowingFee)\n {\n _decayBaseRateFromBorrowing(collateralToken); // decay the baseRate state variable\n borrowingFee = getBorrowingFee(collateralToken, debtAmount);\n\n _checkValidFee(borrowingFee, debtAmount, maxFeePercentage);\n\n if (borrowingFee > 0) {\n _mintRTokens(feeRecipient, borrowingFee);\n emit RBorrowingFeePaid(collateralToken, position, borrowingFee);\n }\n }\n\n // --- Validation check helper functions ---\n\n function _checkValidPosition(IERC20 collateralToken, uint256 positionDebt, uint256 positionCollateral) internal {\n ISplitLiquidationCollateral splitCollateral = collateralInfo[collateralToken].splitLiquidation;\n if (positionDebt < splitCollateral.LOW_TOTAL_DEBT()) {\n revert NetDebtBelowMinimum(positionDebt);\n }\n\n (uint256 price,) = collateralInfo[collateralToken].priceFeed.fetchPrice();\n uint256 newICR = MathUtils._computeCR(positionCollateral, positionDebt, price);\n if (newICR < splitCollateral.MCR()) {\n revert NewICRLowerThanMCR(newICR);\n }\n }\n\n function _checkValidFee(uint256 fee, uint256 amount, uint256 maxFeePercentage) internal pure {\n uint256 feePercentage = fee.divDown(amount);\n\n if (feePercentage > maxFeePercentage) {\n revert FeeExceedsMaxFee(fee, amount, maxFeePercentage);\n }\n }\n}\n\ninterface IRMinter {\n /// @dev Emitted when tokens are recovered from the contract.\n /// @param token The address of the token being recovered.\n /// @param to The address receiving the recovered tokens.\n /// @param amount The amount of tokens recovered.\n event TokensRecovered(IERC20 token, address to, uint256 amount);\n\n /// @return Address of the R token.\n function r() external view returns (IRToken);\n\n /// @return Address of the Position manager contract responsible for minting R.\n function positionManager() external view returns (IPositionManager);\n\n /// @dev Recover accidentally sent tokens to the contract\n /// @param token Address of the token contract.\n /// @param to Address of the receiver of the tokens.\n /// @param amount Number of tokens to recover.\n function recoverTokens(IERC20 token, address to, uint256 amount) external;\n}\n\ninterface ILock {\n /// @dev Thrown when contract usage is locked.\n error ContractLocked();\n\n /// @dev Unauthorized call to lock/unlock.\n error Unauthorized();\n\n /// @dev Retrieves if contract is currently locked or not.\n function locked() external view returns (bool);\n\n /// @dev Retrieves address of the locker who can unlock contract.\n function locker() external view returns (address);\n\n /// @dev Unlcoks the usage of the contract.\n function unlock() external;\n\n /// @dev Locks the usage of the contract.\n function lock() external;\n}\n\nabstract contract ERC20RMinter is IRMinter, ERC20, Ownable2Step {\n using SafeERC20 for IERC20;\n\n IRToken public immutable override r;\n IPositionManager public immutable override positionManager;\n\n constructor(IRToken rToken_, string memory name_, string memory symbol_) ERC20(name_, symbol_) {\n r = rToken_;\n positionManager = IPositionManager(rToken_.positionManager());\n\n _approve(address(this), address(positionManager), type(uint256).max);\n }\n\n modifier unlockCall() {\n ILock lockContract = ILock(address(positionManager.priceFeed(IERC20(this))));\n lockContract.unlock();\n _;\n lockContract.lock();\n }\n\n function recoverTokens(IERC20 token, address to, uint256 amount) external override onlyOwner {\n token.safeTransfer(to, amount);\n emit TokensRecovered(token, to, amount);\n }\n\n function _mintR(address to, uint256 amount) internal unlockCall {\n _mint(address(this), amount);\n ERC20PermitSignature memory emptySignature;\n positionManager.managePosition(\n IERC20(address(this)),\n address(this),\n amount,\n true, // collateral increase\n amount,\n true, // debt increase\n 1e18, // 100%\n emptySignature\n );\n r.transfer(to, amount);\n }\n\n function _burnR(address from, uint256 amount) internal unlockCall {\n r.transferFrom(from, address(this), amount);\n ERC20PermitSignature memory emptySignature;\n positionManager.managePosition(\n IERC20(address(this)),\n address(this),\n amount,\n false, // collateral decrease\n amount,\n false, // debt decrease\n 1e18, // 100%\n emptySignature\n );\n _burn(address(this), amount);\n }\n}\n\ncontract InterestRateDebtToken is ERC20Indexable {\n // --- Types ---\n\n using Fixed256x18 for uint256;\n\n // --- Events ---\n\n event IndexIncreasePerSecondSet(uint256 indexIncreasePerSecond);\n\n // --- Immutables ---\n\n IERC20 immutable collateralToken;\n\n // --- Variables ---\n\n uint256 internal storedIndexUpdatedAt;\n\n uint256 public indexIncreasePerSecond;\n\n // --- Constructor ---\n\n constructor(\n address positionManager_,\n string memory name_,\n string memory symbol_,\n IERC20 collateralToken_,\n uint256 cap_,\n uint256 indexIncreasePerSecond_\n )\n ERC20Indexable(positionManager_, name_, symbol_, cap_)\n {\n storedIndexUpdatedAt = block.timestamp;\n collateralToken = collateralToken_;\n setIndexIncreasePerSecond(indexIncreasePerSecond_);\n }\n\n // --- Functions ---\n\n function mint(address to, uint256 amount) public virtual override {\n updateIndexAndPayFees();\n super.mint(to, amount);\n }\n\n function burn(address from, uint256 amount) public virtual override {\n updateIndexAndPayFees();\n super.burn(from, amount);\n }\n\n function currentIndex() public view virtual override returns (uint256) {\n return storedIndex.mulUp(INDEX_PRECISION + indexIncreasePerSecond * (block.timestamp - storedIndexUpdatedAt));\n }\n\n function updateIndexAndPayFees() public {\n uint256 currentIndex_ = currentIndex();\n _payFees(currentIndex_);\n storedIndexUpdatedAt = block.timestamp;\n storedIndex = currentIndex_;\n emit IndexUpdated(currentIndex_);\n }\n\n function setIndexIncreasePerSecond(uint256 indexIncreasePerSecond_) public onlyOwner {\n updateIndexAndPayFees();\n indexIncreasePerSecond = indexIncreasePerSecond_;\n emit IndexIncreasePerSecondSet(indexIncreasePerSecond_);\n }\n\n function unpaidFees() external view returns (uint256) {\n return _unpaidFees(currentIndex());\n }\n\n function _unpaidFees(uint256 currentIndex_) private view returns (uint256) {\n return totalSupply().mulDown(currentIndex_ - storedIndex);\n }\n\n function _payFees(uint256 currentIndex_) private {\n uint256 unpaidFees = _unpaidFees(currentIndex_);\n if (unpaidFees > 0) {\n IInterestRatePositionManager(positionManager).mintFees(collateralToken, unpaidFees);\n }\n }\n}\n\n/// @dev Implementation of Position Manager. Current implementation does not support rebasing tokens as collateral.\ncontract InterestRatePositionManager is ERC20RMinter, PositionManager, IInterestRatePositionManager {\n // --- Errors ---\n\n error Unsupported();\n\n // --- Constructor ---\n\n /// @dev Initializes the position manager.\n constructor(IRToken rToken_)\n PositionManager(address(rToken_))\n ERC20RMinter(rToken_, \"Interest Rate Posman\", \"IRPM\")\n { }\n\n // --- External functions ---\n\n function managePosition(\n IERC20 collateralToken,\n address position,\n uint256 collateralChange,\n bool isCollateralIncrease,\n uint256 debtChange,\n bool isDebtIncrease,\n uint256 maxFeePercentage,\n ERC20PermitSignature calldata permitSignature\n )\n public\n virtual\n override(IPositionManager, PositionManager)\n returns (uint256 actualCollateralChange, uint256 actualDebtChange)\n {\n if (address(permitSignature.token) == address(r)) {\n PermitHelper.applyPermit(permitSignature, msg.sender, address(this));\n }\n return super.managePosition(\n collateralToken,\n position,\n collateralChange,\n isCollateralIncrease,\n debtChange,\n isDebtIncrease,\n maxFeePercentage,\n permitSignature\n );\n }\n\n function mintFees(IERC20 collateralToken, uint256 amount) external {\n if (msg.sender != address(collateralInfo[collateralToken].debtToken)) {\n revert InvalidDebtToken(msg.sender);\n }\n _mintR(feeRecipient, amount);\n\n emit MintedFees(collateralToken, amount);\n }\n\n function redeemCollateral(IERC20, uint256, uint256) public virtual override(IPositionManager, PositionManager) {\n revert Unsupported();\n }\n\n function addCollateralToken(\n IERC20,\n IPriceFeed,\n ISplitLiquidationCollateral\n )\n public\n override(IPositionManager, PositionManager)\n {\n revert Unsupported();\n }\n\n // --- Helper functions ---\n\n function _mintRTokens(address to, uint256 amount) internal virtual override {\n _mintR(to, amount);\n }\n\n function _burnRTokens(address from, uint256 amount) internal virtual override {\n _burnR(from, amount);\n }\n\n function _triggerBorrowingFee(\n IERC20,\n address,\n uint256,\n uint256\n )\n internal\n pure\n virtual\n override\n returns (uint256)\n {\n return 0;\n }\n}\n"}},"settings":{"remappings":["@balancer-labs/=node_modules/@balancer-labs/","@balancer-labs/v2-interfaces/contracts/=lib/balancer-v2-monorepo/pkg/interfaces/contracts/","@chainlink/=node_modules/@chainlink/","@eth-optimism/=node_modules/@eth-optimism/","@openzeppelin/=node_modules/@openzeppelin/","@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/","@redstone-finance/=node_modules/@redstone-finance/","@smartcontractkit/chainlink/=lib/chainlink/contracts/src/v0.8/","@tempusfinance/=node_modules/@tempusfinance/","@tempusfinance/tempus-utils/contracts/=lib/tempus-utils/contracts/","balancer-v2-monorepo/=lib/balancer-v2-monorepo/","chainlink/=lib/chainlink/","ds-test/=lib/forge-std/lib/ds-test/src/","erc4626-tests/=lib/chainlink/contracts/foundry-lib/openzeppelin-contracts/lib/erc4626-tests/","eth-gas-reporter/=node_modules/eth-gas-reporter/","forge-std/=lib/forge-std/src/","hardhat/=node_modules/hardhat/","openzeppelin-contracts/=lib/openzeppelin-contracts/","tempus-utils/=lib/tempus-utils/contracts/"],"optimizer":{"enabled":true,"runs":200000},"metadata":{"bytecodeHash":"ipfs","appendCBOR":true},"outputSelection":{"*":{"*":["evm.bytecode","evm.deployedBytecode","devdoc","userdoc","metadata","abi"]}},"evmVersion":"london","viaIR":true,"libraries":{}}},"ABI":"[{\"inputs\":[{\"internalType\":\"contract IRToken\",\"name\":\"rToken_\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"AmountIsZero\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"BorrowingSpreadExceedsMaximum\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"CannotLiquidateLastPosition\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"CollateralTokenAddressCannotBeZero\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"CollateralTokenAlreadyAdded\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"CollateralTokenDisabled\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"CollateralTokenNotAdded\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"DelegateNotWhitelisted\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"FeeEatsUpAllReturnedCollateral\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"fee\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxFeePercentage\",\"type\":\"uint256\"}],\"name\":\"FeeExceedsMaxFee\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"InvalidDebtToken\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidDelegateAddress\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidFeeRecipient\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidMaxFeePercentage\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"InvalidPosition\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"MaxFeePercentageOutOfRange\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"netDebt\",\"type\":\"uint256\"}],\"name\":\"NetDebtBelowMinimum\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newICR\",\"type\":\"uint256\"}],\"name\":\"NewICRLowerThanMCR\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NoCollateralOrDebtChange\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NothingToLiquidate\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"PositionCollateralTokenMismatch\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"PriceFeedAddressCannotBeZero\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"RedemptionRebateExceedsMaximum\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"RedemptionSpreadOutOfRange\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"SplitLiquidationCollateralCannotBeZero\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"newTotalCollateral\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"minimumCollateral\",\"type\":\"uint256\"}],\"name\":\"TotalCollateralCannotBeLowerThanMinCollateral\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"newTotalDebt\",\"type\":\"uint256\"}],\"name\":\"TotalDebtCannotBeLowerThanMinDebt\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"Unsupported\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"WrongCollateralParamsForFullRepayment\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"baseRate\",\"type\":\"uint256\"}],\"name\":\"BaseRateUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"borrowingSpread\",\"type\":\"uint256\"}],\"name\":\"BorrowingSpreadUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"collateralAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isCollateralIncrease\",\"type\":\"bool\"}],\"name\":\"CollateralChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"contract IERC20Indexable\",\"name\":\"raftCollateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"contract IERC20Indexable\",\"name\":\"raftDebtToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"contract IPriceFeed\",\"name\":\"priceFeed\",\"type\":\"address\"}],\"name\":\"CollateralTokenAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"}],\"name\":\"CollateralTokenModified\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"debtAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isDebtIncrease\",\"type\":\"bool\"}],\"name\":\"DebtChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"delegate\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"whitelisted\",\"type\":\"bool\"}],\"name\":\"DelegateWhitelisted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"feeRecipient\",\"type\":\"address\"}],\"name\":\"FeeRecipientChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"lastFeeOpTime\",\"type\":\"uint256\"}],\"name\":\"LastFeeOpTimeUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"liquidator\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"debtLiquidated\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"collateralLiquidated\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"collateralSentToLiquidator\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"collateralLiquidationFeePaid\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isRedistribution\",\"type\":\"bool\"}],\"name\":\"Liquidation\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"MintedFees\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferStarted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"}],\"name\":\"PositionClosed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"PositionCreated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IRToken\",\"name\":\"rToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"feeRecipient\",\"type\":\"address\"}],\"name\":\"PositionManagerDeployed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"feeAmount\",\"type\":\"uint256\"}],\"name\":\"RBorrowingFeePaid\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"redeemer\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"collateralSent\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"fee\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"rebate\",\"type\":\"uint256\"}],\"name\":\"Redemption\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"redemptionRebate\",\"type\":\"uint256\"}],\"name\":\"RedemptionRebateUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"redemptionSpread\",\"type\":\"uint256\"}],\"name\":\"RedemptionSpreadUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"contract ISplitLiquidationCollateral\",\"name\":\"newSplitLiquidationCollateral\",\"type\":\"address\"}],\"name\":\"SplitLiquidationCollateralChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"TokensRecovered\",\"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\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"BETA\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MAX_BORROWING_RATE\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MAX_BORROWING_SPREAD\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"MINUTE_DECAY_FACTOR\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"contract IPriceFeed\",\"name\":\"priceFeed\",\"type\":\"address\"},{\"internalType\":\"contract ISplitLiquidationCollateral\",\"name\":\"newSplitLiquidationCollateral\",\"type\":\"address\"},{\"internalType\":\"contract IERC20Indexable\",\"name\":\"raftCollateralToken_\",\"type\":\"address\"},{\"internalType\":\"contract IERC20Indexable\",\"name\":\"raftDebtToken_\",\"type\":\"address\"}],\"name\":\"addCollateralToken\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"contract IPriceFeed\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"contract ISplitLiquidationCollateral\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"addCollateralToken\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"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\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"baseRate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"borrowingSpread\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"collateralEnabled\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"collateralInfo\",\"outputs\":[{\"internalType\":\"contract IERC20Indexable\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"contract IERC20Indexable\",\"name\":\"debtToken\",\"type\":\"address\"},{\"internalType\":\"contract IPriceFeed\",\"name\":\"priceFeed\",\"type\":\"address\"},{\"internalType\":\"contract ISplitLiquidationCollateral\",\"name\":\"splitLiquidation\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"lastFeeOperationTime\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"borrowingSpread\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"baseRate\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"redemptionSpread\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"redemptionRebate\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"}],\"name\":\"collateralTokenForPosition\",\"outputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"subtractedValue\",\"type\":\"uint256\"}],\"name\":\"decreaseAllowance\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"feeRecipient\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"debtAmount\",\"type\":\"uint256\"}],\"name\":\"getBorrowingFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"getBorrowingRate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"getBorrowingRateWithDecay\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"collateralAmount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"priceDeviation\",\"type\":\"uint256\"}],\"name\":\"getRedemptionFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"collateralAmount\",\"type\":\"uint256\"}],\"name\":\"getRedemptionFeeWithDecay\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"redemptionFee\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"getRedemptionRate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"getRedemptionRateWithDecay\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"addedValue\",\"type\":\"uint256\"}],\"name\":\"increaseAllowance\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"delegate\",\"type\":\"address\"}],\"name\":\"isDelegateWhitelisted\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"isWhitelisted\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"lastFeeOperationTime\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"}],\"name\":\"liquidate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"position\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"collateralChange\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"isCollateralIncrease\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"debtChange\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"isDebtIncrease\",\"type\":\"bool\"},{\"internalType\":\"uint256\",\"name\":\"maxFeePercentage\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"contract IERC20Permit\",\"name\":\"token\",\"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\"}],\"internalType\":\"struct ERC20PermitSignature\",\"name\":\"permitSignature\",\"type\":\"tuple\"}],\"name\":\"managePosition\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"actualCollateralChange\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"actualDebtChange\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"mintFees\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pendingOwner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"positionManager\",\"outputs\":[{\"internalType\":\"contract IPositionManager\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"priceFeed\",\"outputs\":[{\"internalType\":\"contract IPriceFeed\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"r\",\"outputs\":[{\"internalType\":\"contract IRToken\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"rToken\",\"outputs\":[{\"internalType\":\"contract IRToken\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"raftCollateralToken\",\"outputs\":[{\"internalType\":\"contract IERC20Indexable\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"raftDebtToken\",\"outputs\":[{\"internalType\":\"contract IERC20Indexable\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"recoverTokens\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"redeemCollateral\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"redemptionRebate\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"redemptionSpread\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"newBorrowingSpread\",\"type\":\"uint256\"}],\"name\":\"setBorrowingSpread\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"isEnabled\",\"type\":\"bool\"}],\"name\":\"setCollateralEnabled\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newFeeRecipient\",\"type\":\"address\"}],\"name\":\"setFeeRecipient\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"newRedemptionRebate\",\"type\":\"uint256\"}],\"name\":\"setRedemptionRebate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"newRedemptionSpread\",\"type\":\"uint256\"}],\"name\":\"setRedemptionSpread\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"},{\"internalType\":\"contract ISplitLiquidationCollateral\",\"name\":\"newSplitLiquidationCollateral\",\"type\":\"address\"}],\"name\":\"setSplitLiquidationCollateral\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"collateralToken\",\"type\":\"address\"}],\"name\":\"splitLiquidationCollateral\",\"outputs\":[{\"internalType\":\"contract ISplitLiquidationCollateral\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"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\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"delegate\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"whitelisted\",\"type\":\"bool\"}],\"name\":\"whitelistDelegate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]","ContractName":"InterestRatePositionManager","CompilerVersion":"v0.8.19+commit.7dd6d404","OptimizationUsed":1,"Runs":200000,"ConstructorArguments":"0x000000000000000000000000183015a9ba6ff60230fdeadc3f43b3d788b13e21","EVMVersion":"london","Library":"","LicenseType":"","Proxy":0,"SwarmSource":""}] \ No newline at end of file diff --git a/testdata/etherscan/0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F/creation_data.json b/testdata/etherscan/0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F/creation_data.json new file mode 100644 index 000000000..7896ef856 --- /dev/null +++ b/testdata/etherscan/0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F/creation_data.json @@ -0,0 +1,5 @@ +{ + "contractAddress": "0x9d27527ada2cf29fbdab2973cfa243845a08bd3f", + "contractCreator": "0x7773ae67403d2e30102a84c48cc939919c4c881c", + "txHash": "0xc757719b7ae11ea651b1b23249978a3e8fca94d358610f5809a5dc19fbf850ce" +} \ No newline at end of file diff --git a/testdata/etherscan/0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F/metadata.json b/testdata/etherscan/0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F/metadata.json new file mode 100644 index 000000000..514b2571c --- /dev/null +++ b/testdata/etherscan/0x9d27527Ada2CF29fBDAB2973cfa243845a08Bd3F/metadata.json @@ -0,0 +1,96 @@ +[ + { + "SourceCode": { + "language": "Solidity", + "sources": { + "@openzeppelin/contracts/utils/Address.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n /**\n * @dev Returns true if `account` is a contract.\n *\n * [IMPORTANT]\n * ====\n * It is unsafe to assume that an address for which this function returns\n * false is an externally-owned account (EOA) and not a contract.\n *\n * Among others, `isContract` will return false for the following\n * types of addresses:\n *\n * - an externally-owned account\n * - a contract in construction\n * - an address where a contract will be created\n * - an address where a contract lived, but was destroyed\n * ====\n */\n function isContract(address account) internal view returns (bool) {\n // This method relies on extcodesize, which returns 0 for contracts in\n // construction, since the code is only stored at the end of the\n // constructor execution.\n\n uint256 size;\n assembly {\n size := extcodesize(account)\n }\n return size > 0;\n }\n\n /**\n * @dev Replacement for Solidity's `transfer`: sends `amount` wei to\n * `recipient`, forwarding all available gas and reverting on errors.\n *\n * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n * of certain opcodes, possibly making contracts go over the 2300 gas limit\n * imposed by `transfer`, making them unable to receive funds via\n * `transfer`. {sendValue} removes this limitation.\n *\n * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n *\n * IMPORTANT: because control is transferred to `recipient`, care must be\n * taken to not create reentrancy vulnerabilities. Consider using\n * {ReentrancyGuard} or the\n * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n */\n function sendValue(address payable recipient, uint256 amount) internal {\n require(address(this).balance >= amount, \"Address: insufficient balance\");\n\n (bool success, ) = recipient.call{value: amount}(\"\");\n require(success, \"Address: unable to send value, recipient may have reverted\");\n }\n\n /**\n * @dev Performs a Solidity function call using a low level `call`. A\n * plain `call` is an unsafe replacement for a function call: use this\n * function instead.\n *\n * If `target` reverts with a revert reason, it is bubbled up by this\n * function (like regular Solidity function calls).\n *\n * Returns the raw returned data. To convert to the expected return value,\n * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].\n *\n * Requirements:\n *\n * - `target` must be a contract.\n * - calling `target` with `data` must not revert.\n *\n * _Available since v3.1._\n */\n function functionCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionCall(target, data, \"Address: low-level call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with\n * `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, 0, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but also transferring `value` wei to `target`.\n *\n * Requirements:\n *\n * - the calling contract must have an ETH balance of at least `value`.\n * - the called Solidity function must be `payable`.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but\n * with `errorMessage` as a fallback revert reason when `target` reverts.\n *\n * _Available since v3.1._\n */\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(address(this).balance >= value, \"Address: insufficient balance for call\");\n require(isContract(target), \"Address: call to non-contract\");\n\n (bool success, bytes memory returndata) = target.call{value: value}(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\n return functionStaticCall(target, data, \"Address: low-level static call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a static call.\n *\n * _Available since v3.3._\n */\n function functionStaticCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal view returns (bytes memory) {\n require(isContract(target), \"Address: static call to non-contract\");\n\n (bool success, bytes memory returndata) = target.staticcall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\n return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\n }\n\n /**\n * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],\n * but performing a delegate call.\n *\n * _Available since v3.4._\n */\n function functionDelegateCall(\n address target,\n bytes memory data,\n string memory errorMessage\n ) internal returns (bytes memory) {\n require(isContract(target), \"Address: delegate call to non-contract\");\n\n (bool success, bytes memory returndata) = target.delegatecall(data);\n return verifyCallResult(success, returndata, errorMessage);\n }\n\n /**\n * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the\n * revert reason using the provided one.\n *\n * _Available since v4.3._\n */\n function verifyCallResult(\n bool success,\n bytes memory returndata,\n string memory errorMessage\n ) internal pure returns (bytes memory) {\n if (success) {\n return returndata;\n } else {\n // Look for revert reason and bubble it up if present\n if (returndata.length > 0) {\n // The easiest way to bubble the revert reason is using memory via assembly\n\n assembly {\n let returndata_size := mload(returndata)\n revert(add(32, returndata), returndata_size)\n }\n } else {\n revert(errorMessage);\n }\n }\n }\n}\n" + }, + "src/base/ERC721Checkpointable.sol": { + "content": "// SPDX-License-Identifier: BSD-3-Clause\n\n/// @title Vote checkpointing for an ERC-721 token\n\n/*********************************\n * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *\n * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *\n * ░░░░░░█████████░░█████████░░░ *\n * ░░░░░░██░░░████░░██░░░████░░░ *\n * ░░██████░░░████████░░░████░░░ *\n * ░░██░░██░░░████░░██░░░████░░░ *\n * ░░██░░██░░░████░░██░░░████░░░ *\n * ░░░░░░█████████░░█████████░░░ *\n * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *\n * ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ *\n *********************************/\n\n// LICENSE\n// ERC721Checkpointable.sol uses and modifies part of Compound Lab's Comp.sol:\n// https://github.com/compound-finance/compound-protocol/blob/ae4388e780a8d596d97619d9704a931a2752c2bc/contracts/Governance/Comp.sol\n//\n// Comp.sol source code Copyright 2020 Compound Labs, Inc. licensed under the BSD-3-Clause license.\n// With modifications by Nounders DAO.\n//\n// Additional conditions of BSD-3-Clause can be found here: https://opensource.org/licenses/BSD-3-Clause\n//\n// MODIFICATIONS\n// Checkpointing logic from Comp.sol has been used with the following modifications:\n// - `delegates` is renamed to `_delegates` and is set to private\n// - `delegates` is a public function that uses the `_delegates` mapping look-up, but unlike\n// Comp.sol, returns the delegator's own address if there is no delegate.\n// This avoids the delegator needing to \"delegate to self\" with an additional transaction\n// - `_transferTokens()` is renamed `_beforeTokenTransfer()` and adapted to hook into OpenZeppelin's ERC721 hooks.\n\npragma solidity 0.8.9;\n\nimport \"./ERC721BaseWithERC4494Permit.sol\";\n\nabstract contract ERC721Checkpointable is ERC721BaseWithERC4494Permit {\n bool internal _useCheckpoints = true; // can only be disabled and never re-enabled\n\n /// @notice Defines decimals as per ERC-20 convention to make integrations with 3rd party governance platforms easier\n uint8 public constant decimals = 0;\n\n /// @notice A record of each accounts delegate\n mapping(address => address) private _delegates;\n\n /// @notice A checkpoint for marking number of votes from a given block\n struct Checkpoint {\n uint32 fromBlock;\n uint96 votes;\n }\n\n /// @notice A record of votes checkpoints for each account, by index\n mapping(address => mapping(uint32 => Checkpoint)) public checkpoints;\n\n /// @notice The number of checkpoints for each account\n mapping(address => uint32) public numCheckpoints;\n\n /// @notice The EIP-712 typehash for the delegation struct used by the contract\n bytes32 public constant DELEGATION_TYPEHASH =\n keccak256(\"Delegation(address delegatee,uint256 nonce,uint256 expiry)\");\n\n /// @notice An event thats emitted when an account changes its delegate\n event DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate);\n\n /// @notice An event thats emitted when a delegate account's vote balance changes\n event DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance);\n\n /**\n * @notice The votes a delegator can delegate, which is the current balance of the delegator.\n * @dev Used when calling `_delegate()`\n */\n function votesToDelegate(address delegator) public view returns (uint96) {\n return safe96(balanceOf(delegator), \"ERC721Checkpointable::votesToDelegate: amount exceeds 96 bits\");\n }\n\n /**\n * @notice Overrides the standard `Comp.sol` delegates mapping to return\n * the delegator's own address if they haven't delegated.\n * This avoids having to delegate to oneself.\n */\n function delegates(address delegator) public view returns (address) {\n address current = _delegates[delegator];\n return current == address(0) ? delegator : current;\n }\n\n /**\n * @notice Adapted from `_transferTokens()` in `Comp.sol` to update delegate votes.\n * @dev hooks into ERC721Base's `ERC721._transfer`\n */\n function _beforeTokenTransfer(\n address from,\n address to,\n uint256 tokenId\n ) internal override {\n super._beforeTokenTransfer(from, to, tokenId);\n if (_useCheckpoints) {\n /// @notice Differs from `_transferTokens()` to use `delegates` override method to simulate auto-delegation\n _moveDelegates(delegates(from), delegates(to), 1);\n }\n }\n\n /**\n * @notice Delegate votes from `msg.sender` to `delegatee`\n * @param delegatee The address to delegate votes to\n */\n function delegate(address delegatee) public {\n if (delegatee == address(0)) delegatee = msg.sender;\n return _delegate(msg.sender, delegatee);\n }\n\n /**\n * @notice Delegates votes from signatory to `delegatee`\n * @param delegatee The address to delegate votes to\n * @param nonce The contract state required to match the signature\n * @param expiry The time at which to expire the signature\n * @param v The recovery byte of the signature\n * @param r Half of the ECDSA signature pair\n * @param s Half of the ECDSA signature pair\n */\n function delegateBySig(\n address delegatee,\n uint256 nonce,\n uint256 expiry,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) public {\n bytes32 digest = keccak256(\n abi.encodePacked(\n \"\\x19\\x01\",\n _DOMAIN_SEPARATOR(),\n keccak256(abi.encode(DELEGATION_TYPEHASH, delegatee, nonce, expiry))\n )\n );\n // TODO support smart contract wallet via IERC721, require change in function signature to know which signer to call first\n address signatory = ecrecover(digest, v, r, s);\n require(signatory != address(0), \"ERC721Checkpointable::delegateBySig: invalid signature\");\n require(nonce == _userNonces[signatory]++, \"ERC721Checkpointable::delegateBySig: invalid nonce\");\n require(block.timestamp <= expiry, \"ERC721Checkpointable::delegateBySig: signature expired\");\n return _delegate(signatory, delegatee);\n }\n\n /**\n * @notice Gets the current votes balance for `account`\n * @param account The address to get votes balance\n * @return The number of current votes for `account`\n */\n function getCurrentVotes(address account) public view returns (uint96) {\n uint32 nCheckpoints = numCheckpoints[account];\n return nCheckpoints > 0 ? checkpoints[account][nCheckpoints - 1].votes : 0;\n }\n\n /**\n * @notice Gets the current votes balance for `account`\n * @param account The address to get votes balance\n * @return The number of current votes for `account`\n */\n function getVotes(address account) external view returns (uint96) {\n return getCurrentVotes(account);\n }\n\n /**\n * @notice Determine the prior number of votes for an account as of a block number\n * @dev Block number must be a finalized block or else this function will revert to prevent misinformation.\n * @param account The address of the account to check\n * @param blockNumber The block number to get the vote balance at\n * @return The number of votes the account had as of the given block\n */\n function getPriorVotes(address account, uint256 blockNumber) public view returns (uint96) {\n require(blockNumber < block.number, \"ERC721Checkpointable::getPriorVotes: not yet determined\");\n\n uint32 nCheckpoints = numCheckpoints[account];\n if (nCheckpoints == 0) {\n return 0;\n }\n\n // First check most recent balance\n if (checkpoints[account][nCheckpoints - 1].fromBlock <= blockNumber) {\n return checkpoints[account][nCheckpoints - 1].votes;\n }\n\n // Next check implicit zero balance\n if (checkpoints[account][0].fromBlock > blockNumber) {\n return 0;\n }\n\n uint32 lower = 0;\n uint32 upper = nCheckpoints - 1;\n while (upper > lower) {\n uint32 center = upper - (upper - lower) / 2; // ceil, avoiding overflow\n Checkpoint memory cp = checkpoints[account][center];\n if (cp.fromBlock == blockNumber) {\n return cp.votes;\n } else if (cp.fromBlock < blockNumber) {\n lower = center;\n } else {\n upper = center - 1;\n }\n }\n return checkpoints[account][lower].votes;\n }\n\n /**\n * @notice Determine the prior number of votes for an account as of a block number\n * @dev Block number must be a finalized block or else this function will revert to prevent misinformation.\n * @param account The address of the account to check\n * @param blockNumber The block number to get the vote balance at\n * @return The number of votes the account had as of the given block\n */\n function getPastVotes(address account, uint256 blockNumber) external view returns (uint96) {\n return this.getPriorVotes(account, blockNumber);\n }\n\n function _delegate(address delegator, address delegatee) internal {\n /// @notice differs from `_delegate()` in `Comp.sol` to use `delegates` override method to simulate auto-delegation\n address currentDelegate = delegates(delegator);\n\n _delegates[delegator] = delegatee;\n\n emit DelegateChanged(delegator, currentDelegate, delegatee);\n\n uint96 amount = votesToDelegate(delegator);\n\n _moveDelegates(currentDelegate, delegatee, amount);\n }\n\n function _moveDelegates(\n address srcRep,\n address dstRep,\n uint96 amount\n ) internal {\n if (srcRep != dstRep && amount > 0) {\n if (srcRep != address(0)) {\n uint32 srcRepNum = numCheckpoints[srcRep];\n uint96 srcRepOld = srcRepNum > 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0;\n uint96 srcRepNew = sub96(srcRepOld, amount, \"ERC721Checkpointable::_moveDelegates: amount underflows\");\n _writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew);\n }\n\n if (dstRep != address(0)) {\n uint32 dstRepNum = numCheckpoints[dstRep];\n uint96 dstRepOld = dstRepNum > 0 ? checkpoints[dstRep][dstRepNum - 1].votes : 0;\n uint96 dstRepNew = add96(dstRepOld, amount, \"ERC721Checkpointable::_moveDelegates: amount overflows\");\n _writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew);\n }\n }\n }\n\n function _writeCheckpoint(\n address delegatee,\n uint32 nCheckpoints,\n uint96 oldVotes,\n uint96 newVotes\n ) internal {\n uint32 blockNumber = safe32(\n block.number,\n \"ERC721Checkpointable::_writeCheckpoint: block number exceeds 32 bits\"\n );\n\n if (nCheckpoints > 0 && checkpoints[delegatee][nCheckpoints - 1].fromBlock == blockNumber) {\n checkpoints[delegatee][nCheckpoints - 1].votes = newVotes;\n } else {\n checkpoints[delegatee][nCheckpoints] = Checkpoint(blockNumber, newVotes);\n numCheckpoints[delegatee] = nCheckpoints + 1;\n }\n\n emit DelegateVotesChanged(delegatee, oldVotes, newVotes);\n }\n\n function safe32(uint256 n, string memory errorMessage) internal pure returns (uint32) {\n require(n < 2**32, errorMessage);\n return uint32(n);\n }\n\n function safe96(uint256 n, string memory errorMessage) internal pure returns (uint96) {\n require(n < 2**96, errorMessage);\n return uint96(n);\n }\n\n function add96(\n uint96 a,\n uint96 b,\n string memory errorMessage\n ) internal pure returns (uint96) {\n uint96 c = a + b;\n require(c >= a, errorMessage);\n return c;\n }\n\n function sub96(\n uint96 a,\n uint96 b,\n string memory errorMessage\n ) internal pure returns (uint96) {\n require(b <= a, errorMessage);\n return a - b;\n }\n}\n" + }, + "src/base/ERC721Base.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\nimport \"@openzeppelin/contracts/utils/Address.sol\";\nimport \"@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol\";\nimport \"@openzeppelin/contracts/token/ERC721/IERC721.sol\";\nimport \"@openzeppelin/contracts/interfaces/IERC165.sol\";\n\nabstract contract ERC721Base is IERC165, IERC721 {\n using Address for address;\n\n bytes4 internal constant ERC721_RECEIVED = 0x150b7a02;\n bytes4 internal constant ERC165ID = 0x01ffc9a7;\n\n uint256 internal constant OPERATOR_FLAG = 0x8000000000000000000000000000000000000000000000000000000000000000;\n uint256 internal constant NOT_OPERATOR_FLAG = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;\n\n mapping(uint256 => uint256) internal _owners;\n mapping(address => uint256) internal _balances;\n mapping(address => mapping(address => bool)) internal _operatorsForAll;\n mapping(uint256 => address) internal _operators;\n\n function name() public pure virtual returns (string memory) {\n revert(\"NOT_IMPLEMENTED\");\n }\n\n /// @notice Approve an operator to transfer a specific token on the senders behalf.\n /// @param operator The address receiving the approval.\n /// @param id The id of the token.\n function approve(address operator, uint256 id) external override {\n (address owner, uint256 blockNumber) = _ownerAndBlockNumberOf(id);\n require(owner != address(0), \"NONEXISTENT_TOKEN\");\n require(msg.sender == owner || isApprovedForAll(owner, msg.sender), \"UNAUTHORIZED_APPROVAL\");\n _approveFor(owner, blockNumber, operator, id);\n }\n\n /// @notice Transfer a token between 2 addresses.\n /// @param from The sender of the token.\n /// @param to The recipient of the token.\n /// @param id The id of the token.\n function transferFrom(\n address from,\n address to,\n uint256 id\n ) external override {\n (address owner, bool operatorEnabled) = _ownerAndOperatorEnabledOf(id);\n require(owner != address(0), \"NONEXISTENT_TOKEN\");\n require(owner == from, \"NOT_OWNER\");\n require(to != address(0), \"NOT_TO_ZEROADDRESS\");\n require(to != address(this), \"NOT_TO_THIS\");\n if (msg.sender != from) {\n require(\n (operatorEnabled && _operators[id] == msg.sender) || isApprovedForAll(from, msg.sender),\n \"UNAUTHORIZED_TRANSFER\"\n );\n }\n _transferFrom(from, to, id);\n }\n\n /// @notice Transfer a token between 2 addresses letting the receiver know of the transfer.\n /// @param from The send of the token.\n /// @param to The recipient of the token.\n /// @param id The id of the token.\n function safeTransferFrom(\n address from,\n address to,\n uint256 id\n ) external override {\n safeTransferFrom(from, to, id, \"\");\n }\n\n /// @notice Set the approval for an operator to manage all the tokens of the sender.\n /// @param operator The address receiving the approval.\n /// @param approved The determination of the approval.\n function setApprovalForAll(address operator, bool approved) external override {\n _setApprovalForAll(msg.sender, operator, approved);\n }\n\n /// @notice Get the number of tokens owned by an address.\n /// @param owner The address to look for.\n /// @return balance The number of tokens owned by the address.\n function balanceOf(address owner) public view override returns (uint256 balance) {\n require(owner != address(0), \"ZERO_ADDRESS_OWNER\");\n balance = _balances[owner];\n }\n\n /// @notice Get the owner of a token.\n /// @param id The id of the token.\n /// @return owner The address of the token owner.\n function ownerOf(uint256 id) external view override returns (address owner) {\n owner = _ownerOf(id);\n require(owner != address(0), \"NONEXISTENT_TOKEN\");\n }\n\n /// @notice Get the owner of a token and the blockNumber of the last transfer, useful to voting mechanism.\n /// @param id The id of the token.\n /// @return owner The address of the token owner.\n /// @return blockNumber The blocknumber at which the last transfer of that id happened.\n function ownerAndLastTransferBlockNumberOf(uint256 id) internal view returns (address owner, uint256 blockNumber) {\n return _ownerAndBlockNumberOf(id);\n }\n\n struct OwnerData {\n address owner;\n uint256 lastTransferBlockNumber;\n }\n\n /// @notice Get the list of owner of a token and the blockNumber of its last transfer, useful to voting mechanism.\n /// @param ids The list of token ids to check.\n /// @return ownersData The list of (owner, lastTransferBlockNumber) for each ids given as input.\n function ownerAndLastTransferBlockNumberList(uint256[] calldata ids)\n external\n view\n returns (OwnerData[] memory ownersData)\n {\n ownersData = new OwnerData[](ids.length);\n for (uint256 i = 0; i < ids.length; i++) {\n uint256 data = _owners[ids[i]];\n ownersData[i].owner = address(uint160(data));\n ownersData[i].lastTransferBlockNumber = (data >> 160) & 0xFFFFFFFFFFFFFFFFFFFFFF;\n }\n }\n\n /// @notice Get the approved operator for a specific token.\n /// @param id The id of the token.\n /// @return The address of the operator.\n function getApproved(uint256 id) external view override returns (address) {\n (address owner, bool operatorEnabled) = _ownerAndOperatorEnabledOf(id);\n require(owner != address(0), \"NONEXISTENT_TOKEN\");\n if (operatorEnabled) {\n return _operators[id];\n } else {\n return address(0);\n }\n }\n\n /// @notice Check if the sender approved the operator.\n /// @param owner The address of the owner.\n /// @param operator The address of the operator.\n /// @return isOperator The status of the approval.\n function isApprovedForAll(address owner, address operator) public view virtual override returns (bool isOperator) {\n return _operatorsForAll[owner][operator];\n }\n\n /// @notice Transfer a token between 2 addresses letting the receiver knows of the transfer.\n /// @param from The sender of the token.\n /// @param to The recipient of the token.\n /// @param id The id of the token.\n /// @param data Additional data.\n function safeTransferFrom(\n address from,\n address to,\n uint256 id,\n bytes memory data\n ) public override {\n (address owner, bool operatorEnabled) = _ownerAndOperatorEnabledOf(id);\n require(owner != address(0), \"NONEXISTENT_TOKEN\");\n require(owner == from, \"NOT_OWNER\");\n require(to != address(0), \"NOT_TO_ZEROADDRESS\");\n require(to != address(this), \"NOT_TO_THIS\");\n if (msg.sender != from) {\n require(\n (operatorEnabled && _operators[id] == msg.sender) || isApprovedForAll(from, msg.sender),\n \"UNAUTHORIZED_TRANSFER\"\n );\n }\n _safeTransferFrom(from, to, id, data);\n }\n\n /// @notice Check if the contract supports an interface.\n /// @param id The id of the interface.\n /// @return Whether the interface is supported.\n function supportsInterface(bytes4 id) public pure virtual override returns (bool) {\n /// 0x01ffc9a7 is ERC165.\n /// 0x80ac58cd is ERC721\n /// 0x5b5e139f is for ERC721 metadata\n return id == 0x01ffc9a7 || id == 0x80ac58cd || id == 0x5b5e139f;\n }\n\n function _safeTransferFrom(\n address from,\n address to,\n uint256 id,\n bytes memory data\n ) internal {\n _transferFrom(from, to, id);\n if (to.isContract()) {\n require(_checkOnERC721Received(msg.sender, from, to, id, data), \"ERC721_TRANSFER_REJECTED\");\n }\n }\n\n function _beforeTokenTransfer(\n address from,\n address to,\n uint256 id\n ) internal virtual {}\n\n function _transferFrom(\n address from,\n address to,\n uint256 id\n ) internal {\n _beforeTokenTransfer(from, to, id);\n unchecked {\n _balances[to]++;\n if (from != address(0)) {\n _balances[from]--;\n }\n }\n _owners[id] = (block.number << 160) | uint256(uint160(to));\n emit Transfer(from, to, id);\n }\n\n /// @dev See approve.\n function _approveFor(\n address owner,\n uint256 blockNumber,\n address operator,\n uint256 id\n ) internal {\n if (operator == address(0)) {\n _owners[id] = (blockNumber << 160) | uint256(uint160(owner));\n } else {\n _owners[id] = OPERATOR_FLAG | (blockNumber << 160) | uint256(uint160(owner));\n _operators[id] = operator;\n }\n emit Approval(owner, operator, id);\n }\n\n /// @dev See setApprovalForAll.\n function _setApprovalForAll(\n address sender,\n address operator,\n bool approved\n ) internal {\n _operatorsForAll[sender][operator] = approved;\n\n emit ApprovalForAll(sender, operator, approved);\n }\n\n /// @dev Check if receiving contract accepts erc721 transfers.\n /// @param operator The address of the operator.\n /// @param from The from address, may be different from msg.sender.\n /// @param to The adddress we want to transfer to.\n /// @param id The id of the token we would like to transfer.\n /// @param _data Any additional data to send with the transfer.\n /// @return Whether the expected value of 0x150b7a02 is returned.\n function _checkOnERC721Received(\n address operator,\n address from,\n address to,\n uint256 id,\n bytes memory _data\n ) internal returns (bool) {\n bytes4 retval = IERC721Receiver(to).onERC721Received(operator, from, id, _data);\n return (retval == ERC721_RECEIVED);\n }\n\n /// @dev See ownerOf\n function _ownerOf(uint256 id) internal view returns (address owner) {\n return address(uint160(_owners[id]));\n }\n\n /// @dev Get the owner and operatorEnabled status of a token.\n /// @param id The token to query.\n /// @return owner The owner of the token.\n /// @return operatorEnabled Whether or not operators are enabled for this token.\n function _ownerAndOperatorEnabledOf(uint256 id) internal view returns (address owner, bool operatorEnabled) {\n uint256 data = _owners[id];\n owner = address(uint160(data));\n operatorEnabled = (data & OPERATOR_FLAG) == OPERATOR_FLAG;\n }\n\n // @dev Get the owner and operatorEnabled status of a token.\n /// @param id The token to query.\n /// @return owner The owner of the token.\n /// @return blockNumber the blockNumber at which the owner became the owner (last transfer).\n function _ownerAndBlockNumberOf(uint256 id) internal view returns (address owner, uint256 blockNumber) {\n uint256 data = _owners[id];\n owner = address(uint160(data));\n blockNumber = (data >> 160) & 0xFFFFFFFFFFFFFFFFFFFFFF;\n }\n\n // from https://github.com/Uniswap/v3-periphery/blob/main/contracts/base/Multicall.sol\n /// @notice Call multiple functions in the current contract and return the data from all of them if they all succeed.\n /// @dev The `msg.value` should not be trusted for any method callable from multicall.\n /// @param data The encoded function data for each of the calls to make to this contract.\n /// @return results The results from each of the calls passed in via data.\n function multicall(bytes[] calldata data) public payable returns (bytes[] memory results) {\n results = new bytes[](data.length);\n for (uint256 i = 0; i < data.length; i++) {\n (bool success, bytes memory result) = address(this).delegatecall(data[i]);\n\n if (!success) {\n // Next 5 lines from https://ethereum.stackexchange.com/a/83577\n if (result.length < 68) revert();\n assembly {\n result := add(result, 0x04)\n }\n revert(abi.decode(result, (string)));\n }\n\n results[i] = result;\n }\n }\n}\n" + }, + "@openzeppelin/contracts/token/ERC20/IERC20.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC20 standard as defined in the EIP.\n */\ninterface IERC20 {\n /**\n * @dev Returns the amount of tokens in existence.\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @dev Returns the amount of tokens owned by `account`.\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transfer(address recipient, uint256 amount) external returns (bool);\n\n /**\n * @dev Returns the remaining number of tokens that `spender` will be\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\n * zero by default.\n *\n * This value changes when {approve} or {transferFrom} are called.\n */\n function allowance(address owner, address spender) external view returns (uint256);\n\n /**\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\n * that someone may use both the old and the new allowance by unfortunate\n * transaction ordering. One possible solution to mitigate this race\n * condition is to first reduce the spender's allowance to 0 and set the\n * desired value afterwards:\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n *\n * Emits an {Approval} event.\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\n * allowance mechanism. `amount` is then deducted from the caller's\n * allowance.\n *\n * Returns a boolean value indicating whether the operation succeeded.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address sender,\n address recipient,\n uint256 amount\n ) external returns (bool);\n\n /**\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\n * another (`to`).\n *\n * Note that `value` may be zero.\n */\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n /**\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\n * a call to {approve}. `value` is the new allowance.\n */\n event Approval(address indexed owner, address indexed spender, uint256 value);\n}\n" + }, + "src/base/IERC4494.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\ninterface IERC4494 {\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n\n /// @notice Allows to retrieve current nonce for token\n /// @param tokenId token id\n /// @return current token nonce\n function nonces(uint256 tokenId) external view returns (uint256);\n\n /// @notice function to be called by anyone to approve `spender` using a Permit signature\n /// @dev Anyone can call this to approve `spender`, even a third-party\n /// @param spender the actor to approve\n /// @param tokenId the token id\n /// @param deadline the deadline for the permit to be used\n /// @param signature permit\n function permit(\n address spender,\n uint256 tokenId,\n uint256 deadline,\n bytes memory signature\n ) external;\n}\n\ninterface IERC4494Alternative {\n function DOMAIN_SEPARATOR() external view returns (bytes32);\n\n /// @notice Allows to retrieve current nonce for token\n /// @param tokenId token id\n /// @return current token nonce\n function tokenNonces(uint256 tokenId) external view returns (uint256);\n\n /// @notice function to be called by anyone to approve `spender` using a Permit signature\n /// @dev Anyone can call this to approve `spender`, even a third-party\n /// @param spender the actor to approve\n /// @param tokenId the token id\n /// @param deadline the deadline for the permit to be used\n /// @param signature permit\n function permit(\n address spender,\n uint256 tokenId,\n uint256 deadline,\n bytes memory signature\n ) external;\n}\n" + }, + "src/bleeps/BleepsRoles.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\nimport \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\ninterface ReverseRegistrar {\n function setName(string memory name) external returns (bytes32);\n}\n\ninterface ENS {\n function owner(bytes32 node) external view returns (address);\n}\n\ncontract BleepsRoles {\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n event TokenURIAdminSet(address newTokenURIAdmin);\n event RoyaltyAdminSet(address newRoyaltyAdmin);\n event MinterAdminSet(address newMinterAdmin);\n event GuardianSet(address newGuardian);\n event MinterSet(address newMinter);\n\n bytes32 internal constant ADDR_REVERSE_NODE = 0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2;\n ENS internal immutable _ens;\n\n ///@notice the address of the current owner, that is able to set ENS names and withdraw ERC20 owned by the contract.\n address public owner;\n\n /// @notice tokenURIAdmin can update the tokenURI contract, this is intended to be relinquished once the tokenURI has been heavily tested in the wild and that no modification are needed.\n address public tokenURIAdmin;\n\n /// @notice address allowed to set royalty parameters\n address public royaltyAdmin;\n\n /// @notice minterAdmin can update the minter. At the time being there is 576 Bleeps but there is space for extra instrument and the upper limit is 1024.\n /// could be given to the DAO later so instrument can be added, the sale of these new bleeps could benenfit the DAO too and add new members.\n address public minterAdmin;\n\n /// @notice address allowed to mint, allow the sale contract to be separated from the token contract that can focus on the core logic\n /// Once all 1024 potential bleeps (there could be less, at minimum there are 576 bleeps) are minted, no minter can mint anymore\n address public minter;\n\n /// @notice guardian has some special vetoing power to guide the direction of the DAO. It can only remove rights from the DAO. It could be used to immortalize rules.\n /// For example: the royalty setup could be frozen.\n address public guardian;\n\n constructor(\n address ens,\n address initialOwner,\n address initialTokenURIAdmin,\n address initialMinterAdmin,\n address initialRoyaltyAdmin,\n address initialGuardian\n ) {\n _ens = ENS(ens);\n owner = initialOwner;\n tokenURIAdmin = initialTokenURIAdmin;\n royaltyAdmin = initialRoyaltyAdmin;\n minterAdmin = initialMinterAdmin;\n guardian = initialGuardian;\n emit OwnershipTransferred(address(0), initialOwner);\n emit TokenURIAdminSet(initialTokenURIAdmin);\n emit RoyaltyAdminSet(initialRoyaltyAdmin);\n emit MinterAdminSet(initialMinterAdmin);\n emit GuardianSet(initialGuardian);\n }\n\n function setENSName(string memory name) external {\n require(msg.sender == owner, \"NOT_AUTHORIZED\");\n ReverseRegistrar reverseRegistrar = ReverseRegistrar(_ens.owner(ADDR_REVERSE_NODE));\n reverseRegistrar.setName(name);\n }\n\n function withdrawERC20(IERC20 token, address to) external {\n require(msg.sender == owner, \"NOT_AUTHORIZED\");\n token.transfer(to, token.balanceOf(address(this)));\n }\n\n /**\n * @notice Transfers ownership of the contract to a new account (`newOwner`).\n * Can only be called by the current owner.\n */\n function transferOwnership(address newOwner) external {\n address oldOwner = owner;\n require(msg.sender == oldOwner);\n owner = newOwner;\n emit OwnershipTransferred(oldOwner, newOwner);\n }\n\n /**\n * @notice set the new tokenURIAdmin that can change the tokenURI\n * Can only be called by the current tokenURI admin.\n */\n function setTokenURIAdmin(address newTokenURIAdmin) external {\n require(\n msg.sender == tokenURIAdmin || (msg.sender == guardian && newTokenURIAdmin == address(0)),\n \"NOT_AUTHORIZED\"\n );\n tokenURIAdmin = newTokenURIAdmin;\n emit TokenURIAdminSet(newTokenURIAdmin);\n }\n\n /**\n * @notice set the new royaltyAdmin that can change the royalties\n * Can only be called by the current royalty admin.\n */\n function setRoyaltyAdmin(address newRoyaltyAdmin) external {\n require(\n msg.sender == royaltyAdmin || (msg.sender == guardian && newRoyaltyAdmin == address(0)),\n \"NOT_AUTHORIZED\"\n );\n royaltyAdmin = newRoyaltyAdmin;\n emit RoyaltyAdminSet(newRoyaltyAdmin);\n }\n\n /**\n * @notice set the new minterAdmin that can set the minter for Bleeps\n * Can only be called by the current minter admin.\n */\n function setMinterAdmin(address newMinterAdmin) external {\n require(\n msg.sender == minterAdmin || (msg.sender == guardian && newMinterAdmin == address(0)),\n \"NOT_AUTHORIZED\"\n );\n minterAdmin = newMinterAdmin;\n emit MinterAdminSet(newMinterAdmin);\n }\n\n /**\n * @notice set the new guardian that can freeze the other admins (except owner).\n * Can only be called by the current guardian.\n */\n function setGuardian(address newGuardian) external {\n require(msg.sender == guardian, \"NOT_AUTHORIZED\");\n guardian = newGuardian;\n emit GuardianSet(newGuardian);\n }\n\n /**\n * @notice set the new minter that can mint Bleeps (up to 1024).\n * Can only be called by the minter admin.\n */\n function setMinter(address newMinter) external {\n require(msg.sender == minterAdmin, \"NOT_AUTHORIZED\");\n minter = newMinter;\n emit MinterSet(newMinter);\n }\n}\n" + }, + "@openzeppelin/contracts/token/ERC721/IERC721.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport \"../../utils/introspection/IERC165.sol\";\n\n/**\n * @dev Required interface of an ERC721 compliant contract.\n */\ninterface IERC721 is IERC165 {\n /**\n * @dev Emitted when `tokenId` token is transferred from `from` to `to`.\n */\n event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);\n\n /**\n * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.\n */\n event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);\n\n /**\n * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.\n */\n event ApprovalForAll(address indexed owner, address indexed operator, bool approved);\n\n /**\n * @dev Returns the number of tokens in ``owner``'s account.\n */\n function balanceOf(address owner) external view returns (uint256 balance);\n\n /**\n * @dev Returns the owner of the `tokenId` token.\n *\n * Requirements:\n *\n * - `tokenId` must exist.\n */\n function ownerOf(uint256 tokenId) external view returns (address owner);\n\n /**\n * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients\n * are aware of the ERC721 protocol to prevent tokens from being forever locked.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must exist and be owned by `from`.\n * - If the caller is not `from`, it must be have been allowed to move this token by either {approve} or {setApprovalForAll}.\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\n *\n * Emits a {Transfer} event.\n */\n function safeTransferFrom(\n address from,\n address to,\n uint256 tokenId\n ) external;\n\n /**\n * @dev Transfers `tokenId` token from `from` to `to`.\n *\n * WARNING: Usage of this method is discouraged, use {safeTransferFrom} whenever possible.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must be owned by `from`.\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\n *\n * Emits a {Transfer} event.\n */\n function transferFrom(\n address from,\n address to,\n uint256 tokenId\n ) external;\n\n /**\n * @dev Gives permission to `to` to transfer `tokenId` token to another account.\n * The approval is cleared when the token is transferred.\n *\n * Only a single account can be approved at a time, so approving the zero address clears previous approvals.\n *\n * Requirements:\n *\n * - The caller must own the token or be an approved operator.\n * - `tokenId` must exist.\n *\n * Emits an {Approval} event.\n */\n function approve(address to, uint256 tokenId) external;\n\n /**\n * @dev Returns the account approved for `tokenId` token.\n *\n * Requirements:\n *\n * - `tokenId` must exist.\n */\n function getApproved(uint256 tokenId) external view returns (address operator);\n\n /**\n * @dev Approve or remove `operator` as an operator for the caller.\n * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.\n *\n * Requirements:\n *\n * - The `operator` cannot be the caller.\n *\n * Emits an {ApprovalForAll} event.\n */\n function setApprovalForAll(address operator, bool _approved) external;\n\n /**\n * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.\n *\n * See {setApprovalForAll}\n */\n function isApprovedForAll(address owner, address operator) external view returns (bool);\n\n /**\n * @dev Safely transfers `tokenId` token from `from` to `to`.\n *\n * Requirements:\n *\n * - `from` cannot be the zero address.\n * - `to` cannot be the zero address.\n * - `tokenId` token must exist and be owned by `from`.\n * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.\n * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.\n *\n * Emits a {Transfer} event.\n */\n function safeTransferFrom(\n address from,\n address to,\n uint256 tokenId,\n bytes calldata data\n ) external;\n}\n" + }, + "@openzeppelin/contracts/utils/introspection/IERC165.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC165 standard, as defined in the\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\n *\n * Implementers can declare support of contract interfaces, which can then be\n * queried by others ({ERC165Checker}).\n *\n * For an implementation, see {ERC165}.\n */\ninterface IERC165 {\n /**\n * @dev Returns true if this contract implements the interface defined by\n * `interfaceId`. See the corresponding\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\n * to learn more about how these ids are created.\n *\n * This function call must use less than 30 000 gas.\n */\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n" + }, + "src/interfaces/ITokenURI.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\ninterface ITokenURI {\n function tokenURI(uint256 id) external view returns (string memory);\n\n function contractURI(address receiver, uint96 per10Thousands) external view returns (string memory);\n}\n" + }, + "src/base/WithSupportForOpenSeaProxies.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\ncontract OwnableDelegateProxy {}\n\ncontract ProxyRegistry {\n mapping(address => OwnableDelegateProxy) public proxies;\n}\n\nabstract contract WithSupportForOpenSeaProxies {\n address internal immutable _proxyRegistryAddress;\n\n constructor(address proxyRegistryAddress) {\n _proxyRegistryAddress = proxyRegistryAddress;\n }\n\n function _isOpenSeaProxy(address owner, address operator) internal view returns (bool) {\n if (_proxyRegistryAddress == address(0)) {\n return false;\n }\n // Whitelist OpenSea proxy contract for easy trading.\n ProxyRegistry proxyRegistry = ProxyRegistry(_proxyRegistryAddress);\n return address(proxyRegistry.proxies(owner)) == operator;\n }\n}\n" + }, + "@openzeppelin/contracts/interfaces/IERC1271.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Interface of the ERC1271 standard signature validation method for\n * contracts as defined in https://eips.ethereum.org/EIPS/eip-1271[ERC-1271].\n *\n * _Available since v4.1._\n */\ninterface IERC1271 {\n /**\n * @dev Should return whether the signature provided is valid for the provided data\n * @param hash Hash of the data to be signed\n * @param signature Signature byte array associated with _data\n */\n function isValidSignature(bytes32 hash, bytes memory signature) external view returns (bytes4 magicValue);\n}\n" + }, + "@openzeppelin/contracts/interfaces/IERC165.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport \"../utils/introspection/IERC165.sol\";\n" + }, + "src/bleeps/Bleeps.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\nimport \"./BleepsRoles.sol\";\nimport \"../base/ERC721Checkpointable.sol\";\n\nimport \"../interfaces/ITokenURI.sol\";\nimport \"@openzeppelin/contracts/token/ERC721/IERC721.sol\";\n\nimport \"../base/WithSupportForOpenSeaProxies.sol\";\n\ncontract Bleeps is IERC721, WithSupportForOpenSeaProxies, ERC721Checkpointable, BleepsRoles {\n event RoyaltySet(address receiver, uint256 royaltyPer10Thousands);\n event TokenURIContractSet(ITokenURI newTokenURIContract);\n event CheckpointingDisablerSet(address newCheckpointingDisabler);\n event CheckpointingDisabled();\n\n /// @notice the contract that actually generate the sound (and all metadata via the a data: uri via tokenURI call).\n ITokenURI public tokenURIContract;\n\n struct Royalty {\n address receiver;\n uint96 per10Thousands;\n }\n\n Royalty internal _royalty;\n\n /// @dev address that is able to switch off the use of checkpointing, this will make token transfers much cheaper in term of gas, but require the design of a new governance system.\n address public checkpointingDisabler;\n\n /// @dev Create the Bleeps contract\n /// @param ens ENS address for the network the contract is deployed to\n /// @param initialOwner address that can set the ENS name of the contract and that can witthdraw ERC20 tokens sent by mistake here.\n /// @param initialTokenURIAdmin admin able to update the tokenURI contract.\n /// @param initialMinterAdmin admin able to set the minter contract.\n /// @param initialRoyaltyAdmin admin able to update the royalty receiver and rates.\n /// @param initialGuardian guardian able to immortalize rules\n /// @param openseaProxyRegistry allow Bleeps to be sold on opensea without prior approval tx as long as the user have already an opensea proxy.\n /// @param initialRoyaltyReceiver receiver of royalties\n /// @param imitialRoyaltyPer10Thousands amount of royalty in 10,000 basis point\n /// @param initialTokenURIContract initial tokenURI contract that generate the metadata including the wav file.\n /// @param initialCheckpointingDisabler admin able to cancel checkpointing\n constructor(\n address ens,\n address initialOwner,\n address initialTokenURIAdmin,\n address initialMinterAdmin,\n address initialRoyaltyAdmin,\n address initialGuardian,\n address openseaProxyRegistry,\n address initialRoyaltyReceiver,\n uint96 imitialRoyaltyPer10Thousands,\n ITokenURI initialTokenURIContract,\n address initialCheckpointingDisabler\n )\n WithSupportForOpenSeaProxies(openseaProxyRegistry)\n BleepsRoles(ens, initialOwner, initialTokenURIAdmin, initialMinterAdmin, initialRoyaltyAdmin, initialGuardian)\n {\n tokenURIContract = initialTokenURIContract;\n emit TokenURIContractSet(initialTokenURIContract);\n checkpointingDisabler = initialCheckpointingDisabler;\n emit CheckpointingDisablerSet(initialCheckpointingDisabler);\n\n _royalty.receiver = initialRoyaltyReceiver;\n _royalty.per10Thousands = imitialRoyaltyPer10Thousands;\n emit RoyaltySet(initialRoyaltyReceiver, imitialRoyaltyPer10Thousands);\n }\n\n /// @notice A descriptive name for a collection of NFTs in this contract.\n function name() public pure override returns (string memory) {\n return \"Bleeps\";\n }\n\n /// @notice An abbreviated name for NFTs in this contract.\n function symbol() external pure returns (string memory) {\n return \"BLEEP\";\n }\n\n /// @notice Returns the Uniform Resource Identifier (URI) for the token collection.\n function contractURI() external view returns (string memory) {\n return tokenURIContract.contractURI(_royalty.receiver, _royalty.per10Thousands);\n }\n\n /// @notice Returns the Uniform Resource Identifier (URI) for token `id`.\n function tokenURI(uint256 id) external view returns (string memory) {\n return tokenURIContract.tokenURI(id);\n }\n\n /// @notice set a new tokenURI contract, that generate the metadata including the wav file, Can only be set by the `tokenURIAdmin`.\n /// @param newTokenURIContract The address of the new tokenURI contract.\n function setTokenURIContract(ITokenURI newTokenURIContract) external {\n require(msg.sender == tokenURIAdmin, \"NOT_AUTHORIZED\");\n tokenURIContract = newTokenURIContract;\n emit TokenURIContractSet(newTokenURIContract);\n }\n\n /// @notice give the list of owners for the list of ids given.\n /// @param ids The list if token ids to check.\n /// @return addresses The list of addresses, corresponding to the list of ids.\n function owners(uint256[] calldata ids) external view returns (address[] memory addresses) {\n addresses = new address[](ids.length);\n for (uint256 i = 0; i < ids.length; i++) {\n uint256 id = ids[i];\n addresses[i] = address(uint160(_owners[id]));\n }\n }\n\n /// @notice Check if the sender approved the operator.\n /// @param owner The address of the owner.\n /// @param operator The address of the operator.\n /// @return isOperator The status of the approval.\n function isApprovedForAll(address owner, address operator)\n public\n view\n virtual\n override(ERC721Base, IERC721)\n returns (bool isOperator)\n {\n return super.isApprovedForAll(owner, operator) || _isOpenSeaProxy(owner, operator);\n }\n\n /// @notice Check if the contract supports an interface.\n /// @param id The id of the interface.\n /// @return Whether the interface is supported.\n function supportsInterface(bytes4 id)\n public\n pure\n virtual\n override(ERC721BaseWithERC4494Permit, IERC165)\n returns (bool)\n {\n return super.supportsInterface(id) || id == 0x2a55205a; /// 0x2a55205a is ERC2981 (royalty standard)\n }\n\n /// @notice Called with the sale price to determine how much royalty is owed and to whom.\n /// @param id - the token queried for royalty information.\n /// @param salePrice - the sale price of the token specified by id.\n /// @return receiver - address of who should be sent the royalty payment.\n /// @return royaltyAmount - the royalty payment amount for salePrice.\n function royaltyInfo(uint256 id, uint256 salePrice)\n external\n view\n returns (address receiver, uint256 royaltyAmount)\n {\n receiver = _royalty.receiver;\n royaltyAmount = (salePrice * uint256(_royalty.per10Thousands)) / 10000;\n }\n\n /// @notice set a new royalty receiver and rate, Can only be set by the `royaltyAdmin`.\n /// @param newReceiver the address that should receive the royalty proceeds.\n /// @param royaltyPer10Thousands the share of the salePrice (in 1/10000) given to the receiver.\n function setRoyaltyParameters(address newReceiver, uint96 royaltyPer10Thousands) external {\n require(msg.sender == royaltyAdmin, \"NOT_AUTHORIZED\");\n // require(royaltyPer10Thousands <= 50, \"ROYALTY_TOO_HIGH\"); ?\n _royalty.receiver = newReceiver;\n _royalty.per10Thousands = royaltyPer10Thousands;\n emit RoyaltySet(newReceiver, royaltyPer10Thousands);\n }\n\n /// @notice disable checkpointing overhead\n /// This can be used if the governance system can switch to use ownerAndLastTransferBlockNumberOf instead of checkpoints\n function disableTheUseOfCheckpoints() external {\n require(msg.sender == checkpointingDisabler, \"NOT_AUTHORIZED\");\n _useCheckpoints = false;\n checkpointingDisabler = address(0);\n emit CheckpointingDisablerSet(address(0));\n emit CheckpointingDisabled();\n }\n\n /// @notice update the address that can disable the use of checkpinting, can be used to disable it entirely.\n /// @param newCheckpointingDisabler new address that can disable the use of checkpointing. can be the zero address to remove the ability to change.\n function setCheckpointingDisabler(address newCheckpointingDisabler) external {\n require(msg.sender == checkpointingDisabler, \"NOT_AUTHORIZED\");\n checkpointingDisabler = newCheckpointingDisabler;\n emit CheckpointingDisablerSet(newCheckpointingDisabler);\n }\n\n /// @notice mint one of bleep if not already minted. Can only be called by `minter`.\n /// @param id bleep id which represent a pair of (note, instrument).\n /// @param to address that will receive the Bleep.\n function mint(uint16 id, address to) external {\n require(msg.sender == minter, \"ONLY_MINTER_ALLOWED\");\n require(id < 1024, \"INVALID_BLEEP\");\n\n require(to != address(0), \"NOT_TO_ZEROADDRESS\");\n require(to != address(this), \"NOT_TO_THIS\");\n address owner = _ownerOf(id);\n require(owner == address(0), \"ALREADY_CREATED\");\n _safeTransferFrom(address(0), to, id, \"\");\n }\n\n /// @notice mint multiple bleeps if not already minted. Can only be called by `minter`.\n /// @param ids list of bleep id which represent each a pair of (note, instrument).\n /// @param tos addresses that will receive the Bleeps. (if only one, use for all)\n function multiMint(uint16[] calldata ids, address[] calldata tos) external {\n require(msg.sender == minter, \"ONLY_MINTER_ALLOWED\");\n\n address to;\n if (tos.length == 1) {\n to = tos[0];\n }\n for (uint256 i = 0; i < ids.length; i++) {\n uint256 id = ids[i];\n if (tos.length > 1) {\n to = tos[i];\n }\n require(to != address(0), \"NOT_TO_ZEROADDRESS\");\n require(to != address(this), \"NOT_TO_THIS\");\n require(id < 1024, \"INVALID_BLEEP\");\n address owner = _ownerOf(id);\n require(owner == address(0), \"ALREADY_CREATED\");\n _safeTransferFrom(address(0), to, id, \"\");\n }\n }\n\n /// @notice gives the note and instrument for a particular Bleep id.\n /// @param id bleep id which represent a pair of (note, instrument).\n /// @return note the note index (0 to 63) starting from C2 to D#7\n /// @return instrument the instrument index (0 to 16). At launch there is only 9 instrument but the DAO could add more (up to 16 in total).\n function sound(uint256 id) external pure returns (uint8 note, uint8 instrument) {\n note = uint8(id & 0x3F);\n instrument = uint8(uint256(id >> 6) & 0x0F);\n }\n}\n" + }, + "@openzeppelin/contracts/utils/cryptography/ECDSA.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.\n *\n * These functions can be used to verify that a message was signed by the holder\n * of the private keys of a given address.\n */\nlibrary ECDSA {\n enum RecoverError {\n NoError,\n InvalidSignature,\n InvalidSignatureLength,\n InvalidSignatureS,\n InvalidSignatureV\n }\n\n function _throwError(RecoverError error) private pure {\n if (error == RecoverError.NoError) {\n return; // no error: do nothing\n } else if (error == RecoverError.InvalidSignature) {\n revert(\"ECDSA: invalid signature\");\n } else if (error == RecoverError.InvalidSignatureLength) {\n revert(\"ECDSA: invalid signature length\");\n } else if (error == RecoverError.InvalidSignatureS) {\n revert(\"ECDSA: invalid signature 's' value\");\n } else if (error == RecoverError.InvalidSignatureV) {\n revert(\"ECDSA: invalid signature 'v' value\");\n }\n }\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with\n * `signature` or error string. This address can then be used for verification purposes.\n *\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {toEthSignedMessageHash} on it.\n *\n * Documentation for signature generation:\n * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]\n * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]\n *\n * _Available since v4.3._\n */\n function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {\n // Check the signature length\n // - case 65: r,s,v signature (standard)\n // - case 64: r,vs signature (cf https://eips.ethereum.org/EIPS/eip-2098) _Available since v4.1._\n if (signature.length == 65) {\n bytes32 r;\n bytes32 s;\n uint8 v;\n // ecrecover takes the signature parameters, and the only way to get them\n // currently is to use assembly.\n assembly {\n r := mload(add(signature, 0x20))\n s := mload(add(signature, 0x40))\n v := byte(0, mload(add(signature, 0x60)))\n }\n return tryRecover(hash, v, r, s);\n } else if (signature.length == 64) {\n bytes32 r;\n bytes32 vs;\n // ecrecover takes the signature parameters, and the only way to get them\n // currently is to use assembly.\n assembly {\n r := mload(add(signature, 0x20))\n vs := mload(add(signature, 0x40))\n }\n return tryRecover(hash, r, vs);\n } else {\n return (address(0), RecoverError.InvalidSignatureLength);\n }\n }\n\n /**\n * @dev Returns the address that signed a hashed message (`hash`) with\n * `signature`. This address can then be used for verification purposes.\n *\n * The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:\n * this function rejects them by requiring the `s` value to be in the lower\n * half order, and the `v` value to be either 27 or 28.\n *\n * IMPORTANT: `hash` _must_ be the result of a hash operation for the\n * verification to be secure: it is possible to craft signatures that\n * recover to arbitrary addresses for non-hashed data. A safe way to ensure\n * this is by receiving a hash of the original message (which may otherwise\n * be too long), and then calling {toEthSignedMessageHash} on it.\n */\n function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, signature);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.\n *\n * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]\n *\n * _Available since v4.3._\n */\n function tryRecover(\n bytes32 hash,\n bytes32 r,\n bytes32 vs\n ) internal pure returns (address, RecoverError) {\n bytes32 s;\n uint8 v;\n assembly {\n s := and(vs, 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff)\n v := add(shr(255, vs), 27)\n }\n return tryRecover(hash, v, r, s);\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.\n *\n * _Available since v4.2._\n */\n function recover(\n bytes32 hash,\n bytes32 r,\n bytes32 vs\n ) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, r, vs);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Overload of {ECDSA-tryRecover} that receives the `v`,\n * `r` and `s` signature fields separately.\n *\n * _Available since v4.3._\n */\n function tryRecover(\n bytes32 hash,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal pure returns (address, RecoverError) {\n // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature\n // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines\n // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most\n // signatures from current libraries generate a unique signature with an s-value in the lower half order.\n //\n // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value\n // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or\n // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept\n // these malleable signatures as well.\n if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {\n return (address(0), RecoverError.InvalidSignatureS);\n }\n if (v != 27 && v != 28) {\n return (address(0), RecoverError.InvalidSignatureV);\n }\n\n // If the signature is valid (and not malleable), return the signer address\n address signer = ecrecover(hash, v, r, s);\n if (signer == address(0)) {\n return (address(0), RecoverError.InvalidSignature);\n }\n\n return (signer, RecoverError.NoError);\n }\n\n /**\n * @dev Overload of {ECDSA-recover} that receives the `v`,\n * `r` and `s` signature fields separately.\n */\n function recover(\n bytes32 hash,\n uint8 v,\n bytes32 r,\n bytes32 s\n ) internal pure returns (address) {\n (address recovered, RecoverError error) = tryRecover(hash, v, r, s);\n _throwError(error);\n return recovered;\n }\n\n /**\n * @dev Returns an Ethereum Signed Message, created from a `hash`. This\n * produces hash corresponding to the one signed with the\n * https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]\n * JSON-RPC method as part of EIP-191.\n *\n * See {recover}.\n */\n function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {\n // 32 is the length in bytes of hash,\n // enforced by the type signature above\n return keccak256(abi.encodePacked(\"\\x19Ethereum Signed Message:\\n32\", hash));\n }\n\n /**\n * @dev Returns an Ethereum Signed Typed Data, created from a\n * `domainSeparator` and a `structHash`. This produces hash corresponding\n * to the one signed with the\n * https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]\n * JSON-RPC method as part of EIP-712.\n *\n * See {recover}.\n */\n function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {\n return keccak256(abi.encodePacked(\"\\x19\\x01\", domainSeparator, structHash));\n }\n}\n" + }, + "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport \"./ECDSA.sol\";\nimport \"../Address.sol\";\nimport \"../../interfaces/IERC1271.sol\";\n\n/**\n * @dev Signature verification helper: Provide a single mechanism to verify both private-key (EOA) ECDSA signature and\n * ERC1271 contract sigantures. Using this instead of ECDSA.recover in your contract will make them compatible with\n * smart contract wallets such as Argent and Gnosis.\n *\n * Note: unlike ECDSA signatures, contract signature's are revocable, and the outcome of this function can thus change\n * through time. It could return true at block N and false at block N+1 (or the opposite).\n *\n * _Available since v4.1._\n */\nlibrary SignatureChecker {\n function isValidSignatureNow(\n address signer,\n bytes32 hash,\n bytes memory signature\n ) internal view returns (bool) {\n (address recovered, ECDSA.RecoverError error) = ECDSA.tryRecover(hash, signature);\n if (error == ECDSA.RecoverError.NoError && recovered == signer) {\n return true;\n }\n\n (bool success, bytes memory result) = signer.staticcall(\n abi.encodeWithSelector(IERC1271.isValidSignature.selector, hash, signature)\n );\n return (success && result.length == 32 && abi.decode(result, (bytes4)) == IERC1271.isValidSignature.selector);\n }\n}\n" + }, + "src/base/ERC721BaseWithERC4494Permit.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity 0.8.9;\n\nimport \"./ERC721Base.sol\";\nimport \"@openzeppelin/contracts/utils/Address.sol\";\nimport \"@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol\";\nimport \"./IERC4494.sol\";\n\nabstract contract ERC721BaseWithERC4494Permit is ERC721Base {\n using Address for address;\n\n bytes32 public constant PERMIT_TYPEHASH =\n keccak256(\"Permit(address spender,uint256 tokenId,uint256 nonce,uint256 deadline)\");\n bytes32 public constant PERMIT_FOR_ALL_TYPEHASH =\n keccak256(\"PermitForAll(address spender,uint256 nonce,uint256 deadline)\");\n bytes32 public constant DOMAIN_TYPEHASH =\n keccak256(\"EIP712Domain(string name,uint256 chainId,address verifyingContract)\");\n\n uint256 private immutable _deploymentChainId;\n bytes32 private immutable _deploymentDomainSeparator;\n\n mapping(address => uint256) internal _userNonces;\n\n constructor() {\n uint256 chainId;\n //solhint-disable-next-line no-inline-assembly\n assembly {\n chainId := chainid()\n }\n _deploymentChainId = chainId;\n _deploymentDomainSeparator = _calculateDomainSeparator(chainId);\n }\n\n /// @dev Return the DOMAIN_SEPARATOR.\n function DOMAIN_SEPARATOR() external view returns (bytes32) {\n return _DOMAIN_SEPARATOR();\n }\n\n /// @notice return the account nonce, used for approvalForAll permit or other account related matter\n /// @param account the account to query\n /// @return nonce\n function nonces(address account) external view virtual returns (uint256 nonce) {\n return _userNonces[account];\n }\n\n /// @notice return the token nonce, used for individual approve permit or other token related matter\n /// @param id token id to query\n /// @return nonce\n function nonces(uint256 id) public view virtual returns (uint256 nonce) {\n (address owner, uint256 blockNumber) = _ownerAndBlockNumberOf(id);\n require(owner != address(0), \"NONEXISTENT_TOKEN\");\n return blockNumber;\n }\n\n /// @notice return the token nonce, used for individual approve permit or other token related matter\n /// @param id token id to query\n /// @return nonce\n function tokenNonces(uint256 id) external view returns (uint256 nonce) {\n return nonces(id);\n }\n\n function permit(\n address spender,\n uint256 tokenId,\n uint256 deadline,\n bytes memory sig\n ) external {\n require(deadline >= block.timestamp, \"PERMIT_DEADLINE_EXPIRED\");\n\n (address owner, uint256 blockNumber) = _ownerAndBlockNumberOf(tokenId);\n require(owner != address(0), \"NONEXISTENT_TOKEN\");\n\n // We use blockNumber as nonce as we already store it per tokens. It can thus act as an increasing transfer counter.\n // while technically multiple transfer could happen in the same block, the signed message would be using a previous block.\n // And the transfer would use then a more recent blockNumber, invalidating that message when transfer is executed.\n _requireValidPermit(owner, spender, tokenId, deadline, blockNumber, sig);\n\n _approveFor(owner, blockNumber, spender, tokenId);\n }\n\n function permitForAll(\n address signer,\n address spender,\n uint256 deadline,\n bytes memory sig\n ) external {\n require(deadline >= block.timestamp, \"PERMIT_DEADLINE_EXPIRED\");\n\n _requireValidPermitForAll(signer, spender, deadline, _userNonces[signer]++, sig);\n\n _setApprovalForAll(signer, spender, true);\n }\n\n /// @notice Check if the contract supports an interface.\n /// @param id The id of the interface.\n /// @return Whether the interface is supported.\n function supportsInterface(bytes4 id) public pure virtual override returns (bool) {\n return\n super.supportsInterface(id) ||\n id == type(IERC4494).interfaceId ||\n id == type(IERC4494Alternative).interfaceId;\n }\n\n // -------------------------------------------------------- INTERNAL --------------------------------------------------------------------\n\n function _requireValidPermit(\n address signer,\n address spender,\n uint256 id,\n uint256 deadline,\n uint256 nonce,\n bytes memory sig\n ) internal view {\n bytes32 digest = keccak256(\n abi.encodePacked(\n \"\\x19\\x01\",\n _DOMAIN_SEPARATOR(),\n keccak256(abi.encode(PERMIT_TYPEHASH, spender, id, nonce, deadline))\n )\n );\n require(SignatureChecker.isValidSignatureNow(signer, digest, sig), \"INVALID_SIGNATURE\");\n }\n\n function _requireValidPermitForAll(\n address signer,\n address spender,\n uint256 deadline,\n uint256 nonce,\n bytes memory sig\n ) internal view {\n bytes32 digest = keccak256(\n abi.encodePacked(\n \"\\x19\\x01\",\n _DOMAIN_SEPARATOR(),\n keccak256(abi.encode(PERMIT_FOR_ALL_TYPEHASH, spender, nonce, deadline))\n )\n );\n require(SignatureChecker.isValidSignatureNow(signer, digest, sig), \"INVALID_SIGNATURE\");\n }\n\n /// @dev Return the DOMAIN_SEPARATOR.\n function _DOMAIN_SEPARATOR() internal view returns (bytes32) {\n uint256 chainId;\n //solhint-disable-next-line no-inline-assembly\n assembly {\n chainId := chainid()\n }\n\n // in case a fork happen, to support the chain that had to change its chainId,, we compute the domain operator\n return chainId == _deploymentChainId ? _deploymentDomainSeparator : _calculateDomainSeparator(chainId);\n }\n\n /// @dev Calculate the DOMAIN_SEPARATOR.\n function _calculateDomainSeparator(uint256 chainId) private view returns (bytes32) {\n return keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name())), chainId, address(this)));\n }\n}\n" + }, + "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol": { + "content": "// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @title ERC721 token receiver interface\n * @dev Interface for any contract that wants to support safeTransfers\n * from ERC721 asset contracts.\n */\ninterface IERC721Receiver {\n /**\n * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}\n * by `operator` from `from`, this function is called.\n *\n * It must return its Solidity selector to confirm the token transfer.\n * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.\n *\n * The selector can be obtained in Solidity with `IERC721.onERC721Received.selector`.\n */\n function onERC721Received(\n address operator,\n address from,\n uint256 tokenId,\n bytes calldata data\n ) external returns (bytes4);\n}\n" + } + }, + "settings": { + "evmVersion": "london", + "libraries": {}, + "metadata": { + "bytecodeHash": "ipfs", + "useLiteralContent": true + }, + "optimizer": { + "enabled": true, + "runs": 999999 + }, + "remappings": [], + "outputSelection": { + "*": { + "*": [ + "evm.bytecode", + "evm.deployedBytecode", + "devdoc", + "userdoc", + "metadata", + "abi" + ] + } + } + } + }, + "ABI": "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"ens\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"initialOwner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"initialTokenURIAdmin\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"initialMinterAdmin\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"initialRoyaltyAdmin\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"initialGuardian\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"openseaProxyRegistry\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"initialRoyaltyReceiver\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"imitialRoyaltyPer10Thousands\",\"type\":\"uint96\"},{\"internalType\":\"contract ITokenURI\",\"name\":\"initialTokenURIContract\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"initialCheckpointingDisabler\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"approved\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"approved\",\"type\":\"bool\"}],\"name\":\"ApprovalForAll\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"CheckpointingDisabled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newCheckpointingDisabler\",\"type\":\"address\"}],\"name\":\"CheckpointingDisablerSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"delegator\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"fromDelegate\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"toDelegate\",\"type\":\"address\"}],\"name\":\"DelegateChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"delegate\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"previousBalance\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"newBalance\",\"type\":\"uint256\"}],\"name\":\"DelegateVotesChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newGuardian\",\"type\":\"address\"}],\"name\":\"GuardianSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newMinterAdmin\",\"type\":\"address\"}],\"name\":\"MinterAdminSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newMinter\",\"type\":\"address\"}],\"name\":\"MinterSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newRoyaltyAdmin\",\"type\":\"address\"}],\"name\":\"RoyaltyAdminSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"royaltyPer10Thousands\",\"type\":\"uint256\"}],\"name\":\"RoyaltySet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"newTokenURIAdmin\",\"type\":\"address\"}],\"name\":\"TokenURIAdminSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"contract ITokenURI\",\"name\":\"newTokenURIContract\",\"type\":\"address\"}],\"name\":\"TokenURIContractSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"DELEGATION_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DOMAIN_SEPARATOR\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DOMAIN_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"PERMIT_FOR_ALL_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"PERMIT_TYPEHASH\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"checkpointingDisabler\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"name\":\"checkpoints\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"fromBlock\",\"type\":\"uint32\"},{\"internalType\":\"uint96\",\"name\":\"votes\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"contractURI\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"decimals\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"delegatee\",\"type\":\"address\"}],\"name\":\"delegate\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"delegatee\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"expiry\",\"type\":\"uint256\"},{\"internalType\":\"uint8\",\"name\":\"v\",\"type\":\"uint8\"},{\"internalType\":\"bytes32\",\"name\":\"r\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"s\",\"type\":\"bytes32\"}],\"name\":\"delegateBySig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"delegator\",\"type\":\"address\"}],\"name\":\"delegates\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"disableTheUseOfCheckpoints\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"getApproved\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"getCurrentVotes\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"name\":\"getPastVotes\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"name\":\"getPriorVotes\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"getVotes\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"guardian\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"}],\"name\":\"isApprovedForAll\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"isOperator\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint16\",\"name\":\"id\",\"type\":\"uint16\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"mint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"minter\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"minterAdmin\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint16[]\",\"name\":\"ids\",\"type\":\"uint16[]\"},{\"internalType\":\"address[]\",\"name\":\"tos\",\"type\":\"address[]\"}],\"name\":\"multiMint\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes[]\",\"name\":\"data\",\"type\":\"bytes[]\"}],\"name\":\"multicall\",\"outputs\":[{\"internalType\":\"bytes[]\",\"name\":\"results\",\"type\":\"bytes[]\"}],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"nonces\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"nonces\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"numCheckpoints\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[]\",\"name\":\"ids\",\"type\":\"uint256[]\"}],\"name\":\"ownerAndLastTransferBlockNumberList\",\"outputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"lastTransferBlockNumber\",\"type\":\"uint256\"}],\"internalType\":\"struct ERC721Base.OwnerData[]\",\"name\":\"ownersData\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"ownerOf\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256[]\",\"name\":\"ids\",\"type\":\"uint256[]\"}],\"name\":\"owners\",\"outputs\":[{\"internalType\":\"address[]\",\"name\":\"addresses\",\"type\":\"address[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"tokenId\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"sig\",\"type\":\"bytes\"}],\"name\":\"permit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"signer\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"sig\",\"type\":\"bytes\"}],\"name\":\"permitForAll\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"royaltyAdmin\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"salePrice\",\"type\":\"uint256\"}],\"name\":\"royaltyInfo\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"receiver\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"royaltyAmount\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"safeTransferFrom\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"safeTransferFrom\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"approved\",\"type\":\"bool\"}],\"name\":\"setApprovalForAll\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newCheckpointingDisabler\",\"type\":\"address\"}],\"name\":\"setCheckpointingDisabler\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"}],\"name\":\"setENSName\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newGuardian\",\"type\":\"address\"}],\"name\":\"setGuardian\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newMinter\",\"type\":\"address\"}],\"name\":\"setMinter\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newMinterAdmin\",\"type\":\"address\"}],\"name\":\"setMinterAdmin\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newRoyaltyAdmin\",\"type\":\"address\"}],\"name\":\"setRoyaltyAdmin\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newReceiver\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"royaltyPer10Thousands\",\"type\":\"uint96\"}],\"name\":\"setRoyaltyParameters\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newTokenURIAdmin\",\"type\":\"address\"}],\"name\":\"setTokenURIAdmin\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract ITokenURI\",\"name\":\"newTokenURIContract\",\"type\":\"address\"}],\"name\":\"setTokenURIContract\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"sound\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"note\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"instrument\",\"type\":\"uint8\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"id\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"symbol\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"tokenNonces\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"tokenURI\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"tokenURIAdmin\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"tokenURIContract\",\"outputs\":[{\"internalType\":\"contract ITokenURI\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"delegator\",\"type\":\"address\"}],\"name\":\"votesToDelegate\",\"outputs\":[{\"internalType\":\"uint96\",\"name\":\"\",\"type\":\"uint96\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"}],\"name\":\"withdrawERC20\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + "ContractName": "Bleeps", + "CompilerVersion": "v0.8.9+commit.e5eed63a", + "OptimizationUsed": 1, + "Runs": 999999, + "ConstructorArguments": "0x00000000000000000000000000000000000c2e074ec69a0dfb2997ba6c7d2e1e0000000000000000000000007773ae67403d2e30102a84c48cc939919c4c881c000000000000000000000000dca9d1fa839bb9fe65ddc4de5161bca43751d4b40000000000000000000000007773ae67403d2e30102a84c48cc939919c4c881c000000000000000000000000dca9d1fa839bb9fe65ddc4de5161bca43751d4b4000000000000000000000000dca9d1fa839bb9fe65ddc4de5161bca43751d4b4000000000000000000000000a5409ec958c83c3f309868babaca7c86dcb077c10000000000000000000000008350c9989ef11325b36ce6f7549004d418dbcee700000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000e114dce59a333f8d351371f54188f92c287b73e6000000000000000000000000dca9d1fa839bb9fe65ddc4de5161bca43751d4b4", + "EVMVersion": "Default", + "Library": "", + "LicenseType": "MIT", + "Proxy": 0, + "SwarmSource": "" + } +] \ No newline at end of file diff --git a/testdata/etherscan/0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec/creation_data.json b/testdata/etherscan/0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec/creation_data.json new file mode 100644 index 000000000..646ea065a --- /dev/null +++ b/testdata/etherscan/0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec/creation_data.json @@ -0,0 +1 @@ +{"contractAddress":"0xdb53f47ac61fe54f456a4eb3e09832d08dd7beec","contractCreator":"0xc7f8d87734ab2cbf70030ac8aa82abfe3e8126cb","txHash":"0x196898c69f6b1944f1011120b15c0903329d46259c8cdc0fbcad71da1fe58245"} \ No newline at end of file diff --git a/testdata/etherscan/0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec/metadata.json b/testdata/etherscan/0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec/metadata.json new file mode 100644 index 000000000..df45d9be3 --- /dev/null +++ b/testdata/etherscan/0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec/metadata.json @@ -0,0 +1 @@ +[{"SourceCode":{"language":"Solidity","sources":{"@solidstate/contracts/token/ERC1155/base/ERC1155Base.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { IERC1155 } from '../IERC1155.sol';\nimport { IERC1155Receiver } from '../IERC1155Receiver.sol';\nimport { ERC1155BaseInternal, ERC1155BaseStorage } from './ERC1155BaseInternal.sol';\n\n/**\n * @title Base ERC1155 contract\n * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts/ (MIT license)\n */\nabstract contract ERC1155Base is IERC1155, ERC1155BaseInternal {\n /**\n * @inheritdoc IERC1155\n */\n function balanceOf(address account, uint256 id)\n public\n view\n virtual\n override\n returns (uint256)\n {\n return _balanceOf(account, id);\n }\n\n /**\n * @inheritdoc IERC1155\n */\n function balanceOfBatch(address[] memory accounts, uint256[] memory ids)\n public\n view\n virtual\n override\n returns (uint256[] memory)\n {\n require(\n accounts.length == ids.length,\n 'ERC1155: accounts and ids length mismatch'\n );\n\n mapping(uint256 => mapping(address => uint256))\n storage balances = ERC1155BaseStorage.layout().balances;\n\n uint256[] memory batchBalances = new uint256[](accounts.length);\n\n unchecked {\n for (uint256 i; i < accounts.length; i++) {\n require(\n accounts[i] != address(0),\n 'ERC1155: batch balance query for the zero address'\n );\n batchBalances[i] = balances[ids[i]][accounts[i]];\n }\n }\n\n return batchBalances;\n }\n\n /**\n * @inheritdoc IERC1155\n */\n function isApprovedForAll(address account, address operator)\n public\n view\n virtual\n override\n returns (bool)\n {\n return ERC1155BaseStorage.layout().operatorApprovals[account][operator];\n }\n\n /**\n * @inheritdoc IERC1155\n */\n function setApprovalForAll(address operator, bool status)\n public\n virtual\n override\n {\n require(\n msg.sender != operator,\n 'ERC1155: setting approval status for self'\n );\n ERC1155BaseStorage.layout().operatorApprovals[msg.sender][\n operator\n ] = status;\n emit ApprovalForAll(msg.sender, operator, status);\n }\n\n /**\n * @inheritdoc IERC1155\n */\n function safeTransferFrom(\n address from,\n address to,\n uint256 id,\n uint256 amount,\n bytes memory data\n ) public virtual override {\n require(\n from == msg.sender || isApprovedForAll(from, msg.sender),\n 'ERC1155: caller is not owner nor approved'\n );\n _safeTransfer(msg.sender, from, to, id, amount, data);\n }\n\n /**\n * @inheritdoc IERC1155\n */\n function safeBatchTransferFrom(\n address from,\n address to,\n uint256[] memory ids,\n uint256[] memory amounts,\n bytes memory data\n ) public virtual override {\n require(\n from == msg.sender || isApprovedForAll(from, msg.sender),\n 'ERC1155: caller is not owner nor approved'\n );\n _safeTransferBatch(msg.sender, from, to, ids, amounts, data);\n }\n}\n"},"@solidstate/contracts/introspection/IERC165.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @title ERC165 interface registration interface\n * @dev see https://eips.ethereum.org/EIPS/eip-165\n */\ninterface IERC165 {\n /**\n * @notice query whether contract has registered support for given interface\n * @param interfaceId interface id\n * @return bool whether interface is supported\n */\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\n}\n"},"abdk-libraries-solidity/ABDKMath64x64.sol":{"content":"// SPDX-License-Identifier: BSD-4-Clause\n/*\n * ABDK Math 64.64 Smart Contract Library. Copyright © 2019 by ABDK Consulting.\n * Author: Mikhail Vladimirov \n */\npragma solidity ^0.8.0;\n\n/**\n * Smart contract library of mathematical functions operating with signed\n * 64.64-bit fixed point numbers. Signed 64.64-bit fixed point number is\n * basically a simple fraction whose numerator is signed 128-bit integer and\n * denominator is 2^64. As long as denominator is always the same, there is no\n * need to store it, thus in Solidity signed 64.64-bit fixed point numbers are\n * represented by int128 type holding only the numerator.\n */\nlibrary ABDKMath64x64 {\n /*\n * Minimum value signed 64.64-bit fixed point number may have. \n */\n int128 private constant MIN_64x64 = -0x80000000000000000000000000000000;\n\n /*\n * Maximum value signed 64.64-bit fixed point number may have. \n */\n int128 private constant MAX_64x64 = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF;\n\n /**\n * Convert signed 256-bit integer number into signed 64.64-bit fixed point\n * number. Revert on overflow.\n *\n * @param x signed 256-bit integer number\n * @return signed 64.64-bit fixed point number\n */\n function fromInt (int256 x) internal pure returns (int128) {\n unchecked {\n require (x >= -0x8000000000000000 && x <= 0x7FFFFFFFFFFFFFFF);\n return int128 (x << 64);\n }\n }\n\n /**\n * Convert signed 64.64 fixed point number into signed 64-bit integer number\n * rounding down.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 64-bit integer number\n */\n function toInt (int128 x) internal pure returns (int64) {\n unchecked {\n return int64 (x >> 64);\n }\n }\n\n /**\n * Convert unsigned 256-bit integer number into signed 64.64-bit fixed point\n * number. Revert on overflow.\n *\n * @param x unsigned 256-bit integer number\n * @return signed 64.64-bit fixed point number\n */\n function fromUInt (uint256 x) internal pure returns (int128) {\n unchecked {\n require (x <= 0x7FFFFFFFFFFFFFFF);\n return int128 (int256 (x << 64));\n }\n }\n\n /**\n * Convert signed 64.64 fixed point number into unsigned 64-bit integer\n * number rounding down. Revert on underflow.\n *\n * @param x signed 64.64-bit fixed point number\n * @return unsigned 64-bit integer number\n */\n function toUInt (int128 x) internal pure returns (uint64) {\n unchecked {\n require (x >= 0);\n return uint64 (uint128 (x >> 64));\n }\n }\n\n /**\n * Convert signed 128.128 fixed point number into signed 64.64-bit fixed point\n * number rounding down. Revert on overflow.\n *\n * @param x signed 128.128-bin fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function from128x128 (int256 x) internal pure returns (int128) {\n unchecked {\n int256 result = x >> 64;\n require (result >= MIN_64x64 && result <= MAX_64x64);\n return int128 (result);\n }\n }\n\n /**\n * Convert signed 64.64 fixed point number into signed 128.128 fixed point\n * number.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 128.128 fixed point number\n */\n function to128x128 (int128 x) internal pure returns (int256) {\n unchecked {\n return int256 (x) << 64;\n }\n }\n\n /**\n * Calculate x + y. Revert on overflow.\n *\n * @param x signed 64.64-bit fixed point number\n * @param y signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function add (int128 x, int128 y) internal pure returns (int128) {\n unchecked {\n int256 result = int256(x) + y;\n require (result >= MIN_64x64 && result <= MAX_64x64);\n return int128 (result);\n }\n }\n\n /**\n * Calculate x - y. Revert on overflow.\n *\n * @param x signed 64.64-bit fixed point number\n * @param y signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function sub (int128 x, int128 y) internal pure returns (int128) {\n unchecked {\n int256 result = int256(x) - y;\n require (result >= MIN_64x64 && result <= MAX_64x64);\n return int128 (result);\n }\n }\n\n /**\n * Calculate x * y rounding down. Revert on overflow.\n *\n * @param x signed 64.64-bit fixed point number\n * @param y signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function mul (int128 x, int128 y) internal pure returns (int128) {\n unchecked {\n int256 result = int256(x) * y >> 64;\n require (result >= MIN_64x64 && result <= MAX_64x64);\n return int128 (result);\n }\n }\n\n /**\n * Calculate x * y rounding towards zero, where x is signed 64.64 fixed point\n * number and y is signed 256-bit integer number. Revert on overflow.\n *\n * @param x signed 64.64 fixed point number\n * @param y signed 256-bit integer number\n * @return signed 256-bit integer number\n */\n function muli (int128 x, int256 y) internal pure returns (int256) {\n unchecked {\n if (x == MIN_64x64) {\n require (y >= -0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF &&\n y <= 0x1000000000000000000000000000000000000000000000000);\n return -y << 63;\n } else {\n bool negativeResult = false;\n if (x < 0) {\n x = -x;\n negativeResult = true;\n }\n if (y < 0) {\n y = -y; // We rely on overflow behavior here\n negativeResult = !negativeResult;\n }\n uint256 absoluteResult = mulu (x, uint256 (y));\n if (negativeResult) {\n require (absoluteResult <=\n 0x8000000000000000000000000000000000000000000000000000000000000000);\n return -int256 (absoluteResult); // We rely on overflow behavior here\n } else {\n require (absoluteResult <=\n 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);\n return int256 (absoluteResult);\n }\n }\n }\n }\n\n /**\n * Calculate x * y rounding down, where x is signed 64.64 fixed point number\n * and y is unsigned 256-bit integer number. Revert on overflow.\n *\n * @param x signed 64.64 fixed point number\n * @param y unsigned 256-bit integer number\n * @return unsigned 256-bit integer number\n */\n function mulu (int128 x, uint256 y) internal pure returns (uint256) {\n unchecked {\n if (y == 0) return 0;\n\n require (x >= 0);\n\n uint256 lo = (uint256 (int256 (x)) * (y & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) >> 64;\n uint256 hi = uint256 (int256 (x)) * (y >> 128);\n\n require (hi <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);\n hi <<= 64;\n\n require (hi <=\n 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - lo);\n return hi + lo;\n }\n }\n\n /**\n * Calculate x / y rounding towards zero. Revert on overflow or when y is\n * zero.\n *\n * @param x signed 64.64-bit fixed point number\n * @param y signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function div (int128 x, int128 y) internal pure returns (int128) {\n unchecked {\n require (y != 0);\n int256 result = (int256 (x) << 64) / y;\n require (result >= MIN_64x64 && result <= MAX_64x64);\n return int128 (result);\n }\n }\n\n /**\n * Calculate x / y rounding towards zero, where x and y are signed 256-bit\n * integer numbers. Revert on overflow or when y is zero.\n *\n * @param x signed 256-bit integer number\n * @param y signed 256-bit integer number\n * @return signed 64.64-bit fixed point number\n */\n function divi (int256 x, int256 y) internal pure returns (int128) {\n unchecked {\n require (y != 0);\n\n bool negativeResult = false;\n if (x < 0) {\n x = -x; // We rely on overflow behavior here\n negativeResult = true;\n }\n if (y < 0) {\n y = -y; // We rely on overflow behavior here\n negativeResult = !negativeResult;\n }\n uint128 absoluteResult = divuu (uint256 (x), uint256 (y));\n if (negativeResult) {\n require (absoluteResult <= 0x80000000000000000000000000000000);\n return -int128 (absoluteResult); // We rely on overflow behavior here\n } else {\n require (absoluteResult <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);\n return int128 (absoluteResult); // We rely on overflow behavior here\n }\n }\n }\n\n /**\n * Calculate x / y rounding towards zero, where x and y are unsigned 256-bit\n * integer numbers. Revert on overflow or when y is zero.\n *\n * @param x unsigned 256-bit integer number\n * @param y unsigned 256-bit integer number\n * @return signed 64.64-bit fixed point number\n */\n function divu (uint256 x, uint256 y) internal pure returns (int128) {\n unchecked {\n require (y != 0);\n uint128 result = divuu (x, y);\n require (result <= uint128 (MAX_64x64));\n return int128 (result);\n }\n }\n\n /**\n * Calculate -x. Revert on overflow.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function neg (int128 x) internal pure returns (int128) {\n unchecked {\n require (x != MIN_64x64);\n return -x;\n }\n }\n\n /**\n * Calculate |x|. Revert on overflow.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function abs (int128 x) internal pure returns (int128) {\n unchecked {\n require (x != MIN_64x64);\n return x < 0 ? -x : x;\n }\n }\n\n /**\n * Calculate 1 / x rounding towards zero. Revert on overflow or when x is\n * zero.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function inv (int128 x) internal pure returns (int128) {\n unchecked {\n require (x != 0);\n int256 result = int256 (0x100000000000000000000000000000000) / x;\n require (result >= MIN_64x64 && result <= MAX_64x64);\n return int128 (result);\n }\n }\n\n /**\n * Calculate arithmetics average of x and y, i.e. (x + y) / 2 rounding down.\n *\n * @param x signed 64.64-bit fixed point number\n * @param y signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function avg (int128 x, int128 y) internal pure returns (int128) {\n unchecked {\n return int128 ((int256 (x) + int256 (y)) >> 1);\n }\n }\n\n /**\n * Calculate geometric average of x and y, i.e. sqrt (x * y) rounding down.\n * Revert on overflow or in case x * y is negative.\n *\n * @param x signed 64.64-bit fixed point number\n * @param y signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function gavg (int128 x, int128 y) internal pure returns (int128) {\n unchecked {\n int256 m = int256 (x) * int256 (y);\n require (m >= 0);\n require (m <\n 0x4000000000000000000000000000000000000000000000000000000000000000);\n return int128 (sqrtu (uint256 (m)));\n }\n }\n\n /**\n * Calculate x^y assuming 0^0 is 1, where x is signed 64.64 fixed point number\n * and y is unsigned 256-bit integer number. Revert on overflow.\n *\n * @param x signed 64.64-bit fixed point number\n * @param y uint256 value\n * @return signed 64.64-bit fixed point number\n */\n function pow (int128 x, uint256 y) internal pure returns (int128) {\n unchecked {\n bool negative = x < 0 && y & 1 == 1;\n\n uint256 absX = uint128 (x < 0 ? -x : x);\n uint256 absResult;\n absResult = 0x100000000000000000000000000000000;\n\n if (absX <= 0x10000000000000000) {\n absX <<= 63;\n while (y != 0) {\n if (y & 0x1 != 0) {\n absResult = absResult * absX >> 127;\n }\n absX = absX * absX >> 127;\n\n if (y & 0x2 != 0) {\n absResult = absResult * absX >> 127;\n }\n absX = absX * absX >> 127;\n\n if (y & 0x4 != 0) {\n absResult = absResult * absX >> 127;\n }\n absX = absX * absX >> 127;\n\n if (y & 0x8 != 0) {\n absResult = absResult * absX >> 127;\n }\n absX = absX * absX >> 127;\n\n y >>= 4;\n }\n\n absResult >>= 64;\n } else {\n uint256 absXShift = 63;\n if (absX < 0x1000000000000000000000000) { absX <<= 32; absXShift -= 32; }\n if (absX < 0x10000000000000000000000000000) { absX <<= 16; absXShift -= 16; }\n if (absX < 0x1000000000000000000000000000000) { absX <<= 8; absXShift -= 8; }\n if (absX < 0x10000000000000000000000000000000) { absX <<= 4; absXShift -= 4; }\n if (absX < 0x40000000000000000000000000000000) { absX <<= 2; absXShift -= 2; }\n if (absX < 0x80000000000000000000000000000000) { absX <<= 1; absXShift -= 1; }\n\n uint256 resultShift = 0;\n while (y != 0) {\n require (absXShift < 64);\n\n if (y & 0x1 != 0) {\n absResult = absResult * absX >> 127;\n resultShift += absXShift;\n if (absResult > 0x100000000000000000000000000000000) {\n absResult >>= 1;\n resultShift += 1;\n }\n }\n absX = absX * absX >> 127;\n absXShift <<= 1;\n if (absX >= 0x100000000000000000000000000000000) {\n absX >>= 1;\n absXShift += 1;\n }\n\n y >>= 1;\n }\n\n require (resultShift < 64);\n absResult >>= 64 - resultShift;\n }\n int256 result = negative ? -int256 (absResult) : int256 (absResult);\n require (result >= MIN_64x64 && result <= MAX_64x64);\n return int128 (result);\n }\n }\n\n /**\n * Calculate sqrt (x) rounding down. Revert if x < 0.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function sqrt (int128 x) internal pure returns (int128) {\n unchecked {\n require (x >= 0);\n return int128 (sqrtu (uint256 (int256 (x)) << 64));\n }\n }\n\n /**\n * Calculate binary logarithm of x. Revert if x <= 0.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function log_2 (int128 x) internal pure returns (int128) {\n unchecked {\n require (x > 0);\n\n int256 msb = 0;\n int256 xc = x;\n if (xc >= 0x10000000000000000) { xc >>= 64; msb += 64; }\n if (xc >= 0x100000000) { xc >>= 32; msb += 32; }\n if (xc >= 0x10000) { xc >>= 16; msb += 16; }\n if (xc >= 0x100) { xc >>= 8; msb += 8; }\n if (xc >= 0x10) { xc >>= 4; msb += 4; }\n if (xc >= 0x4) { xc >>= 2; msb += 2; }\n if (xc >= 0x2) msb += 1; // No need to shift xc anymore\n\n int256 result = msb - 64 << 64;\n uint256 ux = uint256 (int256 (x)) << uint256 (127 - msb);\n for (int256 bit = 0x8000000000000000; bit > 0; bit >>= 1) {\n ux *= ux;\n uint256 b = ux >> 255;\n ux >>= 127 + b;\n result += bit * int256 (b);\n }\n\n return int128 (result);\n }\n }\n\n /**\n * Calculate natural logarithm of x. Revert if x <= 0.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function ln (int128 x) internal pure returns (int128) {\n unchecked {\n require (x > 0);\n\n return int128 (int256 (\n uint256 (int256 (log_2 (x))) * 0xB17217F7D1CF79ABC9E3B39803F2F6AF >> 128));\n }\n }\n\n /**\n * Calculate binary exponent of x. Revert on overflow.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function exp_2 (int128 x) internal pure returns (int128) {\n unchecked {\n require (x < 0x400000000000000000); // Overflow\n\n if (x < -0x400000000000000000) return 0; // Underflow\n\n uint256 result = 0x80000000000000000000000000000000;\n\n if (x & 0x8000000000000000 > 0)\n result = result * 0x16A09E667F3BCC908B2FB1366EA957D3E >> 128;\n if (x & 0x4000000000000000 > 0)\n result = result * 0x1306FE0A31B7152DE8D5A46305C85EDEC >> 128;\n if (x & 0x2000000000000000 > 0)\n result = result * 0x1172B83C7D517ADCDF7C8C50EB14A791F >> 128;\n if (x & 0x1000000000000000 > 0)\n result = result * 0x10B5586CF9890F6298B92B71842A98363 >> 128;\n if (x & 0x800000000000000 > 0)\n result = result * 0x1059B0D31585743AE7C548EB68CA417FD >> 128;\n if (x & 0x400000000000000 > 0)\n result = result * 0x102C9A3E778060EE6F7CACA4F7A29BDE8 >> 128;\n if (x & 0x200000000000000 > 0)\n result = result * 0x10163DA9FB33356D84A66AE336DCDFA3F >> 128;\n if (x & 0x100000000000000 > 0)\n result = result * 0x100B1AFA5ABCBED6129AB13EC11DC9543 >> 128;\n if (x & 0x80000000000000 > 0)\n result = result * 0x10058C86DA1C09EA1FF19D294CF2F679B >> 128;\n if (x & 0x40000000000000 > 0)\n result = result * 0x1002C605E2E8CEC506D21BFC89A23A00F >> 128;\n if (x & 0x20000000000000 > 0)\n result = result * 0x100162F3904051FA128BCA9C55C31E5DF >> 128;\n if (x & 0x10000000000000 > 0)\n result = result * 0x1000B175EFFDC76BA38E31671CA939725 >> 128;\n if (x & 0x8000000000000 > 0)\n result = result * 0x100058BA01FB9F96D6CACD4B180917C3D >> 128;\n if (x & 0x4000000000000 > 0)\n result = result * 0x10002C5CC37DA9491D0985C348C68E7B3 >> 128;\n if (x & 0x2000000000000 > 0)\n result = result * 0x1000162E525EE054754457D5995292026 >> 128;\n if (x & 0x1000000000000 > 0)\n result = result * 0x10000B17255775C040618BF4A4ADE83FC >> 128;\n if (x & 0x800000000000 > 0)\n result = result * 0x1000058B91B5BC9AE2EED81E9B7D4CFAB >> 128;\n if (x & 0x400000000000 > 0)\n result = result * 0x100002C5C89D5EC6CA4D7C8ACC017B7C9 >> 128;\n if (x & 0x200000000000 > 0)\n result = result * 0x10000162E43F4F831060E02D839A9D16D >> 128;\n if (x & 0x100000000000 > 0)\n result = result * 0x100000B1721BCFC99D9F890EA06911763 >> 128;\n if (x & 0x80000000000 > 0)\n result = result * 0x10000058B90CF1E6D97F9CA14DBCC1628 >> 128;\n if (x & 0x40000000000 > 0)\n result = result * 0x1000002C5C863B73F016468F6BAC5CA2B >> 128;\n if (x & 0x20000000000 > 0)\n result = result * 0x100000162E430E5A18F6119E3C02282A5 >> 128;\n if (x & 0x10000000000 > 0)\n result = result * 0x1000000B1721835514B86E6D96EFD1BFE >> 128;\n if (x & 0x8000000000 > 0)\n result = result * 0x100000058B90C0B48C6BE5DF846C5B2EF >> 128;\n if (x & 0x4000000000 > 0)\n result = result * 0x10000002C5C8601CC6B9E94213C72737A >> 128;\n if (x & 0x2000000000 > 0)\n result = result * 0x1000000162E42FFF037DF38AA2B219F06 >> 128;\n if (x & 0x1000000000 > 0)\n result = result * 0x10000000B17217FBA9C739AA5819F44F9 >> 128;\n if (x & 0x800000000 > 0)\n result = result * 0x1000000058B90BFCDEE5ACD3C1CEDC823 >> 128;\n if (x & 0x400000000 > 0)\n result = result * 0x100000002C5C85FE31F35A6A30DA1BE50 >> 128;\n if (x & 0x200000000 > 0)\n result = result * 0x10000000162E42FF0999CE3541B9FFFCF >> 128;\n if (x & 0x100000000 > 0)\n result = result * 0x100000000B17217F80F4EF5AADDA45554 >> 128;\n if (x & 0x80000000 > 0)\n result = result * 0x10000000058B90BFBF8479BD5A81B51AD >> 128;\n if (x & 0x40000000 > 0)\n result = result * 0x1000000002C5C85FDF84BD62AE30A74CC >> 128;\n if (x & 0x20000000 > 0)\n result = result * 0x100000000162E42FEFB2FED257559BDAA >> 128;\n if (x & 0x10000000 > 0)\n result = result * 0x1000000000B17217F7D5A7716BBA4A9AE >> 128;\n if (x & 0x8000000 > 0)\n result = result * 0x100000000058B90BFBE9DDBAC5E109CCE >> 128;\n if (x & 0x4000000 > 0)\n result = result * 0x10000000002C5C85FDF4B15DE6F17EB0D >> 128;\n if (x & 0x2000000 > 0)\n result = result * 0x1000000000162E42FEFA494F1478FDE05 >> 128;\n if (x & 0x1000000 > 0)\n result = result * 0x10000000000B17217F7D20CF927C8E94C >> 128;\n if (x & 0x800000 > 0)\n result = result * 0x1000000000058B90BFBE8F71CB4E4B33D >> 128;\n if (x & 0x400000 > 0)\n result = result * 0x100000000002C5C85FDF477B662B26945 >> 128;\n if (x & 0x200000 > 0)\n result = result * 0x10000000000162E42FEFA3AE53369388C >> 128;\n if (x & 0x100000 > 0)\n result = result * 0x100000000000B17217F7D1D351A389D40 >> 128;\n if (x & 0x80000 > 0)\n result = result * 0x10000000000058B90BFBE8E8B2D3D4EDE >> 128;\n if (x & 0x40000 > 0)\n result = result * 0x1000000000002C5C85FDF4741BEA6E77E >> 128;\n if (x & 0x20000 > 0)\n result = result * 0x100000000000162E42FEFA39FE95583C2 >> 128;\n if (x & 0x10000 > 0)\n result = result * 0x1000000000000B17217F7D1CFB72B45E1 >> 128;\n if (x & 0x8000 > 0)\n result = result * 0x100000000000058B90BFBE8E7CC35C3F0 >> 128;\n if (x & 0x4000 > 0)\n result = result * 0x10000000000002C5C85FDF473E242EA38 >> 128;\n if (x & 0x2000 > 0)\n result = result * 0x1000000000000162E42FEFA39F02B772C >> 128;\n if (x & 0x1000 > 0)\n result = result * 0x10000000000000B17217F7D1CF7D83C1A >> 128;\n if (x & 0x800 > 0)\n result = result * 0x1000000000000058B90BFBE8E7BDCBE2E >> 128;\n if (x & 0x400 > 0)\n result = result * 0x100000000000002C5C85FDF473DEA871F >> 128;\n if (x & 0x200 > 0)\n result = result * 0x10000000000000162E42FEFA39EF44D91 >> 128;\n if (x & 0x100 > 0)\n result = result * 0x100000000000000B17217F7D1CF79E949 >> 128;\n if (x & 0x80 > 0)\n result = result * 0x10000000000000058B90BFBE8E7BCE544 >> 128;\n if (x & 0x40 > 0)\n result = result * 0x1000000000000002C5C85FDF473DE6ECA >> 128;\n if (x & 0x20 > 0)\n result = result * 0x100000000000000162E42FEFA39EF366F >> 128;\n if (x & 0x10 > 0)\n result = result * 0x1000000000000000B17217F7D1CF79AFA >> 128;\n if (x & 0x8 > 0)\n result = result * 0x100000000000000058B90BFBE8E7BCD6D >> 128;\n if (x & 0x4 > 0)\n result = result * 0x10000000000000002C5C85FDF473DE6B2 >> 128;\n if (x & 0x2 > 0)\n result = result * 0x1000000000000000162E42FEFA39EF358 >> 128;\n if (x & 0x1 > 0)\n result = result * 0x10000000000000000B17217F7D1CF79AB >> 128;\n\n result >>= uint256 (int256 (63 - (x >> 64)));\n require (result <= uint256 (int256 (MAX_64x64)));\n\n return int128 (int256 (result));\n }\n }\n\n /**\n * Calculate natural exponent of x. Revert on overflow.\n *\n * @param x signed 64.64-bit fixed point number\n * @return signed 64.64-bit fixed point number\n */\n function exp (int128 x) internal pure returns (int128) {\n unchecked {\n require (x < 0x400000000000000000); // Overflow\n\n if (x < -0x400000000000000000) return 0; // Underflow\n\n return exp_2 (\n int128 (int256 (x) * 0x171547652B82FE1777D0FFDA0D23A7D12 >> 128));\n }\n }\n\n /**\n * Calculate x / y rounding towards zero, where x and y are unsigned 256-bit\n * integer numbers. Revert on overflow or when y is zero.\n *\n * @param x unsigned 256-bit integer number\n * @param y unsigned 256-bit integer number\n * @return unsigned 64.64-bit fixed point number\n */\n function divuu (uint256 x, uint256 y) private pure returns (uint128) {\n unchecked {\n require (y != 0);\n\n uint256 result;\n\n if (x <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)\n result = (x << 64) / y;\n else {\n uint256 msb = 192;\n uint256 xc = x >> 192;\n if (xc >= 0x100000000) { xc >>= 32; msb += 32; }\n if (xc >= 0x10000) { xc >>= 16; msb += 16; }\n if (xc >= 0x100) { xc >>= 8; msb += 8; }\n if (xc >= 0x10) { xc >>= 4; msb += 4; }\n if (xc >= 0x4) { xc >>= 2; msb += 2; }\n if (xc >= 0x2) msb += 1; // No need to shift xc anymore\n\n result = (x << 255 - msb) / ((y - 1 >> msb - 191) + 1);\n require (result <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);\n\n uint256 hi = result * (y >> 128);\n uint256 lo = result * (y & 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);\n\n uint256 xh = x >> 192;\n uint256 xl = x << 64;\n\n if (xl < lo) xh -= 1;\n xl -= lo; // We rely on overflow behavior here\n lo = hi << 128;\n if (xl < lo) xh -= 1;\n xl -= lo; // We rely on overflow behavior here\n\n assert (xh == hi >> 128);\n\n result += xl / y;\n }\n\n require (result <= 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);\n return uint128 (result);\n }\n }\n\n /**\n * Calculate sqrt (x) rounding down, where x is unsigned 256-bit integer\n * number.\n *\n * @param x unsigned 256-bit integer number\n * @return unsigned 128-bit integer number\n */\n function sqrtu (uint256 x) private pure returns (uint128) {\n unchecked {\n if (x == 0) return 0;\n else {\n uint256 xx = x;\n uint256 r = 1;\n if (xx >= 0x100000000000000000000000000000000) { xx >>= 128; r <<= 64; }\n if (xx >= 0x10000000000000000) { xx >>= 64; r <<= 32; }\n if (xx >= 0x100000000) { xx >>= 32; r <<= 16; }\n if (xx >= 0x10000) { xx >>= 16; r <<= 8; }\n if (xx >= 0x100) { xx >>= 8; r <<= 4; }\n if (xx >= 0x10) { xx >>= 4; r <<= 2; }\n if (xx >= 0x8) { r <<= 1; }\n r = (r + x / r) >> 1;\n r = (r + x / r) >> 1;\n r = (r + x / r) >> 1;\n r = (r + x / r) >> 1;\n r = (r + x / r) >> 1;\n r = (r + x / r) >> 1;\n r = (r + x / r) >> 1; // Seven iterations should be enough\n uint256 r1 = x / r;\n return uint128 (r < r1 ? r : r1);\n }\n }\n }\n}\n"},"@solidstate/contracts/token/ERC20/metadata/IERC20Metadata.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @title ERC20 metadata interface\n */\ninterface IERC20Metadata {\n /**\n * @notice return token name\n * @return token name\n */\n function name() external view returns (string memory);\n\n /**\n * @notice return token symbol\n * @return token symbol\n */\n function symbol() external view returns (string memory);\n\n /**\n * @notice return token decimals, generally used only for display purposes\n * @return token decimals\n */\n function decimals() external view returns (uint8);\n}\n"},"contracts/staking/FeeDiscountStorage.sol":{"content":"// SPDX-License-Identifier: BUSL-1.1\r\n// For further clarification please see https://license.premia.legal\r\n\r\npragma solidity ^0.8.0;\r\n\r\nlibrary FeeDiscountStorage {\r\n bytes32 internal constant STORAGE_SLOT =\r\n keccak256(\"premia.contracts.staking.PremiaFeeDiscount\");\r\n\r\n struct UserInfo {\r\n uint256 balance; // Balance staked by user\r\n uint64 stakePeriod; // Stake period selected by user\r\n uint64 lockedUntil; // Timestamp at which the lock ends\r\n }\r\n\r\n struct Layout {\r\n // User data with xPREMIA balance staked and date at which lock ends\r\n mapping(address => UserInfo) userInfo;\r\n }\r\n\r\n function layout() internal pure returns (Layout storage l) {\r\n bytes32 slot = STORAGE_SLOT;\r\n assembly {\r\n l.slot := slot\r\n }\r\n }\r\n}\r\n"},"contracts/libraries/OptionMath.sol":{"content":"// SPDX-License-Identifier: BUSL-1.1\r\n// For further clarification please see https://license.premia.legal\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport {ABDKMath64x64} from \"abdk-libraries-solidity/ABDKMath64x64.sol\";\r\n\r\nlibrary OptionMath {\r\n using ABDKMath64x64 for int128;\r\n\r\n struct QuoteArgs {\r\n int128 varianceAnnualized64x64; // 64x64 fixed point representation of annualized variance\r\n int128 strike64x64; // 64x64 fixed point representation of strike price\r\n int128 spot64x64; // 64x64 fixed point representation of spot price\r\n int128 timeToMaturity64x64; // 64x64 fixed point representation of duration of option contract (in years)\r\n int128 oldCLevel64x64; // 64x64 fixed point representation of C-Level of Pool before purchase\r\n int128 oldPoolState; // 64x64 fixed point representation of current state of the pool\r\n int128 newPoolState; // 64x64 fixed point representation of state of the pool after trade\r\n int128 steepness64x64; // 64x64 fixed point representation of Pool state delta multiplier\r\n int128 minAPY64x64; // 64x64 fixed point representation of minimum APY for capital locked up to underwrite options\r\n bool isCall; // whether to price \"call\" or \"put\" option\r\n }\r\n\r\n struct CalculateCLevelDecayArgs {\r\n int128 timeIntervalsElapsed64x64; // 64x64 fixed point representation of quantity of discrete arbitrary intervals elapsed since last update\r\n int128 oldCLevel64x64; // 64x64 fixed point representation of C-Level prior to accounting for decay\r\n int128 utilization64x64; // 64x64 fixed point representation of pool capital utilization rate\r\n int128 utilizationLowerBound64x64;\r\n int128 utilizationUpperBound64x64;\r\n int128 cLevelLowerBound64x64;\r\n int128 cLevelUpperBound64x64;\r\n int128 cConvergenceULowerBound64x64;\r\n int128 cConvergenceUUpperBound64x64;\r\n }\r\n\r\n // 64x64 fixed point integer constants\r\n int128 internal constant ONE_64x64 = 0x10000000000000000;\r\n int128 internal constant THREE_64x64 = 0x30000000000000000;\r\n\r\n // 64x64 fixed point constants used in Choudhury’s approximation of the Black-Scholes CDF\r\n int128 private constant CDF_CONST_0 = 0x09109f285df452394; // 2260 / 3989\r\n int128 private constant CDF_CONST_1 = 0x19abac0ea1da65036; // 6400 / 3989\r\n int128 private constant CDF_CONST_2 = 0x0d3c84b78b749bd6b; // 3300 / 3989\r\n\r\n /**\r\n * @notice recalculate C-Level based on change in liquidity\r\n * @param initialCLevel64x64 64x64 fixed point representation of C-Level of Pool before update\r\n * @param oldPoolState64x64 64x64 fixed point representation of liquidity in pool before update\r\n * @param newPoolState64x64 64x64 fixed point representation of liquidity in pool after update\r\n * @param steepness64x64 64x64 fixed point representation of steepness coefficient\r\n * @return 64x64 fixed point representation of new C-Level\r\n */\r\n function calculateCLevel(\r\n int128 initialCLevel64x64,\r\n int128 oldPoolState64x64,\r\n int128 newPoolState64x64,\r\n int128 steepness64x64\r\n ) external pure returns (int128) {\r\n return\r\n newPoolState64x64\r\n .sub(oldPoolState64x64)\r\n .div(\r\n oldPoolState64x64 > newPoolState64x64\r\n ? oldPoolState64x64\r\n : newPoolState64x64\r\n )\r\n .mul(steepness64x64)\r\n .neg()\r\n .exp()\r\n .mul(initialCLevel64x64);\r\n }\r\n\r\n /**\r\n * @notice calculate the price of an option using the Premia Finance model\r\n * @param args arguments of quotePrice\r\n * @return premiaPrice64x64 64x64 fixed point representation of Premia option price\r\n * @return cLevel64x64 64x64 fixed point representation of C-Level of Pool after purchase\r\n */\r\n function quotePrice(QuoteArgs memory args)\r\n external\r\n pure\r\n returns (\r\n int128 premiaPrice64x64,\r\n int128 cLevel64x64,\r\n int128 slippageCoefficient64x64\r\n )\r\n {\r\n int128 deltaPoolState64x64 = args\r\n .newPoolState\r\n .sub(args.oldPoolState)\r\n .div(args.oldPoolState)\r\n .mul(args.steepness64x64);\r\n int128 tradingDelta64x64 = deltaPoolState64x64.neg().exp();\r\n\r\n int128 blackScholesPrice64x64 = _blackScholesPrice(\r\n args.varianceAnnualized64x64,\r\n args.strike64x64,\r\n args.spot64x64,\r\n args.timeToMaturity64x64,\r\n args.isCall\r\n );\r\n\r\n cLevel64x64 = tradingDelta64x64.mul(args.oldCLevel64x64);\r\n slippageCoefficient64x64 = ONE_64x64.sub(tradingDelta64x64).div(\r\n deltaPoolState64x64\r\n );\r\n\r\n premiaPrice64x64 = blackScholesPrice64x64.mul(cLevel64x64).mul(\r\n slippageCoefficient64x64\r\n );\r\n\r\n int128 intrinsicValue64x64;\r\n\r\n if (args.isCall && args.strike64x64 < args.spot64x64) {\r\n intrinsicValue64x64 = args.spot64x64.sub(args.strike64x64);\r\n } else if (!args.isCall && args.strike64x64 > args.spot64x64) {\r\n intrinsicValue64x64 = args.strike64x64.sub(args.spot64x64);\r\n }\r\n\r\n int128 collateralValue64x64 = args.isCall\r\n ? args.spot64x64\r\n : args.strike64x64;\r\n\r\n int128 minPrice64x64 = intrinsicValue64x64.add(\r\n collateralValue64x64.mul(args.minAPY64x64).mul(\r\n args.timeToMaturity64x64\r\n )\r\n );\r\n\r\n if (minPrice64x64 > premiaPrice64x64) {\r\n premiaPrice64x64 = minPrice64x64;\r\n }\r\n }\r\n\r\n /**\r\n * @notice calculate the decay of C-Level based on heat diffusion function\r\n * @param args structured CalculateCLevelDecayArgs\r\n * @return cLevelDecayed64x64 C-Level after accounting for decay\r\n */\r\n function calculateCLevelDecay(CalculateCLevelDecayArgs memory args)\r\n external\r\n pure\r\n returns (int128 cLevelDecayed64x64)\r\n {\r\n int128 convFHighU64x64 = (args.utilization64x64 >=\r\n args.utilizationUpperBound64x64 &&\r\n args.oldCLevel64x64 <= args.cLevelLowerBound64x64)\r\n ? ONE_64x64\r\n : int128(0);\r\n\r\n int128 convFLowU64x64 = (args.utilization64x64 <=\r\n args.utilizationLowerBound64x64 &&\r\n args.oldCLevel64x64 >= args.cLevelUpperBound64x64)\r\n ? ONE_64x64\r\n : int128(0);\r\n\r\n cLevelDecayed64x64 = args\r\n .oldCLevel64x64\r\n .sub(args.cConvergenceULowerBound64x64.mul(convFLowU64x64))\r\n .sub(args.cConvergenceUUpperBound64x64.mul(convFHighU64x64))\r\n .mul(\r\n convFLowU64x64\r\n .mul(ONE_64x64.sub(args.utilization64x64))\r\n .add(convFHighU64x64.mul(args.utilization64x64))\r\n .mul(args.timeIntervalsElapsed64x64)\r\n .neg()\r\n .exp()\r\n )\r\n .add(\r\n args.cConvergenceULowerBound64x64.mul(convFLowU64x64).add(\r\n args.cConvergenceUUpperBound64x64.mul(convFHighU64x64)\r\n )\r\n );\r\n }\r\n\r\n /**\r\n * @notice calculate the exponential decay coefficient for a given interval\r\n * @param oldTimestamp timestamp of previous update\r\n * @param newTimestamp current timestamp\r\n * @return 64x64 fixed point representation of exponential decay coefficient\r\n */\r\n function _decay(uint256 oldTimestamp, uint256 newTimestamp)\r\n internal\r\n pure\r\n returns (int128)\r\n {\r\n return\r\n ONE_64x64.sub(\r\n (-ABDKMath64x64.divu(newTimestamp - oldTimestamp, 7 days)).exp()\r\n );\r\n }\r\n\r\n /**\r\n * @notice calculate Choudhury’s approximation of the Black-Scholes CDF\r\n * @param input64x64 64x64 fixed point representation of random variable\r\n * @return 64x64 fixed point representation of the approximated CDF of x\r\n */\r\n function _N(int128 input64x64) internal pure returns (int128) {\r\n // squaring via mul is cheaper than via pow\r\n int128 inputSquared64x64 = input64x64.mul(input64x64);\r\n\r\n int128 value64x64 = (-inputSquared64x64 >> 1).exp().div(\r\n CDF_CONST_0.add(CDF_CONST_1.mul(input64x64.abs())).add(\r\n CDF_CONST_2.mul(inputSquared64x64.add(THREE_64x64).sqrt())\r\n )\r\n );\r\n\r\n return input64x64 > 0 ? ONE_64x64.sub(value64x64) : value64x64;\r\n }\r\n\r\n /**\r\n * @notice calculate the price of an option using the Black-Scholes model\r\n * @param varianceAnnualized64x64 64x64 fixed point representation of annualized variance\r\n * @param strike64x64 64x64 fixed point representation of strike price\r\n * @param spot64x64 64x64 fixed point representation of spot price\r\n * @param timeToMaturity64x64 64x64 fixed point representation of duration of option contract (in years)\r\n * @param isCall whether to price \"call\" or \"put\" option\r\n * @return 64x64 fixed point representation of Black-Scholes option price\r\n */\r\n function _blackScholesPrice(\r\n int128 varianceAnnualized64x64,\r\n int128 strike64x64,\r\n int128 spot64x64,\r\n int128 timeToMaturity64x64,\r\n bool isCall\r\n ) internal pure returns (int128) {\r\n int128 cumulativeVariance64x64 = timeToMaturity64x64.mul(\r\n varianceAnnualized64x64\r\n );\r\n int128 cumulativeVarianceSqrt64x64 = cumulativeVariance64x64.sqrt();\r\n\r\n int128 d1_64x64 = spot64x64\r\n .div(strike64x64)\r\n .ln()\r\n .add(cumulativeVariance64x64 >> 1)\r\n .div(cumulativeVarianceSqrt64x64);\r\n int128 d2_64x64 = d1_64x64.sub(cumulativeVarianceSqrt64x64);\r\n\r\n if (isCall) {\r\n return\r\n spot64x64.mul(_N(d1_64x64)).sub(strike64x64.mul(_N(d2_64x64)));\r\n } else {\r\n return\r\n -spot64x64.mul(_N(-d1_64x64)).sub(\r\n strike64x64.mul(_N(-d2_64x64))\r\n );\r\n }\r\n }\r\n}\r\n"},"@solidstate/contracts/access/IERC173.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @title Contract ownership standard interface\n * @dev see https://eips.ethereum.org/EIPS/eip-173\n */\ninterface IERC173 {\n event OwnershipTransferred(\n address indexed previousOwner,\n address indexed newOwner\n );\n\n /**\n * @notice get the ERC173 contract owner\n * @return conract owner\n */\n function owner() external view returns (address);\n\n /**\n * @notice transfer contract ownership to new account\n * @param account address of new owner\n */\n function transferOwnership(address account) external;\n}\n"},"contracts/libraries/ABDKMath64x64Token.sol":{"content":"// SPDX-License-Identifier: BUSL-1.1\r\n// For further clarification please see https://license.premia.legal\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport {ABDKMath64x64} from \"abdk-libraries-solidity/ABDKMath64x64.sol\";\r\n\r\nlibrary ABDKMath64x64Token {\r\n using ABDKMath64x64 for int128;\r\n\r\n /**\r\n * @notice convert 64x64 fixed point representation of token amount to decimal\r\n * @param value64x64 64x64 fixed point representation of token amount\r\n * @param decimals token display decimals\r\n * @return value decimal representation of token amount\r\n */\r\n function toDecimals(int128 value64x64, uint8 decimals)\r\n internal\r\n pure\r\n returns (uint256 value)\r\n {\r\n value = value64x64.mulu(10**decimals);\r\n }\r\n\r\n /**\r\n * @notice convert decimal representation of token amount to 64x64 fixed point\r\n * @param value decimal representation of token amount\r\n * @param decimals token display decimals\r\n * @return value64x64 64x64 fixed point representation of token amount\r\n */\r\n function fromDecimals(uint256 value, uint8 decimals)\r\n internal\r\n pure\r\n returns (int128 value64x64)\r\n {\r\n value64x64 = ABDKMath64x64.divu(value, 10**decimals);\r\n }\r\n\r\n /**\r\n * @notice convert 64x64 fixed point representation of token amount to wei (18 decimals)\r\n * @param value64x64 64x64 fixed point representation of token amount\r\n * @return value wei representation of token amount\r\n */\r\n function toWei(int128 value64x64) internal pure returns (uint256 value) {\r\n value = toDecimals(value64x64, 18);\r\n }\r\n\r\n /**\r\n * @notice convert wei representation (18 decimals) of token amount to 64x64 fixed point\r\n * @param value wei representation of token amount\r\n * @return value64x64 64x64 fixed point representation of token amount\r\n */\r\n function fromWei(uint256 value) internal pure returns (int128 value64x64) {\r\n value64x64 = fromDecimals(value, 18);\r\n }\r\n}\r\n"},"@solidstate/contracts/token/ERC1155/IERC1155.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { IERC1155Internal } from './IERC1155Internal.sol';\nimport { IERC165 } from '../../introspection/IERC165.sol';\n\n/**\n * @notice ERC1155 interface\n * @dev see https://github.com/ethereum/EIPs/issues/1155\n */\ninterface IERC1155 is IERC1155Internal, IERC165 {\n /**\n * @notice query the balance of given token held by given address\n * @param account address to query\n * @param id token to query\n * @return token balance\n */\n function balanceOf(address account, uint256 id)\n external\n view\n returns (uint256);\n\n /**\n * @notice query the balances of given tokens held by given addresses\n * @param accounts addresss to query\n * @param ids tokens to query\n * @return token balances\n */\n function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids)\n external\n view\n returns (uint256[] memory);\n\n /**\n * @notice query approval status of given operator with respect to given address\n * @param account address to query for approval granted\n * @param operator address to query for approval received\n * @return whether operator is approved to spend tokens held by account\n */\n function isApprovedForAll(address account, address operator)\n external\n view\n returns (bool);\n\n /**\n * @notice grant approval to or revoke approval from given operator to spend held tokens\n * @param operator address whose approval status to update\n * @param status whether operator should be considered approved\n */\n function setApprovalForAll(address operator, bool status) external;\n\n /**\n * @notice transfer tokens between given addresses, checking for ERC1155Receiver implementation if applicable\n * @param from sender of tokens\n * @param to receiver of tokens\n * @param id token ID\n * @param amount quantity of tokens to transfer\n * @param data data payload\n */\n function safeTransferFrom(\n address from,\n address to,\n uint256 id,\n uint256 amount,\n bytes calldata data\n ) external;\n\n /**\n * @notice transfer batch of tokens between given addresses, checking for ERC1155Receiver implementation if applicable\n * @param from sender of tokens\n * @param to receiver of tokens\n * @param ids list of token IDs\n * @param amounts list of quantities of tokens to transfer\n * @param data data payload\n */\n function safeBatchTransferFrom(\n address from,\n address to,\n uint256[] calldata ids,\n uint256[] calldata amounts,\n bytes calldata data\n ) external;\n}\n"},"contracts/staking/IFeeDiscount.sol":{"content":"// SPDX-License-Identifier: LGPL-3.0-or-later\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport {FeeDiscountStorage} from \"./FeeDiscountStorage.sol\";\r\n\r\ninterface IFeeDiscount {\r\n event Staked(\r\n address indexed user,\r\n uint256 amount,\r\n uint256 stakePeriod,\r\n uint256 lockedUntil\r\n );\r\n event Unstaked(address indexed user, uint256 amount);\r\n\r\n struct StakeLevel {\r\n uint256 amount; // Amount to stake\r\n uint256 discount; // Discount when amount is reached\r\n }\r\n\r\n /**\r\n * @notice Stake using IERC2612 permit\r\n * @param amount The amount of xPremia to stake\r\n * @param period The lockup period (in seconds)\r\n * @param deadline Deadline after which permit will fail\r\n * @param v V\r\n * @param r R\r\n * @param s S\r\n */\r\n function stakeWithPermit(\r\n uint256 amount,\r\n uint256 period,\r\n uint256 deadline,\r\n uint8 v,\r\n bytes32 r,\r\n bytes32 s\r\n ) external;\r\n\r\n /**\r\n * @notice Lockup xPremia for protocol fee discounts\r\n * Longer period of locking will apply a multiplier on the amount staked, in the fee discount calculation\r\n * @param amount The amount of xPremia to stake\r\n * @param period The lockup period (in seconds)\r\n */\r\n function stake(uint256 amount, uint256 period) external;\r\n\r\n /**\r\n * @notice Unstake xPremia (If lockup period has ended)\r\n * @param amount The amount of xPremia to unstake\r\n */\r\n function unstake(uint256 amount) external;\r\n\r\n //////////\r\n // View //\r\n //////////\r\n\r\n /**\r\n * Calculate the stake amount of a user, after applying the bonus from the lockup period chosen\r\n * @param user The user from which to query the stake amount\r\n * @return The user stake amount after applying the bonus\r\n */\r\n function getStakeAmountWithBonus(address user)\r\n external\r\n view\r\n returns (uint256);\r\n\r\n /**\r\n * @notice Calculate the % of fee discount for user, based on his stake\r\n * @param user The _user for which the discount is for\r\n * @return Percentage of protocol fee discount (in basis point)\r\n * Ex : 1000 = 10% fee discount\r\n */\r\n function getDiscount(address user) external view returns (uint256);\r\n\r\n /**\r\n * @notice Get stake levels\r\n * @return Stake levels\r\n * Ex : 2500 = -25%\r\n */\r\n function getStakeLevels() external returns (StakeLevel[] memory);\r\n\r\n /**\r\n * @notice Get stake period multiplier\r\n * @param period The duration (in seconds) for which tokens are locked\r\n * @return The multiplier for this staking period\r\n * Ex : 20000 = x2\r\n */\r\n function getStakePeriodMultiplier(uint256 period)\r\n external\r\n returns (uint256);\r\n\r\n /**\r\n * @notice Get staking infos of a user\r\n * @param user The user address for which to get staking infos\r\n * @return The staking infos of the user\r\n */\r\n function getUserInfo(address user)\r\n external\r\n view\r\n returns (FeeDiscountStorage.UserInfo memory);\r\n}\r\n"},"@solidstate/contracts/token/ERC1155/base/ERC1155BaseInternal.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { AddressUtils } from '../../../utils/AddressUtils.sol';\nimport { IERC1155Internal } from '../IERC1155Internal.sol';\nimport { IERC1155Receiver } from '../IERC1155Receiver.sol';\nimport { ERC1155BaseStorage } from './ERC1155BaseStorage.sol';\n\n/**\n * @title Base ERC1155 internal functions\n * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts/ (MIT license)\n */\nabstract contract ERC1155BaseInternal is IERC1155Internal {\n using AddressUtils for address;\n\n /**\n * @notice query the balance of given token held by given address\n * @param account address to query\n * @param id token to query\n * @return token balance\n */\n function _balanceOf(address account, uint256 id)\n internal\n view\n virtual\n returns (uint256)\n {\n require(\n account != address(0),\n 'ERC1155: balance query for the zero address'\n );\n return ERC1155BaseStorage.layout().balances[id][account];\n }\n\n /**\n * @notice mint given quantity of tokens for given address\n * @dev ERC1155Receiver implementation is not checked\n * @param account beneficiary of minting\n * @param id token ID\n * @param amount quantity of tokens to mint\n * @param data data payload\n */\n function _mint(\n address account,\n uint256 id,\n uint256 amount,\n bytes memory data\n ) internal virtual {\n require(account != address(0), 'ERC1155: mint to the zero address');\n\n _beforeTokenTransfer(\n msg.sender,\n address(0),\n account,\n _asSingletonArray(id),\n _asSingletonArray(amount),\n data\n );\n\n mapping(address => uint256) storage balances = ERC1155BaseStorage\n .layout()\n .balances[id];\n balances[account] += amount;\n\n emit TransferSingle(msg.sender, address(0), account, id, amount);\n }\n\n /**\n * @notice mint given quantity of tokens for given address\n * @param account beneficiary of minting\n * @param id token ID\n * @param amount quantity of tokens to mint\n * @param data data payload\n */\n function _safeMint(\n address account,\n uint256 id,\n uint256 amount,\n bytes memory data\n ) internal virtual {\n _mint(account, id, amount, data);\n\n _doSafeTransferAcceptanceCheck(\n msg.sender,\n address(0),\n account,\n id,\n amount,\n data\n );\n }\n\n /**\n * @notice mint batch of tokens for given address\n * @dev ERC1155Receiver implementation is not checked\n * @param account beneficiary of minting\n * @param ids list of token IDs\n * @param amounts list of quantities of tokens to mint\n * @param data data payload\n */\n function _mintBatch(\n address account,\n uint256[] memory ids,\n uint256[] memory amounts,\n bytes memory data\n ) internal virtual {\n require(account != address(0), 'ERC1155: mint to the zero address');\n require(\n ids.length == amounts.length,\n 'ERC1155: ids and amounts length mismatch'\n );\n\n _beforeTokenTransfer(\n msg.sender,\n address(0),\n account,\n ids,\n amounts,\n data\n );\n\n mapping(uint256 => mapping(address => uint256))\n storage balances = ERC1155BaseStorage.layout().balances;\n\n for (uint256 i; i < ids.length; i++) {\n balances[ids[i]][account] += amounts[i];\n }\n\n emit TransferBatch(msg.sender, address(0), account, ids, amounts);\n }\n\n /**\n * @notice mint batch of tokens for given address\n * @param account beneficiary of minting\n * @param ids list of token IDs\n * @param amounts list of quantities of tokens to mint\n * @param data data payload\n */\n function _safeMintBatch(\n address account,\n uint256[] memory ids,\n uint256[] memory amounts,\n bytes memory data\n ) internal virtual {\n _mintBatch(account, ids, amounts, data);\n\n _doSafeBatchTransferAcceptanceCheck(\n msg.sender,\n address(0),\n account,\n ids,\n amounts,\n data\n );\n }\n\n /**\n * @notice burn given quantity of tokens held by given address\n * @param account holder of tokens to burn\n * @param id token ID\n * @param amount quantity of tokens to burn\n */\n function _burn(\n address account,\n uint256 id,\n uint256 amount\n ) internal virtual {\n require(account != address(0), 'ERC1155: burn from the zero address');\n\n _beforeTokenTransfer(\n msg.sender,\n account,\n address(0),\n _asSingletonArray(id),\n _asSingletonArray(amount),\n ''\n );\n\n mapping(address => uint256) storage balances = ERC1155BaseStorage\n .layout()\n .balances[id];\n\n unchecked {\n require(\n balances[account] >= amount,\n 'ERC1155: burn amount exceeds balances'\n );\n balances[account] -= amount;\n }\n\n emit TransferSingle(msg.sender, account, address(0), id, amount);\n }\n\n /**\n * @notice burn given batch of tokens held by given address\n * @param account holder of tokens to burn\n * @param ids token IDs\n * @param amounts quantities of tokens to burn\n */\n function _burnBatch(\n address account,\n uint256[] memory ids,\n uint256[] memory amounts\n ) internal virtual {\n require(account != address(0), 'ERC1155: burn from the zero address');\n require(\n ids.length == amounts.length,\n 'ERC1155: ids and amounts length mismatch'\n );\n\n _beforeTokenTransfer(msg.sender, account, address(0), ids, amounts, '');\n\n mapping(uint256 => mapping(address => uint256))\n storage balances = ERC1155BaseStorage.layout().balances;\n\n unchecked {\n for (uint256 i; i < ids.length; i++) {\n uint256 id = ids[i];\n require(\n balances[id][account] >= amounts[i],\n 'ERC1155: burn amount exceeds balance'\n );\n balances[id][account] -= amounts[i];\n }\n }\n\n emit TransferBatch(msg.sender, account, address(0), ids, amounts);\n }\n\n /**\n * @notice transfer tokens between given addresses\n * @dev ERC1155Receiver implementation is not checked\n * @param operator executor of transfer\n * @param sender sender of tokens\n * @param recipient receiver of tokens\n * @param id token ID\n * @param amount quantity of tokens to transfer\n * @param data data payload\n */\n function _transfer(\n address operator,\n address sender,\n address recipient,\n uint256 id,\n uint256 amount,\n bytes memory data\n ) internal virtual {\n require(\n recipient != address(0),\n 'ERC1155: transfer to the zero address'\n );\n\n _beforeTokenTransfer(\n operator,\n sender,\n recipient,\n _asSingletonArray(id),\n _asSingletonArray(amount),\n data\n );\n\n mapping(uint256 => mapping(address => uint256))\n storage balances = ERC1155BaseStorage.layout().balances;\n\n unchecked {\n uint256 senderBalance = balances[id][sender];\n require(\n senderBalance >= amount,\n 'ERC1155: insufficient balances for transfer'\n );\n balances[id][sender] = senderBalance - amount;\n }\n\n balances[id][recipient] += amount;\n\n emit TransferSingle(operator, sender, recipient, id, amount);\n }\n\n /**\n * @notice transfer tokens between given addresses\n * @param operator executor of transfer\n * @param sender sender of tokens\n * @param recipient receiver of tokens\n * @param id token ID\n * @param amount quantity of tokens to transfer\n * @param data data payload\n */\n function _safeTransfer(\n address operator,\n address sender,\n address recipient,\n uint256 id,\n uint256 amount,\n bytes memory data\n ) internal virtual {\n _transfer(operator, sender, recipient, id, amount, data);\n\n _doSafeTransferAcceptanceCheck(\n operator,\n sender,\n recipient,\n id,\n amount,\n data\n );\n }\n\n /**\n * @notice transfer batch of tokens between given addresses\n * @dev ERC1155Receiver implementation is not checked\n * @param operator executor of transfer\n * @param sender sender of tokens\n * @param recipient receiver of tokens\n * @param ids token IDs\n * @param amounts quantities of tokens to transfer\n * @param data data payload\n */\n function _transferBatch(\n address operator,\n address sender,\n address recipient,\n uint256[] memory ids,\n uint256[] memory amounts,\n bytes memory data\n ) internal virtual {\n require(\n recipient != address(0),\n 'ERC1155: transfer to the zero address'\n );\n require(\n ids.length == amounts.length,\n 'ERC1155: ids and amounts length mismatch'\n );\n\n _beforeTokenTransfer(operator, sender, recipient, ids, amounts, data);\n\n mapping(uint256 => mapping(address => uint256))\n storage balances = ERC1155BaseStorage.layout().balances;\n\n for (uint256 i; i < ids.length; i++) {\n uint256 token = ids[i];\n uint256 amount = amounts[i];\n\n unchecked {\n uint256 senderBalance = balances[token][sender];\n require(\n senderBalance >= amount,\n 'ERC1155: insufficient balances for transfer'\n );\n balances[token][sender] = senderBalance - amount;\n }\n\n balances[token][recipient] += amount;\n }\n\n emit TransferBatch(operator, sender, recipient, ids, amounts);\n }\n\n /**\n * @notice transfer batch of tokens between given addresses\n * @param operator executor of transfer\n * @param sender sender of tokens\n * @param recipient receiver of tokens\n * @param ids token IDs\n * @param amounts quantities of tokens to transfer\n * @param data data payload\n */\n function _safeTransferBatch(\n address operator,\n address sender,\n address recipient,\n uint256[] memory ids,\n uint256[] memory amounts,\n bytes memory data\n ) internal virtual {\n _transferBatch(operator, sender, recipient, ids, amounts, data);\n\n _doSafeBatchTransferAcceptanceCheck(\n operator,\n sender,\n recipient,\n ids,\n amounts,\n data\n );\n }\n\n /**\n * @notice wrap given element in array of length 1\n * @param element element to wrap\n * @return singleton array\n */\n function _asSingletonArray(uint256 element)\n private\n pure\n returns (uint256[] memory)\n {\n uint256[] memory array = new uint256[](1);\n array[0] = element;\n return array;\n }\n\n /**\n * @notice revert if applicable transfer recipient is not valid ERC1155Receiver\n * @param operator executor of transfer\n * @param from sender of tokens\n * @param to receiver of tokens\n * @param id token ID\n * @param amount quantity of tokens to transfer\n * @param data data payload\n */\n function _doSafeTransferAcceptanceCheck(\n address operator,\n address from,\n address to,\n uint256 id,\n uint256 amount,\n bytes memory data\n ) private {\n if (to.isContract()) {\n try\n IERC1155Receiver(to).onERC1155Received(\n operator,\n from,\n id,\n amount,\n data\n )\n returns (bytes4 response) {\n require(\n response == IERC1155Receiver.onERC1155Received.selector,\n 'ERC1155: ERC1155Receiver rejected tokens'\n );\n } catch Error(string memory reason) {\n revert(reason);\n } catch {\n revert('ERC1155: transfer to non ERC1155Receiver implementer');\n }\n }\n }\n\n /**\n * @notice revert if applicable transfer recipient is not valid ERC1155Receiver\n * @param operator executor of transfer\n * @param from sender of tokens\n * @param to receiver of tokens\n * @param ids token IDs\n * @param amounts quantities of tokens to transfer\n * @param data data payload\n */\n function _doSafeBatchTransferAcceptanceCheck(\n address operator,\n address from,\n address to,\n uint256[] memory ids,\n uint256[] memory amounts,\n bytes memory data\n ) private {\n if (to.isContract()) {\n try\n IERC1155Receiver(to).onERC1155BatchReceived(\n operator,\n from,\n ids,\n amounts,\n data\n )\n returns (bytes4 response) {\n require(\n response ==\n IERC1155Receiver.onERC1155BatchReceived.selector,\n 'ERC1155: ERC1155Receiver rejected tokens'\n );\n } catch Error(string memory reason) {\n revert(reason);\n } catch {\n revert('ERC1155: transfer to non ERC1155Receiver implementer');\n }\n }\n }\n\n /**\n * @notice ERC1155 hook, called before all transfers including mint and burn\n * @dev function should be overridden and new implementation must call super\n * @dev called for both single and batch transfers\n * @param operator executor of transfer\n * @param from sender of tokens\n * @param to receiver of tokens\n * @param ids token IDs\n * @param amounts quantities of tokens to transfer\n * @param data data payload\n */\n function _beforeTokenTransfer(\n address operator,\n address from,\n address to,\n uint256[] memory ids,\n uint256[] memory amounts,\n bytes memory data\n ) internal virtual {}\n}\n"},"@solidstate/contracts/token/ERC20/IERC20Internal.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @title Partial ERC20 interface needed by internal functions\n */\ninterface IERC20Internal {\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n event Approval(\n address indexed owner,\n address indexed spender,\n uint256 value\n );\n}\n"},"@chainlink/contracts/src/v0.8/interfaces/AggregatorInterface.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface AggregatorInterface {\n function latestAnswer()\n external\n view\n returns (\n int256\n );\n \n function latestTimestamp()\n external\n view\n returns (\n uint256\n );\n\n function latestRound()\n external\n view\n returns (\n uint256\n );\n\n function getAnswer(\n uint256 roundId\n )\n external\n view\n returns (\n int256\n );\n\n function getTimestamp(\n uint256 roundId\n )\n external\n view\n returns (\n uint256\n );\n\n event AnswerUpdated(\n int256 indexed current,\n uint256 indexed roundId,\n uint256 updatedAt\n );\n\n event NewRound(\n uint256 indexed roundId,\n address indexed startedBy,\n uint256 startedAt\n );\n}\n"},"contracts/pool/PoolExercise.sol":{"content":"// SPDX-License-Identifier: BUSL-1.1\r\n// For further clarification please see https://license.premia.legal\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport {ERC1155BaseStorage} from \"@solidstate/contracts/token/ERC1155/base/ERC1155BaseStorage.sol\";\r\n\r\nimport {PoolInternal} from \"./PoolInternal.sol\";\r\nimport {IPoolExercise} from \"./IPoolExercise.sol\";\r\n\r\n/**\r\n * @title Premia option pool\r\n * @dev deployed standalone and referenced by PoolProxy\r\n */\r\ncontract PoolExercise is IPoolExercise, PoolInternal {\r\n constructor(\r\n address ivolOracle,\r\n address weth,\r\n address premiaMining,\r\n address feeReceiver,\r\n address feeDiscountAddress,\r\n int128 fee64x64\r\n )\r\n PoolInternal(\r\n ivolOracle,\r\n weth,\r\n premiaMining,\r\n feeReceiver,\r\n feeDiscountAddress,\r\n fee64x64\r\n )\r\n {}\r\n\r\n /**\r\n * @inheritdoc IPoolExercise\r\n */\r\n function exerciseFrom(\r\n address holder,\r\n uint256 longTokenId,\r\n uint256 contractSize\r\n ) external override {\r\n if (msg.sender != holder) {\r\n require(\r\n ERC1155BaseStorage.layout().operatorApprovals[holder][\r\n msg.sender\r\n ],\r\n \"not approved\"\r\n );\r\n }\r\n\r\n _exercise(holder, longTokenId, contractSize);\r\n }\r\n\r\n /**\r\n * @inheritdoc IPoolExercise\r\n */\r\n function processExpired(uint256 longTokenId, uint256 contractSize)\r\n external\r\n override\r\n {\r\n _exercise(address(0), longTokenId, contractSize);\r\n }\r\n}\r\n"},"contracts/pool/IPoolExercise.sol":{"content":"// SPDX-License-Identifier: LGPL-3.0-or-later\r\n\r\npragma solidity ^0.8.0;\r\n\r\n/**\r\n * @notice Pool interface for exercising and processing of expired options\r\n */\r\ninterface IPoolExercise {\r\n /**\r\n * @notice exercise option on behalf of holder\r\n * @param holder owner of long option tokens to exercise\r\n * @param longTokenId long option token id\r\n * @param contractSize quantity of tokens to exercise\r\n */\r\n function exerciseFrom(\r\n address holder,\r\n uint256 longTokenId,\r\n uint256 contractSize\r\n ) external;\r\n\r\n /**\r\n * @notice process expired option, freeing liquidity and distributing profits\r\n * @param longTokenId long option token id\r\n * @param contractSize quantity of tokens to process\r\n */\r\n function processExpired(uint256 longTokenId, uint256 contractSize) external;\r\n}\r\n"},"contracts/pool/PoolStorage.sol":{"content":"// SPDX-License-Identifier: BUSL-1.1\r\n// For further clarification please see https://license.premia.legal\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport {AggregatorInterface} from \"@chainlink/contracts/src/v0.8/interfaces/AggregatorInterface.sol\";\r\nimport {AggregatorV3Interface} from \"@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol\";\r\nimport {EnumerableSet, ERC1155EnumerableStorage} from \"@solidstate/contracts/token/ERC1155/enumerable/ERC1155EnumerableStorage.sol\";\r\n\r\nimport {ABDKMath64x64} from \"abdk-libraries-solidity/ABDKMath64x64.sol\";\r\nimport {ABDKMath64x64Token} from \"../libraries/ABDKMath64x64Token.sol\";\r\nimport {OptionMath} from \"../libraries/OptionMath.sol\";\r\n\r\nlibrary PoolStorage {\r\n using ABDKMath64x64 for int128;\r\n using PoolStorage for PoolStorage.Layout;\r\n\r\n enum TokenType {\r\n UNDERLYING_FREE_LIQ,\r\n BASE_FREE_LIQ,\r\n UNDERLYING_RESERVED_LIQ,\r\n BASE_RESERVED_LIQ,\r\n LONG_CALL,\r\n SHORT_CALL,\r\n LONG_PUT,\r\n SHORT_PUT\r\n }\r\n\r\n struct PoolSettings {\r\n address underlying;\r\n address base;\r\n address underlyingOracle;\r\n address baseOracle;\r\n }\r\n\r\n struct QuoteArgsInternal {\r\n address feePayer; // address of the fee payer\r\n uint64 maturity; // timestamp of option maturity\r\n int128 strike64x64; // 64x64 fixed point representation of strike price\r\n int128 spot64x64; // 64x64 fixed point representation of spot price\r\n uint256 contractSize; // size of option contract\r\n bool isCall; // true for call, false for put\r\n }\r\n\r\n struct QuoteResultInternal {\r\n int128 baseCost64x64; // 64x64 fixed point representation of option cost denominated in underlying currency (without fee)\r\n int128 feeCost64x64; // 64x64 fixed point representation of option fee cost denominated in underlying currency for call, or base currency for put\r\n int128 cLevel64x64; // 64x64 fixed point representation of C-Level of Pool after purchase\r\n int128 slippageCoefficient64x64; // 64x64 fixed point representation of slippage coefficient for given order size\r\n }\r\n\r\n struct BatchData {\r\n uint256 eta;\r\n uint256 totalPendingDeposits;\r\n }\r\n\r\n bytes32 internal constant STORAGE_SLOT =\r\n keccak256(\"premia.contracts.storage.Pool\");\r\n\r\n uint256 private constant C_DECAY_BUFFER = 12 hours;\r\n uint256 private constant C_DECAY_INTERVAL = 4 hours;\r\n\r\n struct Layout {\r\n // ERC20 token addresses\r\n address base;\r\n address underlying;\r\n // AggregatorV3Interface oracle addresses\r\n address baseOracle;\r\n address underlyingOracle;\r\n // token metadata\r\n uint8 underlyingDecimals;\r\n uint8 baseDecimals;\r\n // minimum amounts\r\n uint256 baseMinimum;\r\n uint256 underlyingMinimum;\r\n // deposit caps\r\n uint256 basePoolCap;\r\n uint256 underlyingPoolCap;\r\n // market state\r\n int128 _deprecated_steepness64x64;\r\n int128 cLevelBase64x64;\r\n int128 cLevelUnderlying64x64;\r\n uint256 cLevelBaseUpdatedAt;\r\n uint256 cLevelUnderlyingUpdatedAt;\r\n uint256 updatedAt;\r\n // User -> isCall -> depositedAt\r\n mapping(address => mapping(bool => uint256)) depositedAt;\r\n mapping(address => mapping(bool => uint256)) divestmentTimestamps;\r\n // doubly linked list of free liquidity intervals\r\n // isCall -> User -> User\r\n mapping(bool => mapping(address => address)) liquidityQueueAscending;\r\n mapping(bool => mapping(address => address)) liquidityQueueDescending;\r\n // minimum resolution price bucket => price\r\n mapping(uint256 => int128) bucketPrices64x64;\r\n // sequence id (minimum resolution price bucket / 256) => price update sequence\r\n mapping(uint256 => uint256) priceUpdateSequences;\r\n // isCall -> batch data\r\n mapping(bool => BatchData) nextDeposits;\r\n // user -> batch timestamp -> isCall -> pending amount\r\n mapping(address => mapping(uint256 => mapping(bool => uint256))) pendingDeposits;\r\n EnumerableSet.UintSet tokenIds;\r\n // user -> isCallPool -> total value locked of user (Used for liquidity mining)\r\n mapping(address => mapping(bool => uint256)) userTVL;\r\n // isCallPool -> total value locked\r\n mapping(bool => uint256) totalTVL;\r\n // steepness values\r\n int128 steepnessBase64x64;\r\n int128 steepnessUnderlying64x64;\r\n }\r\n\r\n function layout() internal pure returns (Layout storage l) {\r\n bytes32 slot = STORAGE_SLOT;\r\n assembly {\r\n l.slot := slot\r\n }\r\n }\r\n\r\n /**\r\n * @notice calculate ERC1155 token id for given option parameters\r\n * @param tokenType TokenType enum\r\n * @param maturity timestamp of option maturity\r\n * @param strike64x64 64x64 fixed point representation of strike price\r\n * @return tokenId token id\r\n */\r\n function formatTokenId(\r\n TokenType tokenType,\r\n uint64 maturity,\r\n int128 strike64x64\r\n ) internal pure returns (uint256 tokenId) {\r\n tokenId =\r\n (uint256(tokenType) << 248) +\r\n (uint256(maturity) << 128) +\r\n uint256(int256(strike64x64));\r\n }\r\n\r\n /**\r\n * @notice derive option maturity and strike price from ERC1155 token id\r\n * @param tokenId token id\r\n * @return tokenType TokenType enum\r\n * @return maturity timestamp of option maturity\r\n * @return strike64x64 option strike price\r\n */\r\n function parseTokenId(uint256 tokenId)\r\n internal\r\n pure\r\n returns (\r\n TokenType tokenType,\r\n uint64 maturity,\r\n int128 strike64x64\r\n )\r\n {\r\n assembly {\r\n tokenType := shr(248, tokenId)\r\n maturity := shr(128, tokenId)\r\n strike64x64 := tokenId\r\n }\r\n }\r\n\r\n function getTokenDecimals(Layout storage l, bool isCall)\r\n internal\r\n view\r\n returns (uint8 decimals)\r\n {\r\n decimals = isCall ? l.underlyingDecimals : l.baseDecimals;\r\n }\r\n\r\n /**\r\n * @notice get the total supply of free liquidity tokens, minus pending deposits\r\n * @param l storage layout struct\r\n * @param isCall whether query is for call or put pool\r\n * @return 64x64 fixed point representation of total free liquidity\r\n */\r\n function totalFreeLiquiditySupply64x64(Layout storage l, bool isCall)\r\n internal\r\n view\r\n returns (int128)\r\n {\r\n uint256 tokenId = formatTokenId(\r\n isCall ? TokenType.UNDERLYING_FREE_LIQ : TokenType.BASE_FREE_LIQ,\r\n 0,\r\n 0\r\n );\r\n\r\n return\r\n ABDKMath64x64Token.fromDecimals(\r\n ERC1155EnumerableStorage.layout().totalSupply[tokenId] -\r\n l.nextDeposits[isCall].totalPendingDeposits,\r\n l.getTokenDecimals(isCall)\r\n );\r\n }\r\n\r\n function getReinvestmentStatus(\r\n Layout storage l,\r\n address account,\r\n bool isCallPool\r\n ) internal view returns (bool) {\r\n uint256 timestamp = l.divestmentTimestamps[account][isCallPool];\r\n return timestamp == 0 || timestamp > block.timestamp;\r\n }\r\n\r\n function addUnderwriter(\r\n Layout storage l,\r\n address account,\r\n bool isCallPool\r\n ) internal {\r\n require(account != address(0));\r\n\r\n mapping(address => address) storage asc = l.liquidityQueueAscending[\r\n isCallPool\r\n ];\r\n mapping(address => address) storage desc = l.liquidityQueueDescending[\r\n isCallPool\r\n ];\r\n\r\n if (_isInQueue(account, asc, desc)) return;\r\n\r\n address last = desc[address(0)];\r\n\r\n asc[last] = account;\r\n desc[account] = last;\r\n desc[address(0)] = account;\r\n }\r\n\r\n function removeUnderwriter(\r\n Layout storage l,\r\n address account,\r\n bool isCallPool\r\n ) internal {\r\n require(account != address(0));\r\n\r\n mapping(address => address) storage asc = l.liquidityQueueAscending[\r\n isCallPool\r\n ];\r\n mapping(address => address) storage desc = l.liquidityQueueDescending[\r\n isCallPool\r\n ];\r\n\r\n if (!_isInQueue(account, asc, desc)) return;\r\n\r\n address prev = desc[account];\r\n address next = asc[account];\r\n asc[prev] = next;\r\n desc[next] = prev;\r\n delete asc[account];\r\n delete desc[account];\r\n }\r\n\r\n function isInQueue(\r\n Layout storage l,\r\n address account,\r\n bool isCallPool\r\n ) internal view returns (bool) {\r\n mapping(address => address) storage asc = l.liquidityQueueAscending[\r\n isCallPool\r\n ];\r\n mapping(address => address) storage desc = l.liquidityQueueDescending[\r\n isCallPool\r\n ];\r\n\r\n return _isInQueue(account, asc, desc);\r\n }\r\n\r\n function _isInQueue(\r\n address account,\r\n mapping(address => address) storage asc,\r\n mapping(address => address) storage desc\r\n ) private view returns (bool) {\r\n return asc[account] != address(0) || desc[address(0)] == account;\r\n }\r\n\r\n /**\r\n * @notice get current C-Level, without accounting for pending adjustments\r\n * @param l storage layout struct\r\n * @param isCall whether query is for call or put pool\r\n * @return cLevel64x64 64x64 fixed point representation of C-Level\r\n */\r\n function getRawCLevel64x64(Layout storage l, bool isCall)\r\n internal\r\n view\r\n returns (int128 cLevel64x64)\r\n {\r\n cLevel64x64 = isCall ? l.cLevelUnderlying64x64 : l.cLevelBase64x64;\r\n }\r\n\r\n /**\r\n * @notice get current C-Level, accounting for unrealized decay\r\n * @param l storage layout struct\r\n * @param isCall whether query is for call or put pool\r\n * @return cLevel64x64 64x64 fixed point representation of C-Level\r\n */\r\n function getDecayAdjustedCLevel64x64(Layout storage l, bool isCall)\r\n internal\r\n view\r\n returns (int128 cLevel64x64)\r\n {\r\n // get raw C-Level from storage\r\n cLevel64x64 = l.getRawCLevel64x64(isCall);\r\n\r\n // account for C-Level decay\r\n cLevel64x64 = l.applyCLevelDecayAdjustment(cLevel64x64, isCall);\r\n }\r\n\r\n /**\r\n * @notice calculate updated C-Level, accounting for unrealized decay\r\n * @param l storage layout struct\r\n * @param oldCLevel64x64 64x64 fixed point representation pool C-Level before accounting for decay\r\n * @param isCall whether query is for call or put pool\r\n * @return cLevel64x64 64x64 fixed point representation of C-Level of Pool after accounting for decay\r\n */\r\n function applyCLevelDecayAdjustment(\r\n Layout storage l,\r\n int128 oldCLevel64x64,\r\n bool isCall\r\n ) internal view returns (int128 cLevel64x64) {\r\n uint256 timeElapsed = block.timestamp -\r\n (isCall ? l.cLevelUnderlyingUpdatedAt : l.cLevelBaseUpdatedAt);\r\n\r\n // do not apply C decay if less than 24 hours have elapsed\r\n\r\n if (timeElapsed > C_DECAY_BUFFER) {\r\n timeElapsed -= C_DECAY_BUFFER;\r\n } else {\r\n return oldCLevel64x64;\r\n }\r\n\r\n int128 timeIntervalsElapsed64x64 = ABDKMath64x64.divu(\r\n timeElapsed,\r\n C_DECAY_INTERVAL\r\n );\r\n\r\n uint256 tokenId = formatTokenId(\r\n isCall ? TokenType.UNDERLYING_FREE_LIQ : TokenType.BASE_FREE_LIQ,\r\n 0,\r\n 0\r\n );\r\n\r\n uint256 tvl = l.totalTVL[isCall];\r\n\r\n int128 utilization = ABDKMath64x64.divu(\r\n tvl -\r\n (ERC1155EnumerableStorage.layout().totalSupply[tokenId] -\r\n l.nextDeposits[isCall].totalPendingDeposits),\r\n tvl\r\n );\r\n\r\n return\r\n OptionMath.calculateCLevelDecay(\r\n OptionMath.CalculateCLevelDecayArgs(\r\n timeIntervalsElapsed64x64,\r\n oldCLevel64x64,\r\n utilization,\r\n 0xb333333333333333, // 0.7\r\n 0xe666666666666666, // 0.9\r\n 0x10000000000000000, // 1.0\r\n 0x10000000000000000, // 1.0\r\n 0xe666666666666666, // 0.9\r\n 0x56fc2a2c515da32ea // 2e\r\n )\r\n );\r\n }\r\n\r\n /**\r\n * @notice calculate updated C-Level, accounting for pending deposits\r\n * @param l storage layout struct\r\n * @param oldCLevel64x64 64x64 fixed point representation pool C-Level before accounting for liquidity change\r\n * @param oldLiquidity64x64 64x64 fixed point representation of previous liquidity\r\n * @param isCall whether to update C-Level for call or put pool\r\n * @return cLevel64x64 64x64 fixed point representation of C-Level\r\n * @return liquidity64x64 64x64 fixed point representation of new liquidity amount\r\n */\r\n function applyCLevelPendingDepositAdjustment(\r\n Layout storage l,\r\n int128 oldCLevel64x64,\r\n int128 oldLiquidity64x64,\r\n bool isCall\r\n ) internal view returns (int128 cLevel64x64, int128 liquidity64x64) {\r\n PoolStorage.BatchData storage batchData = l.nextDeposits[isCall];\r\n int128 pendingDeposits64x64;\r\n\r\n if (\r\n batchData.totalPendingDeposits > 0 &&\r\n batchData.eta != 0 &&\r\n block.timestamp >= batchData.eta\r\n ) {\r\n pendingDeposits64x64 = ABDKMath64x64Token.fromDecimals(\r\n batchData.totalPendingDeposits,\r\n l.getTokenDecimals(isCall)\r\n );\r\n\r\n liquidity64x64 = oldLiquidity64x64.add(pendingDeposits64x64);\r\n\r\n cLevel64x64 = l.applyCLevelLiquidityChangeAdjustment(\r\n oldCLevel64x64,\r\n oldLiquidity64x64,\r\n liquidity64x64,\r\n isCall\r\n );\r\n } else {\r\n cLevel64x64 = oldCLevel64x64;\r\n liquidity64x64 = oldLiquidity64x64;\r\n }\r\n }\r\n\r\n /**\r\n * @notice calculate updated C-Level, accounting for change in liquidity\r\n * @param l storage layout struct\r\n * @param oldCLevel64x64 64x64 fixed point representation pool C-Level before accounting for liquidity change\r\n * @param oldLiquidity64x64 64x64 fixed point representation of previous liquidity\r\n * @param newLiquidity64x64 64x64 fixed point representation of current liquidity\r\n * @param isCallPool whether to update C-Level for call or put pool\r\n * @return cLevel64x64 64x64 fixed point representation of C-Level\r\n */\r\n function applyCLevelLiquidityChangeAdjustment(\r\n Layout storage l,\r\n int128 oldCLevel64x64,\r\n int128 oldLiquidity64x64,\r\n int128 newLiquidity64x64,\r\n bool isCallPool\r\n ) internal view returns (int128 cLevel64x64) {\r\n int128 steepness64x64 = isCallPool\r\n ? l.steepnessUnderlying64x64\r\n : l.steepnessBase64x64;\r\n\r\n // fallback to deprecated storage value if side-specific value is not set\r\n if (steepness64x64 == 0) steepness64x64 = l._deprecated_steepness64x64;\r\n\r\n cLevel64x64 = OptionMath.calculateCLevel(\r\n oldCLevel64x64,\r\n oldLiquidity64x64,\r\n newLiquidity64x64,\r\n steepness64x64\r\n );\r\n\r\n if (cLevel64x64 < 0xb333333333333333) {\r\n cLevel64x64 = int128(0xb333333333333333); // 64x64 fixed point representation of 0.7\r\n }\r\n }\r\n\r\n /**\r\n * @notice set C-Level to arbitrary pre-calculated value\r\n * @param cLevel64x64 new C-Level of pool\r\n * @param isCallPool whether to update C-Level for call or put pool\r\n */\r\n function setCLevel(\r\n Layout storage l,\r\n int128 cLevel64x64,\r\n bool isCallPool\r\n ) internal {\r\n if (isCallPool) {\r\n l.cLevelUnderlying64x64 = cLevel64x64;\r\n l.cLevelUnderlyingUpdatedAt = block.timestamp;\r\n } else {\r\n l.cLevelBase64x64 = cLevel64x64;\r\n l.cLevelBaseUpdatedAt = block.timestamp;\r\n }\r\n }\r\n\r\n function setOracles(\r\n Layout storage l,\r\n address baseOracle,\r\n address underlyingOracle\r\n ) internal {\r\n require(\r\n AggregatorV3Interface(baseOracle).decimals() ==\r\n AggregatorV3Interface(underlyingOracle).decimals(),\r\n \"Pool: oracle decimals must match\"\r\n );\r\n\r\n l.baseOracle = baseOracle;\r\n l.underlyingOracle = underlyingOracle;\r\n }\r\n\r\n function fetchPriceUpdate(Layout storage l)\r\n internal\r\n view\r\n returns (int128 price64x64)\r\n {\r\n int256 priceUnderlying = AggregatorInterface(l.underlyingOracle)\r\n .latestAnswer();\r\n int256 priceBase = AggregatorInterface(l.baseOracle).latestAnswer();\r\n\r\n return ABDKMath64x64.divi(priceUnderlying, priceBase);\r\n }\r\n\r\n /**\r\n * @notice set price update for hourly bucket corresponding to given timestamp\r\n * @param l storage layout struct\r\n * @param timestamp timestamp to update\r\n * @param price64x64 64x64 fixed point representation of price\r\n */\r\n function setPriceUpdate(\r\n Layout storage l,\r\n uint256 timestamp,\r\n int128 price64x64\r\n ) internal {\r\n uint256 bucket = timestamp / (1 hours);\r\n l.bucketPrices64x64[bucket] = price64x64;\r\n l.priceUpdateSequences[bucket >> 8] += 1 << (255 - (bucket & 255));\r\n }\r\n\r\n /**\r\n * @notice get price update for hourly bucket corresponding to given timestamp\r\n * @param l storage layout struct\r\n * @param timestamp timestamp to query\r\n * @return 64x64 fixed point representation of price\r\n */\r\n function getPriceUpdate(Layout storage l, uint256 timestamp)\r\n internal\r\n view\r\n returns (int128)\r\n {\r\n return l.bucketPrices64x64[timestamp / (1 hours)];\r\n }\r\n\r\n /**\r\n * @notice get first price update available following given timestamp\r\n * @param l storage layout struct\r\n * @param timestamp timestamp to query\r\n * @return 64x64 fixed point representation of price\r\n */\r\n function getPriceUpdateAfter(Layout storage l, uint256 timestamp)\r\n internal\r\n view\r\n returns (int128)\r\n {\r\n // price updates are grouped into hourly buckets\r\n uint256 bucket = timestamp / (1 hours);\r\n // divide by 256 to get the index of the relevant price update sequence\r\n uint256 sequenceId = bucket >> 8;\r\n\r\n // get position within sequence relevant to current price update\r\n\r\n uint256 offset = bucket & 255;\r\n // shift to skip buckets from earlier in sequence\r\n uint256 sequence = (l.priceUpdateSequences[sequenceId] << offset) >>\r\n offset;\r\n\r\n // iterate through future sequences until a price update is found\r\n // sequence corresponding to current timestamp used as upper bound\r\n\r\n uint256 currentPriceUpdateSequenceId = block.timestamp / (256 hours);\r\n\r\n while (sequence == 0 && sequenceId <= currentPriceUpdateSequenceId) {\r\n sequence = l.priceUpdateSequences[++sequenceId];\r\n }\r\n\r\n // if no price update is found (sequence == 0) function will return 0\r\n // this should never occur, as each relevant external function triggers a price update\r\n\r\n // the most significant bit of the sequence corresponds to the offset of the relevant bucket\r\n\r\n uint256 msb;\r\n\r\n for (uint256 i = 128; i > 0; i >>= 1) {\r\n if (sequence >> i > 0) {\r\n msb += i;\r\n sequence >>= i;\r\n }\r\n }\r\n\r\n return l.bucketPrices64x64[((sequenceId + 1) << 8) - msb - 1];\r\n }\r\n\r\n function fromBaseToUnderlyingDecimals(Layout storage l, uint256 value)\r\n internal\r\n view\r\n returns (uint256)\r\n {\r\n int128 valueFixed64x64 = ABDKMath64x64Token.fromDecimals(\r\n value,\r\n l.baseDecimals\r\n );\r\n return\r\n ABDKMath64x64Token.toDecimals(\r\n valueFixed64x64,\r\n l.underlyingDecimals\r\n );\r\n }\r\n\r\n function fromUnderlyingToBaseDecimals(Layout storage l, uint256 value)\r\n internal\r\n view\r\n returns (uint256)\r\n {\r\n int128 valueFixed64x64 = ABDKMath64x64Token.fromDecimals(\r\n value,\r\n l.underlyingDecimals\r\n );\r\n return ABDKMath64x64Token.toDecimals(valueFixed64x64, l.baseDecimals);\r\n }\r\n}\r\n"},"@solidstate/contracts/utils/AddressUtils.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nlibrary AddressUtils {\n function toString(address account) internal pure returns (string memory) {\n bytes32 value = bytes32(uint256(uint160(account)));\n bytes memory alphabet = '0123456789abcdef';\n bytes memory chars = new bytes(42);\n\n chars[0] = '0';\n chars[1] = 'x';\n\n for (uint256 i = 0; i < 20; i++) {\n chars[2 + i * 2] = alphabet[uint8(value[i + 12] >> 4)];\n chars[3 + i * 2] = alphabet[uint8(value[i + 12] & 0x0f)];\n }\n\n return string(chars);\n }\n\n function isContract(address account) internal view returns (bool) {\n uint256 size;\n assembly {\n size := extcodesize(account)\n }\n return size > 0;\n }\n\n function sendValue(address payable account, uint256 amount) internal {\n (bool success, ) = account.call{ value: amount }('');\n require(success, 'AddressUtils: failed to send value');\n }\n\n function functionCall(address target, bytes memory data)\n internal\n returns (bytes memory)\n {\n return\n functionCall(target, data, 'AddressUtils: failed low-level call');\n }\n\n function functionCall(\n address target,\n bytes memory data,\n string memory error\n ) internal returns (bytes memory) {\n return _functionCallWithValue(target, data, 0, error);\n }\n\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value\n ) internal returns (bytes memory) {\n return\n functionCallWithValue(\n target,\n data,\n value,\n 'AddressUtils: failed low-level call with value'\n );\n }\n\n function functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory error\n ) internal returns (bytes memory) {\n require(\n address(this).balance >= value,\n 'AddressUtils: insufficient balance for call'\n );\n return _functionCallWithValue(target, data, value, error);\n }\n\n function _functionCallWithValue(\n address target,\n bytes memory data,\n uint256 value,\n string memory error\n ) private returns (bytes memory) {\n require(\n isContract(target),\n 'AddressUtils: function call to non-contract'\n );\n\n (bool success, bytes memory returnData) = target.call{ value: value }(\n data\n );\n\n if (success) {\n return returnData;\n } else if (returnData.length > 0) {\n assembly {\n let returnData_size := mload(returnData)\n revert(add(32, returnData), returnData_size)\n }\n } else {\n revert(error);\n }\n }\n}\n"},"@solidstate/contracts/token/ERC1155/enumerable/ERC1155Enumerable.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { EnumerableSet } from '../../../utils/EnumerableSet.sol';\nimport { ERC1155Base, ERC1155BaseInternal } from '../base/ERC1155Base.sol';\nimport { IERC1155Enumerable } from './IERC1155Enumerable.sol';\nimport { ERC1155EnumerableInternal, ERC1155EnumerableStorage } from './ERC1155EnumerableInternal.sol';\n\n/**\n * @title ERC1155 implementation including enumerable and aggregate functions\n */\nabstract contract ERC1155Enumerable is\n IERC1155Enumerable,\n ERC1155Base,\n ERC1155EnumerableInternal\n{\n using EnumerableSet for EnumerableSet.AddressSet;\n using EnumerableSet for EnumerableSet.UintSet;\n\n /**\n * @inheritdoc IERC1155Enumerable\n */\n function totalSupply(uint256 id)\n public\n view\n virtual\n override\n returns (uint256)\n {\n return ERC1155EnumerableStorage.layout().totalSupply[id];\n }\n\n /**\n * @inheritdoc IERC1155Enumerable\n */\n function totalHolders(uint256 id)\n public\n view\n virtual\n override\n returns (uint256)\n {\n return ERC1155EnumerableStorage.layout().accountsByToken[id].length();\n }\n\n /**\n * @inheritdoc IERC1155Enumerable\n */\n function accountsByToken(uint256 id)\n public\n view\n virtual\n override\n returns (address[] memory)\n {\n EnumerableSet.AddressSet storage accounts = ERC1155EnumerableStorage\n .layout()\n .accountsByToken[id];\n\n address[] memory addresses = new address[](accounts.length());\n\n for (uint256 i; i < accounts.length(); i++) {\n addresses[i] = accounts.at(i);\n }\n\n return addresses;\n }\n\n /**\n * @inheritdoc IERC1155Enumerable\n */\n function tokensByAccount(address account)\n public\n view\n virtual\n override\n returns (uint256[] memory)\n {\n EnumerableSet.UintSet storage tokens = ERC1155EnumerableStorage\n .layout()\n .tokensByAccount[account];\n\n uint256[] memory ids = new uint256[](tokens.length());\n\n for (uint256 i; i < tokens.length(); i++) {\n ids[i] = tokens.at(i);\n }\n\n return ids;\n }\n\n /**\n * @notice ERC1155 hook: update aggregate values\n * @inheritdoc ERC1155EnumerableInternal\n */\n function _beforeTokenTransfer(\n address operator,\n address from,\n address to,\n uint256[] memory ids,\n uint256[] memory amounts,\n bytes memory data\n )\n internal\n virtual\n override(ERC1155BaseInternal, ERC1155EnumerableInternal)\n {\n super._beforeTokenTransfer(operator, from, to, ids, amounts, data);\n }\n}\n"},"@solidstate/contracts/token/ERC1155/IERC1155Receiver.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { IERC165 } from '../../introspection/IERC165.sol';\n\n/**\n * @title ERC1155 transfer receiver interface\n */\ninterface IERC1155Receiver is IERC165 {\n /**\n * @notice validate receipt of ERC1155 transfer\n * @param operator executor of transfer\n * @param from sender of tokens\n * @param id token ID received\n * @param value quantity of tokens received\n * @param data data payload\n * @return function's own selector if transfer is accepted\n */\n function onERC1155Received(\n address operator,\n address from,\n uint256 id,\n uint256 value,\n bytes calldata data\n ) external returns (bytes4);\n\n /**\n * @notice validate receipt of ERC1155 batch transfer\n * @param operator executor of transfer\n * @param from sender of tokens\n * @param ids token IDs received\n * @param values quantities of tokens received\n * @param data data payload\n * @return function's own selector if transfer is accepted\n */\n function onERC1155BatchReceived(\n address operator,\n address from,\n uint256[] calldata ids,\n uint256[] calldata values,\n bytes calldata data\n ) external returns (bytes4);\n}\n"},"contracts/pool/IPoolEvents.sol":{"content":"// SPDX-License-Identifier: LGPL-3.0-or-later\r\n\r\npragma solidity ^0.8.0;\r\n\r\ninterface IPoolEvents {\r\n event Purchase(\r\n address indexed user,\r\n uint256 longTokenId,\r\n uint256 contractSize,\r\n uint256 baseCost,\r\n uint256 feeCost,\r\n int128 spot64x64\r\n );\r\n\r\n event Exercise(\r\n address indexed user,\r\n uint256 longTokenId,\r\n uint256 contractSize,\r\n uint256 exerciseValue,\r\n uint256 fee\r\n );\r\n\r\n event Underwrite(\r\n address indexed underwriter,\r\n address indexed longReceiver,\r\n uint256 shortTokenId,\r\n uint256 intervalContractSize,\r\n uint256 intervalPremium,\r\n bool isManualUnderwrite\r\n );\r\n\r\n event AssignExercise(\r\n address indexed underwriter,\r\n uint256 shortTokenId,\r\n uint256 freedAmount,\r\n uint256 intervalContractSize,\r\n uint256 fee\r\n );\r\n\r\n event Deposit(address indexed user, bool isCallPool, uint256 amount);\r\n\r\n event Withdrawal(\r\n address indexed user,\r\n bool isCallPool,\r\n uint256 depositedAt,\r\n uint256 amount\r\n );\r\n\r\n event FeeWithdrawal(bool indexed isCallPool, uint256 amount);\r\n\r\n event Annihilate(uint256 shortTokenId, uint256 amount);\r\n\r\n event UpdateCLevel(\r\n bool indexed isCall,\r\n int128 cLevel64x64,\r\n int128 oldLiquidity64x64,\r\n int128 newLiquidity64x64\r\n );\r\n\r\n event UpdateSteepness(int128 steepness64x64, bool isCallPool);\r\n}\r\n"},"contracts/oracle/VolatilitySurfaceOracleStorage.sol":{"content":"// SPDX-License-Identifier: BUSL-1.1\r\n// For further clarification please see https://license.premia.legal\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport {EnumerableSet} from \"@solidstate/contracts/utils/EnumerableSet.sol\";\r\n\r\nlibrary VolatilitySurfaceOracleStorage {\r\n bytes32 internal constant STORAGE_SLOT =\r\n keccak256(\"premia.contracts.storage.VolatilitySurfaceOracle\");\r\n\r\n uint256 internal constant COEFF_BITS = 51;\r\n uint256 internal constant COEFF_BITS_MINUS_ONE = 50;\r\n uint256 internal constant COEFF_AMOUNT = 5;\r\n // START_BIT = COEFF_BITS * (COEFF_AMOUNT - 1)\r\n uint256 internal constant START_BIT = 204;\r\n\r\n struct Update {\r\n uint256 updatedAt;\r\n bytes32 callCoefficients;\r\n bytes32 putCoefficients;\r\n }\r\n\r\n struct Layout {\r\n // Base token -> Underlying token -> Update\r\n mapping(address => mapping(address => Update)) volatilitySurfaces;\r\n // Relayer addresses which can be trusted to provide accurate option trades\r\n EnumerableSet.AddressSet whitelistedRelayers;\r\n }\r\n\r\n function layout() internal pure returns (Layout storage l) {\r\n bytes32 slot = STORAGE_SLOT;\r\n assembly {\r\n l.slot := slot\r\n }\r\n }\r\n\r\n function getCoefficients(\r\n Layout storage l,\r\n address baseToken,\r\n address underlyingToken,\r\n bool isCall\r\n ) internal view returns (bytes32) {\r\n Update storage u = l.volatilitySurfaces[baseToken][underlyingToken];\r\n return isCall ? u.callCoefficients : u.putCoefficients;\r\n }\r\n\r\n function parseVolatilitySurfaceCoefficients(bytes32 input)\r\n internal\r\n pure\r\n returns (int256[] memory coefficients)\r\n {\r\n coefficients = new int256[](COEFF_AMOUNT);\r\n\r\n // Value to add to negative numbers to cast them to int256\r\n int256 toAdd = (int256(-1) >> COEFF_BITS) << COEFF_BITS;\r\n\r\n assembly {\r\n let i := 0\r\n // Value equal to -1\r\n let mid := shl(COEFF_BITS_MINUS_ONE, 1)\r\n\r\n for {\r\n\r\n } lt(i, COEFF_AMOUNT) {\r\n\r\n } {\r\n let offset := sub(START_BIT, mul(COEFF_BITS, i))\r\n let coeff := shr(\r\n offset,\r\n sub(\r\n input,\r\n shl(\r\n add(offset, COEFF_BITS),\r\n shr(add(offset, COEFF_BITS), input)\r\n )\r\n )\r\n )\r\n\r\n // Check if value is a negative number and needs casting\r\n if or(eq(coeff, mid), gt(coeff, mid)) {\r\n coeff := add(coeff, toAdd)\r\n }\r\n\r\n // Store result in the coefficients array\r\n mstore(add(coefficients, add(0x20, mul(0x20, i))), coeff)\r\n\r\n i := add(i, 1)\r\n }\r\n }\r\n }\r\n\r\n function formatVolatilitySurfaceCoefficients(int256[5] memory coefficients)\r\n internal\r\n pure\r\n returns (bytes32 result)\r\n {\r\n for (uint256 i = 0; i < COEFF_AMOUNT; i++) {\r\n int256 max = int256(1 << COEFF_BITS_MINUS_ONE);\r\n require(\r\n coefficients[i] < max && coefficients[i] > -max,\r\n \"Out of bounds\"\r\n );\r\n }\r\n\r\n assembly {\r\n let i := 0\r\n\r\n for {\r\n\r\n } lt(i, COEFF_AMOUNT) {\r\n\r\n } {\r\n let offset := sub(START_BIT, mul(COEFF_BITS, i))\r\n let coeff := mload(add(coefficients, mul(0x20, i)))\r\n\r\n result := add(\r\n result,\r\n shl(\r\n offset,\r\n sub(coeff, shl(COEFF_BITS, shr(COEFF_BITS, coeff)))\r\n )\r\n )\r\n\r\n i := add(i, 1)\r\n }\r\n }\r\n }\r\n}\r\n"},"@solidstate/contracts/token/ERC1155/enumerable/IERC1155Enumerable.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @title ERC1155 enumerable and aggregate function interface\n */\ninterface IERC1155Enumerable {\n /**\n * @notice query total minted supply of given token\n * @param id token id to query\n * @return token supply\n */\n function totalSupply(uint256 id) external view returns (uint256);\n\n /**\n * @notice query total number of holders for given token\n * @param id token id to query\n * @return quantity of holders\n */\n function totalHolders(uint256 id) external view returns (uint256);\n\n /**\n * @notice query holders of given token\n * @param id token id to query\n * @return list of holder addresses\n */\n function accountsByToken(uint256 id)\n external\n view\n returns (address[] memory);\n\n /**\n * @notice query tokens held by given address\n * @param account address to query\n * @return list of token ids\n */\n function tokensByAccount(address account)\n external\n view\n returns (uint256[] memory);\n}\n"},"@solidstate/contracts/token/ERC20/IERC20.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { IERC20Internal } from './IERC20Internal.sol';\n\n/**\n * @title ERC20 interface\n * @dev see https://github.com/ethereum/EIPs/issues/20\n */\ninterface IERC20 is IERC20Internal {\n /**\n * @notice query the total minted token supply\n * @return token supply\n */\n function totalSupply() external view returns (uint256);\n\n /**\n * @notice query the token balance of given account\n * @param account address to query\n * @return token balance\n */\n function balanceOf(address account) external view returns (uint256);\n\n /**\n * @notice query the allowance granted from given holder to given spender\n * @param holder approver of allowance\n * @param spender recipient of allowance\n * @return token allowance\n */\n function allowance(address holder, address spender)\n external\n view\n returns (uint256);\n\n /**\n * @notice grant approval to spender to spend tokens\n * @dev prefer ERC20Extended functions to avoid transaction-ordering vulnerability (see https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729)\n * @param spender recipient of allowance\n * @param amount quantity of tokens approved for spending\n * @return success status (always true; otherwise function should revert)\n */\n function approve(address spender, uint256 amount) external returns (bool);\n\n /**\n * @notice transfer tokens to given recipient\n * @param recipient beneficiary of token transfer\n * @param amount quantity of tokens to transfer\n * @return success status (always true; otherwise function should revert)\n */\n function transfer(address recipient, uint256 amount)\n external\n returns (bool);\n\n /**\n * @notice transfer tokens to given recipient on behalf of given holder\n * @param holder holder of tokens prior to transfer\n * @param recipient beneficiary of token transfer\n * @param amount quantity of tokens to transfer\n * @return success status (always true; otherwise function should revert)\n */\n function transferFrom(\n address holder,\n address recipient,\n uint256 amount\n ) external returns (bool);\n}\n"},"contracts/mining/IPremiaMining.sol":{"content":"// SPDX-License-Identifier: LGPL-3.0-or-later\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport {PremiaMiningStorage} from \"./PremiaMiningStorage.sol\";\r\n\r\ninterface IPremiaMining {\r\n function addPremiaRewards(uint256 _amount) external;\r\n\r\n function premiaRewardsAvailable() external view returns (uint256);\r\n\r\n function getTotalAllocationPoints() external view returns (uint256);\r\n\r\n function getPoolInfo(address pool, bool isCallPool)\r\n external\r\n view\r\n returns (PremiaMiningStorage.PoolInfo memory);\r\n\r\n function getPremiaPerYear() external view returns (uint256);\r\n\r\n function addPool(address _pool, uint256 _allocPoints) external;\r\n\r\n function setPoolAllocPoints(\r\n address[] memory _pools,\r\n uint256[] memory _allocPoints\r\n ) external;\r\n\r\n function pendingPremia(\r\n address _pool,\r\n bool _isCallPool,\r\n address _user\r\n ) external view returns (uint256);\r\n\r\n function updatePool(\r\n address _pool,\r\n bool _isCallPool,\r\n uint256 _totalTVL\r\n ) external;\r\n\r\n function allocatePending(\r\n address _user,\r\n address _pool,\r\n bool _isCallPool,\r\n uint256 _userTVLOld,\r\n uint256 _userTVLNew,\r\n uint256 _totalTVL\r\n ) external;\r\n\r\n function claim(\r\n address _user,\r\n address _pool,\r\n bool _isCallPool,\r\n uint256 _userTVLOld,\r\n uint256 _userTVLNew,\r\n uint256 _totalTVL\r\n ) external;\r\n}\r\n"},"@solidstate/contracts/token/ERC1155/enumerable/ERC1155EnumerableStorage.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { EnumerableSet } from '../../../utils/EnumerableSet.sol';\n\nlibrary ERC1155EnumerableStorage {\n struct Layout {\n mapping(uint256 => uint256) totalSupply;\n mapping(uint256 => EnumerableSet.AddressSet) accountsByToken;\n mapping(address => EnumerableSet.UintSet) tokensByAccount;\n }\n\n bytes32 internal constant STORAGE_SLOT =\n keccak256('solidstate.contracts.storage.ERC1155Enumerable');\n\n function layout() internal pure returns (Layout storage l) {\n bytes32 slot = STORAGE_SLOT;\n assembly {\n l.slot := slot\n }\n }\n}\n"},"@solidstate/contracts/token/ERC1155/enumerable/ERC1155EnumerableInternal.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { EnumerableSet } from '../../../utils/EnumerableSet.sol';\nimport { ERC1155BaseInternal, ERC1155BaseStorage } from '../base/ERC1155BaseInternal.sol';\nimport { ERC1155EnumerableStorage } from './ERC1155EnumerableStorage.sol';\n\n/**\n * @title ERC1155Enumerable internal functions\n */\nabstract contract ERC1155EnumerableInternal is ERC1155BaseInternal {\n using EnumerableSet for EnumerableSet.AddressSet;\n using EnumerableSet for EnumerableSet.UintSet;\n\n /**\n * @notice ERC1155 hook: update aggregate values\n * @inheritdoc ERC1155BaseInternal\n */\n function _beforeTokenTransfer(\n address operator,\n address from,\n address to,\n uint256[] memory ids,\n uint256[] memory amounts,\n bytes memory data\n ) internal virtual override {\n super._beforeTokenTransfer(operator, from, to, ids, amounts, data);\n\n if (from != to) {\n ERC1155EnumerableStorage.Layout storage l = ERC1155EnumerableStorage\n .layout();\n mapping(uint256 => EnumerableSet.AddressSet)\n storage tokenAccounts = l.accountsByToken;\n EnumerableSet.UintSet storage fromTokens = l.tokensByAccount[from];\n EnumerableSet.UintSet storage toTokens = l.tokensByAccount[to];\n\n for (uint256 i; i < ids.length; i++) {\n uint256 amount = amounts[i];\n\n if (amount > 0) {\n uint256 id = ids[i];\n\n if (from == address(0)) {\n l.totalSupply[id] += amount;\n } else if (_balanceOf(from, id) == amount) {\n tokenAccounts[id].remove(from);\n fromTokens.remove(id);\n }\n\n if (to == address(0)) {\n l.totalSupply[id] -= amount;\n } else if (_balanceOf(to, id) == 0) {\n tokenAccounts[id].add(to);\n toTokens.add(id);\n }\n }\n }\n }\n }\n}\n"},"contracts/oracle/IVolatilitySurfaceOracle.sol":{"content":"// SPDX-License-Identifier: LGPL-3.0-or-later\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport {VolatilitySurfaceOracleStorage} from \"./VolatilitySurfaceOracleStorage.sol\";\r\n\r\ninterface IVolatilitySurfaceOracle {\r\n function getWhitelistedRelayers() external view returns (address[] memory);\r\n\r\n function getVolatilitySurface(address baseToken, address underlyingToken)\r\n external\r\n view\r\n returns (VolatilitySurfaceOracleStorage.Update memory);\r\n\r\n function getVolatilitySurfaceCoefficientsUnpacked(\r\n address baseToken,\r\n address underlyingToken,\r\n bool isCall\r\n ) external view returns (int256[] memory);\r\n\r\n function getTimeToMaturity64x64(uint64 maturity)\r\n external\r\n view\r\n returns (int128);\r\n\r\n function getAnnualizedVolatility64x64(\r\n address baseToken,\r\n address underlyingToken,\r\n int128 spot64x64,\r\n int128 strike64x64,\r\n int128 timeToMaturity64x64,\r\n bool isCall\r\n ) external view returns (int128);\r\n\r\n function getBlackScholesPrice64x64(\r\n address baseToken,\r\n address underlyingToken,\r\n int128 strike64x64,\r\n int128 spot64x64,\r\n int128 timeToMaturity64x64,\r\n bool isCall\r\n ) external view returns (int128);\r\n\r\n function getBlackScholesPrice(\r\n address baseToken,\r\n address underlyingToken,\r\n int128 strike64x64,\r\n int128 spot64x64,\r\n int128 timeToMaturity64x64,\r\n bool isCall\r\n ) external view returns (uint256);\r\n}\r\n"},"contracts/pool/PoolInternal.sol":{"content":"// SPDX-License-Identifier: BUSL-1.1\r\n// For further clarification please see https://license.premia.legal\r\n\r\npragma solidity ^0.8.0;\r\n\r\nimport {IERC173} from \"@solidstate/contracts/access/IERC173.sol\";\r\nimport {OwnableStorage} from \"@solidstate/contracts/access/OwnableStorage.sol\";\r\nimport {IERC20} from \"@solidstate/contracts/token/ERC20/IERC20.sol\";\r\nimport {ERC1155EnumerableInternal, ERC1155EnumerableStorage, EnumerableSet} from \"@solidstate/contracts/token/ERC1155/enumerable/ERC1155Enumerable.sol\";\r\nimport {IWETH} from \"@solidstate/contracts/utils/IWETH.sol\";\r\n\r\nimport {PoolStorage} from \"./PoolStorage.sol\";\r\n\r\nimport {ABDKMath64x64} from \"abdk-libraries-solidity/ABDKMath64x64.sol\";\r\nimport {ABDKMath64x64Token} from \"../libraries/ABDKMath64x64Token.sol\";\r\nimport {OptionMath} from \"../libraries/OptionMath.sol\";\r\nimport {IFeeDiscount} from \"../staking/IFeeDiscount.sol\";\r\nimport {IPoolEvents} from \"./IPoolEvents.sol\";\r\nimport {IPremiaMining} from \"../mining/IPremiaMining.sol\";\r\nimport {IVolatilitySurfaceOracle} from \"../oracle/IVolatilitySurfaceOracle.sol\";\r\n\r\n/**\r\n * @title Premia option pool\r\n * @dev deployed standalone and referenced by PoolProxy\r\n */\r\ncontract PoolInternal is IPoolEvents, ERC1155EnumerableInternal {\r\n using ABDKMath64x64 for int128;\r\n using EnumerableSet for EnumerableSet.AddressSet;\r\n using EnumerableSet for EnumerableSet.UintSet;\r\n using PoolStorage for PoolStorage.Layout;\r\n\r\n address internal immutable WETH_ADDRESS;\r\n address internal immutable PREMIA_MINING_ADDRESS;\r\n address internal immutable FEE_RECEIVER_ADDRESS;\r\n address internal immutable FEE_DISCOUNT_ADDRESS;\r\n address internal immutable IVOL_ORACLE_ADDRESS;\r\n\r\n int128 internal immutable FEE_64x64;\r\n\r\n uint256 internal immutable UNDERLYING_FREE_LIQ_TOKEN_ID;\r\n uint256 internal immutable BASE_FREE_LIQ_TOKEN_ID;\r\n\r\n uint256 internal immutable UNDERLYING_RESERVED_LIQ_TOKEN_ID;\r\n uint256 internal immutable BASE_RESERVED_LIQ_TOKEN_ID;\r\n\r\n uint256 internal constant INVERSE_BASIS_POINT = 1e4;\r\n uint256 internal constant BATCHING_PERIOD = 260;\r\n\r\n // Minimum APY for capital locked up to underwrite options.\r\n // The quote will return a minimum price corresponding to this APY\r\n int128 internal constant MIN_APY_64x64 = 0x4ccccccccccccccd; // 0.3\r\n\r\n constructor(\r\n address ivolOracle,\r\n address weth,\r\n address premiaMining,\r\n address feeReceiver,\r\n address feeDiscountAddress,\r\n int128 fee64x64\r\n ) {\r\n IVOL_ORACLE_ADDRESS = ivolOracle;\r\n WETH_ADDRESS = weth;\r\n PREMIA_MINING_ADDRESS = premiaMining;\r\n FEE_RECEIVER_ADDRESS = feeReceiver;\r\n // PremiaFeeDiscount contract address\r\n FEE_DISCOUNT_ADDRESS = feeDiscountAddress;\r\n FEE_64x64 = fee64x64;\r\n\r\n UNDERLYING_FREE_LIQ_TOKEN_ID = PoolStorage.formatTokenId(\r\n PoolStorage.TokenType.UNDERLYING_FREE_LIQ,\r\n 0,\r\n 0\r\n );\r\n BASE_FREE_LIQ_TOKEN_ID = PoolStorage.formatTokenId(\r\n PoolStorage.TokenType.BASE_FREE_LIQ,\r\n 0,\r\n 0\r\n );\r\n\r\n UNDERLYING_RESERVED_LIQ_TOKEN_ID = PoolStorage.formatTokenId(\r\n PoolStorage.TokenType.UNDERLYING_RESERVED_LIQ,\r\n 0,\r\n 0\r\n );\r\n BASE_RESERVED_LIQ_TOKEN_ID = PoolStorage.formatTokenId(\r\n PoolStorage.TokenType.BASE_RESERVED_LIQ,\r\n 0,\r\n 0\r\n );\r\n }\r\n\r\n modifier onlyProtocolOwner() {\r\n require(\r\n msg.sender == IERC173(OwnableStorage.layout().owner).owner(),\r\n \"Not protocol owner\"\r\n );\r\n _;\r\n }\r\n\r\n function _getFeeDiscount(address feePayer)\r\n internal\r\n view\r\n returns (uint256 discount)\r\n {\r\n if (FEE_DISCOUNT_ADDRESS != address(0)) {\r\n discount = IFeeDiscount(FEE_DISCOUNT_ADDRESS).getDiscount(feePayer);\r\n }\r\n }\r\n\r\n function _getFeeWithDiscount(address feePayer, uint256 fee)\r\n internal\r\n view\r\n returns (uint256)\r\n {\r\n uint256 discount = _getFeeDiscount(feePayer);\r\n return fee - ((fee * discount) / INVERSE_BASIS_POINT);\r\n }\r\n\r\n function _withdrawFees(bool isCall) internal returns (uint256 amount) {\r\n uint256 tokenId = _getReservedLiquidityTokenId(isCall);\r\n amount = _balanceOf(FEE_RECEIVER_ADDRESS, tokenId);\r\n\r\n if (amount > 0) {\r\n _burn(FEE_RECEIVER_ADDRESS, tokenId, amount);\r\n emit FeeWithdrawal(isCall, amount);\r\n }\r\n }\r\n\r\n /**\r\n * @notice calculate price of option contract\r\n * @param args structured quote arguments\r\n * @return result quote result\r\n */\r\n function _quote(PoolStorage.QuoteArgsInternal memory args)\r\n internal\r\n view\r\n returns (PoolStorage.QuoteResultInternal memory result)\r\n {\r\n require(\r\n args.strike64x64 > 0 && args.spot64x64 > 0 && args.maturity > 0,\r\n \"invalid args\"\r\n );\r\n PoolStorage.Layout storage l = PoolStorage.layout();\r\n\r\n int128 contractSize64x64 = ABDKMath64x64Token.fromDecimals(\r\n args.contractSize,\r\n l.underlyingDecimals\r\n );\r\n bool isCall = args.isCall;\r\n\r\n (int128 adjustedCLevel64x64, int128 oldLiquidity64x64) = l\r\n .applyCLevelPendingDepositAdjustment(\r\n l.getDecayAdjustedCLevel64x64(isCall),\r\n l.totalFreeLiquiditySupply64x64(isCall),\r\n isCall\r\n );\r\n\r\n require(oldLiquidity64x64 > 0, \"no liq\");\r\n\r\n int128 timeToMaturity64x64 = ABDKMath64x64.divu(\r\n args.maturity - block.timestamp,\r\n 365 days\r\n );\r\n\r\n int128 annualizedVolatility64x64 = IVolatilitySurfaceOracle(\r\n IVOL_ORACLE_ADDRESS\r\n ).getAnnualizedVolatility64x64(\r\n l.base,\r\n l.underlying,\r\n args.spot64x64,\r\n args.strike64x64,\r\n timeToMaturity64x64,\r\n isCall\r\n );\r\n\r\n require(annualizedVolatility64x64 > 0, \"vol = 0\");\r\n\r\n (\r\n int128 price64x64,\r\n int128 cLevel64x64,\r\n int128 slippageCoefficient64x64\r\n ) = OptionMath.quotePrice(\r\n OptionMath.QuoteArgs(\r\n annualizedVolatility64x64.mul(annualizedVolatility64x64),\r\n args.strike64x64,\r\n args.spot64x64,\r\n timeToMaturity64x64,\r\n adjustedCLevel64x64,\r\n oldLiquidity64x64,\r\n oldLiquidity64x64.sub(contractSize64x64),\r\n 0x10000000000000000, // 64x64 fixed point representation of 1\r\n MIN_APY_64x64,\r\n isCall\r\n )\r\n );\r\n\r\n result.baseCost64x64 = isCall\r\n ? price64x64.mul(contractSize64x64).div(args.spot64x64)\r\n : price64x64.mul(contractSize64x64);\r\n result.feeCost64x64 = result.baseCost64x64.mul(FEE_64x64);\r\n result.cLevel64x64 = cLevel64x64;\r\n result.slippageCoefficient64x64 = slippageCoefficient64x64;\r\n\r\n int128 discount = ABDKMath64x64.divu(\r\n _getFeeDiscount(args.feePayer),\r\n INVERSE_BASIS_POINT\r\n );\r\n result.feeCost64x64 -= result.feeCost64x64.mul(discount);\r\n }\r\n\r\n /**\r\n * @notice burn corresponding long and short option tokens\r\n * @param account holder of tokens to annihilate\r\n * @param maturity timestamp of option maturity\r\n * @param strike64x64 64x64 fixed point representation of strike price\r\n * @param isCall true for call, false for put\r\n * @param contractSize quantity of option contract tokens to annihilate\r\n */\r\n function _annihilate(\r\n address account,\r\n uint64 maturity,\r\n int128 strike64x64,\r\n bool isCall,\r\n uint256 contractSize\r\n ) internal {\r\n uint256 longTokenId = PoolStorage.formatTokenId(\r\n _getTokenType(isCall, true),\r\n maturity,\r\n strike64x64\r\n );\r\n uint256 shortTokenId = PoolStorage.formatTokenId(\r\n _getTokenType(isCall, false),\r\n maturity,\r\n strike64x64\r\n );\r\n\r\n _burn(account, longTokenId, contractSize);\r\n _burn(account, shortTokenId, contractSize);\r\n\r\n emit Annihilate(shortTokenId, contractSize);\r\n }\r\n\r\n /**\r\n * @notice purchase option\r\n * @param l storage layout struct\r\n * @param account recipient of purchased option\r\n * @param maturity timestamp of option maturity\r\n * @param strike64x64 64x64 fixed point representation of strike price\r\n * @param isCall true for call, false for put\r\n * @param contractSize size of option contract\r\n * @param newPrice64x64 64x64 fixed point representation of current spot price\r\n * @return baseCost quantity of tokens required to purchase long position\r\n * @return feeCost quantity of tokens required to pay fees\r\n */\r\n function _purchase(\r\n PoolStorage.Layout storage l,\r\n address account,\r\n uint64 maturity,\r\n int128 strike64x64,\r\n bool isCall,\r\n uint256 contractSize,\r\n int128 newPrice64x64\r\n ) internal returns (uint256 baseCost, uint256 feeCost) {\r\n require(maturity > block.timestamp, \"expired\");\r\n require(contractSize >= l.underlyingMinimum, \"too small\");\r\n\r\n {\r\n uint256 size = isCall\r\n ? contractSize\r\n : l.fromUnderlyingToBaseDecimals(\r\n strike64x64.mulu(contractSize)\r\n );\r\n\r\n require(\r\n size <=\r\n ERC1155EnumerableStorage.layout().totalSupply[\r\n _getFreeLiquidityTokenId(isCall)\r\n ] -\r\n l.nextDeposits[isCall].totalPendingDeposits,\r\n \"insuf liq\"\r\n );\r\n }\r\n\r\n PoolStorage.QuoteResultInternal memory quote = _quote(\r\n PoolStorage.QuoteArgsInternal(\r\n account,\r\n maturity,\r\n strike64x64,\r\n newPrice64x64,\r\n contractSize,\r\n isCall\r\n )\r\n );\r\n\r\n baseCost = ABDKMath64x64Token.toDecimals(\r\n quote.baseCost64x64,\r\n l.getTokenDecimals(isCall)\r\n );\r\n\r\n feeCost = ABDKMath64x64Token.toDecimals(\r\n quote.feeCost64x64,\r\n l.getTokenDecimals(isCall)\r\n );\r\n\r\n uint256 longTokenId = PoolStorage.formatTokenId(\r\n _getTokenType(isCall, true),\r\n maturity,\r\n strike64x64\r\n );\r\n\r\n uint256 shortTokenId = PoolStorage.formatTokenId(\r\n _getTokenType(isCall, false),\r\n maturity,\r\n strike64x64\r\n );\r\n\r\n // mint long option token for buyer\r\n _mint(account, longTokenId, contractSize);\r\n\r\n int128 oldLiquidity64x64 = l.totalFreeLiquiditySupply64x64(isCall);\r\n // burn free liquidity tokens from other underwriters\r\n _mintShortTokenLoop(\r\n l,\r\n account,\r\n contractSize,\r\n baseCost,\r\n shortTokenId,\r\n isCall\r\n );\r\n int128 newLiquidity64x64 = l.totalFreeLiquiditySupply64x64(isCall);\r\n\r\n _setCLevel(l, oldLiquidity64x64, newLiquidity64x64, isCall);\r\n\r\n // mint reserved liquidity tokens for fee receiver\r\n _mint(\r\n FEE_RECEIVER_ADDRESS,\r\n _getReservedLiquidityTokenId(isCall),\r\n feeCost\r\n );\r\n\r\n emit Purchase(\r\n account,\r\n longTokenId,\r\n contractSize,\r\n baseCost,\r\n feeCost,\r\n newPrice64x64\r\n );\r\n }\r\n\r\n /**\r\n * @notice reassign short position to new underwriter\r\n * @param l storage layout struct\r\n * @param account holder of positions to be reassigned\r\n * @param maturity timestamp of option maturity\r\n * @param strike64x64 64x64 fixed point representation of strike price\r\n * @param isCall true for call, false for put\r\n * @param contractSize quantity of option contract tokens to reassign\r\n * @param newPrice64x64 64x64 fixed point representation of current spot price\r\n * @return baseCost quantity of tokens required to reassign short position\r\n * @return feeCost quantity of tokens required to pay fees\r\n * @return amountOut quantity of liquidity freed\r\n */\r\n function _reassign(\r\n PoolStorage.Layout storage l,\r\n address account,\r\n uint64 maturity,\r\n int128 strike64x64,\r\n bool isCall,\r\n uint256 contractSize,\r\n int128 newPrice64x64\r\n )\r\n internal\r\n returns (\r\n uint256 baseCost,\r\n uint256 feeCost,\r\n uint256 amountOut\r\n )\r\n {\r\n (baseCost, feeCost) = _purchase(\r\n l,\r\n account,\r\n maturity,\r\n strike64x64,\r\n isCall,\r\n contractSize,\r\n newPrice64x64\r\n );\r\n\r\n _annihilate(account, maturity, strike64x64, isCall, contractSize);\r\n\r\n uint256 annihilateAmount = isCall\r\n ? contractSize\r\n : l.fromUnderlyingToBaseDecimals(strike64x64.mulu(contractSize));\r\n\r\n amountOut = annihilateAmount - baseCost - feeCost;\r\n }\r\n\r\n /**\r\n * @notice exercise option on behalf of holder\r\n * @dev used for processing of expired options if passed holder is zero address\r\n * @param holder owner of long option tokens to exercise\r\n * @param longTokenId long option token id\r\n * @param contractSize quantity of tokens to exercise\r\n */\r\n function _exercise(\r\n address holder,\r\n uint256 longTokenId,\r\n uint256 contractSize\r\n ) internal {\r\n uint64 maturity;\r\n int128 strike64x64;\r\n bool isCall;\r\n\r\n bool onlyExpired = holder == address(0);\r\n\r\n {\r\n PoolStorage.TokenType tokenType;\r\n (tokenType, maturity, strike64x64) = PoolStorage.parseTokenId(\r\n longTokenId\r\n );\r\n require(\r\n tokenType == PoolStorage.TokenType.LONG_CALL ||\r\n tokenType == PoolStorage.TokenType.LONG_PUT,\r\n \"invalid type\"\r\n );\r\n require(!onlyExpired || maturity < block.timestamp, \"not expired\");\r\n isCall = tokenType == PoolStorage.TokenType.LONG_CALL;\r\n }\r\n\r\n PoolStorage.Layout storage l = PoolStorage.layout();\r\n\r\n int128 spot64x64 = _update(l);\r\n\r\n if (maturity < block.timestamp) {\r\n spot64x64 = l.getPriceUpdateAfter(maturity);\r\n }\r\n\r\n require(\r\n onlyExpired ||\r\n (\r\n isCall\r\n ? (spot64x64 > strike64x64)\r\n : (spot64x64 < strike64x64)\r\n ),\r\n \"not ITM\"\r\n );\r\n\r\n uint256 exerciseValue;\r\n // option has a non-zero exercise value\r\n if (isCall) {\r\n if (spot64x64 > strike64x64) {\r\n exerciseValue = spot64x64.sub(strike64x64).div(spot64x64).mulu(\r\n contractSize\r\n );\r\n }\r\n } else {\r\n if (spot64x64 < strike64x64) {\r\n exerciseValue = l.fromUnderlyingToBaseDecimals(\r\n strike64x64.sub(spot64x64).mulu(contractSize)\r\n );\r\n }\r\n }\r\n\r\n uint256 totalFee;\r\n\r\n if (onlyExpired) {\r\n totalFee += _burnLongTokenLoop(\r\n contractSize,\r\n exerciseValue,\r\n longTokenId,\r\n isCall\r\n );\r\n } else {\r\n // burn long option tokens from sender\r\n _burn(holder, longTokenId, contractSize);\r\n\r\n uint256 fee;\r\n\r\n if (exerciseValue > 0) {\r\n fee = _getFeeWithDiscount(\r\n holder,\r\n FEE_64x64.mulu(exerciseValue)\r\n );\r\n totalFee += fee;\r\n\r\n _pushTo(holder, _getPoolToken(isCall), exerciseValue - fee);\r\n }\r\n\r\n emit Exercise(\r\n holder,\r\n longTokenId,\r\n contractSize,\r\n exerciseValue,\r\n fee\r\n );\r\n }\r\n\r\n totalFee += _burnShortTokenLoop(\r\n contractSize,\r\n exerciseValue,\r\n PoolStorage.formatTokenId(\r\n _getTokenType(isCall, false),\r\n maturity,\r\n strike64x64\r\n ),\r\n isCall\r\n );\r\n\r\n _mint(\r\n FEE_RECEIVER_ADDRESS,\r\n _getReservedLiquidityTokenId(isCall),\r\n totalFee\r\n );\r\n }\r\n\r\n function _mintShortTokenLoop(\r\n PoolStorage.Layout storage l,\r\n address buyer,\r\n uint256 contractSize,\r\n uint256 premium,\r\n uint256 shortTokenId,\r\n bool isCall\r\n ) internal {\r\n uint256 freeLiqTokenId = _getFreeLiquidityTokenId(isCall);\r\n (, , int128 strike64x64) = PoolStorage.parseTokenId(shortTokenId);\r\n\r\n uint256 toPay = isCall\r\n ? contractSize\r\n : l.fromUnderlyingToBaseDecimals(strike64x64.mulu(contractSize));\r\n\r\n while (toPay > 0) {\r\n address underwriter = l.liquidityQueueAscending[isCall][address(0)];\r\n uint256 balance = _balanceOf(underwriter, freeLiqTokenId);\r\n\r\n // If dust left, we remove underwriter and skip to next\r\n if (balance < _getMinimumAmount(l, isCall)) {\r\n l.removeUnderwriter(underwriter, isCall);\r\n continue;\r\n }\r\n\r\n if (!l.getReinvestmentStatus(underwriter, isCall)) {\r\n _burn(underwriter, freeLiqTokenId, balance);\r\n _mint(\r\n underwriter,\r\n _getReservedLiquidityTokenId(isCall),\r\n balance\r\n );\r\n _subUserTVL(l, underwriter, isCall, balance);\r\n continue;\r\n }\r\n\r\n // amount of liquidity provided by underwriter, accounting for reinvested premium\r\n uint256 intervalContractSize = ((balance -\r\n l.pendingDeposits[underwriter][l.nextDeposits[isCall].eta][\r\n isCall\r\n ]) * (toPay + premium)) / toPay;\r\n if (intervalContractSize == 0) continue;\r\n if (intervalContractSize > toPay) intervalContractSize = toPay;\r\n\r\n // amount of premium paid to underwriter\r\n uint256 intervalPremium = (premium * intervalContractSize) / toPay;\r\n premium -= intervalPremium;\r\n toPay -= intervalContractSize;\r\n _addUserTVL(l, underwriter, isCall, intervalPremium);\r\n\r\n // burn free liquidity tokens from underwriter\r\n _burn(\r\n underwriter,\r\n freeLiqTokenId,\r\n intervalContractSize - intervalPremium\r\n );\r\n\r\n if (isCall == false) {\r\n // For PUT, conversion to contract amount is done here (Prior to this line, it is token amount)\r\n intervalContractSize = l.fromBaseToUnderlyingDecimals(\r\n strike64x64.inv().mulu(intervalContractSize)\r\n );\r\n }\r\n\r\n // mint short option tokens for underwriter\r\n // toPay == 0 ? contractSize : intervalContractSize : To prevent minting less than amount,\r\n // because of rounding (Can happen for put, because of fixed point precision)\r\n _mint(\r\n underwriter,\r\n shortTokenId,\r\n toPay == 0 ? contractSize : intervalContractSize\r\n );\r\n\r\n emit Underwrite(\r\n underwriter,\r\n buyer,\r\n shortTokenId,\r\n toPay == 0 ? contractSize : intervalContractSize,\r\n intervalPremium,\r\n false\r\n );\r\n\r\n contractSize -= intervalContractSize;\r\n }\r\n }\r\n\r\n function _burnLongTokenLoop(\r\n uint256 contractSize,\r\n uint256 exerciseValue,\r\n uint256 longTokenId,\r\n bool isCall\r\n ) internal returns (uint256 totalFee) {\r\n EnumerableSet.AddressSet storage holders = ERC1155EnumerableStorage\r\n .layout()\r\n .accountsByToken[longTokenId];\r\n\r\n while (contractSize > 0) {\r\n address longTokenHolder = holders.at(holders.length() - 1);\r\n\r\n uint256 intervalContractSize = _balanceOf(\r\n longTokenHolder,\r\n longTokenId\r\n );\r\n if (intervalContractSize > contractSize)\r\n intervalContractSize = contractSize;\r\n\r\n uint256 intervalExerciseValue;\r\n\r\n uint256 fee;\r\n if (exerciseValue > 0) {\r\n intervalExerciseValue =\r\n (exerciseValue * intervalContractSize) /\r\n contractSize;\r\n\r\n fee = _getFeeWithDiscount(\r\n longTokenHolder,\r\n FEE_64x64.mulu(intervalExerciseValue)\r\n );\r\n totalFee += fee;\r\n\r\n exerciseValue -= intervalExerciseValue;\r\n _pushTo(\r\n longTokenHolder,\r\n _getPoolToken(isCall),\r\n intervalExerciseValue - fee\r\n );\r\n }\r\n\r\n contractSize -= intervalContractSize;\r\n\r\n emit Exercise(\r\n longTokenHolder,\r\n longTokenId,\r\n intervalContractSize,\r\n intervalExerciseValue - fee,\r\n fee\r\n );\r\n\r\n _burn(longTokenHolder, longTokenId, intervalContractSize);\r\n }\r\n }\r\n\r\n function _burnShortTokenLoop(\r\n uint256 contractSize,\r\n uint256 exerciseValue,\r\n uint256 shortTokenId,\r\n bool isCall\r\n ) internal returns (uint256 totalFee) {\r\n EnumerableSet.AddressSet storage underwriters = ERC1155EnumerableStorage\r\n .layout()\r\n .accountsByToken[shortTokenId];\r\n (, , int128 strike64x64) = PoolStorage.parseTokenId(shortTokenId);\r\n\r\n while (contractSize > 0) {\r\n address underwriter = underwriters.at(underwriters.length() - 1);\r\n\r\n // amount of liquidity provided by underwriter\r\n uint256 intervalContractSize = _balanceOf(\r\n underwriter,\r\n shortTokenId\r\n );\r\n if (intervalContractSize > contractSize)\r\n intervalContractSize = contractSize;\r\n\r\n // amount of value claimed by buyer\r\n uint256 intervalExerciseValue = (exerciseValue *\r\n intervalContractSize) / contractSize;\r\n exerciseValue -= intervalExerciseValue;\r\n contractSize -= intervalContractSize;\r\n\r\n uint256 freeLiq = isCall\r\n ? intervalContractSize - intervalExerciseValue\r\n : PoolStorage.layout().fromUnderlyingToBaseDecimals(\r\n strike64x64.mulu(intervalContractSize)\r\n ) - intervalExerciseValue;\r\n\r\n uint256 fee = _getFeeWithDiscount(\r\n underwriter,\r\n FEE_64x64.mulu(freeLiq)\r\n );\r\n totalFee += fee;\r\n\r\n uint256 tvlToSubtract = intervalExerciseValue;\r\n\r\n // mint free liquidity tokens for underwriter\r\n if (\r\n PoolStorage.layout().getReinvestmentStatus(underwriter, isCall)\r\n ) {\r\n _addToDepositQueue(underwriter, freeLiq - fee, isCall);\r\n tvlToSubtract += fee;\r\n } else {\r\n _mint(\r\n underwriter,\r\n _getReservedLiquidityTokenId(isCall),\r\n freeLiq - fee\r\n );\r\n tvlToSubtract += freeLiq;\r\n }\r\n\r\n _subUserTVL(\r\n PoolStorage.layout(),\r\n underwriter,\r\n isCall,\r\n tvlToSubtract\r\n );\r\n\r\n // burn short option tokens from underwriter\r\n _burn(underwriter, shortTokenId, intervalContractSize);\r\n\r\n emit AssignExercise(\r\n underwriter,\r\n shortTokenId,\r\n freeLiq - fee,\r\n intervalContractSize,\r\n fee\r\n );\r\n }\r\n }\r\n\r\n function _addToDepositQueue(\r\n address account,\r\n uint256 amount,\r\n bool isCallPool\r\n ) internal {\r\n PoolStorage.Layout storage l = PoolStorage.layout();\r\n\r\n _mint(account, _getFreeLiquidityTokenId(isCallPool), amount);\r\n\r\n uint256 nextBatch = (block.timestamp / BATCHING_PERIOD) *\r\n BATCHING_PERIOD +\r\n BATCHING_PERIOD;\r\n l.pendingDeposits[account][nextBatch][isCallPool] += amount;\r\n\r\n PoolStorage.BatchData storage batchData = l.nextDeposits[isCallPool];\r\n batchData.totalPendingDeposits += amount;\r\n batchData.eta = nextBatch;\r\n }\r\n\r\n function _processPendingDeposits(PoolStorage.Layout storage l, bool isCall)\r\n internal\r\n {\r\n PoolStorage.BatchData storage data = l.nextDeposits[isCall];\r\n\r\n if (data.eta == 0 || block.timestamp < data.eta) return;\r\n\r\n int128 oldLiquidity64x64 = l.totalFreeLiquiditySupply64x64(isCall);\r\n\r\n _setCLevel(\r\n l,\r\n oldLiquidity64x64,\r\n oldLiquidity64x64.add(\r\n ABDKMath64x64Token.fromDecimals(\r\n data.totalPendingDeposits,\r\n l.getTokenDecimals(isCall)\r\n )\r\n ),\r\n isCall\r\n );\r\n\r\n delete l.nextDeposits[isCall];\r\n }\r\n\r\n function _getFreeLiquidityTokenId(bool isCall)\r\n internal\r\n view\r\n returns (uint256 freeLiqTokenId)\r\n {\r\n freeLiqTokenId = isCall\r\n ? UNDERLYING_FREE_LIQ_TOKEN_ID\r\n : BASE_FREE_LIQ_TOKEN_ID;\r\n }\r\n\r\n function _getReservedLiquidityTokenId(bool isCall)\r\n internal\r\n view\r\n returns (uint256 reservedLiqTokenId)\r\n {\r\n reservedLiqTokenId = isCall\r\n ? UNDERLYING_RESERVED_LIQ_TOKEN_ID\r\n : BASE_RESERVED_LIQ_TOKEN_ID;\r\n }\r\n\r\n function _getPoolToken(bool isCall) internal view returns (address token) {\r\n token = isCall\r\n ? PoolStorage.layout().underlying\r\n : PoolStorage.layout().base;\r\n }\r\n\r\n function _getTokenType(bool isCall, bool isLong)\r\n internal\r\n pure\r\n returns (PoolStorage.TokenType tokenType)\r\n {\r\n if (isCall) {\r\n tokenType = isLong\r\n ? PoolStorage.TokenType.LONG_CALL\r\n : PoolStorage.TokenType.SHORT_CALL;\r\n } else {\r\n tokenType = isLong\r\n ? PoolStorage.TokenType.LONG_PUT\r\n : PoolStorage.TokenType.SHORT_PUT;\r\n }\r\n }\r\n\r\n function _getMinimumAmount(PoolStorage.Layout storage l, bool isCall)\r\n internal\r\n view\r\n returns (uint256 minimumAmount)\r\n {\r\n minimumAmount = isCall ? l.underlyingMinimum : l.baseMinimum;\r\n }\r\n\r\n function _getPoolCapAmount(PoolStorage.Layout storage l, bool isCall)\r\n internal\r\n view\r\n returns (uint256 poolCapAmount)\r\n {\r\n poolCapAmount = isCall ? l.underlyingPoolCap : l.basePoolCap;\r\n }\r\n\r\n function _setCLevel(\r\n PoolStorage.Layout storage l,\r\n int128 oldLiquidity64x64,\r\n int128 newLiquidity64x64,\r\n bool isCallPool\r\n ) internal {\r\n int128 oldCLevel64x64 = l.getDecayAdjustedCLevel64x64(isCallPool);\r\n\r\n int128 cLevel64x64 = l.applyCLevelLiquidityChangeAdjustment(\r\n oldCLevel64x64,\r\n oldLiquidity64x64,\r\n newLiquidity64x64,\r\n isCallPool\r\n );\r\n\r\n l.setCLevel(cLevel64x64, isCallPool);\r\n\r\n emit UpdateCLevel(\r\n isCallPool,\r\n cLevel64x64,\r\n oldLiquidity64x64,\r\n newLiquidity64x64\r\n );\r\n }\r\n\r\n /**\r\n * @notice calculate and store updated market state\r\n * @param l storage layout struct\r\n * @return newPrice64x64 64x64 fixed point representation of current spot price\r\n */\r\n function _update(PoolStorage.Layout storage l)\r\n internal\r\n returns (int128 newPrice64x64)\r\n {\r\n if (l.updatedAt == block.timestamp) {\r\n return (l.getPriceUpdate(block.timestamp));\r\n }\r\n\r\n newPrice64x64 = l.fetchPriceUpdate();\r\n\r\n if (l.getPriceUpdate(block.timestamp) == 0) {\r\n l.setPriceUpdate(block.timestamp, newPrice64x64);\r\n }\r\n\r\n l.updatedAt = block.timestamp;\r\n\r\n _processPendingDeposits(l, true);\r\n _processPendingDeposits(l, false);\r\n }\r\n\r\n /**\r\n * @notice transfer ERC20 tokens to message sender\r\n * @param token ERC20 token address\r\n * @param amount quantity of token to transfer\r\n */\r\n function _pushTo(\r\n address to,\r\n address token,\r\n uint256 amount\r\n ) internal {\r\n if (amount == 0) return;\r\n\r\n require(IERC20(token).transfer(to, amount), \"ERC20 transfer failed\");\r\n }\r\n\r\n /**\r\n * @notice transfer ERC20 tokens from message sender\r\n * @param from address from which tokens are pulled from\r\n * @param token ERC20 token address\r\n * @param amount quantity of token to transfer\r\n * @param skipWethDeposit if false, will not try to deposit weth from attach eth\r\n */\r\n function _pullFrom(\r\n address from,\r\n address token,\r\n uint256 amount,\r\n bool skipWethDeposit\r\n ) internal {\r\n if (!skipWethDeposit) {\r\n if (token == WETH_ADDRESS) {\r\n if (msg.value > 0) {\r\n if (msg.value > amount) {\r\n IWETH(WETH_ADDRESS).deposit{value: amount}();\r\n\r\n (bool success, ) = payable(msg.sender).call{\r\n value: msg.value - amount\r\n }(\"\");\r\n\r\n require(success, \"ETH refund failed\");\r\n\r\n amount = 0;\r\n } else {\r\n unchecked {\r\n amount -= msg.value;\r\n }\r\n\r\n IWETH(WETH_ADDRESS).deposit{value: msg.value}();\r\n }\r\n }\r\n } else {\r\n require(msg.value == 0, \"not WETH deposit\");\r\n }\r\n }\r\n\r\n if (amount > 0) {\r\n require(\r\n IERC20(token).transferFrom(from, address(this), amount),\r\n \"ERC20 transfer failed\"\r\n );\r\n }\r\n }\r\n\r\n function _mint(\r\n address account,\r\n uint256 tokenId,\r\n uint256 amount\r\n ) internal {\r\n _mint(account, tokenId, amount, \"\");\r\n }\r\n\r\n function _addUserTVL(\r\n PoolStorage.Layout storage l,\r\n address user,\r\n bool isCallPool,\r\n uint256 amount\r\n ) internal {\r\n uint256 userTVL = l.userTVL[user][isCallPool];\r\n uint256 totalTVL = l.totalTVL[isCallPool];\r\n\r\n IPremiaMining(PREMIA_MINING_ADDRESS).allocatePending(\r\n user,\r\n address(this),\r\n isCallPool,\r\n userTVL,\r\n userTVL + amount,\r\n totalTVL\r\n );\r\n\r\n l.userTVL[user][isCallPool] = userTVL + amount;\r\n l.totalTVL[isCallPool] = totalTVL + amount;\r\n }\r\n\r\n function _subUserTVL(\r\n PoolStorage.Layout storage l,\r\n address user,\r\n bool isCallPool,\r\n uint256 amount\r\n ) internal {\r\n uint256 userTVL = l.userTVL[user][isCallPool];\r\n uint256 totalTVL = l.totalTVL[isCallPool];\r\n\r\n IPremiaMining(PREMIA_MINING_ADDRESS).allocatePending(\r\n user,\r\n address(this),\r\n isCallPool,\r\n userTVL,\r\n userTVL - amount,\r\n totalTVL\r\n );\r\n l.userTVL[user][isCallPool] = userTVL - amount;\r\n l.totalTVL[isCallPool] = totalTVL - amount;\r\n }\r\n\r\n /**\r\n * @notice ERC1155 hook: track eligible underwriters\r\n * @param operator transaction sender\r\n * @param from token sender\r\n * @param to token receiver\r\n * @param ids token ids transferred\r\n * @param amounts token quantities transferred\r\n * @param data data payload\r\n */\r\n function _beforeTokenTransfer(\r\n address operator,\r\n address from,\r\n address to,\r\n uint256[] memory ids,\r\n uint256[] memory amounts,\r\n bytes memory data\r\n ) internal virtual override {\r\n super._beforeTokenTransfer(operator, from, to, ids, amounts, data);\r\n\r\n PoolStorage.Layout storage l = PoolStorage.layout();\r\n\r\n for (uint256 i; i < ids.length; i++) {\r\n uint256 id = ids[i];\r\n uint256 amount = amounts[i];\r\n\r\n if (amount == 0) continue;\r\n\r\n if (from == address(0)) {\r\n l.tokenIds.add(id);\r\n }\r\n\r\n if (\r\n to == address(0) &&\r\n ERC1155EnumerableStorage.layout().totalSupply[id] == 0\r\n ) {\r\n l.tokenIds.remove(id);\r\n }\r\n\r\n // prevent transfer of free and reserved liquidity during waiting period\r\n\r\n if (\r\n id == UNDERLYING_FREE_LIQ_TOKEN_ID ||\r\n id == BASE_FREE_LIQ_TOKEN_ID ||\r\n id == UNDERLYING_RESERVED_LIQ_TOKEN_ID ||\r\n id == BASE_RESERVED_LIQ_TOKEN_ID\r\n ) {\r\n if (from != address(0) && to != address(0)) {\r\n bool isCallPool = id == UNDERLYING_FREE_LIQ_TOKEN_ID ||\r\n id == UNDERLYING_RESERVED_LIQ_TOKEN_ID;\r\n\r\n require(\r\n l.depositedAt[from][isCallPool] + (1 days) <\r\n block.timestamp,\r\n \"liq lock 1d\"\r\n );\r\n }\r\n }\r\n\r\n if (\r\n id == UNDERLYING_FREE_LIQ_TOKEN_ID ||\r\n id == BASE_FREE_LIQ_TOKEN_ID\r\n ) {\r\n bool isCallPool = id == UNDERLYING_FREE_LIQ_TOKEN_ID;\r\n uint256 minimum = _getMinimumAmount(l, isCallPool);\r\n\r\n if (from != address(0)) {\r\n uint256 balance = _balanceOf(from, id);\r\n\r\n if (balance > minimum && balance <= amount + minimum) {\r\n require(\r\n balance -\r\n l.pendingDeposits[from][\r\n l.nextDeposits[isCallPool].eta\r\n ][isCallPool] >=\r\n amount,\r\n \"Insuf balance\"\r\n );\r\n l.removeUnderwriter(from, isCallPool);\r\n }\r\n\r\n if (to != address(0)) {\r\n _subUserTVL(l, from, isCallPool, amounts[i]);\r\n _addUserTVL(l, to, isCallPool, amounts[i]);\r\n }\r\n }\r\n\r\n if (to != address(0)) {\r\n uint256 balance = _balanceOf(to, id);\r\n\r\n if (balance <= minimum && balance + amount > minimum) {\r\n l.addUnderwriter(to, isCallPool);\r\n }\r\n }\r\n }\r\n\r\n // Update userTVL on SHORT options transfers\r\n (\r\n PoolStorage.TokenType tokenType,\r\n ,\r\n int128 strike64x64\r\n ) = PoolStorage.parseTokenId(id);\r\n\r\n if (\r\n (from != address(0) && to != address(0)) &&\r\n (tokenType == PoolStorage.TokenType.SHORT_CALL ||\r\n tokenType == PoolStorage.TokenType.SHORT_PUT)\r\n ) {\r\n bool isCall = tokenType == PoolStorage.TokenType.SHORT_CALL;\r\n uint256 collateral = isCall\r\n ? amount\r\n : l.fromUnderlyingToBaseDecimals(strike64x64.mulu(amount));\r\n\r\n _subUserTVL(l, from, isCall, collateral);\r\n _addUserTVL(l, to, isCall, collateral);\r\n }\r\n }\r\n }\r\n}\r\n"},"@solidstate/contracts/token/ERC1155/base/ERC1155BaseStorage.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nlibrary ERC1155BaseStorage {\n struct Layout {\n mapping(uint256 => mapping(address => uint256)) balances;\n mapping(address => mapping(address => bool)) operatorApprovals;\n }\n\n bytes32 internal constant STORAGE_SLOT =\n keccak256('solidstate.contracts.storage.ERC1155Base');\n\n function layout() internal pure returns (Layout storage l) {\n bytes32 slot = STORAGE_SLOT;\n assembly {\n l.slot := slot\n }\n }\n}\n"},"@solidstate/contracts/utils/EnumerableSet.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\n/**\n * @title Set implementation with enumeration functions\n * @dev derived from https://github.com/OpenZeppelin/openzeppelin-contracts (MIT license)\n */\nlibrary EnumerableSet {\n struct Set {\n bytes32[] _values;\n // 1-indexed to allow 0 to signify nonexistence\n mapping(bytes32 => uint256) _indexes;\n }\n\n struct Bytes32Set {\n Set _inner;\n }\n\n struct AddressSet {\n Set _inner;\n }\n\n struct UintSet {\n Set _inner;\n }\n\n function at(Bytes32Set storage set, uint256 index)\n internal\n view\n returns (bytes32)\n {\n return _at(set._inner, index);\n }\n\n function at(AddressSet storage set, uint256 index)\n internal\n view\n returns (address)\n {\n return address(uint160(uint256(_at(set._inner, index))));\n }\n\n function at(UintSet storage set, uint256 index)\n internal\n view\n returns (uint256)\n {\n return uint256(_at(set._inner, index));\n }\n\n function contains(Bytes32Set storage set, bytes32 value)\n internal\n view\n returns (bool)\n {\n return _contains(set._inner, value);\n }\n\n function contains(AddressSet storage set, address value)\n internal\n view\n returns (bool)\n {\n return _contains(set._inner, bytes32(uint256(uint160(value))));\n }\n\n function contains(UintSet storage set, uint256 value)\n internal\n view\n returns (bool)\n {\n return _contains(set._inner, bytes32(value));\n }\n\n function indexOf(Bytes32Set storage set, bytes32 value)\n internal\n view\n returns (uint256)\n {\n return _indexOf(set._inner, value);\n }\n\n function indexOf(AddressSet storage set, address value)\n internal\n view\n returns (uint256)\n {\n return _indexOf(set._inner, bytes32(uint256(uint160(value))));\n }\n\n function indexOf(UintSet storage set, uint256 value)\n internal\n view\n returns (uint256)\n {\n return _indexOf(set._inner, bytes32(value));\n }\n\n function length(Bytes32Set storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n function length(AddressSet storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n function length(UintSet storage set) internal view returns (uint256) {\n return _length(set._inner);\n }\n\n function add(Bytes32Set storage set, bytes32 value)\n internal\n returns (bool)\n {\n return _add(set._inner, value);\n }\n\n function add(AddressSet storage set, address value)\n internal\n returns (bool)\n {\n return _add(set._inner, bytes32(uint256(uint160(value))));\n }\n\n function add(UintSet storage set, uint256 value) internal returns (bool) {\n return _add(set._inner, bytes32(value));\n }\n\n function remove(Bytes32Set storage set, bytes32 value)\n internal\n returns (bool)\n {\n return _remove(set._inner, value);\n }\n\n function remove(AddressSet storage set, address value)\n internal\n returns (bool)\n {\n return _remove(set._inner, bytes32(uint256(uint160(value))));\n }\n\n function remove(UintSet storage set, uint256 value)\n internal\n returns (bool)\n {\n return _remove(set._inner, bytes32(value));\n }\n\n function _at(Set storage set, uint256 index)\n private\n view\n returns (bytes32)\n {\n require(\n set._values.length > index,\n 'EnumerableSet: index out of bounds'\n );\n return set._values[index];\n }\n\n function _contains(Set storage set, bytes32 value)\n private\n view\n returns (bool)\n {\n return set._indexes[value] != 0;\n }\n\n function _indexOf(Set storage set, bytes32 value)\n private\n view\n returns (uint256)\n {\n unchecked {\n return set._indexes[value] - 1;\n }\n }\n\n function _length(Set storage set) private view returns (uint256) {\n return set._values.length;\n }\n\n function _add(Set storage set, bytes32 value) private returns (bool) {\n if (!_contains(set, value)) {\n set._values.push(value);\n set._indexes[value] = set._values.length;\n return true;\n } else {\n return false;\n }\n }\n\n function _remove(Set storage set, bytes32 value) private returns (bool) {\n uint256 valueIndex = set._indexes[value];\n\n if (valueIndex != 0) {\n uint256 index = valueIndex - 1;\n bytes32 last = set._values[set._values.length - 1];\n\n // move last value to now-vacant index\n\n set._values[index] = last;\n set._indexes[last] = index + 1;\n\n // clear last index\n\n set._values.pop();\n delete set._indexes[value];\n\n return true;\n } else {\n return false;\n }\n }\n}\n"},"contracts/mining/PremiaMiningStorage.sol":{"content":"// SPDX-License-Identifier: BUSL-1.1\r\n// For further clarification please see https://license.premia.legal\r\n\r\npragma solidity ^0.8.0;\r\n\r\nlibrary PremiaMiningStorage {\r\n bytes32 internal constant STORAGE_SLOT =\r\n keccak256(\"premia.contracts.storage.PremiaMining\");\r\n\r\n // Info of each pool.\r\n struct PoolInfo {\r\n uint256 allocPoint; // How many allocation points assigned to this pool. PREMIA to distribute per block.\r\n uint256 lastRewardTimestamp; // Last timestamp that PREMIA distribution occurs\r\n uint256 accPremiaPerShare; // Accumulated PREMIA per share, times 1e12. See below.\r\n }\r\n\r\n // Info of each user.\r\n struct UserInfo {\r\n uint256 reward; // Total allocated unclaimed reward\r\n uint256 rewardDebt; // Reward debt. See explanation below.\r\n //\r\n // We do some fancy math here. Basically, any point in time, the amount of PREMIA\r\n // entitled to a user but is pending to be distributed is:\r\n //\r\n // pending reward = (user.amount * pool.accPremiaPerShare) - user.rewardDebt\r\n //\r\n // Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens:\r\n // 1. The pool's `accPremiaPerShare` (and `lastRewardBlock`) gets updated.\r\n // 2. User receives the pending reward sent to his/her address.\r\n // 3. User's `amount` gets updated.\r\n // 4. User's `rewardDebt` gets updated.\r\n }\r\n\r\n struct Layout {\r\n // Total PREMIA left to distribute\r\n uint256 premiaAvailable;\r\n // Amount of premia distributed per year\r\n uint256 premiaPerYear;\r\n // pool -> isCallPool -> PoolInfo\r\n mapping(address => mapping(bool => PoolInfo)) poolInfo;\r\n // pool -> isCallPool -> user -> UserInfo\r\n mapping(address => mapping(bool => mapping(address => UserInfo))) userInfo;\r\n // Total allocation points. Must be the sum of all allocation points in all pools.\r\n uint256 totalAllocPoint;\r\n }\r\n\r\n function layout() internal pure returns (Layout storage l) {\r\n bytes32 slot = STORAGE_SLOT;\r\n assembly {\r\n l.slot := slot\r\n }\r\n }\r\n}\r\n"},"@solidstate/contracts/utils/IWETH.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { IERC20 } from '../token/ERC20/IERC20.sol';\nimport { IERC20Metadata } from '../token/ERC20/metadata/IERC20Metadata.sol';\n\n/**\n * @title WETH (Wrapped ETH) interface\n */\ninterface IWETH is IERC20, IERC20Metadata {\n /**\n * @notice convert ETH to WETH\n */\n function deposit() external payable;\n\n /**\n * @notice convert WETH to ETH\n * @dev if caller is a contract, it should have a fallback or receive function\n * @param amount quantity of WETH to convert, denominated in wei\n */\n function withdraw(uint256 amount) external;\n}\n"},"@solidstate/contracts/token/ERC1155/IERC1155Internal.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nimport { IERC165 } from '../../introspection/IERC165.sol';\n\n/**\n * @notice Partial ERC1155 interface needed by internal functions\n */\ninterface IERC1155Internal {\n event TransferSingle(\n address indexed operator,\n address indexed from,\n address indexed to,\n uint256 id,\n uint256 value\n );\n\n event TransferBatch(\n address indexed operator,\n address indexed from,\n address indexed to,\n uint256[] ids,\n uint256[] values\n );\n\n event ApprovalForAll(\n address indexed account,\n address indexed operator,\n bool approved\n );\n}\n"},"@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface AggregatorV3Interface {\n\n function decimals()\n external\n view\n returns (\n uint8\n );\n\n function description()\n external\n view\n returns (\n string memory\n );\n\n function version()\n external\n view\n returns (\n uint256\n );\n\n // getRoundData and latestRoundData should both raise \"No data present\"\n // if they do not have data to report, instead of returning unset values\n // which could be misinterpreted as actual reported values.\n function getRoundData(\n uint80 _roundId\n )\n external\n view\n returns (\n uint80 roundId,\n int256 answer,\n uint256 startedAt,\n uint256 updatedAt,\n uint80 answeredInRound\n );\n\n function latestRoundData()\n external\n view\n returns (\n uint80 roundId,\n int256 answer,\n uint256 startedAt,\n uint256 updatedAt,\n uint80 answeredInRound\n );\n\n}\n"},"@solidstate/contracts/access/OwnableStorage.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.8.0;\n\nlibrary OwnableStorage {\n struct Layout {\n address owner;\n }\n\n bytes32 internal constant STORAGE_SLOT =\n keccak256('solidstate.contracts.storage.Ownable');\n\n function layout() internal pure returns (Layout storage l) {\n bytes32 slot = STORAGE_SLOT;\n assembly {\n l.slot := slot\n }\n }\n\n function setOwner(Layout storage l, address owner) internal {\n l.owner = owner;\n }\n}\n"}},"settings":{"optimizer":{"enabled":true,"runs":200},"outputSelection":{"*":{"*":["evm.bytecode","evm.deployedBytecode","devdoc","userdoc","metadata","abi"]}},"libraries":{"contracts/libraries/OptionMath.sol":{"OptionMath":"0x0f6e8ef18fb5bb61d545fee60f779d8aed60408f"}}}},"ABI":"[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"ivolOracle\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"weth\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"premiaMining\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"feeReceiver\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"feeDiscountAddress\",\"type\":\"address\"},{\"internalType\":\"int128\",\"name\":\"fee64x64\",\"type\":\"int128\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"shortTokenId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Annihilate\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"approved\",\"type\":\"bool\"}],\"name\":\"ApprovalForAll\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"underwriter\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"shortTokenId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"freedAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"intervalContractSize\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"fee\",\"type\":\"uint256\"}],\"name\":\"AssignExercise\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isCallPool\",\"type\":\"bool\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Deposit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"longTokenId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"contractSize\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"exerciseValue\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"fee\",\"type\":\"uint256\"}],\"name\":\"Exercise\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bool\",\"name\":\"isCallPool\",\"type\":\"bool\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"FeeWithdrawal\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"longTokenId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"contractSize\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"baseCost\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"feeCost\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"int128\",\"name\":\"spot64x64\",\"type\":\"int128\"}],\"name\":\"Purchase\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256[]\",\"name\":\"ids\",\"type\":\"uint256[]\"},{\"indexed\":false,\"internalType\":\"uint256[]\",\"name\":\"values\",\"type\":\"uint256[]\"}],\"name\":\"TransferBatch\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"operator\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"id\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"TransferSingle\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"underwriter\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"longReceiver\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"shortTokenId\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"intervalContractSize\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"intervalPremium\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isManualUnderwrite\",\"type\":\"bool\"}],\"name\":\"Underwrite\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bool\",\"name\":\"isCall\",\"type\":\"bool\"},{\"indexed\":false,\"internalType\":\"int128\",\"name\":\"cLevel64x64\",\"type\":\"int128\"},{\"indexed\":false,\"internalType\":\"int128\",\"name\":\"oldLiquidity64x64\",\"type\":\"int128\"},{\"indexed\":false,\"internalType\":\"int128\",\"name\":\"newLiquidity64x64\",\"type\":\"int128\"}],\"name\":\"UpdateCLevel\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"int128\",\"name\":\"steepness64x64\",\"type\":\"int128\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isCallPool\",\"type\":\"bool\"}],\"name\":\"UpdateSteepness\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"isCallPool\",\"type\":\"bool\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"depositedAt\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Withdrawal\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"holder\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"longTokenId\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"contractSize\",\"type\":\"uint256\"}],\"name\":\"exerciseFrom\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"longTokenId\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"contractSize\",\"type\":\"uint256\"}],\"name\":\"processExpired\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]","ContractName":"PoolExercise","CompilerVersion":"v0.8.9+commit.e5eed63a","OptimizationUsed":1,"Runs":200,"ConstructorArguments":"0x0000000000000000000000003a87bb29b984d672664aa1dd2d19d2e8b24f0f2a000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc20000000000000000000000009abb27581c2e46a114f8c367355851e0580e9703000000000000000000000000c4b2c51f969e0713e799de73b7f130fb7bb604cf000000000000000000000000f1bb87563a122211d40d393ebf1c633c330377f900000000000000000000000000000000000000000000000007ae147ae147ae14","EVMVersion":"Default","Library":"","LicenseType":"","Proxy":0,"SwarmSource":""}] \ No newline at end of file diff --git a/testdata/fixtures/Json/Issue4402.json b/testdata/fixtures/Json/Issue4402.json new file mode 100644 index 000000000..e981817fe --- /dev/null +++ b/testdata/fixtures/Json/Issue4402.json @@ -0,0 +1,4 @@ +{ + "tokens": ["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"], + "empty": [] +} \ No newline at end of file diff --git a/testdata/fixtures/Json/test.json b/testdata/fixtures/Json/test.json index 4e4ade783..1f59ba456 100644 --- a/testdata/fixtures/Json/test.json +++ b/testdata/fixtures/Json/test.json @@ -1,28 +1,31 @@ { - "str": "hai", - "uintArray": [42, 43], - "strArray": ["hai", "there"], - "bool": true, - "boolArray": [true, false], + "basicString": "hai", + "null": null, + "stringArray": ["hai", "there"], "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "addressArray": [ "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "0x7109709ECfa91a80626fF3989D68f67F5b1DD12D" ], "H160NotAddress": "0000000000000000000000000000000000001337", + "boolTrue": true, + "boolFalse": false, + "boolArray": [true, false], + "boolString": "true", + "boolStringArray": [true, "false"], + "uintNumber": 115792089237316195423570985008687907853269984665640564039457584007913129639935, + "uintString": "115792089237316195423570985008687907853269984665640564039457584007913129639935", + "uintHex": "0x12C980", + "uintArray": [42, 43], + "uintStringArray": [1231232, "0x12C980", "1231232"], + "intNumber": -12, + "intString": "-12", + "intHex": "-0xC", + "bytesString": "0x01", + "bytesStringArray": ["0x01", "0x02"], "nestedObject": { "number": 115792089237316195423570985008687907853269984665640564039457584007913129639935, "str": "NEST" }, - "bytesArray": "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000966666920776f726b730000000000000000000000000000000000000000000000", - "hexUint": "0x12C980", - "stringUint": "115792089237316195423570985008687907853269984665640564039457584007913129639935", - "numberUint": 115792089237316195423570985008687907853269984665640564039457584007913129639935, - "arrayUint": [1231232, "0x12C980", "1231232"], - "stringInt": "-12", - "numberInt": -12, - "hexInt": "-0xC", - "booleanString": "true", - "booleanArray": [true, "false"], "advancedJsonPath": [{ "id": 1 }, { "id": 2 }] } diff --git a/testdata/fixtures/Json/wholeJson.json b/testdata/fixtures/Json/whole_json.json similarity index 100% rename from testdata/fixtures/Json/wholeJson.json rename to testdata/fixtures/Json/whole_json.json diff --git a/testdata/fixtures/Toml/Issue4402.toml b/testdata/fixtures/Toml/Issue4402.toml new file mode 100644 index 000000000..8f7d11023 --- /dev/null +++ b/testdata/fixtures/Toml/Issue4402.toml @@ -0,0 +1,2 @@ +tokens = ["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"] +empty = [] diff --git a/testdata/fixtures/Toml/test.toml b/testdata/fixtures/Toml/test.toml new file mode 100644 index 000000000..ce735b8f1 --- /dev/null +++ b/testdata/fixtures/Toml/test.toml @@ -0,0 +1,50 @@ +basicString = "hai" +nullString = "null" +multilineString = """ +hai +there +""" +stringArray = ["hai", "there"] + +address = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" +addressArray = [ + "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", + "0x7109709ECfa91a80626fF3989D68f67F5b1DD12D", +] +H160NotAddress = "0000000000000000000000000000000000001337" + +boolTrue = true +boolFalse = false +boolArray = [true, false] +boolString = "true" +boolStringArray = ["true", "false"] # Array values can't have mixed types + +datetime = 2021-08-10T14:48:00Z +datetimeArray = [2021-08-10T14:48:00Z, 2021-08-10T14:48:00Z] + +uintNumber = 9223372036854775807 # TOML is limited to 64-bit integers +uintString = "115792089237316195423570985008687907853269984665640564039457584007913129639935" +uintHex = "0x12C980" +uintArray = [42, 43] +uintStringArray = [ + "1231232", + "0x12C980", + "1231232", +] # Array values can't have mixed types + +intNumber = -12 +intString = "-12" +intHex = "-0xC" + +bytesString = "0x01" +bytesStringArray = ["0x01", "0x02"] + +[nestedObject] +number = 9223372036854775807 # TOML is limited to 64-bit integers +str = "NEST" + +[[advancedJsonPath]] +id = 1 + +[[advancedJsonPath]] +id = 2 diff --git a/testdata/fixtures/Toml/whole_toml.toml b/testdata/fixtures/Toml/whole_toml.toml new file mode 100644 index 000000000..badbd9fbb --- /dev/null +++ b/testdata/fixtures/Toml/whole_toml.toml @@ -0,0 +1,3 @@ +str = "hai" +uintArray = [42, 43] +strArray = ["hai", "there"] diff --git a/testdata/fixtures/Toml/write_complex_test.toml b/testdata/fixtures/Toml/write_complex_test.toml new file mode 100644 index 000000000..60692bc75 --- /dev/null +++ b/testdata/fixtures/Toml/write_complex_test.toml @@ -0,0 +1,6 @@ +a = 123 +b = "test" + +[c] +a = 123 +b = "test" diff --git a/testdata/fixtures/Toml/write_test.toml b/testdata/fixtures/Toml/write_test.toml new file mode 100644 index 000000000..6c084e370 --- /dev/null +++ b/testdata/fixtures/Toml/write_test.toml @@ -0,0 +1,2 @@ +a = 123 +b = "0x000000000000000000000000000000000000bEEF" diff --git a/testdata/foundry.toml b/testdata/foundry.toml index 94bdeea39..e9189bb00 100644 --- a/testdata/foundry.toml +++ b/testdata/foundry.toml @@ -16,7 +16,7 @@ ffi = false force = false invariant_fail_on_revert = false invariant_call_override = false -invariant_shrink_sequence = true +invariant_shrink_run_limit = 5000 gas_limit = 9223372036854775807 gas_price = 0 gas_reports = ["*"] diff --git a/testdata/multi-version/Counter.sol b/testdata/multi-version/Counter.sol new file mode 100644 index 000000000..4f0c35033 --- /dev/null +++ b/testdata/multi-version/Counter.sol @@ -0,0 +1,11 @@ +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} diff --git a/testdata/multi-version/Importer.sol b/testdata/multi-version/Importer.sol new file mode 100644 index 000000000..d8e274e8f --- /dev/null +++ b/testdata/multi-version/Importer.sol @@ -0,0 +1,7 @@ +pragma solidity 0.8.17; + +import "./Counter.sol"; + +// Please do not remove or change version pragma for this file. +// If you need to ensure that some of the files are compiled with +// solc 0.8.17, you should add imports of them to this file. diff --git a/testdata/multi-version/cheats/GetCode.t.sol b/testdata/multi-version/cheats/GetCode.t.sol new file mode 100644 index 000000000..e4a7bd14a --- /dev/null +++ b/testdata/multi-version/cheats/GetCode.t.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; +import "../Counter.sol"; + +contract GetCodeTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testGetCodeMultiVersion() public { + assertEq(vm.getCode("Counter.sol"), type(Counter).creationCode); + require( + keccak256(vm.getCode("Counter.sol")) != keccak256(vm.getCode("Counter.sol:Counter:0.8.17")), + "Invalid artifact" + ); + assertEq(vm.getCode("Counter.sol"), vm.getCode("Counter.sol:Counter:0.8.18")); + } + + function testGetCodeByNameMultiVersion() public { + assertEq(vm.getCode("Counter"), type(Counter).creationCode); + require(keccak256(vm.getCode("Counter")) != keccak256(vm.getCode("Counter:0.8.17")), "Invalid artifact"); + assertEq(vm.getCode("Counter"), vm.getCode("Counter:0.8.18")); + } +} diff --git a/testdata/multi-version/cheats/GetCode17.t.sol b/testdata/multi-version/cheats/GetCode17.t.sol new file mode 100644 index 000000000..068a910cf --- /dev/null +++ b/testdata/multi-version/cheats/GetCode17.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.17; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; +import "../Counter.sol"; + +// Same as GetCode.t.sol but for 0.8.17 version +contract GetCodeTest17 is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testGetCodeMultiVersion() public { + assertEq(vm.getCode("Counter.sol"), type(Counter).creationCode); + require( + keccak256(vm.getCode("Counter.sol")) != keccak256(vm.getCode("Counter.sol:Counter:0.8.18")), + "Invalid artifact" + ); + assertEq(vm.getCode("Counter.sol"), vm.getCode("Counter.sol:Counter:0.8.17")); + } + + function testGetCodeByNameMultiVersion() public { + assertEq(vm.getCode("Counter"), type(Counter).creationCode); + require(keccak256(vm.getCode("Counter")) != keccak256(vm.getCode("Counter:0.8.18")), "Invalid artifact"); + assertEq(vm.getCode("Counter.sol"), vm.getCode("Counter:0.8.17")); + } +} diff --git a/testdata/script/broadcast/deploy.sol/31337/run-latest.json b/testdata/script/broadcast/deploy.sol/31337/run-latest.json deleted file mode 100644 index 9a05e1cb7..000000000 --- a/testdata/script/broadcast/deploy.sol/31337/run-latest.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "transactions": [ - { - "hash": null, - "type": "CREATE", - "contractName": null, - "contractAddress": "0x731a10897d267e19b34503ad902d0a29173ba4b1", - "function": null, - "arguments": null, - "transaction": { - "type": "0x02", - "from": "0x00a329c0648769a73afac7f9381e08fb43dbea72", - "gas": "0x54653", - "value": "0x0", - "data": "0x608060405234801561001057600080fd5b506103d9806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063d5dcf1271461003b578063f8194e4814610050575b600080fd5b61004e6100493660046100e9565b600155565b005b61006361005e366004610118565b610079565b60405161007091906101f9565b60405180910390f35b6060600061008783826102b5565b5060008260405160200161009b9190610375565b60405160208183030381529060405290507fefdeaaf566f7751d16a12c7fa8909eb74120f42cba334d07dd5246c48f1fba81816040516100db91906101f9565b60405180910390a192915050565b6000602082840312156100fb57600080fd5b5035919050565b634e487b7160e01b600052604160045260246000fd5b60006020828403121561012a57600080fd5b813567ffffffffffffffff8082111561014257600080fd5b818401915084601f83011261015657600080fd5b81358181111561016857610168610102565b604051601f8201601f19908116603f0116810190838211818310171561019057610190610102565b816040528281528760208487010111156101a957600080fd5b826020860160208301376000928101602001929092525095945050505050565b60005b838110156101e45781810151838201526020016101cc565b838111156101f3576000848401525b50505050565b60208152600082518060208401526102188160408501602087016101c9565b601f01601f19169190910160400192915050565b600181811c9082168061024057607f821691505b60208210810361026057634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156102b057600081815260208120601f850160051c8101602086101561028d5750805b601f850160051c820191505b818110156102ac57828155600101610299565b5050505b505050565b815167ffffffffffffffff8111156102cf576102cf610102565b6102e3816102dd845461022c565b84610266565b602080601f83116001811461031857600084156103005750858301515b600019600386901b1c1916600185901b1785556102ac565b600085815260208120601f198616915b8281101561034757888601518255948401946001909101908401610328565b50858210156103655787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b6502432b63637960d51b8152600082516103968160068501602087016101c9565b919091016006019291505056fea2646970667358221220a380cb042b6ca762a5a0f97e497c4cffa21c45dc21e2dab4107e5415921a704a64736f6c634300080f0033", - "nonce": "0x0", - "accessList": [] - } - }, - { - "hash": null, - "type": "CALL", - "contractName": null, - "contractAddress": "0x731a10897d267e19b34503ad902d0a29173ba4b1", - "function": null, - "arguments": null, - "transaction": { - "type": "0x02", - "from": "0x00a329c0648769a73afac7f9381e08fb43dbea72", - "to": "0x731a10897d267e19b34503ad902d0a29173ba4b1", - "gas": "0xef15", - "value": "0x0", - "data": "0xf8194e48000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000046a6f686e00000000000000000000000000000000000000000000000000000000", - "nonce": "0x1", - "accessList": [] - } - }, - { - "hash": null, - "type": "CALL", - "contractName": null, - "contractAddress": "0x731a10897d267e19b34503ad902d0a29173ba4b1", - "function": null, - "arguments": null, - "transaction": { - "type": "0x02", - "from": "0x00a329c0648769a73afac7f9381e08fb43dbea72", - "to": "0x731a10897d267e19b34503ad902d0a29173ba4b1", - "gas": "0xdcde", - "value": "0x0", - "data": "0xd5dcf127000000000000000000000000000000000000000000000000000000000000007b", - "nonce": "0x2", - "accessList": [] - } - } - ], - "receipts": [], - "libraries": [], - "pending": [], - "path": "broadcast/deploy.sol/31337/run-latest.json", - "returns": {}, - "timestamp": 1658913881 -} \ No newline at end of file diff --git a/zk-tests/foundry.toml b/zk-tests/foundry.toml index df1bac85c..60751e179 100644 --- a/zk-tests/foundry.toml +++ b/zk-tests/foundry.toml @@ -3,6 +3,7 @@ src = 'src' out = 'out' libs = ['lib'] fs_permissions = [{ access = "read", path = "./zkout/ConstantNumber.sol/ConstantNumber.json"}] +evm_version = "paris" [fuzz] no_zksync_reserved_addresses = true @@ -12,10 +13,11 @@ local = "${ERA_TEST_NODE_RPC_URL}" mainnet = "https://mainnet.era.zksync.io:443" testnet = "https://testnet.era.zksync.dev:443" -[invariant] -runs = 10 - # See more config options https://github.com/foundry-rs/foundry/tree/master/config +[profile.default.invariant] +runs = 10 +depth = 1 + [etherscan] testnet = { key = "PLACEHOLDER", chain = "300", url = "https://api-sepolia-era.zksync.network/api" } diff --git a/zk-tests/src/Basic.t.sol b/zk-tests/src/Basic.t.sol index 14bbb7cae..f2a2a1886 100644 --- a/zk-tests/src/Basic.t.sol +++ b/zk-tests/src/Basic.t.sol @@ -16,7 +16,7 @@ contract BlockEnv { chainid = block.chainid; } - function ZkBlockhash(uint256 _blockNumber) public view returns (bytes32) { + function zkBlockhash(uint256 _blockNumber) public view returns (bytes32) { return blockhash(_blockNumber); } } @@ -96,13 +96,21 @@ contract ZkBasicTest is Test { be.chainid() == block.chainid, "propagated block chainid is the same as current" ); + require( - be.ZkBlockhash(block.number) == blockhash(block.number), - "propagated blockhash is the same as current" + be.zkBlockhash(block.number) == blockhash(block.number), + "blockhash of the current block should be zero" ); + + // this corresponds to the the genesis block since the test runs in block #1 require( - be.ZkBlockhash(block.number) == bytes32(0), - "blockhash mismatch" + be.zkBlockhash(block.number - 1) == blockhash(block.number - 1), + "blockhash of the previous block should be equal" + ); + + require( + be.zkBlockhash(0) == blockhash(0), + "blockhash of the genesis block should be equal" ); be = new BlockEnv(); @@ -157,11 +165,11 @@ contract ZkBasicTest is Test { ); } - function testZkBlockhashWithNewerBlocks() public { + function testzkBlockhashWithNewerBlocks() public { vm.selectFork(latestForkEth); BlockEnv be = new BlockEnv(); require( - be.ZkBlockhash(block.number) == blockhash(block.number), + be.zkBlockhash(block.number) == blockhash(block.number), "blockhash mismatch" ); } diff --git a/zk-tests/src/Cheatcodes.t.sol b/zk-tests/src/Cheatcodes.t.sol index e7f586d13..8f4141660 100644 --- a/zk-tests/src/Cheatcodes.t.sol +++ b/zk-tests/src/Cheatcodes.t.sol @@ -219,4 +219,26 @@ contract ZkCheatcodesTest is Test { transactor.transact(); } + + function testZkCheatcodesCanBeUsedAfterFork() public { + assertEq( + 0, + address(0x4e59b44847b379578588920cA78FbF26c0B4956C).balance + ); + + vm.createSelectFork( + "https://eth-mainnet.alchemyapi.io/v2/Lc7oIGYeL_QvInzI0Wiu_pOZZDEKBrdf", // trufflehog:ignore + ETH_FORK_BLOCK + ); + assertEq( + 0, + address(0x4e59b44847b379578588920cA78FbF26c0B4956C).balance + ); + + vm.deal(0x4e59b44847b379578588920cA78FbF26c0B4956C, 1 ether); + assertEq( + 1 ether, + address(0x4e59b44847b379578588920cA78FbF26c0B4956C).balance + ); + } } diff --git a/zk-tests/src/Contracts.t.sol b/zk-tests/src/Contracts.t.sol index 69b08cabd..1bde15fd3 100644 --- a/zk-tests/src/Contracts.t.sol +++ b/zk-tests/src/Contracts.t.sol @@ -55,7 +55,8 @@ contract PayableFixedNumber { } function transfer(address payable dest, uint256 amt) public { - dest.call{value: amt}(""); + (bool success, ) = dest.call{value: amt}(""); + require(success); } function five() public pure returns (uint8) { @@ -121,11 +122,11 @@ contract ZkContractsTest is Test { ); } - function testFoo() public { + function testZkContractCanCallMethod() public { FixedGreeter g = new FixedGreeter(); vm.makePersistent(address(g)); vm.selectFork(forkEra); - console.log(g.greet("hi")); + assertEq("Hello hi", g.greet("hi")); } function testZkContractsPersistedDeployedContractNoArgs() public { @@ -178,16 +179,23 @@ contract ZkContractsTest is Test { vm.selectFork(forkEra); uint balanceBefore = address(this).balance; - PayableFixedNumber one = new PayableFixedNumber{ - value: 10 - }(); + PayableFixedNumber one = new PayableFixedNumber{value: 10}(); PayableFixedNumber two = new PayableFixedNumber(); one.transfer(payable(address(two)), 5); - require(address(one).balance == 5, "first contract's balance not decreased"); - require(address(two).balance == 5, "second contract's balance not increased"); - require(address(this).balance == balanceBefore - 10, "test address balance not decreased"); + require( + address(one).balance == 5, + "first contract's balance not decreased" + ); + require( + address(two).balance == 5, + "second contract's balance not increased" + ); + require( + address(this).balance == balanceBefore - 10, + "test address balance not decreased" + ); } function testZkContractsInlineDeployedContractComplexArgs() public { diff --git a/zk-tests/src/Deposit.sol b/zk-tests/src/Deposit.sol index fe0cca314..f6ccd4bc7 100644 --- a/zk-tests/src/Deposit.sol +++ b/zk-tests/src/Deposit.sol @@ -15,4 +15,4 @@ contract Deposit { (bool s, ) = msg.sender.call{value: amount}(""); require(s, "failed to send"); } -} \ No newline at end of file +} diff --git a/zk-tests/src/Maths.sol b/zk-tests/src/Maths.sol deleted file mode 100644 index 764242fde..000000000 --- a/zk-tests/src/Maths.sol +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -library Maths { - function square(uint256 x) public pure returns (uint256) { - return x * x; - } -} diff --git a/zk-tests/src/MissingLibraries.sol b/zk-tests/src/MissingLibraries.sol deleted file mode 100644 index 3314d9288..000000000 --- a/zk-tests/src/MissingLibraries.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Maths} from "./Maths.sol"; -import 'forge-std/Script.sol'; - -contract Mathematician { - uint256 public number; - - constructor(uint256 _number) { - number = _number; - } - - function square() public view returns (uint256) { - return Maths.square(number); - } -} - -contract MathematicianScript is Script { - function run() external { - Mathematician maths = new Mathematician(2); - - assert(maths.square() == 4); - } -} diff --git a/zk-tests/src/NestedMaths.sol b/zk-tests/src/NestedMaths.sol deleted file mode 100644 index cb0a19c87..000000000 --- a/zk-tests/src/NestedMaths.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Maths} from './Maths.sol'; - -library NestedMaths { - function square(uint256 x) public pure returns (uint256) { - return Maths.square(x); - } -} diff --git a/zk-tests/src/NestedMissingLibraries.sol b/zk-tests/src/NestedMissingLibraries.sol deleted file mode 100644 index 6b1b9050c..000000000 --- a/zk-tests/src/NestedMissingLibraries.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {NestedMaths} from "./NestedMaths.sol"; -import 'forge-std/Script.sol'; - -contract NestedMathematician { - uint256 public number; - - constructor(uint256 _number) { - number = _number; - } - - function square() public view returns (uint256) { - return NestedMaths.square(number); - } -} - -contract NestedMathematicianScript is Script { - function run() external { - NestedMathematician maths = new NestedMathematician(2); - - assert(maths.square() == 4); - } -} diff --git a/zk-tests/test.sh b/zk-tests/test.sh index f270a81d6..0d0e2e953 100755 --- a/zk-tests/test.sh +++ b/zk-tests/test.sh @@ -8,7 +8,7 @@ SOLC_VERSION=${SOLC_VERSION:-"v0.8.20"} SOLC="solc-${SOLC_VERSION}" FORGE="${REPO_ROOT}/target/release/forge" CAST="${REPO_ROOT}/target/release/cast" -ERA_TEST_NODE_VERSION="v0.1.0-alpha.15" +ERA_TEST_NODE_VERSION="v0.1.0-alpha.25" ERA_TEST_NODE_PID=0 RPC_URL="http://localhost:8011" PRIVATE_KEY="0x3d3cbc973389cb26f657686445bcc75662b415b656078503592ac8c1abb8810e" @@ -127,17 +127,6 @@ build_forge "${REPO_ROOT}" start_era_test_node -# Test missing libraries detection and deploy -output=$(RUST_LOG=warn "${FORGE}" build --zk-compile 2>&1 || true) - -if echo "$output" | grep -q "Missing libraries detected"; then - RUST_LOG=warn "${FORGE}" create --deploy-missing-libraries --rpc-url $RPC_URL --private-key $PRIVATE_KEY --zk-compile - RUST_LOG=warn "${FORGE}" script ./src/MissingLibraries.sol:MathematicianScript --rpc-url $RPC_URL --private-key $PRIVATE_KEY --zk-startup --chain 260 --use "./${SOLC}" -vvv || fail "forge script with libs failed" - RUST_LOG=warn "${FORGE}" script ./src/NestedMissingLibraries.sol:NestedMathematicianScript --rpc-url $RPC_URL --private-key $PRIVATE_KEY --zk-startup --chain 260 --use "./${SOLC}" -vvv || fail "forge script with nested libs failed" -else - echo "No missing libraries detected." -fi - echo "Running tests..." RUST_LOG=warn "${FORGE}" test --use "./${SOLC}" --chain 300 -vvv --zk-compile || fail "forge test failed"