From 29a756ed87e400e209c115a5c1d2be2839e8d6e7 Mon Sep 17 00:00:00 2001 From: Darioush Jalali Date: Wed, 21 Aug 2024 09:04:36 -0700 Subject: [PATCH] diff with coreth --- .github/CODEOWNERS | 3 +- .github/CONTRIBUTING.md | 52 +- .github/ISSUE_TEMPLATE/feature_spec.md | 19 - .github/ISSUE_TEMPLATE/release_checklist.md | 30 - .github/bug_report.md | 32 - .github/pull_request_template.md | 2 - .github/workflows/bench.yml | 19 - .github/workflows/check-clean-branch.sh | 9 - .github/workflows/codeql-analysis.yml | 8 +- .../workflows/notify-metrics-availability.sh | 19 - .../workflows/publish_antithesis_images.yml | 32 - .github/workflows/publish_docker.yml | 30 - .github/workflows/publish_docker_image.sh | 35 - .github/workflows/release.yml | 54 - .github/workflows/tests.yml | 220 - .github/workflows/trigger-antithesis.yml | 40 - .gitignore | 15 +- .golangci.yml | 2 + .goreleaser.yml | 37 - .markdownlint.json | 5 - Dockerfile | 52 +- README.md | 138 +- SECURITY.md | 1 + accounts/abi/bind/backends/simulated.go | 2 +- accounts/abi/bind/backends/simulated_test.go | 2 +- accounts/abi/bind/base.go | 55 + accounts/abi/bind/base_test.go | 76 + accounts/abi/bind/bind.go | 128 +- accounts/abi/bind/bind_test.go | 4 +- .../bind/precompilebind/precompile_bind.go | 244 - .../precompilebind/precompile_bind_test.go | 709 -- .../precompile_config_template.go | 93 - .../precompile_config_test_template.go | 107 - .../precompile_contract_template.go | 363 - .../precompile_contract_test_template.go | 261 - .../precompile_event_template.go | 123 - .../precompile_module_template.go | 80 - accounts/abi/bind/template.go | 24 +- cmd/abigen/namefilter.go | 1 - cmd/abigen/namefilter_test.go | 1 - cmd/evm/README.md | 625 -- cmd/evm/compiler.go | 65 - cmd/evm/disasm.go | 65 - cmd/evm/internal/compiler/compiler.go | 49 - cmd/evm/internal/t8ntool/block.go | 305 - cmd/evm/internal/t8ntool/execution.go | 376 - cmd/evm/internal/t8ntool/flags.go | 159 - cmd/evm/internal/t8ntool/gen_header.go | 153 - cmd/evm/internal/t8ntool/gen_stenv.go | 157 - cmd/evm/internal/t8ntool/tracewriter.go | 81 - cmd/evm/internal/t8ntool/transaction.go | 187 - cmd/evm/internal/t8ntool/transition.go | 339 - cmd/evm/internal/t8ntool/tx_iterator.go | 194 - cmd/evm/internal/t8ntool/utils.go | 64 - cmd/evm/main.go | 265 - cmd/evm/runner.go | 312 - cmd/evm/staterunner.go | 138 - cmd/evm/t8n_test.go | 615 -- cmd/evm/testdata/1/alloc.json | 12 - cmd/evm/testdata/1/env.json | 7 - cmd/evm/testdata/1/exp.json | 45 - cmd/evm/testdata/1/txs.json | 26 - cmd/evm/testdata/13/alloc.json | 23 - cmd/evm/testdata/13/env.json | 12 - cmd/evm/testdata/13/exp.json | 3 - cmd/evm/testdata/13/exp2.json | 42 - cmd/evm/testdata/13/exp2.json.diff | 12 - cmd/evm/testdata/13/readme.md | 4 - cmd/evm/testdata/13/signed_txs.rlp | 1 - cmd/evm/testdata/13/txs.json | 34 - cmd/evm/testdata/14/alloc.json | 12 - cmd/evm/testdata/14/env.json | 9 - cmd/evm/testdata/14/env.uncles.json | 10 - cmd/evm/testdata/14/exp.json | 13 - cmd/evm/testdata/14/exp2.json | 13 - cmd/evm/testdata/14/exp_berlin.json | 13 - cmd/evm/testdata/14/readme.md | 45 - cmd/evm/testdata/14/txs.json | 1 - cmd/evm/testdata/15/blockheader.rlp | 1 - cmd/evm/testdata/15/exp.json | 10 - cmd/evm/testdata/15/exp2.json | 12 - cmd/evm/testdata/15/exp3.json | 47 - cmd/evm/testdata/15/signed_txs.rlp | 1 - cmd/evm/testdata/15/signed_txs.rlp.json | 4 - cmd/evm/testdata/16/exp.json | 13 - cmd/evm/testdata/16/signed_txs.rlp | 1 - cmd/evm/testdata/16/unsigned_txs.json | 34 - cmd/evm/testdata/17/exp.json | 22 - cmd/evm/testdata/17/rlpdata.txt | 46 - cmd/evm/testdata/17/signed_txs.rlp | 1 - cmd/evm/testdata/18/README.md | 9 - cmd/evm/testdata/18/invalid.rlp | 1 - cmd/evm/testdata/19/alloc.json | 12 - cmd/evm/testdata/19/env.json | 9 - cmd/evm/testdata/19/exp_arrowglacier.json | 13 - cmd/evm/testdata/19/exp_grayglacier.json | 13 - cmd/evm/testdata/19/exp_london.json | 13 - cmd/evm/testdata/19/readme.md | 25 - cmd/evm/testdata/19/txs.json | 1 - cmd/evm/testdata/20/exp.json | 4 - cmd/evm/testdata/20/header.json | 14 - cmd/evm/testdata/20/ommers.json | 1 - cmd/evm/testdata/20/readme.md | 11 - cmd/evm/testdata/20/txs.rlp | 1 - cmd/evm/testdata/22/exp-clique.json | 4 - cmd/evm/testdata/22/exp.json | 4 - cmd/evm/testdata/22/header.json | 11 - cmd/evm/testdata/22/ommers.json | 1 - cmd/evm/testdata/22/readme.md | 11 - cmd/evm/testdata/22/txs.rlp | 1 - cmd/evm/testdata/23/alloc.json | 16 - cmd/evm/testdata/23/env.json | 7 - cmd/evm/testdata/23/exp.json | 26 - cmd/evm/testdata/23/readme.md | 1 - cmd/evm/testdata/23/txs.json | 15 - cmd/evm/testdata/24/alloc.json | 14 - cmd/evm/testdata/24/env.json | 9 - cmd/evm/testdata/24/exp.json | 56 - cmd/evm/testdata/24/exp.json.diff | 30 - cmd/evm/testdata/24/txs.json | 28 - cmd/evm/testdata/25/alloc.json | 8 - cmd/evm/testdata/25/env.json | 12 - cmd/evm/testdata/25/env.json.diff | 18 - cmd/evm/testdata/25/exp.json | 39 - cmd/evm/testdata/25/exp.json.diff | 32 - cmd/evm/testdata/25/txs.json | 15 - cmd/evm/testdata/28/alloc.json | 16 - cmd/evm/testdata/28/env.json | 23 - cmd/evm/testdata/28/exp.json | 46 - cmd/evm/testdata/28/txs.rlp | 1 - cmd/evm/testdata/29/alloc.json | 16 - cmd/evm/testdata/29/env.json | 21 - cmd/evm/testdata/29/exp.json | 47 - cmd/evm/testdata/29/readme.md | 29 - cmd/evm/testdata/29/txs.json | 19 - cmd/evm/testdata/3/alloc.json | 16 - cmd/evm/testdata/3/env.json | 8 - cmd/evm/testdata/3/exp.json | 39 - cmd/evm/testdata/3/readme.md | 2 - cmd/evm/testdata/3/txs.json | 14 - cmd/evm/testdata/30/README.txt | 77 - cmd/evm/testdata/30/alloc.json | 23 - cmd/evm/testdata/30/env.json | 24 - cmd/evm/testdata/30/exp.json | 66 - cmd/evm/testdata/30/txs.rlp | 1 - cmd/evm/testdata/30/txs_more.rlp | 1 - cmd/evm/testdata/4/alloc.json | 16 - cmd/evm/testdata/4/env.json | 8 - cmd/evm/testdata/4/readme.md | 3 - cmd/evm/testdata/4/txs.json | 14 - cmd/evm/testdata/5/alloc.json | 1 - cmd/evm/testdata/5/env.json | 11 - cmd/evm/testdata/5/exp.json | 23 - cmd/evm/testdata/5/readme.md | 1 - cmd/evm/testdata/5/txs.json | 1 - cmd/evm/transition-test.sh | 518 -- cmd/precompilegen/main.go | 198 - cmd/precompilegen/template-readme.md | 25 - ...0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC | 1 - cmd/simulator/README.md | 62 - cmd/simulator/config/flags.go | 129 - cmd/simulator/key/key.go | 85 - cmd/simulator/load/funder.go | 125 - cmd/simulator/load/loader.go | 247 - cmd/simulator/load/worker.go | 122 - cmd/simulator/main/main.go | 58 - cmd/simulator/metrics/metrics.go | 139 - cmd/simulator/txs/agent.go | 142 - cmd/simulator/txs/tx_generator.go | 90 - commontype/fee_config.go | 151 - commontype/fee_config_test.go | 143 - commontype/test_fee_config.go | 19 - compatibility.json | 52 - consensus/consensus.go | 8 - consensus/dummy/README.md | 18 +- consensus/dummy/consensus.go | 249 +- consensus/dummy/consensus_test.go | 101 +- consensus/dummy/dynamic_fees.go | 154 +- consensus/dummy/dynamic_fees_test.go | 220 +- contracts/.gitignore | 150 - contracts/.npmrc | 1 - contracts/.prettierrc | 15 - contracts/README.md | 108 - contracts/contracts/AllowList.sol | 82 - contracts/contracts/ERC20NativeMinter.sol | 59 - contracts/contracts/ExampleDeployerList.sol | 22 - contracts/contracts/ExampleFeeManager.sol | 104 - contracts/contracts/ExampleRewardManager.sol | 34 - contracts/contracts/ExampleTxAllowList.sol | 20 - contracts/contracts/ExampleWarp.sol | 2 +- contracts/contracts/interfaces/IAllowList.sol | 21 - .../contracts/interfaces/IFeeManager.sol | 47 - .../contracts/interfaces/INativeMinter.sol | 9 - .../contracts/interfaces/IRewardManager.sol | 33 - .../contracts/interfaces/IWarpMessenger.sol | 2 +- contracts/contracts/test/AllowListTest.sol | 11 - .../contracts/test/ERC20NativeMinterTest.sol | 144 - .../test/ExampleDeployerListTest.sol | 122 - .../contracts/test/ExampleFeeManagerTest.sol | 127 - .../test/ExampleRewardManagerTest.sol | 108 - .../contracts/test/ExampleTxAllowListTest.sol | 303 - contracts/hardhat.config.ts | 44 - contracts/index.ts | 1 - contracts/package-lock.json | 7419 ----------------- contracts/package.json | 45 - contracts/scripts/deployERC20NativeMinter.ts | 15 - .../scripts/deployExampleDeployerList.ts | 15 - .../scripts/deployExampleRewardManager.ts | 16 - contracts/scripts/deployExampleTxAllowList.ts | 16 - contracts/tasks.ts | 270 - contracts/test/README.md | 64 - .../test/contract_deployer_allow_list.ts | 101 - contracts/test/contract_native_minter.ts | 63 - contracts/test/fee_manager.ts | 125 - contracts/test/reward_manager.ts | 85 - contracts/test/tx_allow_list.ts | 135 - contracts/test/utils.ts | 132 - contracts/test/warp.ts | 40 - contracts/tsconfig.json | 18 - core/README.md | 2 +- core/TrieStressTest.abi | 1 - core/TrieStressTest.bin | 1 - core/TrieStressTest.sol | 22 - core/blockchain.go | 137 +- core/blockchain_log_test.go | 6 +- core/blockchain_reader.go | 74 - core/blockchain_repair_test.go | 10 +- core/blockchain_snapshot_test.go | 2 +- core/blockchain_test.go | 41 +- core/chain_makers.go | 35 +- core/evm.go | 34 +- core/gen_genesis.go | 18 - core/gen_genesis_account.go | 6 + core/genesis.go | 114 +- core/genesis_test.go | 124 +- core/headerchain_test.go | 2 +- core/rawdb/accessors_chain.go | 2 +- core/rawdb/accessors_metadata.go | 20 - core/rawdb/accessors_state.go | 4 +- core/rawdb/database.go | 2 - core/rawdb/schema.go | 10 +- core/rlp_test.go | 14 +- core/state/dump.go | 3 + core/state/journal.go | 11 + core/state/snapshot/generate_test.go | 26 +- core/state/state_object.go | 66 +- core/state/state_test.go | 10 +- core/state/statedb.go | 59 +- core/state/statedb_test.go | 153 + core/state/test_statedb.go | 1 - core/state_processor.go | 20 +- core/state_processor_test.go | 114 +- core/state_transition.go | 28 +- core/test_blockchain.go | 230 +- core/trie_stress_bench_test.go | 120 - core/txpool/blobpool/blobpool.go | 12 - core/txpool/blobpool/blobpool_test.go | 27 +- core/txpool/blobpool/interface.go | 5 - core/txpool/legacypool/legacypool.go | 32 +- core/txpool/legacypool/legacypool_test.go | 21 +- core/txpool/txpool.go | 9 +- core/txpool/validation.go | 13 +- core/types/block.go | 62 +- core/types/block_test.go | 109 +- core/types/gen_account_rlp.go | 1 + core/types/gen_header_json.go | 13 + core/types/gen_header_rlp.go | 30 +- core/types/hashes.go | 3 + core/types/state_account.go | 34 +- core/types/transaction_signing.go | 9 +- core/vm/contracts.go | 101 +- core/vm/eips.go | 14 + core/vm/evm.go | 168 +- core/vm/evm_test.go | 3 +- core/vm/gas_table.go | 94 + core/vm/gas_table_test.go | 4 +- core/vm/instructions.go | 64 + core/vm/instructions_test.go | 2 +- core/vm/interface.go | 5 + core/vm/interpreter.go | 25 +- core/vm/jump_table.go | 55 +- core/vm/jump_table_export.go | 11 +- core/vm/memory_table.go | 16 + core/vm/opcodes.go | 9 + core/vm/operations_acl.go | 2 +- core/vm/runtime/env.go | 22 +- core/vm/runtime/runtime.go | 15 +- core/vm/runtime/runtime_test.go | 2 + ...ng - OpenZeppelin (November 16th 2023).pdf | Bin 198844 -> 0 bytes ...acts - Least Authority (July 7th 2023).pdf | Bin 285080 -> 0 bytes eth/api_backend.go | 5 - eth/api_debug_test.go | 33 +- eth/backend.go | 15 +- eth/ethconfig/config.go | 4 +- eth/filters/bench_test.go | 4 +- eth/filters/filter_system.go | 15 +- eth/gasprice/fee_info_provider_test.go | 5 +- eth/gasprice/feehistory_test.go | 2 +- eth/gasprice/gasprice.go | 48 +- eth/gasprice/gasprice_test.go | 192 +- eth/tracers/api.go | 46 +- eth/tracers/api_extra_test.go | 417 - eth/tracers/api_test.go | 6 +- .../internal/tracetest/calltrace_test.go | 5 +- .../internal/tracetest/flat_calltrace_test.go | 1 - .../internal/tracetest/prestate_test.go | 22 +- .../testdata/call_tracer/create.json | 1 + .../testdata/call_tracer/deep_calls.json | 1 + .../testdata/call_tracer/delegatecall.json | 1 + .../inner_create_oog_outer_throw.json | 1 + .../testdata/call_tracer/inner_instafail.json | 1 + .../call_tracer/inner_revert_reason.json | 4 +- .../call_tracer/inner_throw_outer_revert.json | 1 + .../tracetest/testdata/call_tracer/oog.json | 1 + .../testdata/call_tracer/revert.json | 1 + .../testdata/call_tracer/revert_reason.json | 1 + .../testdata/call_tracer/selfdestruct.json | 1 + .../testdata/call_tracer/simple.json | 1 + .../tracetest/testdata/call_tracer/throw.json | 1 + .../create_failed.json | 4 +- eth/tracers/internal/tracetest/util.go | 1 - eth/tracers/js/tracer_test.go | 2 +- ethclient/ethclient.go | 14 +- .../subnetevmclient/subnet_evm_client.go | 234 - go.mod | 23 +- go.sum | 55 +- internal/cmdtest/test_cmd.go | 310 - internal/ethapi/api.go | 29 +- internal/ethapi/api_extra.go | 82 +- internal/ethapi/api_test.go | 46 +- internal/ethapi/backend.go | 2 - .../testdata/eth_getBlockByHash-hash-1.json | 19 +- .../eth_getBlockByHash-hash-genesis.json | 10 +- ...h_getBlockByHash-hash-latest-1-fullTx.json | 29 +- .../eth_getBlockByHash-hash-latest.json | 19 +- .../eth_getBlockByNumber-number-0.json | 10 +- .../eth_getBlockByNumber-number-1.json | 19 +- .../eth_getBlockByNumber-number-latest-1.json | 29 +- .../eth_getBlockByNumber-tag-latest.json | 19 +- ...h_getBlockReceipts-block-with-blob-tx.json | 8 +- ...eceipts-block-with-contract-create-tx.json | 8 +- ...ockReceipts-block-with-dynamic-fee-tx.json | 8 +- ...ts-block-with-legacy-contract-call-tx.json | 12 +- ...eceipts-block-with-legacy-transfer-tx.json | 8 +- .../eth_getBlockReceipts-tag-latest.json | 8 +- .../testdata/eth_getHeaderByHash-hash-0.json | 7 +- .../testdata/eth_getHeaderByHash-hash-1.json | 14 +- .../eth_getHeaderByHash-hash-latest-1.json | 14 +- .../eth_getHeaderByHash-hash-latest.json | 14 +- .../eth_getHeaderByNumber-number-0.json | 7 +- .../eth_getHeaderByNumber-number-1.json | 14 +- ...eth_getHeaderByNumber-number-latest-1.json | 14 +- .../eth_getHeaderByNumber-tag-latest.json | 14 +- .../eth_getTransactionReceipt-blob-tx.json | 8 +- ...TransactionReceipt-create-contract-tx.json | 8 +- ...eipt-create-contract-with-access-list.json | 8 +- ...ansactionReceipt-dynamic-tx-with-logs.json | 8 +- ...TransactionReceipt-normal-transfer-tx.json | 8 +- .../eth_getTransactionReceipt-with-logs.json | 12 +- internal/ethapi/transaction_args.go | 25 +- internal/ethapi/transaction_args_test.go | 4 +- internal/flags/flags.go | 387 - internal/flags/flags_test.go | 71 - internal/flags/helpers.go | 86 - miner/README.md | 6 +- miner/worker.go | 64 +- node/api.go | 2 +- node/config.go | 4 +- params/config.go | 459 +- params/config_extra.go | 143 +- params/config_test.go | 192 +- params/network_upgrades.go | 263 +- params/network_upgrades_test.go | 302 - params/precompile_config_test.go | 343 - params/precompile_upgrade.go | 23 - params/precompile_upgrade_test.go | 300 - params/protocol_params.go | 13 + params/state_upgrade.go | 107 - params/state_upgrade_test.go | 187 - peer/network_test.go | 6 + plugin/evm/README.md | 10 +- plugin/evm/block.go | 175 +- plugin/evm/block_builder.go | 9 +- plugin/evm/block_test.go | 97 - plugin/evm/block_verification.go | 175 +- plugin/evm/client.go | 163 +- plugin/evm/config.go | 54 +- plugin/evm/config_test.go | 8 +- plugin/evm/factory.go | 3 +- plugin/evm/gossip.go | 29 +- plugin/evm/gossip_stats.go | 41 +- plugin/evm/gossip_test.go | 110 +- plugin/evm/gossiper_eth_gossiping_test.go | 6 +- plugin/evm/handler.go | 75 +- plugin/evm/log.go | 23 +- plugin/evm/message/codec.go | 1 + plugin/evm/message/handler.go | 11 + plugin/evm/message/handler_test.go | 23 +- plugin/evm/message/leafs_request.go | 50 +- plugin/evm/message/leafs_request_test.go | 60 +- plugin/evm/message/message.go | 13 + plugin/evm/message/message_test.go | 25 +- plugin/evm/message/syncable.go | 6 +- plugin/evm/network_handler.go | 23 +- plugin/evm/service.go | 497 ++ plugin/evm/static_service.go | 88 +- plugin/evm/static_service_test.go | 95 - plugin/evm/syncervm_client.go | 43 +- plugin/evm/syncervm_server.go | 19 +- plugin/evm/syncervm_test.go | 124 +- plugin/evm/tx_gossip_test.go | 368 +- plugin/evm/version.go | 4 +- plugin/evm/version_test.go | 29 - plugin/evm/vm.go | 1135 ++- plugin/evm/vm_test.go | 2962 ++++--- plugin/evm/vm_upgrade_bytes_test.go | 388 - plugin/evm/vm_warp_test.go | 50 +- plugin/main.go | 25 +- plugin/runner/keys.go | 8 - plugin/runner/params.go | 45 - plugin/runner/runner.go | 33 - precompile/allowlist/allowlist.abi | 104 - precompile/allowlist/allowlist.go | 210 - precompile/allowlist/allowlist_test.go | 76 - precompile/allowlist/config.go | 112 - precompile/allowlist/config_test.go | 25 - precompile/allowlist/event.go | 37 - precompile/allowlist/role.go | 122 - precompile/allowlist/role_test.go | 149 - precompile/allowlist/test_allowlist.go | 712 -- precompile/allowlist/test_allowlist_config.go | 233 - precompile/allowlist/unpack_pack_test.go | 243 - precompile/contract/interfaces.go | 2 + precompile/contract/mocks.go | 30 + precompile/contract/test_utils.go | 49 - precompile/contract/utils.go | 4 - .../contracts/deployerallowlist/config.go | 59 - .../deployerallowlist/config_test.go | 48 - .../contracts/deployerallowlist/contract.go | 26 - .../deployerallowlist/contract_test.go | 19 - .../contracts/deployerallowlist/module.go | 52 - precompile/contracts/feemanager/config.go | 82 - .../contracts/feemanager/config_test.go | 90 - precompile/contracts/feemanager/contract.abi | 291 - precompile/contracts/feemanager/contract.go | 399 - .../contracts/feemanager/contract_test.go | 457 - precompile/contracts/feemanager/event.go | 80 - precompile/contracts/feemanager/module.go | 67 - .../contracts/feemanager/unpack_pack_test.go | 485 -- precompile/contracts/nativeminter/config.go | 100 - .../contracts/nativeminter/config_test.go | 119 - .../contracts/nativeminter/contract.abi | 116 - precompile/contracts/nativeminter/contract.go | 148 - .../contracts/nativeminter/contract_test.go | 276 - precompile/contracts/nativeminter/event.go | 34 - precompile/contracts/nativeminter/module.go | 61 - .../nativeminter/unpack_pack_test.go | 184 - precompile/contracts/rewardmanager/config.go | 121 - .../contracts/rewardmanager/config_test.go | 81 - .../contracts/rewardmanager/contract.abi | 177 - .../contracts/rewardmanager/contract.go | 338 - .../contracts/rewardmanager/contract_test.go | 489 -- precompile/contracts/rewardmanager/event.go | 41 - precompile/contracts/rewardmanager/module.go | 70 - precompile/contracts/txallowlist/config.go | 59 - .../contracts/txallowlist/config_test.go | 48 - precompile/contracts/txallowlist/contract.go | 25 - .../contracts/txallowlist/contract_test.go | 19 - precompile/contracts/txallowlist/module.go | 52 - precompile/contracts/warp/README.md | 2 +- precompile/precompileconfig/config.go | 9 +- precompile/precompileconfig/mocks.go | 29 - precompile/registry/registry.go | 33 - precompile/testutils/test_config.go | 3 - precompile/testutils/test_precompile.go | 19 +- rpc/client_opt_test.go | 1 - rpc/handler.go | 2 +- rpc/subscription_test.go | 2 +- scripts/avalanche_header.txt | 10 - scripts/build.sh | 28 +- scripts/build_antithesis_images.sh | 77 - scripts/build_antithesis_workload.sh | 11 - scripts/build_bench_precompiles.sh | 19 - scripts/build_docker_image.sh | 31 +- scripts/build_test.sh | 18 +- scripts/constants.sh | 40 +- scripts/diff_against.sh | 33 - scripts/format_add_avalanche_header.sh | 14 - scripts/format_as_fork.sh | 53 - scripts/format_as_upstream.sh | 47 - scripts/generate_precompile.sh | 21 - scripts/install_avalanchego_release.sh | 127 - scripts/lint_allowed_geth_imports.sh | 9 +- scripts/mock.gen.sh | 7 +- scripts/run.sh | 48 - scripts/run_ginkgo_load.sh | 29 - scripts/run_ginkgo_precompile.sh | 29 - scripts/run_ginkgo_warp.sh | 30 - scripts/run_prometheus.sh | 120 - scripts/run_promtail.sh | 115 - scripts/run_simulator.sh | 44 - scripts/shellcheck.sh | 18 +- scripts/tests.build_antithesis_images.sh | 32 - scripts/versions.sh | 8 +- stateupgrade/interfaces.go | 35 - stateupgrade/state_upgrade.go | 46 - sync/README.md | 23 +- sync/client/client_test.go | 117 +- sync/client/leaf_syncer.go | 10 +- sync/client/stats/stats.go | 17 +- sync/handlers/leafs_request.go | 27 +- sync/handlers/leafs_request_test.go | 211 +- sync/statesync/state_syncer.go | 2 +- sync/statesync/trie_segments.go | 2 + tests/README.md | 37 - tests/antithesis/Dockerfile.config | 6 - tests/antithesis/Dockerfile.node | 32 - tests/antithesis/Dockerfile.workload | 28 - tests/antithesis/README.md | 11 - tests/antithesis/gencomposeconfig/main.go | 41 - tests/antithesis/main.go | 204 - tests/init.go | 155 +- tests/load/genesis/genesis.json | 33 - tests/load/load_test.go | 102 - .../genesis/contract_deployer_allow_list.json | 48 - .../genesis/contract_native_minter.json | 48 - tests/precompile/genesis/fee_manager.json | 48 - tests/precompile/genesis/reward_manager.json | 48 - tests/precompile/genesis/tx_allow_list.json | 48 - tests/precompile/genesis/warp.json | 45 - tests/precompile/precompile_test.go | 22 - tests/precompile/solidity/suites.go | 103 - tests/state_test_util.go | 389 - tests/utils/command.go | 125 - tests/utils/constants.go | 16 - tests/utils/proposervm.go | 64 - tests/utils/subnet.go | 189 - tests/utils/tmpnet.go | 81 - tests/warp/warp_test.go | 749 -- utils/numbers.go | 10 - utils/snow.go | 22 +- vmerrs/vmerrs.go | 32 +- 542 files changed, 8660 insertions(+), 39144 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/feature_spec.md delete mode 100644 .github/ISSUE_TEMPLATE/release_checklist.md delete mode 100644 .github/bug_report.md delete mode 100644 .github/workflows/bench.yml delete mode 100755 .github/workflows/check-clean-branch.sh delete mode 100755 .github/workflows/notify-metrics-availability.sh delete mode 100644 .github/workflows/publish_antithesis_images.yml delete mode 100644 .github/workflows/publish_docker.yml delete mode 100755 .github/workflows/publish_docker_image.sh delete mode 100644 .github/workflows/release.yml delete mode 100644 .github/workflows/tests.yml delete mode 100644 .github/workflows/trigger-antithesis.yml delete mode 100644 .goreleaser.yml delete mode 100644 .markdownlint.json delete mode 100644 accounts/abi/bind/precompilebind/precompile_bind.go delete mode 100644 accounts/abi/bind/precompilebind/precompile_bind_test.go delete mode 100644 accounts/abi/bind/precompilebind/precompile_config_template.go delete mode 100644 accounts/abi/bind/precompilebind/precompile_config_test_template.go delete mode 100644 accounts/abi/bind/precompilebind/precompile_contract_template.go delete mode 100644 accounts/abi/bind/precompilebind/precompile_contract_test_template.go delete mode 100644 accounts/abi/bind/precompilebind/precompile_event_template.go delete mode 100644 accounts/abi/bind/precompilebind/precompile_module_template.go delete mode 100644 cmd/evm/README.md delete mode 100644 cmd/evm/compiler.go delete mode 100644 cmd/evm/disasm.go delete mode 100644 cmd/evm/internal/compiler/compiler.go delete mode 100644 cmd/evm/internal/t8ntool/block.go delete mode 100644 cmd/evm/internal/t8ntool/execution.go delete mode 100644 cmd/evm/internal/t8ntool/flags.go delete mode 100644 cmd/evm/internal/t8ntool/gen_header.go delete mode 100644 cmd/evm/internal/t8ntool/gen_stenv.go delete mode 100644 cmd/evm/internal/t8ntool/tracewriter.go delete mode 100644 cmd/evm/internal/t8ntool/transaction.go delete mode 100644 cmd/evm/internal/t8ntool/transition.go delete mode 100644 cmd/evm/internal/t8ntool/tx_iterator.go delete mode 100644 cmd/evm/internal/t8ntool/utils.go delete mode 100644 cmd/evm/main.go delete mode 100644 cmd/evm/runner.go delete mode 100644 cmd/evm/staterunner.go delete mode 100644 cmd/evm/t8n_test.go delete mode 100644 cmd/evm/testdata/1/alloc.json delete mode 100644 cmd/evm/testdata/1/env.json delete mode 100644 cmd/evm/testdata/1/exp.json delete mode 100644 cmd/evm/testdata/1/txs.json delete mode 100644 cmd/evm/testdata/13/alloc.json delete mode 100644 cmd/evm/testdata/13/env.json delete mode 100644 cmd/evm/testdata/13/exp.json delete mode 100644 cmd/evm/testdata/13/exp2.json delete mode 100644 cmd/evm/testdata/13/exp2.json.diff delete mode 100644 cmd/evm/testdata/13/readme.md delete mode 100644 cmd/evm/testdata/13/signed_txs.rlp delete mode 100644 cmd/evm/testdata/13/txs.json delete mode 100644 cmd/evm/testdata/14/alloc.json delete mode 100644 cmd/evm/testdata/14/env.json delete mode 100644 cmd/evm/testdata/14/env.uncles.json delete mode 100644 cmd/evm/testdata/14/exp.json delete mode 100644 cmd/evm/testdata/14/exp2.json delete mode 100644 cmd/evm/testdata/14/exp_berlin.json delete mode 100644 cmd/evm/testdata/14/readme.md delete mode 100644 cmd/evm/testdata/14/txs.json delete mode 100644 cmd/evm/testdata/15/blockheader.rlp delete mode 100644 cmd/evm/testdata/15/exp.json delete mode 100644 cmd/evm/testdata/15/exp2.json delete mode 100644 cmd/evm/testdata/15/exp3.json delete mode 100644 cmd/evm/testdata/15/signed_txs.rlp delete mode 100644 cmd/evm/testdata/15/signed_txs.rlp.json delete mode 100644 cmd/evm/testdata/16/exp.json delete mode 100644 cmd/evm/testdata/16/signed_txs.rlp delete mode 100644 cmd/evm/testdata/16/unsigned_txs.json delete mode 100644 cmd/evm/testdata/17/exp.json delete mode 100644 cmd/evm/testdata/17/rlpdata.txt delete mode 100644 cmd/evm/testdata/17/signed_txs.rlp delete mode 100644 cmd/evm/testdata/18/README.md delete mode 100644 cmd/evm/testdata/18/invalid.rlp delete mode 100644 cmd/evm/testdata/19/alloc.json delete mode 100644 cmd/evm/testdata/19/env.json delete mode 100644 cmd/evm/testdata/19/exp_arrowglacier.json delete mode 100644 cmd/evm/testdata/19/exp_grayglacier.json delete mode 100644 cmd/evm/testdata/19/exp_london.json delete mode 100644 cmd/evm/testdata/19/readme.md delete mode 100644 cmd/evm/testdata/19/txs.json delete mode 100644 cmd/evm/testdata/20/exp.json delete mode 100644 cmd/evm/testdata/20/header.json delete mode 100644 cmd/evm/testdata/20/ommers.json delete mode 100644 cmd/evm/testdata/20/readme.md delete mode 100644 cmd/evm/testdata/20/txs.rlp delete mode 100644 cmd/evm/testdata/22/exp-clique.json delete mode 100644 cmd/evm/testdata/22/exp.json delete mode 100644 cmd/evm/testdata/22/header.json delete mode 100644 cmd/evm/testdata/22/ommers.json delete mode 100644 cmd/evm/testdata/22/readme.md delete mode 100644 cmd/evm/testdata/22/txs.rlp delete mode 100644 cmd/evm/testdata/23/alloc.json delete mode 100644 cmd/evm/testdata/23/env.json delete mode 100644 cmd/evm/testdata/23/exp.json delete mode 100644 cmd/evm/testdata/23/readme.md delete mode 100644 cmd/evm/testdata/23/txs.json delete mode 100644 cmd/evm/testdata/24/alloc.json delete mode 100644 cmd/evm/testdata/24/env.json delete mode 100644 cmd/evm/testdata/24/exp.json delete mode 100644 cmd/evm/testdata/24/exp.json.diff delete mode 100644 cmd/evm/testdata/24/txs.json delete mode 100644 cmd/evm/testdata/25/alloc.json delete mode 100644 cmd/evm/testdata/25/env.json delete mode 100644 cmd/evm/testdata/25/env.json.diff delete mode 100644 cmd/evm/testdata/25/exp.json delete mode 100644 cmd/evm/testdata/25/exp.json.diff delete mode 100644 cmd/evm/testdata/25/txs.json delete mode 100644 cmd/evm/testdata/28/alloc.json delete mode 100644 cmd/evm/testdata/28/env.json delete mode 100644 cmd/evm/testdata/28/exp.json delete mode 100644 cmd/evm/testdata/28/txs.rlp delete mode 100644 cmd/evm/testdata/29/alloc.json delete mode 100644 cmd/evm/testdata/29/env.json delete mode 100644 cmd/evm/testdata/29/exp.json delete mode 100644 cmd/evm/testdata/29/readme.md delete mode 100644 cmd/evm/testdata/29/txs.json delete mode 100644 cmd/evm/testdata/3/alloc.json delete mode 100644 cmd/evm/testdata/3/env.json delete mode 100644 cmd/evm/testdata/3/exp.json delete mode 100644 cmd/evm/testdata/3/readme.md delete mode 100644 cmd/evm/testdata/3/txs.json delete mode 100644 cmd/evm/testdata/30/README.txt delete mode 100644 cmd/evm/testdata/30/alloc.json delete mode 100644 cmd/evm/testdata/30/env.json delete mode 100644 cmd/evm/testdata/30/exp.json delete mode 100644 cmd/evm/testdata/30/txs.rlp delete mode 100644 cmd/evm/testdata/30/txs_more.rlp delete mode 100644 cmd/evm/testdata/4/alloc.json delete mode 100644 cmd/evm/testdata/4/env.json delete mode 100644 cmd/evm/testdata/4/readme.md delete mode 100644 cmd/evm/testdata/4/txs.json delete mode 100644 cmd/evm/testdata/5/alloc.json delete mode 100644 cmd/evm/testdata/5/env.json delete mode 100644 cmd/evm/testdata/5/exp.json delete mode 100644 cmd/evm/testdata/5/readme.md delete mode 100644 cmd/evm/testdata/5/txs.json delete mode 100755 cmd/evm/transition-test.sh delete mode 100644 cmd/precompilegen/main.go delete mode 100644 cmd/precompilegen/template-readme.md delete mode 100644 cmd/simulator/.simulator/keys/0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC delete mode 100644 cmd/simulator/README.md delete mode 100644 cmd/simulator/config/flags.go delete mode 100644 cmd/simulator/key/key.go delete mode 100644 cmd/simulator/load/funder.go delete mode 100644 cmd/simulator/load/loader.go delete mode 100644 cmd/simulator/load/worker.go delete mode 100644 cmd/simulator/main/main.go delete mode 100644 cmd/simulator/metrics/metrics.go delete mode 100644 cmd/simulator/txs/agent.go delete mode 100644 cmd/simulator/txs/tx_generator.go delete mode 100644 commontype/fee_config.go delete mode 100644 commontype/fee_config_test.go delete mode 100644 commontype/test_fee_config.go delete mode 100644 compatibility.json delete mode 100644 contracts/.gitignore delete mode 100644 contracts/.npmrc delete mode 100644 contracts/.prettierrc delete mode 100644 contracts/README.md delete mode 100644 contracts/contracts/AllowList.sol delete mode 100644 contracts/contracts/ERC20NativeMinter.sol delete mode 100644 contracts/contracts/ExampleDeployerList.sol delete mode 100644 contracts/contracts/ExampleFeeManager.sol delete mode 100644 contracts/contracts/ExampleRewardManager.sol delete mode 100644 contracts/contracts/ExampleTxAllowList.sol delete mode 100644 contracts/contracts/interfaces/IAllowList.sol delete mode 100644 contracts/contracts/interfaces/IFeeManager.sol delete mode 100644 contracts/contracts/interfaces/INativeMinter.sol delete mode 100644 contracts/contracts/interfaces/IRewardManager.sol delete mode 100644 contracts/contracts/test/AllowListTest.sol delete mode 100644 contracts/contracts/test/ERC20NativeMinterTest.sol delete mode 100644 contracts/contracts/test/ExampleDeployerListTest.sol delete mode 100644 contracts/contracts/test/ExampleFeeManagerTest.sol delete mode 100644 contracts/contracts/test/ExampleRewardManagerTest.sol delete mode 100644 contracts/contracts/test/ExampleTxAllowListTest.sol delete mode 100644 contracts/hardhat.config.ts delete mode 100644 contracts/index.ts delete mode 100644 contracts/package-lock.json delete mode 100644 contracts/package.json delete mode 100644 contracts/scripts/deployERC20NativeMinter.ts delete mode 100644 contracts/scripts/deployExampleDeployerList.ts delete mode 100644 contracts/scripts/deployExampleRewardManager.ts delete mode 100644 contracts/scripts/deployExampleTxAllowList.ts delete mode 100644 contracts/tasks.ts delete mode 100644 contracts/test/README.md delete mode 100644 contracts/test/contract_deployer_allow_list.ts delete mode 100644 contracts/test/contract_native_minter.ts delete mode 100644 contracts/test/fee_manager.ts delete mode 100644 contracts/test/reward_manager.ts delete mode 100644 contracts/test/tx_allow_list.ts delete mode 100644 contracts/test/utils.ts delete mode 100644 contracts/test/warp.ts delete mode 100644 contracts/tsconfig.json delete mode 100644 core/TrieStressTest.abi delete mode 100644 core/TrieStressTest.bin delete mode 100644 core/TrieStressTest.sol delete mode 100644 core/trie_stress_bench_test.go delete mode 100644 docs/audits/Avalanche Warp Messaging - OpenZeppelin (November 16th 2023).pdf delete mode 100644 docs/audits/Bridge Smart Contracts - Least Authority (July 7th 2023).pdf delete mode 100644 eth/tracers/api_extra_test.go delete mode 100644 ethclient/subnetevmclient/subnet_evm_client.go delete mode 100644 internal/cmdtest/test_cmd.go delete mode 100644 internal/flags/flags.go delete mode 100644 internal/flags/flags_test.go delete mode 100644 params/network_upgrades_test.go delete mode 100644 params/precompile_config_test.go delete mode 100644 params/precompile_upgrade_test.go delete mode 100644 params/state_upgrade.go delete mode 100644 params/state_upgrade_test.go delete mode 100644 plugin/evm/block_test.go delete mode 100644 plugin/evm/static_service_test.go delete mode 100644 plugin/evm/version_test.go delete mode 100644 plugin/evm/vm_upgrade_bytes_test.go delete mode 100644 plugin/runner/keys.go delete mode 100644 plugin/runner/params.go delete mode 100644 plugin/runner/runner.go delete mode 100644 precompile/allowlist/allowlist.abi delete mode 100644 precompile/allowlist/allowlist.go delete mode 100644 precompile/allowlist/allowlist_test.go delete mode 100644 precompile/allowlist/config.go delete mode 100644 precompile/allowlist/config_test.go delete mode 100644 precompile/allowlist/event.go delete mode 100644 precompile/allowlist/role.go delete mode 100644 precompile/allowlist/role_test.go delete mode 100644 precompile/allowlist/test_allowlist.go delete mode 100644 precompile/allowlist/test_allowlist_config.go delete mode 100644 precompile/allowlist/unpack_pack_test.go delete mode 100644 precompile/contract/test_utils.go delete mode 100644 precompile/contracts/deployerallowlist/config.go delete mode 100644 precompile/contracts/deployerallowlist/config_test.go delete mode 100644 precompile/contracts/deployerallowlist/contract.go delete mode 100644 precompile/contracts/deployerallowlist/contract_test.go delete mode 100644 precompile/contracts/deployerallowlist/module.go delete mode 100644 precompile/contracts/feemanager/config.go delete mode 100644 precompile/contracts/feemanager/config_test.go delete mode 100644 precompile/contracts/feemanager/contract.abi delete mode 100644 precompile/contracts/feemanager/contract.go delete mode 100644 precompile/contracts/feemanager/contract_test.go delete mode 100644 precompile/contracts/feemanager/event.go delete mode 100644 precompile/contracts/feemanager/module.go delete mode 100644 precompile/contracts/feemanager/unpack_pack_test.go delete mode 100644 precompile/contracts/nativeminter/config.go delete mode 100644 precompile/contracts/nativeminter/config_test.go delete mode 100644 precompile/contracts/nativeminter/contract.abi delete mode 100644 precompile/contracts/nativeminter/contract.go delete mode 100644 precompile/contracts/nativeminter/contract_test.go delete mode 100644 precompile/contracts/nativeminter/event.go delete mode 100644 precompile/contracts/nativeminter/module.go delete mode 100644 precompile/contracts/nativeminter/unpack_pack_test.go delete mode 100644 precompile/contracts/rewardmanager/config.go delete mode 100644 precompile/contracts/rewardmanager/config_test.go delete mode 100644 precompile/contracts/rewardmanager/contract.abi delete mode 100644 precompile/contracts/rewardmanager/contract.go delete mode 100644 precompile/contracts/rewardmanager/contract_test.go delete mode 100644 precompile/contracts/rewardmanager/event.go delete mode 100644 precompile/contracts/rewardmanager/module.go delete mode 100644 precompile/contracts/txallowlist/config.go delete mode 100644 precompile/contracts/txallowlist/config_test.go delete mode 100644 precompile/contracts/txallowlist/contract.go delete mode 100644 precompile/contracts/txallowlist/contract_test.go delete mode 100644 precompile/contracts/txallowlist/module.go delete mode 100644 scripts/avalanche_header.txt delete mode 100755 scripts/build_antithesis_images.sh delete mode 100755 scripts/build_antithesis_workload.sh delete mode 100755 scripts/build_bench_precompiles.sh delete mode 100755 scripts/diff_against.sh delete mode 100755 scripts/format_add_avalanche_header.sh delete mode 100755 scripts/format_as_fork.sh delete mode 100755 scripts/format_as_upstream.sh delete mode 100755 scripts/generate_precompile.sh delete mode 100755 scripts/install_avalanchego_release.sh delete mode 100755 scripts/run.sh delete mode 100755 scripts/run_ginkgo_load.sh delete mode 100755 scripts/run_ginkgo_precompile.sh delete mode 100755 scripts/run_ginkgo_warp.sh delete mode 100755 scripts/run_prometheus.sh delete mode 100755 scripts/run_promtail.sh delete mode 100755 scripts/run_simulator.sh delete mode 100755 scripts/tests.build_antithesis_images.sh delete mode 100644 stateupgrade/interfaces.go delete mode 100644 stateupgrade/state_upgrade.go delete mode 100644 tests/README.md delete mode 100644 tests/antithesis/Dockerfile.config delete mode 100644 tests/antithesis/Dockerfile.node delete mode 100644 tests/antithesis/Dockerfile.workload delete mode 100644 tests/antithesis/README.md delete mode 100644 tests/antithesis/gencomposeconfig/main.go delete mode 100644 tests/antithesis/main.go delete mode 100644 tests/load/genesis/genesis.json delete mode 100644 tests/load/load_test.go delete mode 100644 tests/precompile/genesis/contract_deployer_allow_list.json delete mode 100644 tests/precompile/genesis/contract_native_minter.json delete mode 100644 tests/precompile/genesis/fee_manager.json delete mode 100644 tests/precompile/genesis/reward_manager.json delete mode 100644 tests/precompile/genesis/tx_allow_list.json delete mode 100644 tests/precompile/genesis/warp.json delete mode 100644 tests/precompile/precompile_test.go delete mode 100644 tests/precompile/solidity/suites.go delete mode 100644 tests/utils/command.go delete mode 100644 tests/utils/constants.go delete mode 100644 tests/utils/proposervm.go delete mode 100644 tests/utils/subnet.go delete mode 100644 tests/utils/tmpnet.go delete mode 100644 tests/warp/warp_test.go diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0f82322672..4de07544cd 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -6,5 +6,4 @@ # review whenever someone opens a pull request. -* @ceyonur @darioush - +* @darioush @ceyonur diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 1dc750fb60..87c79fcfe2 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,45 +1,35 @@ # Contributing -Thank you for considering to help out with the source code! We welcome -contributions from anyone on the internet, and are grateful for even the +Thank you for considering to help out with the source code! We welcome +contributions from anyone on the internet, and are grateful for even the smallest of fixes! -If you'd like to contribute to subnet-evm, please fork, fix, commit and send a +If you'd like to contribute to coreth, please fork, fix, commit and send a pull request for the maintainers to review and merge into the main code base. If -you wish to submit more complex changes though, please check up with the core -devs first on [Discord](https://chat.avalabs.org) to -ensure those changes are in line with the general philosophy of the project +you wish to submit more complex changes though, please check up with the core +devs first on [Discord](https://chat.avalabs.org) to +ensure those changes are in line with the general philosophy of the project and/or get some early feedback which can make both your efforts much lighter as well as our review and merge procedures quick and simple. ## Coding guidelines -Please make sure your contributions adhere to our coding and documentation -guidelines: - -- Code must adhere to the official Go - [formatting](https://go.dev/doc/effective_go#formatting) guidelines - (i.e. uses [gofmt](https://pkg.go.dev/cmd/gofmt)). -- Pull requests need to be based on and opened against the `master` branch. -- Pull reuqests should include a detailed description -- Commits are required to be signed. See [here](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits) - for information on signing commits. -- Commit messages should be prefixed with the package(s) they modify. - - E.g. "eth, rpc: make trace configs optional" - -## Documentation guidelines - -- Code should be well commented, so it is easier to read and maintain. - Any complex sections or invariants should be documented explicitly. -- Code must be documented adhering to the official Go - [commentary](https://go.dev/doc/effective_go#commentary) guidelines. -- Changes with user facing impact (e.g., addition or modification of flags and - options) should be accompanied by a link to a pull request to the [avalanche-docs](https://github.com/ava-labs/avalanche-docs) - repository. [example](https://github.com/ava-labs/avalanche-docs/pull/1119/files). -- Changes that modify a package significantly or add new features should - either update the existing or include a new `README.md` file in that package. +Please make sure your contributions adhere to our coding guidelines: + + * Code must adhere to the official Go +[formatting](https://go.dev/doc/effective_go#formatting) guidelines +(i.e. uses [gofmt](https://pkg.go.dev/cmd/gofmt)). + * Code must be documented adhering to the official Go +[commentary](https://go.dev/doc/effective_go#commentary) guidelines. + * Pull requests need to be based on and opened against the `master` branch. + * Pull reuqests should include a detailed description + * Commits are required to be signed. See [here](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits) + for information on signing commits. + * Commit messages should be prefixed with the package(s) they modify. + * E.g. "eth, rpc: make trace configs optional" ## Can I have feature X -Before you submit a feature request, please check and make sure that it isn't +Before you submit a feature request, please check and make sure that it isn't possible through some other means. + diff --git a/.github/ISSUE_TEMPLATE/feature_spec.md b/.github/ISSUE_TEMPLATE/feature_spec.md deleted file mode 100644 index a219c86753..0000000000 --- a/.github/ISSUE_TEMPLATE/feature_spec.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Feature specification -about: Discussion on design and implementation of new features for subnet-evm. -title: '' -labels: enhancement -assignees: '' - ---- - -**Context and scope** -Include a short description of the context and scope of the suggested feature. -Include goals the change will accomplish if relevant. - -**Discussion and alternatives** -Include a description of the changes to be made to the code along with alternatives -that were considered, including pro/con analysis where relevant. - -**Open questions** -Questions that are still being discussed. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md deleted file mode 100644 index f71bab419f..0000000000 --- a/.github/ISSUE_TEMPLATE/release_checklist.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -name: Release Checklist -about: Create a ticket to track a release -title: '' -labels: release -assignees: '' - ---- - -## Release - -The release version and a description of the planned changes to be included in the release. - -## Issues - -Link the major issues planned to be included in the release. - -## Documentation - -Link the relevant documentation PRs for this release. - -## Checklist - -- [ ] Update version in plugin/evm/version.go -- [ ] Bump AvalancheGo dependency in go.mod for RPCChainVM Compatibility -- [ ] Update AvalancheGo dependency in scripts/versions.sh for e2e tests. -- [ ] Add new entry in compatibility.json for RPCChainVM Compatibility -- [ ] Update AvalancheGo compatibility in README -- [ ] Deploy to WAGMI -- [ ] Confirm goreleaser job has successfully generated binaries by checking the releases page diff --git a/.github/bug_report.md b/.github/bug_report.md deleted file mode 100644 index dfd45bd27b..0000000000 --- a/.github/bug_report.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: bug -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior. - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Logs** -If applicable, please include the relevant logs that indicate a problem and/or the log directory of your node. By default, this can be found at `~/.avalanchego/logs/`. - -**Metrics** -If applicable, please include any metrics gathered from your node to assist us in diagnosing the problem. - -**Operating System** -Which OS you used to reveal the bug. - -**Additional context** -Add any other context about the problem here. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 119cac2eb9..8200c0597f 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -3,5 +3,3 @@ ## How this works ## How this was tested - -## How is this documented diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml deleted file mode 100644 index 726c877428..0000000000 --- a/.github/workflows/bench.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Bench - -on: - workflow_dispatch: - pull_request: - -jobs: - bench: - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v4 - with: - go-version: "~1.21.12" - check-latest: true - - run: go mod download - shell: bash - - run: ./scripts/build_bench_precompiles.sh - shell: bash diff --git a/.github/workflows/check-clean-branch.sh b/.github/workflows/check-clean-branch.sh deleted file mode 100755 index 1eec74803a..0000000000 --- a/.github/workflows/check-clean-branch.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -# Exits if any uncommitted changes are found. - -set -o errexit -set -o nounset -set -o pipefail - -git update-index --really-refresh >> /dev/null -git diff-index --quiet HEAD diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 639963adec..6148b6d883 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -42,18 +42,18 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. - queries: security-extended + # queries: ./path/to/local/query, your-org/your-repo/queries@main # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # ℹī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -67,4 +67,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/notify-metrics-availability.sh b/.github/workflows/notify-metrics-availability.sh deleted file mode 100755 index fd69064045..0000000000 --- a/.github/workflows/notify-metrics-availability.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# Timestamps are in seconds -from_timestamp="$(date '+%s')" -monitoring_period=900 # 15 minutes -to_timestamp="$((from_timestamp + monitoring_period))" - -# Grafana expects microseconds, so pad timestamps with 3 zeros -metrics_url="${GRAFANA_URL}&var-filter=gh_job_id%7C%3D%7C${GH_JOB_ID}&from=${from_timestamp}000&to=${to_timestamp}000" - -# Optionally ensure that the link displays metrics only for the shared -# network rather than mixing it with the results for private networks. -if [[ -n "${FILTER_BY_OWNER:-}" ]]; then - metrics_url="${metrics_url}&var-filter=network_owner%7C%3D%7C${FILTER_BY_OWNER}" -fi - -echo "::notice links::metrics ${metrics_url}" diff --git a/.github/workflows/publish_antithesis_images.yml b/.github/workflows/publish_antithesis_images.yml deleted file mode 100644 index d941f2fad4..0000000000 --- a/.github/workflows/publish_antithesis_images.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Publish Antithesis Images - -on: - workflow_dispatch: - push: - branches: - - master - -env: - REGISTRY: us-central1-docker.pkg.dev - REPOSITORY: molten-verve-216720/avalanche-repository - -jobs: - antithesis: - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - - - name: Login to GAR - uses: docker/login-action@v3 - with: - registry: ${{ env.REGISTRY }} - username: _json_key - password: ${{ secrets.ANTITHESIS_GAR_JSON_KEY }} - - - name: Build and publish images - run: bash -x ./scripts/build_antithesis_images.sh - env: - IMAGE_PREFIX: ${{ env.REGISTRY }}/${{ env.REPOSITORY }} - IMAGE_TAG: latest diff --git a/.github/workflows/publish_docker.yml b/.github/workflows/publish_docker.yml deleted file mode 100644 index 009909c1ba..0000000000 --- a/.github/workflows/publish_docker.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Publish Docker Image - -on: - workflow_dispatch: - inputs: - vm_id: - description: 'The ID of the VM (binary dst in Docker image)' - default: '' - required: false - type: string - - push: - tags: - - "*" - branches: - - master - - -jobs: - publish_docker_image: - name: Publish Docker Image - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v4 - - name: Publish image to Dockerhub - env: - DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} - DOCKER_PASS: ${{ secrets.DOCKER_PASS }} - DOCKER_REPO: "avaplatform/subnet-evm" - run: .github/workflows/publish_docker_image.sh ${{ inputs.vm_id }} diff --git a/.github/workflows/publish_docker_image.sh b/.github/workflows/publish_docker_image.sh deleted file mode 100755 index 71bce26c12..0000000000 --- a/.github/workflows/publish_docker_image.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash - -set -o errexit -set -o nounset -set -o pipefail - -# If this is not a trusted build (Docker Credentials are not set) -if [[ -z "$DOCKER_USERNAME" ]]; then - exit 0; -fi - -# Avalanche root directory -SUBNET_EVM_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd ../.. && pwd ) - -# Load the versions -source "$SUBNET_EVM_PATH"/scripts/versions.sh - -# Set the vm id if provided -if [[ $# -eq 1 ]]; then - VM_ID=$1 -fi - -# Buld the docker image -source "$SUBNET_EVM_PATH"/scripts/build_docker_image.sh - -if [[ $CURRENT_BRANCH == "master" ]]; then - echo "Tagging current image as $DOCKERHUB_REPO:latest" - docker tag "$DOCKERHUB_REPO:$BUILD_IMAGE_ID" "$DOCKERHUB_REPO:latest" -fi - -echo "Pushing $DOCKERHUB_REPO:$BUILD_IMAGE_ID" - -echo "$DOCKER_PASS" | docker login --username "$DOCKER_USERNAME" --password-stdin - -docker push -a "$DOCKERHUB_REPO" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 21fff68b02..0000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Release - -on: - workflow_dispatch: - inputs: - tag: - description: "Tag to include in artifact name" - required: true - push: - tags: - - "v[0-9]+.[0-9]+.[0-9]+" - -jobs: - release: - # needs: [lint_test, unit_test, e2e_test, simulator_test] - runs-on: ubuntu-20.04 - steps: - - name: Git checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - path: subnet-evm - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: "~1.21.12" - check-latest: true - - name: Set up arm64 cross compiler - run: | - sudo apt-get -y update - sudo apt-get -y install gcc-aarch64-linux-gnu - - name: Checkout osxcross - uses: actions/checkout@v4 - with: - repository: tpoechtrager/osxcross - path: osxcross - - name: Build osxcross - run: | - sudo apt-get -y install clang llvm-dev libxml2-dev uuid-dev libssl-dev bash patch make tar xz-utils bzip2 gzip sed cpio libbz2-dev - cd osxcross - wget https://github.com/phracker/MacOSX-SDKs/releases/download/11.3/MacOSX11.3.sdk.tar.xz -O tarballs/MacOSX11.3.sdk.tar.xz - echo cd4f08a75577145b8f05245a2975f7c81401d75e9535dcffbb879ee1deefcbf4 tarballs/MacOSX11.3.sdk.tar.xz | sha256sum -c - - UNATTENDED=1 ./build.sh - echo "$PWD/target/bin" >> "$GITHUB_PATH" - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v3 - with: - distribution: goreleaser - version: latest - args: release --clean - workdir: ./subnet-evm/ - env: - # https://docs.github.com/en/actions/security-guides/automatic-token-authentication#about-the-github_token-secret - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 87f0a8d60b..0000000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,220 +0,0 @@ -name: Tests - -on: - push: - branches: - - master - tags: - - "*" - pull_request: - -env: - min_go_version: "~1.21.12" - -jobs: - lint_test: - name: Lint - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v4 - - run: ./scripts/lint_allowed_geth_imports.sh - shell: bash - - uses: actions/setup-go@v5 - with: - go-version: ${{ env.min_go_version }} - check-latest: true - - name: golangci-lint - uses: golangci/golangci-lint-action@v3 - with: - version: v1.56 - working-directory: . - args: --timeout 10m - skip-pkg-cache: true - - name: Run shellcheck - shell: bash - run: scripts/shellcheck.sh - - name: Run actionlint - shell: bash - run: scripts/actionlint.sh - - unit_test: - name: Golang Unit Tests (${{ matrix.os }}) - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [macos-latest, ubuntu-20.04, ubuntu-latest, windows-latest] - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version: ${{ env.min_go_version }} - check-latest: true - - name: Set timeout on Windows # Windows UT run slower and need a longer timeout - shell: bash - if: matrix.os == 'windows-latest' - run: echo "TIMEOUT=1200s" >> "$GITHUB_ENV" - - run: go mod download - shell: bash - - run: ./scripts/build.sh - shell: bash - - run: ./scripts/build_test.sh - env: - TIMEOUT: ${{ env.TIMEOUT }} - shell: bash - - run: ./scripts/coverage.sh - shell: bash - - e2e_precompile: - name: e2e precompile tests - runs-on: ubuntu-latest - steps: - - name: Git checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.min_go_version }} - check-latest: true - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: "20.13" - - name: NPM Clean Install - run: npm ci - working-directory: ./contracts - - name: Hardhat Clean - run: npx hardhat clean - working-directory: ./contracts - - name: Hardhat Compile - run: npx hardhat compile - working-directory: ./contracts - - name: Install AvalancheGo Release - shell: bash - run: BASEDIR=/tmp/e2e-test AVALANCHEGO_BUILD_PATH=/tmp/e2e-test/avalanchego ./scripts/install_avalanchego_release.sh - - name: Build Subnet-EVM Plugin Binary - shell: bash - run: ./scripts/build.sh - - name: Run E2E Precompile Tests - shell: bash - run: AVALANCHEGO_BUILD_PATH=/tmp/e2e-test/avalanchego DATA_DIR=/tmp/e2e-test/precompile-data ./scripts/run_ginkgo_precompile.sh - - name: Upload Artifact - if: always() - uses: actions/upload-artifact@v4 - with: - name: subnet-evm-e2e-logs-precompile - path: /tmp/e2e-test/precompile-data - retention-days: 5 - e2e_warp: - name: e2e warp tests - runs-on: ubuntu-latest - steps: - - name: Git checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.min_go_version }} - check-latest: true - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: "20.13" - - name: NPM Clean Install - run: npm ci - working-directory: ./contracts - - name: Hardhat Clean - run: npx hardhat clean - working-directory: ./contracts - - name: Hardhat Compile - run: npx hardhat compile - working-directory: ./contracts - - name: Install AvalancheGo Release - shell: bash - run: BASEDIR=/tmp/e2e-test AVALANCHEGO_BUILD_PATH=/tmp/e2e-test/avalanchego ./scripts/install_avalanchego_release.sh - - name: Build Subnet-EVM Plugin Binary - shell: bash - run: ./scripts/build.sh - - name: Run Warp E2E Tests - uses: ava-labs/avalanchego/.github/actions/run-monitored-tmpnet-cmd@v1-actions - with: - run: AVALANCHEGO_BUILD_PATH=/tmp/e2e-test/avalanchego ./scripts/run_ginkgo_warp.sh - prometheus_id: ${{ secrets.PROMETHEUS_ID || '' }} - prometheus_password: ${{ secrets.PROMETHEUS_PASSWORD || '' }} - loki_id: ${{ secrets.LOKI_ID || '' }} - loki_password: ${{ secrets.LOKI_PASSWORD || '' }} - - name: Upload tmpnet network dir for warp testing - uses: ava-labs/avalanchego/.github/actions/upload-tmpnet-artifact@v1-actions - if: always() - with: - name: warp-tmpnet-data - e2e_load: - name: e2e load tests - runs-on: ubuntu-latest - steps: - - name: Git checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.min_go_version }} - check-latest: true - - name: Install AvalancheGo Release - shell: bash - run: BASEDIR=/tmp/e2e-test AVALANCHEGO_BUILD_PATH=/tmp/e2e-test/avalanchego ./scripts/install_avalanchego_release.sh - - name: Build Subnet-EVM Plugin Binary - shell: bash - run: ./scripts/build.sh - - name: Run E2E Load Tests - uses: ava-labs/avalanchego/.github/actions/run-monitored-tmpnet-cmd@v1-actions - with: - run: AVALANCHEGO_BUILD_PATH=/tmp/e2e-test/avalanchego ./scripts/run_ginkgo_load.sh - prometheus_id: ${{ secrets.PROMETHEUS_ID || '' }} - prometheus_password: ${{ secrets.PROMETHEUS_PASSWORD || '' }} - loki_id: ${{ secrets.LOKI_ID || '' }} - loki_password: ${{ secrets.LOKI_PASSWORD || '' }} - - name: Upload tmpnet network dir for load testing - uses: ava-labs/avalanchego/.github/actions/upload-tmpnet-artifact@v1-actions - if: always() - with: - name: load-tmpnet-data - mock_gen: - name: MockGen Check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v4 - with: - go-version: ${{ env.min_go_version }} - check-latest: true - - shell: bash - run: scripts/mock.gen.sh - - shell: bash - run: .github/workflows/check-clean-branch.sh - test_build_antithesis_images: - name: Build Antithesis images - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version: ${{ env.min_go_version }} - check-latest: true - - name: Install AvalancheGo Release - shell: bash - run: BASEDIR=/tmp/e2e-test AVALANCHEGO_BUILD_PATH=/tmp/e2e-test/avalanchego ./scripts/install_avalanchego_release.sh - - name: Build Subnet-EVM Plugin Binary - shell: bash - run: ./scripts/build.sh - - name: Check that the antithesis workload is sane - shell: bash - run: go run ./tests/antithesis --avalanchego-path=/tmp/e2e-test/avalanchego/avalanchego --duration=60s - - name: Check antithesis image build - shell: bash - run: bash -x scripts/tests.build_antithesis_images.sh diff --git a/.github/workflows/trigger-antithesis.yml b/.github/workflows/trigger-antithesis.yml deleted file mode 100644 index 1d3128f974..0000000000 --- a/.github/workflows/trigger-antithesis.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Trigger Antithesis - -on: - # TODO(marun) Add a schedule - workflow_dispatch: - inputs: - duration: - description: 'The duration (in hours) to run the test for' - default: '0.5' - required: true - type: string - recipients: - description: 'Comma-seperated email addresses to send the test report to' - required: true - type: string - image_tag: - description: 'The image tag to target' - default: latest - required: true - type: string - -jobs: - antithesis: - name: Run Antithesis - runs-on: ubuntu-latest - steps: - - uses: antithesishq/antithesis-trigger-action@v0.5 - with: - notebook_name: avalanche - tenant: avalanche - username: ${{ secrets.ANTITHESIS_USERNAME }} - password: ${{ secrets.ANTITHESIS_PASSWORD }} - github_token: ${{ secrets.ANTITHESIS_GH_PAT }} - config_image: antithesis-subnet-evm-config:${{ github.event.inputs.image_tag || 'latest' }} - images: antithesis-subnet-evm-workload:${{ github.event.inputs.image_tag || 'latest' }};antithesis-subnet-evm-node:${{ github.event.inputs.image_tag || 'latest' }} - email_recipients: ${{ github.event.inputs.recipients || secrets.ANTITHESIS_RECIPIENTS }} - # Duration is in hours - additional_parameters: |- - custom.duration=${{ github.event.inputs.duration || '11.25' }} - custom.workload=subnet-evm diff --git a/.gitignore b/.gitignore index 1ba452b444..a93619ce95 100644 --- a/.gitignore +++ b/.gitignore @@ -26,7 +26,6 @@ awscpu *logs/ .vscode* -workspace.code-workspace *.pb* @@ -46,15 +45,5 @@ workspace.code-workspace bin/ build/ -cmd/evm/evm -cmd/simulator/.simulator/* -cmd/simulator/simulator - -# goreleaser -dist/ - -# Outputs of `scripts/diff_against.sh` -diffs/ - -# clone used for antithesis image builds -avalanchego/ +# Used for e2e testing +avalanchego diff --git a/.golangci.yml b/.golangci.yml index c6c8d5748a..31c41897e1 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -8,6 +8,8 @@ run: skip-dirs-use-default: true # Include non-test files tagged as test-only. # Context: https://github.com/ava-labs/avalanchego/pull/3173 + build-tags: + - test linters: disable-all: true diff --git a/.goreleaser.yml b/.goreleaser.yml deleted file mode 100644 index bec5952578..0000000000 --- a/.goreleaser.yml +++ /dev/null @@ -1,37 +0,0 @@ -# ref. https://goreleaser.com/customization/build/ -builds: - - id: subnet-evm - main: ./plugin - binary: subnet-evm - flags: - - -v - ldflags: -X github.com/ava-labs/subnet-evm/plugin/evm.Version=v{{.Version}} - goos: - - linux - - darwin - goarch: - - amd64 - - arm64 - env: - - CGO_ENABLED=1 - - CGO_CFLAGS=-O -D__BLST_PORTABLE__ # Set the CGO flags to use the portable version of BLST - overrides: - - goos: linux - goarch: arm64 - env: - - CC=aarch64-linux-gnu-gcc - - goos: darwin - goarch: arm64 - env: - - CC=oa64-clang - - goos: darwin - goarch: amd64 - goamd64: v1 - env: - - CC=o64-clang -release: - # Repo in which the release will be created. - # Default is extracted from the origin remote URL or empty if its private hosted. - github: - owner: ava-labs - name: subnet-evm diff --git a/.markdownlint.json b/.markdownlint.json deleted file mode 100644 index 07074b5b42..0000000000 --- a/.markdownlint.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "MD041": false, - "MD013": false, - "MD033": false -} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 2b08a3d49c..1161501c0a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,36 +1,38 @@ -# syntax=docker/dockerfile:experimental - -# ============= Setting up base Stage ================ -# AVALANCHEGO_NODE_IMAGE needs to identify an existing node image and should include the tag -ARG AVALANCHEGO_NODE_IMAGE - # ============= Compilation Stage ================ FROM golang:1.21.12-bullseye AS builder -WORKDIR /build +ARG AVALANCHE_VERSION -# Copy avalanche dependencies first (intermediate docker image caching) -# Copy avalanchego directory if present (for manual CI case, which uses local dependency) -COPY go.mod go.sum avalanchego* ./ - -# Download avalanche dependencies using go mod -RUN go mod download && go mod tidy -compat=1.21 +RUN mkdir -p $GOPATH/src/github.com/ava-labs +WORKDIR $GOPATH/src/github.com/ava-labs -# Copy the code into the container -COPY . . +RUN git clone -b $AVALANCHE_VERSION --single-branch https://github.com/ava-labs/avalanchego.git -# Ensure pre-existing builds are not available for inclusion in the final image -RUN [ -d ./build ] && rm -rf ./build/* || true +# Copy coreth repo into desired location +COPY . coreth -# Pass in SUBNET_EVM_COMMIT as an arg to allow the build script to set this externally -ARG SUBNET_EVM_COMMIT -ARG CURRENT_BRANCH +# Set the workdir to AvalancheGo and update coreth dependency to local version +WORKDIR $GOPATH/src/github.com/ava-labs/avalanchego +# Run go mod download here to improve caching of AvalancheGo specific depednencies +RUN go mod download +# Replace the coreth dependency +RUN go mod edit -replace github.com/ava-labs/subnet-evm=../coreth +RUN go mod download && go mod tidy -compat=1.21 -RUN export SUBNET_EVM_COMMIT=$SUBNET_EVM_COMMIT && export CURRENT_BRANCH=$CURRENT_BRANCH && ./scripts/build.sh build/subnet-evm +# Build the AvalancheGo binary with local version of coreth. +RUN ./scripts/build_avalanche.sh +# Create the plugins directory in the standard location so the build directory will be recognized +# as valid. +RUN mkdir build/plugins # ============= Cleanup Stage ================ -FROM $AVALANCHEGO_NODE_IMAGE AS builtImage +FROM debian:11-slim AS execution + +# Maintain compatibility with previous images +RUN mkdir -p /avalanchego/build +WORKDIR /avalanchego/build + +# Copy the executables into the container +COPY --from=builder /go/src/github.com/ava-labs/avalanchego/build . -# Copy the evm binary into the correct location in the container -ARG VM_ID=srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy -COPY --from=builder /build/build/subnet-evm /avalanchego/build/plugins/$VM_ID +CMD [ "./avalanchego" ] diff --git a/README.md b/README.md index 4fb70c2095..fe847188bd 100644 --- a/README.md +++ b/README.md @@ -1,132 +1,76 @@ -# Subnet EVM +# Coreth and the C-Chain -[![Build + Test + Release](https://github.com/ava-labs/subnet-evm/actions/workflows/lint-tests-release.yml/badge.svg)](https://github.com/ava-labs/subnet-evm/actions/workflows/lint-tests-release.yml) -[![CodeQL](https://github.com/ava-labs/subnet-evm/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/ava-labs/subnet-evm/actions/workflows/codeql-analysis.yml) - -[Avalanche](https://docs.avax.network/overview/getting-started/avalanche-platform) is a network composed of multiple blockchains. +[Avalanche](https://docs.avax.network/intro) is a network composed of multiple blockchains. Each blockchain is an instance of a Virtual Machine (VM), much like an object in an object-oriented language is an instance of a class. That is, the VM defines the behavior of the blockchain. - -Subnet EVM is the [Virtual Machine (VM)](https://docs.avax.network/learn/avalanche/virtual-machines) that defines the Subnet Contract Chains. Subnet EVM is a simplified version of [Coreth VM (C-Chain)](https://github.com/ava-labs/coreth). - +Coreth (from core Ethereum) is the [Virtual Machine (VM)](https://docs.avax.network/learn/avalanche/virtual-machines) that defines the Contract Chain (C-Chain). This chain implements the Ethereum Virtual Machine and supports Solidity smart contracts as well as most other Ethereum client functionality. ## Building -The Subnet EVM runs in a separate process from the main AvalancheGo process and communicates with it over a local gRPC connection. - -### AvalancheGo Compatibility - -```text -[v0.1.0] AvalancheGo@v1.7.0-v1.7.4 (Protocol Version: 9) -[v0.1.1-v0.1.2] AvalancheGo@v1.7.5-v1.7.6 (Protocol Version: 10) -[v0.2.0] AvalancheGo@v1.7.7-v1.7.9 (Protocol Version: 11) -[v0.2.1] AvalancheGo@v1.7.10 (Protocol Version: 12) -[v0.2.2] AvalancheGo@v1.7.11-v1.7.12 (Protocol Version: 14) -[v0.2.3] AvalancheGo@v1.7.13-v1.7.16 (Protocol Version: 15) -[v0.2.4] AvalancheGo@v1.7.13-v1.7.16 (Protocol Version: 15) -[v0.2.5] AvalancheGo@v1.7.13-v1.7.16 (Protocol Version: 15) -[v0.2.6] AvalancheGo@v1.7.13-v1.7.16 (Protocol Version: 15) -[v0.2.7] AvalancheGo@v1.7.13-v1.7.16 (Protocol Version: 15) -[v0.2.8] AvalancheGo@v1.7.13-v1.7.18 (Protocol Version: 15) -[v0.2.9] AvalancheGo@v1.7.13-v1.7.18 (Protocol Version: 15) -[v0.3.0] AvalancheGo@v1.8.0-v1.8.6 (Protocol Version: 16) -[v0.4.0] AvalancheGo@v1.9.0 (Protocol Version: 17) -[v0.4.1] AvalancheGo@v1.9.1 (Protocol Version: 18) -[v0.4.2] AvalancheGo@v1.9.1 (Protocol Version: 18) -[v0.4.3] AvalancheGo@v1.9.2-v1.9.3 (Protocol Version: 19) -[v0.4.4] AvalancheGo@v1.9.2-v1.9.3 (Protocol Version: 19) -[v0.4.5] AvalancheGo@v1.9.4 (Protocol Version: 20) -[v0.4.6] AvalancheGo@v1.9.4 (Protocol Version: 20) -[v0.4.7] AvalancheGo@v1.9.5 (Protocol Version: 21) -[v0.4.8] AvalancheGo@v1.9.6-v1.9.8 (Protocol Version: 22) -[v0.4.9] AvalancheGo@v1.9.9 (Protocol Version: 23) -[v0.4.10] AvalancheGo@v1.9.9 (Protocol Version: 23) -[v0.4.11] AvalancheGo@v1.9.10-v1.9.16 (Protocol Version: 24) -[v0.4.12] AvalancheGo@v1.9.10-v1.9.16 (Protocol Version: 24) -[v0.5.0] AvalancheGo@v1.10.0 (Protocol Version: 25) -[v0.5.1] AvalancheGo@v1.10.1-v1.10.4 (Protocol Version: 26) -[v0.5.2] AvalancheGo@v1.10.1-v1.10.4 (Protocol Version: 26) -[v0.5.3] AvalancheGo@v1.10.5-v1.10.8 (Protocol Version: 27) -[v0.5.4] AvalancheGo@v1.10.9-v1.10.12 (Protocol Version: 28) -[v0.5.5] AvalancheGo@v1.10.9-v1.10.12 (Protocol Version: 28) -[v0.5.6] AvalancheGo@v1.10.9-v1.10.12 (Protocol Version: 28) -[v0.5.7] AvalancheGo@v1.10.13-v1.10.14 (Protocol Version: 29) -[v0.5.8] AvalancheGo@v1.10.13-v1.10.14 (Protocol Version: 29) -[v0.5.9] AvalancheGo@v1.10.15-v1.10.17 (Protocol Version: 30) -[v0.5.10] AvalancheGo@v1.10.15-v1.10.17 (Protocol Version: 30) -[v0.5.11] AvalancheGo@v1.10.18-v1.10.19 (Protocol Version: 31) -[v0.6.0] AvalancheGo@v1.11.0-v1.11.1 (Protocol Version: 33) -[v0.6.1] AvalancheGo@v1.11.0-v1.11.1 (Protocol Version: 33) -[v0.6.2] AvalancheGo@v1.11.2 (Protocol Version: 34) -[v0.6.3] AvalancheGo@v1.11.3-v1.11.9 (Protocol Version: 35) -[v0.6.4] AvalancheGo@v1.11.3-v1.11.9 (Protocol Version: 35) -[v0.6.5] AvalancheGo@v1.11.3-v1.11.9 (Protocol Version: 35) -[v0.6.6] AvalancheGo@v1.11.3-v1.11.9 (Protocol Version: 35) -[v0.6.7] AvalancheGo@v1.11.3-v1.11.9 (Protocol Version: 35) -[v0.6.8] AvalancheGo@v1.11.10 (Protocol Version: 36) +Coreth is a dependency of AvalancheGo which is used to implement the EVM based Virtual Machine for the Avalanche C-Chain. In order to run with a local version of Coreth, users must update their Coreth dependency within AvalancheGo to point to their local Coreth directory. If Coreth and AvalancheGo are at the standard location within your GOPATH, this will look like the following: + +```bash +cd $GOPATH/src/github.com/ava-labs/avalanchego +go mod edit -replace github.com/ava-labs/subnet-evm=../coreth ``` +Now that AvalancheGo depends on the local version of Coreth, we can build with the normal build script: + +```bash +./scripts/build.sh +./build/avalanchego +``` + +Note: the C-Chain originally ran in a separate process from the main AvalancheGo process and communicated with it over a local gRPC connection. When this was the case, AvalancheGo's build script would download Coreth, compile it, and place the binary into the `avalanchego/build/plugins` directory. + ## API -The Subnet EVM supports the following API namespaces: +The C-Chain supports the following API namespaces: - `eth` - `personal` - `txpool` - `debug` -Only the `eth` namespace is enabled by default. -Subnet EVM is a simplified version of [Coreth VM (C-Chain)](https://github.com/ava-labs/coreth). -Full documentation for the C-Chain's API can be found [here](https://docs.avax.network/apis/avalanchego/apis/c-chain). +Only the `eth` namespace is enabled by default. +To enable the other namespaces see the instructions for passing the C-Chain config to AvalancheGo [here.](https://docs.avax.network/nodes/configure/chain-config-flags#enabling-evm-apis) +Full documentation for the C-Chain's API can be found [here.](https://docs.avax.network/reference/avalanchego/c-chain/api) ## Compatibility -The Subnet EVM is compatible with almost all Ethereum tooling, including [Remix](https://docs.avax.network/build/dapp/smart-contracts/remix-deploy), [Metamask](https://docs.avax.network/build/dapp/chain-settings), and [Foundry](https://docs.avax.network/build/dapp/smart-contracts/toolchains/foundry). +The C-Chain is compatible with almost all Ethereum tooling, including [Core,](https://docs.avax.network/build/dapp/launch-dapp#through-core) [Metamask,](https://docs.avax.network/build/dapp/launch-dapp#through-metamask) [Remix](https://docs.avax.network/build/tutorials/smart-contracts/deploy-a-smart-contract-on-avalanche-using-remix-and-metamask) and [Truffle.](https://docs.avax.network/build/tutorials/smart-contracts/using-truffle-with-the-avalanche-c-chain) -## Differences Between Subnet EVM and Coreth +## Differences Between Avalanche C-Chain and Ethereum -- Added configurable fees and gas limits in genesis -- Merged Avalanche hardforks into the single "Subnet EVM" hardfork -- Removed Atomic Txs and Shared Memory -- Removed Multicoin Contract and State +### Atomic Transactions -## Block Format - -To support these changes, there have been a number of changes to the SubnetEVM block format compared to what exists on the C-Chain and Ethereum. Here we list the changes to the block format as compared to Ethereum. - -### Block Header +As a network composed of multiple blockchains, Avalanche uses *atomic transactions* to move assets between chains. Coreth modifies the Ethereum block format by adding an *ExtraData* field, which contains the atomic transactions. -- `BaseFee`: Added by EIP-1559 to represent the base fee of the block (present in Ethereum as of EIP-1559) -- `BlockGasCost`: surcharge for producing a block faster than the target rate +### Block Timing -## Create an EVM Subnet on a Local Network +Blocks are produced asynchronously in Snowman Consensus, so the timing assumptions that apply to Ethereum do not apply to Coreth. To support block production in an async environment, a block is permitted to have the same timestamp as its parent. Since there is no general assumption that a block will be produced every 10 seconds, smart contracts built on Avalanche should use the block timestamp instead of the block number for their timing assumptions. -### Clone Subnet-evm +A block with a timestamp more than 10 seconds in the future will not be considered valid. However, a block with a timestamp more than 10 seconds in the past will still be considered valid as long as its timestamp is greater than or equal to the timestamp of its parent block. -First install Go 1.21.12 or later. Follow the instructions [here](https://go.dev/doc/install). You can verify by running `go version`. +## Difficulty and Random OpCode -Set `$GOPATH` environment variable properly for Go to look for Go Workspaces. Please read [this](https://go.dev/doc/code) for details. You can verify by running `echo $GOPATH`. +Snowman consensus does not use difficulty in any way, so the difficulty of every block is required to be set to 1. This means that the DIFFICULTY opcode should not be used as a source of randomness. -As a few software will be installed into `$GOPATH/bin`, please make sure that `$GOPATH/bin` is in your `$PATH`, otherwise, you may get error running the commands below. +Additionally, with the change from the DIFFICULTY OpCode to the RANDOM OpCode (RANDOM replaces DIFFICULTY directly), there is no planned change to provide a stronger source of randomness. The RANDOM OpCode relies on the Eth2.0 Randomness Beacon, which has no direct parallel within the context of either Coreth or Snowman consensus. Therefore, instead of providing a weaker source of randomness that may be manipulated, the RANDOM OpCode will not be supported. Instead, it will continue the behavior of the DIFFICULTY OpCode of returning the block's difficulty, such that it will always return 1. -Download the `subnet-evm` repository into your `$GOPATH`: - -```sh -cd $GOPATH -mkdir -p src/github.com/ava-labs -cd src/github.com/ava-labs -git clone git@github.com:ava-labs/subnet-evm.git -cd subnet-evm -``` +## Block Format -This will clone and checkout to `master` branch. +To support these changes, there have been a number of changes to the C-Chain block format compared to what exists on Ethereum. -### Run Local Network +### Block Body -To run a local network, it is recommended to use the [avalanche-cli](https://github.com/ava-labs/avalanche-cli#avalanche-cli) to set up an instance of Subnet-EVM on a local Avalanche Network. +* `Version`: provides version of the `ExtData` in the block. Currently, this field is always 0. +* `ExtData`: extra data field within the block body to store atomic transaction bytes. -There are two options when using the Avalanche-CLI: +### Block Header -1. Use an official Subnet-EVM release: https://docs.avax.network/subnets/build-first-subnet -2. Build and deploy a locally built (and optionally modified) version of Subnet-EVM: https://docs.avax.network/subnets/create-custom-subnet +* `ExtDataHash`: the hash of the bytes in the `ExtDataHash` field +* `BaseFee`: Added by EIP-1559 to represent the base fee of the block (present in Ethereum as of EIP-1559) +* `ExtDataGasUsed`: amount of gas consumed by the atomic transactions in the block +* `BlockGasCost`: surcharge for producing a block faster than the target rate diff --git a/SECURITY.md b/SECURITY.md index f8d1d61f11..07a728ac92 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -17,3 +17,4 @@ Please refer to the [Bug Bounty Page](https://hackenproof.com/avalanche) for the ## Supported Versions Please use the [most recently released version](https://github.com/ava-labs/subnet-evm/releases/latest) to perform testing and to validate security issues. + diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index ee15adcb42..01dabab0b2 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -681,7 +681,7 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call interfaces.Cal if call.GasPrice != nil && (call.GasFeeCap != nil || call.GasTipCap != nil) { return nil, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified") } - if !b.blockchain.Config().IsSubnetEVM(header.Time) { + if !b.blockchain.Config().IsApricotPhase3(header.Time) { // If there's no basefee, then it must be a non-1559 execution if call.GasPrice == nil { call.GasPrice = new(big.Int) diff --git a/accounts/abi/bind/backends/simulated_test.go b/accounts/abi/bind/backends/simulated_test.go index dd99f55d7e..dd868b17c3 100644 --- a/accounts/abi/bind/backends/simulated_test.go +++ b/accounts/abi/bind/backends/simulated_test.go @@ -1305,7 +1305,7 @@ Example contract to test event emission: function Call() public { emit Called(); } } */ -// The fork tests are commented out because transactions are not indexed in subnet-evm until they are marked +// The fork tests are commented out because transactions are not indexed in coreth until they are marked // as accepted, which breaks the logic of these tests. // const callableAbi = "[{\"anonymous\":false,\"inputs\":[],\"name\":\"Called\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"Call\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" diff --git a/accounts/abi/bind/base.go b/accounts/abi/bind/base.go index 9aff177c1c..893fc05479 100644 --- a/accounts/abi/bind/base.go +++ b/accounts/abi/bind/base.go @@ -36,6 +36,7 @@ import ( "github.com/ava-labs/subnet-evm/accounts/abi" "github.com/ava-labs/subnet-evm/core/types" + "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/interfaces" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -47,6 +48,9 @@ const basefeeWiggleMultiplier = 2 var ( errNoEventSignature = errors.New("no event signature") errEventSignatureMismatch = errors.New("event signature mismatch") + + ErrNilAssetAmount = errors.New("cannot specify nil asset amount for native asset call") + errNativeAssetDeployContract = errors.New("cannot specify native asset params while deploying a contract") ) // SignerFn is a signer function callback when a contract requires a method to @@ -62,6 +66,12 @@ type CallOpts struct { Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) } +// NativeAssetCallOpts contains params for native asset call +type NativeAssetCallOpts struct { + AssetID common.Hash // Asset ID + AssetAmount *big.Int // Asset amount +} + // TransactOpts is the collection of authorization data required to create a // valid Ethereum transaction. type TransactOpts struct { @@ -78,6 +88,14 @@ type TransactOpts struct { Context context.Context // Network context to support cancellation and timeouts (nil = no timeout) NoSend bool // Do all transact steps but do not send the transaction + + // If set, the transaction is transformed to perform the requested call through the native asset + // precompile. This will update the to address of the transaction to that of the native asset precompile + // and pack the requested [to] address, [assetID], [assetAmount], and [input] data for the transaction + // into the call data of the transaction. When executed within the EVM, the precompile will parse the input + // data and attempt to atomically transfer [assetAmount] of [assetID] to the [to] address and invoke the + // contract at [to] if present, passing in the original [input] data. + NativeAssetCall *NativeAssetCallOpts } // FilterOpts is the collection of options to fine tune filtering for events @@ -269,6 +287,38 @@ func (c *BoundContract) Transfer(opts *TransactOpts) (*types.Transaction, error) return c.transact(opts, &c.address, nil) } +// wrapNativeAssetCall preprocesses the arguments to transform the requested call to go through the +// native asset call precompile if it is specified on [opts]. +func wrapNativeAssetCall(opts *TransactOpts, contract *common.Address, input []byte) (*common.Address, []byte, error) { + if opts.NativeAssetCall != nil { + // Prevent the user from sending a non-zero value through native asset call precompile as this will + // transfer the funds to the precompile address and essentially burn the funds. + if opts.Value != nil && opts.Value.Cmp(common.Big0) != 0 { + return nil, nil, fmt.Errorf("value must be 0 when performing native asset call, found %d", opts.Value) + } + if opts.NativeAssetCall.AssetAmount == nil { + return nil, nil, ErrNilAssetAmount + } + if opts.NativeAssetCall.AssetAmount.Cmp(common.Big0) < 0 { + return nil, nil, fmt.Errorf("asset value cannot be < 0 when performing native asset call, found %d", opts.NativeAssetCall.AssetAmount) + } + // Prevent potential panic if [contract] is nil in the case that transact is called through DeployContract. + if contract == nil { + return nil, nil, errNativeAssetDeployContract + } + // wrap input with native asset call params + input = vm.PackNativeAssetCallInput( + *contract, + opts.NativeAssetCall.AssetID, + opts.NativeAssetCall.AssetAmount, + input, + ) + // target addr is now precompile + contract = &vm.NativeAssetCallAddr + } + return contract, input, nil +} + func (c *BoundContract) createDynamicTx(opts *TransactOpts, contract *common.Address, input []byte, head *types.Header) (*types.Transaction, error) { // Normalize value value := opts.Value @@ -404,6 +454,11 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i rawTx *types.Transaction err error ) + // Preprocess native asset call arguments if present + contract, input, err = wrapNativeAssetCall(opts, contract, input) + if err != nil { + return nil, err + } if opts.GasPrice != nil { rawTx, err = c.createLegacyTx(opts, contract, input) } else if opts.GasFeeCap != nil && opts.GasTipCap != nil { diff --git a/accounts/abi/bind/base_test.go b/accounts/abi/bind/base_test.go index 0f420a32eb..513cabf98d 100644 --- a/accounts/abi/bind/base_test.go +++ b/accounts/abi/bind/base_test.go @@ -29,6 +29,7 @@ package bind_test import ( "context" "errors" + "fmt" "math/big" "reflect" "strings" @@ -37,6 +38,7 @@ import ( "github.com/ava-labs/subnet-evm/accounts/abi" "github.com/ava-labs/subnet-evm/accounts/abi/bind" "github.com/ava-labs/subnet-evm/core/types" + "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/interfaces" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -338,6 +340,80 @@ func TestUnpackIndexedBytesTyLogIntoMap(t *testing.T) { unpackAndCheck(t, bc, expectedReceivedMap, mockLog) } +func TestTransactNativeAssetCallNilAssetAmount(t *testing.T) { + assert := assert.New(t) + mt := &mockTransactor{} + bc := bind.NewBoundContract(common.Address{}, abi.ABI{}, nil, mt, nil) + opts := &bind.TransactOpts{ + Signer: mockSign, + } + // fails if asset amount is nil + opts.NativeAssetCall = &bind.NativeAssetCallOpts{ + AssetID: common.Hash{}, + AssetAmount: nil, + } + _, err := bc.Transact(opts, "") + assert.ErrorIs(err, bind.ErrNilAssetAmount) +} + +func TestTransactNativeAssetCallNonZeroValue(t *testing.T) { + assert := assert.New(t) + mt := &mockTransactor{} + bc := bind.NewBoundContract(common.Address{}, abi.ABI{}, nil, mt, nil) + opts := &bind.TransactOpts{ + Signer: mockSign, + } + opts.NativeAssetCall = &bind.NativeAssetCallOpts{ + AssetID: common.Hash{}, + AssetAmount: big.NewInt(11), + } + // fails if value > 0 + opts.Value = big.NewInt(11) + _, err := bc.Transact(opts, "") + assert.Equal(err.Error(), fmt.Sprintf("value must be 0 when performing native asset call, found %v", opts.Value)) + // fails if value < 0 + opts.Value = big.NewInt(-11) + _, err = bc.Transact(opts, "") + assert.Equal(err.Error(), fmt.Sprintf("value must be 0 when performing native asset call, found %v", opts.Value)) +} + +func TestTransactNativeAssetCall(t *testing.T) { + assert := assert.New(t) + json := `[{"type":"function","name":"method","inputs":[{"type":"uint256" },{"type":"string"}]}]` + parsed, err := abi.JSON(strings.NewReader(json)) + assert.Nil(err) + mt := &mockTransactor{} + contractAddr := common.Address{11} + bc := bind.NewBoundContract(contractAddr, parsed, nil, mt, nil) + opts := &bind.TransactOpts{ + Signer: mockSign, + } + // normal call tx + methodName := "method" + arg1 := big.NewInt(22) + arg2 := "33" + normalCallTx, err := bc.Transact(opts, methodName, arg1, arg2) + assert.Nil(err) + // native asset call tx + assetID := common.Hash{44} + assetAmount := big.NewInt(55) + opts.NativeAssetCall = &bind.NativeAssetCallOpts{ + AssetID: assetID, + AssetAmount: assetAmount, + } + nativeCallTx, err := bc.Transact(opts, methodName, arg1, arg2) + assert.Nil(err) + // verify transformations + assert.Equal(vm.NativeAssetCallAddr, *nativeCallTx.To()) + unpackedAddr, unpackedAssetID, unpackedAssetAmount, unpackedData, err := vm.UnpackNativeAssetCallInput(nativeCallTx.Data()) + assert.Nil(err) + assert.NotEmpty(unpackedData) + assert.Equal(unpackedData, normalCallTx.Data()) + assert.Equal(unpackedAddr, contractAddr) + assert.Equal(unpackedAssetID, assetID) + assert.Equal(unpackedAssetAmount, assetAmount) +} + func TestTransactGasFee(t *testing.T) { t.Parallel() assert := assert.New(t) diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index 7ad368e5ad..61365c1703 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -43,9 +43,6 @@ import ( "github.com/ethereum/go-ethereum/log" ) -// BindHook is a callback function that can be used to customize the binding. -type BindHook func(lang Lang, pkg string, types []string, contracts map[string]*TmplContract, structs map[string]*TmplStruct) (data interface{}, templateSource string, err error) - // Lang is a target programming language selector to generate bindings for. type Lang int @@ -53,7 +50,7 @@ const ( LangGo Lang = iota ) -func IsKeyWord(arg string) bool { +func isKeyWord(arg string) bool { switch arg { case "break": case "case": @@ -95,16 +92,12 @@ func IsKeyWord(arg string) bool { // enforces compile time type safety and naming convention as opposed to having to // manually maintain hard coded strings that break on runtime. func Bind(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string) (string, error) { - return BindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, nil) -} - -func BindHelper(types []string, abis []string, bytecodes []string, fsigs []map[string]string, pkg string, lang Lang, libs map[string]string, aliases map[string]string, bindHook BindHook) (string, error) { var ( // contracts is the map of each individual contract requested binding - contracts = make(map[string]*TmplContract) + contracts = make(map[string]*tmplContract) // structs is the map of all redeclared structs shared by passed contracts. - structs = make(map[string]*TmplStruct) + structs = make(map[string]*tmplStruct) // isLib is the map used to flag each encountered library as such isLib = make(map[string]struct{}) @@ -125,11 +118,11 @@ func BindHelper(types []string, abis []string, bytecodes []string, fsigs []map[s // Extract the call and transact methods; events, struct definitions; and sort them alphabetically var ( - calls = make(map[string]*TmplMethod) - transacts = make(map[string]*TmplMethod) + calls = make(map[string]*tmplMethod) + transacts = make(map[string]*tmplMethod) events = make(map[string]*tmplEvent) - fallback *TmplMethod - receive *TmplMethod + fallback *tmplMethod + receive *tmplMethod // identifiers are used to detect duplicated identifiers of functions // and events. For all calls, transacts and events, abigen will generate @@ -172,7 +165,7 @@ func BindHelper(types []string, abis []string, bytecodes []string, fsigs []map[s normalized.Inputs = make([]abi.Argument, len(original.Inputs)) copy(normalized.Inputs, original.Inputs) for j, input := range normalized.Inputs { - if input.Name == "" || IsKeyWord(input.Name) { + if input.Name == "" || isKeyWord(input.Name) { normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) } if hasStruct(input.Type) { @@ -191,9 +184,9 @@ func BindHelper(types []string, abis []string, bytecodes []string, fsigs []map[s } // Append the methods to the call or transact lists if original.IsConstant() { - calls[original.Name] = &TmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} + calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} } else { - transacts[original.Name] = &TmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} + transacts[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original.Outputs)} } } for _, original := range evmABI.Events { @@ -224,7 +217,7 @@ func BindHelper(types []string, abis []string, bytecodes []string, fsigs []map[s normalized.Inputs = make([]abi.Argument, len(original.Inputs)) copy(normalized.Inputs, original.Inputs) for j, input := range normalized.Inputs { - if input.Name == "" || IsKeyWord(input.Name) { + if input.Name == "" || isKeyWord(input.Name) { normalized.Inputs[j].Name = fmt.Sprintf("arg%d", j) } // Event is a bit special, we need to define event struct in binding, @@ -245,12 +238,12 @@ func BindHelper(types []string, abis []string, bytecodes []string, fsigs []map[s } // Add two special fallback functions if they exist if evmABI.HasFallback() { - fallback = &TmplMethod{Original: evmABI.Fallback} + fallback = &tmplMethod{Original: evmABI.Fallback} } if evmABI.HasReceive() { - receive = &TmplMethod{Original: evmABI.Receive} + receive = &tmplMethod{Original: evmABI.Receive} } - contracts[types[i]] = &TmplContract{ + contracts[types[i]] = &tmplContract{ Type: capitalise(types[i]), InputABI: strings.ReplaceAll(strippedABI, "\"", "\\\""), InputBin: strings.TrimPrefix(strings.TrimSpace(bytecodes[i]), "0x"), @@ -287,43 +280,23 @@ func BindHelper(types []string, abis []string, bytecodes []string, fsigs []map[s _, ok := isLib[types[i]] contracts[types[i]].Library = ok } - - var ( - data interface{} - templateSource string - ) - - // Generate the contract template data according to hook - if bindHook != nil { - var err error - data, templateSource, err = bindHook(lang, pkg, types, contracts, structs) - if err != nil { - return "", err - } - } else { // default to generate contract binding - templateSource = tmplSource[lang] - data = &tmplData{ - Package: pkg, - Contracts: contracts, - Libraries: libs, - Structs: structs, - } + // Generate the contract template data content and render it + data := &tmplData{ + Package: pkg, + Contracts: contracts, + Libraries: libs, + Structs: structs, } buffer := new(bytes.Buffer) funcs := map[string]interface{}{ "bindtype": bindType[lang], - "add": add, - "bindtypenew": bindTypeNew[lang], "bindtopictype": bindTopicType[lang], "namedtype": namedType[lang], "capitalise": capitalise, "decapitalise": decapitalise, - "mkList": mkList, } - - // render the template - tmpl := template.Must(template.New("").Funcs(funcs).Parse(templateSource)) + tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource[lang])) if err := tmpl.Execute(buffer, data); err != nil { return "", err } @@ -341,14 +314,10 @@ func BindHelper(types []string, abis []string, bytecodes []string, fsigs []map[s // bindType is a set of type binders that convert Solidity types to some supported // programming language types. -var bindType = map[Lang]func(kind abi.Type, structs map[string]*TmplStruct) string{ +var bindType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ LangGo: bindTypeGo, } -var bindTypeNew = map[Lang]func(kind abi.Type, structs map[string]*TmplStruct) string{ - LangGo: bindTypeNewGo, -} - // bindBasicTypeGo converts basic solidity types(except array, slice and tuple) to Go ones. func bindBasicTypeGo(kind abi.Type) string { switch kind.T { @@ -373,43 +342,10 @@ func bindBasicTypeGo(kind abi.Type) string { } } -// bindTypeNewGo converts new types to Go ones. -func bindTypeNewGo(kind abi.Type, structs map[string]*TmplStruct) string { - switch kind.T { - case abi.TupleTy: - return structs[kind.TupleRawName+kind.String()].Name + "{}" - case abi.ArrayTy: - return fmt.Sprintf("[%d]", kind.Size) + bindTypeGo(*kind.Elem, structs) + "{}" - case abi.SliceTy: - return "nil" - case abi.AddressTy: - return "common.Address{}" - case abi.IntTy, abi.UintTy: - parts := regexp.MustCompile(`(u)?int([0-9]*)`).FindStringSubmatch(kind.String()) - switch parts[2] { - case "8", "16", "32", "64": - return "0" - } - return "new(big.Int)" - case abi.FixedBytesTy: - return fmt.Sprintf("[%d]byte", kind.Size) + "{}" - case abi.BytesTy: - return "[]byte{}" - case abi.FunctionTy: - return "[24]byte{}" - case abi.BoolTy: - return "false" - case abi.StringTy: - return `""` - default: - return "nil" - } -} - // bindTypeGo converts solidity types to Go ones. Since there is no clear mapping // from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly // mapped will use an upscaled type (e.g. BigDecimal). -func bindTypeGo(kind abi.Type, structs map[string]*TmplStruct) string { +func bindTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { switch kind.T { case abi.TupleTy: return structs[kind.TupleRawName+kind.String()].Name @@ -424,13 +360,13 @@ func bindTypeGo(kind abi.Type, structs map[string]*TmplStruct) string { // bindTopicType is a set of type binders that convert Solidity types to some // supported programming language topic types. -var bindTopicType = map[Lang]func(kind abi.Type, structs map[string]*TmplStruct) string{ +var bindTopicType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ LangGo: bindTopicTypeGo, } // bindTopicTypeGo converts a Solidity topic type to a Go one. It is almost the same // functionality as for simple types, but dynamic types get converted to hashes. -func bindTopicTypeGo(kind abi.Type, structs map[string]*TmplStruct) string { +func bindTopicTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { bound := bindTypeGo(kind, structs) // todo(rjl493456442) according solidity documentation, indexed event @@ -447,14 +383,14 @@ func bindTopicTypeGo(kind abi.Type, structs map[string]*TmplStruct) string { // bindStructType is a set of type binders that convert Solidity tuple types to some supported // programming language struct definition. -var bindStructType = map[Lang]func(kind abi.Type, structs map[string]*TmplStruct) string{ +var bindStructType = map[Lang]func(kind abi.Type, structs map[string]*tmplStruct) string{ LangGo: bindStructTypeGo, } // bindStructTypeGo converts a Solidity tuple type to a Go one and records the mapping // in the given map. // Notably, this function will resolve and record nested struct recursively. -func bindStructTypeGo(kind abi.Type, structs map[string]*TmplStruct) string { +func bindStructTypeGo(kind abi.Type, structs map[string]*tmplStruct) string { switch kind.T { case abi.TupleTy: // We compose a raw struct name and a canonical parameter expression @@ -483,7 +419,7 @@ func bindStructTypeGo(kind abi.Type, structs map[string]*TmplStruct) string { } name = capitalise(name) - structs[id] = &TmplStruct{ + structs[id] = &tmplStruct{ Name: name, Fields: fields, } @@ -568,11 +504,3 @@ func hasStruct(t abi.Type) bool { return false } } - -func mkList(args ...interface{}) []interface{} { - return args -} - -func add(a, b int) int { - return a + b -} diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index 4ccb21ba91..35a13fc87d 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -549,7 +549,7 @@ var bindTests = []struct { struct A { bytes32 B; } - + function F() public view returns (A[] memory a, uint256[] memory c, bool[] memory d) { A[] memory a = new A[](2); a[0].B = bytes32(uint256(1234) << 96); @@ -1790,7 +1790,7 @@ var bindTests = []struct { if !gotEvent { t.Fatal("Expect to receive event emitted by receive") } - + // Test fallback function gotEvent = false opts.Value = nil diff --git a/accounts/abi/bind/precompilebind/precompile_bind.go b/accounts/abi/bind/precompilebind/precompile_bind.go deleted file mode 100644 index 28fde58688..0000000000 --- a/accounts/abi/bind/precompilebind/precompile_bind.go +++ /dev/null @@ -1,244 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. -// -// This file is a derived work, based on the go-ethereum library whose original -// notices appear below. -// -// It is distributed under a license compatible with the licensing terms of the -// original code from which it is derived. -// -// Much love to the original authors for their work. -// ********** -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Package bind generates Ethereum contract Go bindings. -// -// Detailed usage document and tutorial available on the go-ethereum Wiki page: -// https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts -package precompilebind - -import ( - "errors" - "fmt" - "strings" - - "github.com/ava-labs/subnet-evm/accounts/abi" - "github.com/ava-labs/subnet-evm/accounts/abi/bind" - "github.com/ava-labs/subnet-evm/precompile/allowlist" -) - -var errNoAnonymousEvent = errors.New("event type must not be anonymous") - -const ( - ContractFileName = "contract.go" - ConfigFileName = "config.go" - ModuleFileName = "module.go" - EventFileName = "event.go" - ContractTestFileName = "contract_test.go" - ConfigTestFileName = "config_test.go" -) - -type PrecompileBindFile struct { - // FileName is the name of the file to be generated. - FileName string - // Content is the content of the file to be generated. - Content string - // IsTest indicates whether the file is a test file. - IsTest bool -} - -func NewPrecompileBindFile(fileName string, content string, isTest bool) PrecompileBindFile { - return PrecompileBindFile{ - FileName: fileName, - Content: content, - IsTest: isTest, - } -} - -// PrecompileBind generates a Go binding for a precompiled contract. It returns a slice of -// PrecompileBindFile structs containing the file name and its contents. -func PrecompileBind(types []string, abiData string, bytecodes []string, fsigs []map[string]string, pkg string, lang bind.Lang, libs map[string]string, aliases map[string]string, abifilename string, generateTests bool) ([]PrecompileBindFile, error) { - // create hooks - configHook := createPrecompileHook(abifilename, tmplSourcePrecompileConfigGo) - contractHook := createPrecompileHook(abifilename, tmplSourcePrecompileContractGo) - moduleHook := createPrecompileHook(abifilename, tmplSourcePrecompileModuleGo) - eventHook := createPrecompileHook(abifilename, tmplSourcePrecompileEventGo) - configTestHook := createPrecompileHook(abifilename, tmplSourcePrecompileConfigTestGo) - contractTestHook := createPrecompileHook(abifilename, tmplSourcePrecompileContractTestGo) - - if err := verifyABI(abiData); err != nil { - return nil, err - } - - abis := []string{abiData} - - configBind, err := bind.BindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, configHook) - if err != nil { - return nil, fmt.Errorf("failed to generate config binding: %w", err) - } - contractBind, err := bind.BindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, contractHook) - if err != nil { - return nil, fmt.Errorf("failed to generate contract binding: %w", err) - } - moduleBind, err := bind.BindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, moduleHook) - if err != nil { - return nil, fmt.Errorf("failed to generate module binding: %w", err) - } - eventBind, err := bind.BindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, eventHook) - if err != nil { - return nil, fmt.Errorf("failed to generate event binding: %w", err) - } - - var result []PrecompileBindFile - result = append(result, NewPrecompileBindFile(ConfigFileName, configBind, false)) - result = append(result, NewPrecompileBindFile(ContractFileName, contractBind, false)) - result = append(result, NewPrecompileBindFile(ModuleFileName, moduleBind, false)) - result = append(result, NewPrecompileBindFile(EventFileName, eventBind, false)) - - if generateTests { - configTestBind, err := bind.BindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, configTestHook) - if err != nil { - return nil, fmt.Errorf("failed to generate config test binding: %w", err) - } - result = append(result, NewPrecompileBindFile(ConfigTestFileName, configTestBind, true)) - - contractTestBind, err := bind.BindHelper(types, abis, bytecodes, fsigs, pkg, lang, libs, aliases, contractTestHook) - if err != nil { - return nil, fmt.Errorf("failed to generate contract test binding: %w", err) - } - result = append(result, NewPrecompileBindFile(ContractTestFileName, contractTestBind, true)) - } - - return result, nil -} - -// createPrecompileHook creates a bind hook for precompiled contracts. -func createPrecompileHook(abifilename string, template string) bind.BindHook { - return func(lang bind.Lang, pkg string, types []string, contracts map[string]*bind.TmplContract, structs map[string]*bind.TmplStruct) (interface{}, string, error) { - // verify first - if lang != bind.LangGo { - return nil, "", errors.New("only GoLang binding for precompiled contracts is supported yet") - } - - if len(types) != 1 { - return nil, "", errors.New("cannot generate more than 1 contract") - } - funcs := make(map[string]*bind.TmplMethod) - - contract := contracts[types[0]] - - for k, v := range contract.Transacts { - funcs[k] = v - } - - for k, v := range contract.Calls { - funcs[k] = v - } - isAllowList := allowListEnabled(funcs) - if isAllowList { - // these functions are not needed for binded contract. - // AllowList struct can provide the same functionality, - // so we don't need to generate them. - for key := range allowlist.AllowListABI.Methods { - delete(funcs, key) - } - for events := range allowlist.AllowListABI.Events { - delete(contract.Events, events) - } - } - - precompileContract := &tmplPrecompileContract{ - TmplContract: contract, - AllowList: isAllowList, - Funcs: funcs, - ABIFilename: abifilename, - } - - data := &tmplPrecompileData{ - Contract: precompileContract, - Structs: structs, - Package: pkg, - } - return data, template, nil - } -} - -func allowListEnabled(funcs map[string]*bind.TmplMethod) bool { - for key := range allowlist.AllowListABI.Methods { - if _, ok := funcs[key]; !ok { - return false - } - } - return true -} - -func verifyABI(abiData string) error { - // check abi first - evmABI, err := abi.JSON(strings.NewReader(abiData)) - if err != nil { - return err - } - if len(evmABI.Methods) == 0 { - return errors.New("no ABI methods found") - } - - for _, event := range evmABI.Events { - if event.Anonymous { - return fmt.Errorf("%w: %s", errNoAnonymousEvent, event.Name) - } - eventNames := make(map[string]bool) - for _, arg := range event.Inputs { - if bind.IsKeyWord(arg.Name) { - return fmt.Errorf("event input name %s is a keyword", arg.Name) - } - name := abi.ToCamelCase(arg.Name) - if eventNames[name] { - return fmt.Errorf("normalized event input name is duplicated: %s", name) - } - eventNames[name] = true - } - } - - for _, method := range evmABI.Methods { - names := make(map[string]bool) - for _, input := range method.Inputs { - if bind.IsKeyWord(input.Name) { - return fmt.Errorf("input name %s is a keyword", input.Name) - } - name := abi.ToCamelCase(input.Name) - if names[name] { - return fmt.Errorf("normalized input name is duplicated: %s", name) - } - names[name] = true - } - names = make(map[string]bool) - for _, output := range method.Outputs { - if output.Name == "" { - return fmt.Errorf("ABI outputs for %s require a name to generate the precompile binding, re-generate the ABI from a Solidity source file with all named outputs", method.Name) - } - if bind.IsKeyWord(output.Name) { - return fmt.Errorf("output name %s is a keyword", output.Name) - } - name := abi.ToCamelCase(output.Name) - if names[name] { - return fmt.Errorf("normalized output name is duplicated: %s", name) - } - names[name] = true - } - } - - return nil -} diff --git a/accounts/abi/bind/precompilebind/precompile_bind_test.go b/accounts/abi/bind/precompilebind/precompile_bind_test.go deleted file mode 100644 index a1633388dc..0000000000 --- a/accounts/abi/bind/precompilebind/precompile_bind_test.go +++ /dev/null @@ -1,709 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. -// -// This file is a derived work, based on the go-ethereum library whose original -// notices appear below. -// -// It is distributed under a license compatible with the licensing terms of the -// original code from which it is derived. -// -// Much love to the original authors for their work. -// ********** -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package precompilebind - -import ( - "fmt" - "os" - "os/exec" - "path/filepath" - "runtime" - "strings" - "testing" - - "github.com/ava-labs/subnet-evm/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" -) - -var bindTests = []struct { - name string - contract string - abi string - imports string - tester string - errMsg string - expectAllowlist bool -}{ - { - "AnonOutputChecker", - "", - ` - [ - {"type":"function","name":"anonOutput","constant":true,"inputs":[],"outputs":[{"name":"","type":"string"}]} - ] - `, - "", - "", - "ABI outputs for anonOutput require a name to generate the precompile binding, re-generate the ABI from a Solidity source file with all named outputs", - false, - }, - { - "AnonOutputsChecker", - "", - ` - [ - {"type":"function","name":"anonOutputs","constant":true,"inputs":[],"outputs":[{"name":"","type":"string"},{"name":"","type":"string"}]} - ] - `, - "", - "", - "ABI outputs for anonOutputs require a name to generate the precompile binding, re-generate the ABI from a Solidity source file with all named outputs", - false, - }, - { - "MixedOutputsChecker", - "", - ` - [ - {"type":"function","name":"mixedOutputs","constant":true,"inputs":[],"outputs":[{"name":"","type":"string"},{"name":"str","type":"string"}]} - ] - `, - "", - "", - "ABI outputs for mixedOutputs require a name to generate the precompile binding, re-generate the ABI from a Solidity source file with all named outputs", - false, - }, - // Test that module is generated correctly - { - `EmptyContract`, - `contract EmptyContract {}`, - "[]", - "", - "", - "no ABI methods found", - false, - }, - // Test that named and anonymous inputs are handled correctly - { - `InputChecker`, ``, - ` - [ - {"type":"function","name":"noInput","constant":true,"inputs":[],"outputs":[]}, - {"type":"function","name":"namedInput","constant":true,"inputs":[{"name":"str","type":"string"}],"outputs":[]}, - {"type":"function","name":"namedInputs","constant":true,"inputs":[{"name":"str1","type":"string"},{"name":"str2","type":"string"}],"outputs":[]} - ] - `, - ` - "github.com/stretchr/testify/require" - `, - ` - testInput := "test" - packedInput, err := PackNamedInput(testInput) - require.NoError(t, err) - // remove the first 4 bytes of the packed input - packedInput = packedInput[4:] - unpackedInput, err := UnpackNamedInputInput(packedInput) - require.NoError(t, err) - require.Equal(t, testInput, unpackedInput) - - testInputStruct := NamedInputsInput{ - Str1: "test1", - Str2: "test2", - } - packedInputStruct, err := PackNamedInputs(testInputStruct) - require.NoError(t, err) - // remove the first 4 bytes of the packed input - packedInputStruct = packedInputStruct[4:] - unpackedInputStruct, err := UnpackNamedInputsInput(packedInputStruct) - require.NoError(t, err) - require.Equal(t, unpackedInputStruct, testInputStruct) - `, - "", - false, - }, - // Test that named and anonymous outputs are handled correctly - { - `OutputChecker`, ``, - ` - [ - {"type":"function","name":"noOutput","constant":true,"inputs":[],"outputs":[]}, - {"type":"function","name":"namedOutput","constant":true,"inputs":[],"outputs":[{"name":"str","type":"string"}]}, - {"type":"function","name":"namedOutputs","constant":true,"inputs":[],"outputs":[{"name":"str1","type":"string"},{"name":"str2","type":"string"}]} - ] - `, - ` - "github.com/stretchr/testify/require" - `, - ` - testOutput := "test" - packedOutput, err := PackNamedOutputOutput(testOutput) - require.NoError(t, err) - unpackedOutput, err := UnpackNamedOutputOutput(packedOutput) - require.NoError(t, err) - require.Equal(t, testOutput, unpackedOutput) - - testNamedOutputs := NamedOutputsOutput{ - Str1: "test1", - Str2: "test2", - } - packedNamedOutputs, err := PackNamedOutputsOutput(testNamedOutputs) - require.NoError(t, err) - unpackedNamedOutputs, err := UnpackNamedOutputsOutput(packedNamedOutputs) - require.NoError(t, err) - require.Equal(t, testNamedOutputs, unpackedNamedOutputs) - `, - "", - false, - }, - { - `Tupler`, - ` - interface Tupler { - function tuple() constant returns (string a, int b, bytes32 c); - } - `, - `[{"constant":true,"inputs":[],"name":"tuple","outputs":[{"name":"a","type":"string"},{"name":"b","type":"int256"},{"name":"c","type":"bytes32"}],"type":"function"}]`, - ` - "math/big" - "github.com/stretchr/testify/require" - `, - ` - testOutput := TupleOutput{"Hi", big.NewInt(123), [32]byte{1, 2, 3}} - packedOutput, err := PackTupleOutput(testOutput) - require.NoError(t, err) - unpackedOutput, err := UnpackTupleOutput(packedOutput) - require.NoError(t, err) - require.Equal(t, testOutput, unpackedOutput) - `, - "", - false, - }, - { - `Slicer`, - ` - interface Slicer { - function echoAddresses(address[] input) constant returns (address[] output); - function echoInts(int[] input) constant returns (int[] output); - function echoFancyInts(uint8[23] input) constant returns (uint8[23] output); - function echoBools(bool[] input) constant returns (bool[] output); - } - `, - `[{"constant":true,"inputs":[{"name":"input","type":"address[]"}],"name":"echoAddresses","outputs":[{"name":"output","type":"address[]"}],"type":"function"},{"constant":true,"inputs":[{"name":"input","type":"uint8[23]"}],"name":"echoFancyInts","outputs":[{"name":"output","type":"uint8[23]"}],"type":"function"},{"constant":true,"inputs":[{"name":"input","type":"int256[]"}],"name":"echoInts","outputs":[{"name":"output","type":"int256[]"}],"type":"function"},{"constant":true,"inputs":[{"name":"input","type":"bool[]"}],"name":"echoBools","outputs":[{"name":"output","type":"bool[]"}],"type":"function"}]`, - ` - "math/big" - "github.com/stretchr/testify/require" - "github.com/ethereum/go-ethereum/common" - `, - ` - testArgs := []common.Address{common.HexToAddress("1"), common.HexToAddress("2"), common.HexToAddress("3")} - packedOutput, err := PackEchoAddressesOutput(testArgs) - require.NoError(t, err) - unpackedOutput, err := UnpackEchoAddressesOutput(packedOutput) - require.NoError(t, err) - require.Equal(t, testArgs, unpackedOutput) - packedInput, err := PackEchoAddresses(testArgs) - // remove the first 4 bytes of the packed input - packedInput = packedInput[4:] - require.NoError(t, err) - unpackedInput, err := UnpackEchoAddressesInput(packedInput) - require.NoError(t, err) - require.Equal(t, testArgs, unpackedInput) - - testArgs2 := []*big.Int{common.Big1, common.Big2, common.Big3} - packedOutput2, err := PackEchoIntsOutput(testArgs2) - require.NoError(t, err) - unpackedOutput2, err := UnpackEchoIntsOutput(packedOutput2) - require.NoError(t, err) - require.Equal(t, testArgs2, unpackedOutput2) - packedInput2, err := PackEchoInts(testArgs2) - // remove the first 4 bytes of the packed input - packedInput2 = packedInput2[4:] - require.NoError(t, err) - unpackedInput2, err := UnpackEchoIntsInput(packedInput2) - require.NoError(t, err) - require.Equal(t, testArgs2, unpackedInput2) - - testArgs3 := [23]uint8{1, 2, 3} - packedOutput3, err := PackEchoFancyIntsOutput(testArgs3) - require.NoError(t, err) - unpackedOutput3, err := UnpackEchoFancyIntsOutput(packedOutput3) - require.NoError(t, err) - require.Equal(t, testArgs3, unpackedOutput3) - packedInput3, err := PackEchoFancyInts(testArgs3) - // remove the first 4 bytes of the packed input - packedInput3 = packedInput3[4:] - require.NoError(t, err) - unpackedInput3, err := UnpackEchoFancyIntsInput(packedInput3) - require.NoError(t, err) - require.Equal(t, testArgs3, unpackedInput3) - - testArgs4 := []bool{true, false, true} - packedOutput4, err := PackEchoBoolsOutput(testArgs4) - require.NoError(t, err) - unpackedOutput4, err := UnpackEchoBoolsOutput(packedOutput4) - require.NoError(t, err) - require.Equal(t, testArgs4, unpackedOutput4) - packedInput4, err := PackEchoBools(testArgs4) - // remove the first 4 bytes of the packed input - packedInput4 = packedInput4[4:] - require.NoError(t, err) - unpackedInput4, err := UnpackEchoBoolsInput(packedInput4) - require.NoError(t, err) - require.Equal(t, testArgs4, unpackedInput4) - `, - "", - false, - }, - { - `Fallback`, - ` - interface Fallback { - fallback() external payable; - - receive() external payable; - function testFunction(uint t) external; - } - `, - `[{"stateMutability":"payable","type":"fallback"},{"inputs":[{"internalType":"uint256","name":"t","type":"uint256"}],"name":"testFunction","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]`, - ` - "github.com/stretchr/testify/require" - "math/big" - `, - ` - packedInput, err := PackTestFunction(big.NewInt(5)) - require.NoError(t, err) - // remove the first 4 bytes of the packed input - packedInput = packedInput[4:] - unpackedInput, err := UnpackTestFunctionInput(packedInput) - require.NoError(t, err) - require.Equal(t, big.NewInt(5), unpackedInput) - `, - "", - false, - }, - { - `Structs`, - ` - interface Struct { - struct A { - bytes32 B; - } - function F() external view returns (A[] memory a, uint256[] memory c, bool[] memory d); - function G() external view returns (A[] memory a); - } - `, - `[{"inputs":[],"name":"F","outputs":[{"components":[{"internalType":"bytes32","name":"B","type":"bytes32"}],"internalType":"struct Structs.A[]","name":"a","type":"tuple[]"},{"internalType":"uint256[]","name":"c","type":"uint256[]"},{"internalType":"bool[]","name":"d","type":"bool[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"G","outputs":[{"components":[{"internalType":"bytes32","name":"B","type":"bytes32"}],"internalType":"struct Structs.A[]","name":"a","type":"tuple[]"}],"stateMutability":"view","type":"function"}]`, - ` - "github.com/stretchr/testify/require" - "math/big" - `, - ` - testOutput := FOutput{ - A: []StructsA{ - { - B: [32]byte{1}, - }, - }, - C: []*big.Int{big.NewInt(2)}, - D: []bool{true,false}, - } - packedOutput, err := PackFOutput(testOutput) - require.NoError(t, err) - unpackedInput, err := UnpackFOutput(packedOutput) - require.NoError(t, err) - require.Equal(t, testOutput, unpackedInput) - `, - "", - false, - }, - { - `Underscorer`, - ` - interface Underscorer { - function UnderscoredOutput() external returns (int _int, string _string); - } - `, - `[{"inputs":[],"name":"UnderscoredOutput","outputs":[{"internalType":"int256","name":"_int","type":"int256"},{"internalType":"string","name":"_string","type":"string"}],"stateMutability":"nonpayable","type":"function"}]`, - ` - "github.com/stretchr/testify/require" - "math/big" - `, - ` - testOutput := UnderscoredOutputOutput{ - Int: big.NewInt(5), - String: "hello", - } - packedOutput, err := PackUnderscoredOutputOutput(testOutput) - require.NoError(t, err) - unpackedInput, err := UnpackUnderscoredOutputOutput(packedOutput) - require.NoError(t, err) - require.Equal(t, testOutput, unpackedInput) - `, - "", - false, - }, - { - `OutputCollision`, - ` - interface Collision { - function LowerLowerCollision() external returns (int _res, int res, int res_); - `, - `[{"inputs":[],"name":"LowerLowerCollision","outputs":[{"internalType":"int256","name":"_res","type":"int256"},{"internalType":"int256","name":"res","type":"int256"},{"internalType":"int256","name":"res_","type":"int256"}],"stateMutability":"nonpayable","type":"function"}]`, - "", - "", - "normalized output name is duplicated", - false, - }, - - { - `InputCollision`, - ` - interface Collision { - function LowerUpperCollision(int _res, int Res) external; - } - `, - `[{"inputs":[{"internalType":"int256","name":"_res","type":"int256"},{"internalType":"int256","name":"Res","type":"int256"}],"name":"LowerUpperCollision","outputs":[],"stateMutability":"nonpayable","type":"function"}]`, "", - "", - "normalized input name is duplicated", - false, - }, - { - `DeeplyNestedArray`, - ` - interface DeeplyNestedArray { - function storeDeepUintArray(uint64[3][4][5] arr) external public; - function retrieveDeepArray() public external view returns (uint64[3][4][5] arr); - } - `, - `[{"inputs":[],"name":"retrieveDeepArray","outputs":[{"internalType":"uint64[3][4][5]","name":"arr","type":"uint64[3][4][5]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64[3][4][5]","name":"arr","type":"uint64[3][4][5]"}],"name":"storeDeepUintArray","outputs":[],"stateMutability":"nonpayable","type":"function"}]`, ` - "github.com/stretchr/testify/require" - `, - ` - testArr := [5][4][3]uint64{ - { - {1, 2, 3}, - {4, 5, 6}, - {7, 8, 9}, - {10, 11, 12}, - }, - { - {13, 14, 15}, - {16, 17, 18}, - {19, 20, 21}, - {22, 23, 24}, - }, - } - packedInput, err := PackStoreDeepUintArray(testArr) - require.NoError(t, err) - // remove the first 4 bytes of the packed input - packedInput = packedInput[4:] - unpackedInput, err := UnpackStoreDeepUintArrayInput(packedInput) - require.NoError(t, err) - require.Equal(t, testArr, unpackedInput) - - packedOutput, err := PackRetrieveDeepArrayOutput(testArr) - require.NoError(t, err) - unpackedOutput, err := UnpackRetrieveDeepArrayOutput(packedOutput) - require.NoError(t, err) - require.Equal(t, testArr, unpackedOutput) - `, - "", - false, - }, - { - "RangeKeyword", - ` - interface keywordcontract { - function functionWithKeywordParameter(uint8 func, uint8 range) external pure; - } - `, - `[{"inputs":[{"internalType":"uint8","name":"func","type":"uint8"},{"internalType":"uint8","name":"range","type":"uint8"}],"name":"functionWithKeywordParameter","outputs":[],"stateMutability":"pure","type":"function"}]`, - "", - "", - "input name func is a keyword", - false, - }, - { - `HelloWorld`, - `interface IHelloWorld is IAllowList { - // sayHello returns the stored greeting string - function sayHello() external view returns (string calldata result); - - // setGreeting stores the greeting string - function setGreeting(string calldata response) external; - } - `, - `[{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"readAllowList","outputs":[{"internalType":"uint256","name":"role","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"sayHello","outputs":[{"internalType":"string","name":"result","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"response","type":"string"}],"name":"setGreeting","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setNone","outputs":[],"stateMutability":"nonpayable","type":"function"}]`, - `"github.com/stretchr/testify/require" - "math/big" - "github.com/ethereum/go-ethereum/common" - "github.com/ava-labs/subnet-evm/core/state" - "github.com/ava-labs/subnet-evm/precompile/allowlist" - `, - ` - testGreeting := "test" - packedGreeting, err := PackSetGreeting(testGreeting) - require.NoError(t, err) - // remove the first 4 bytes of the packed greeting - packedGreeting = packedGreeting[4:] - unpackedGreeting, err := UnpackSetGreetingInput(packedGreeting) - require.NoError(t, err) - require.Equal(t, testGreeting, unpackedGreeting) - - // test that the allow list is generated correctly - stateDB := state.NewTestStateDB(t) - address := common.BigToAddress(big.NewInt(1)) - SetHelloWorldAllowListStatus(stateDB, address, allowlist.EnabledRole) - role := GetHelloWorldAllowListStatus(stateDB, address) - require.Equal(t, role, allowlist.EnabledRole) - `, - "", - true, - }, - { - `HelloWorldNoAL`, - `interface IHelloWorld{ - // sayHello returns the stored greeting string - function sayHello() external view returns (string calldata result); - - // setGreeting stores the greeting string - function setGreeting(string calldata response) external; - } - `, - // This ABI does not contain readAllowlist and setEnabled. - `[{"inputs":[],"name":"sayHello","outputs":[{"internalType":"string","name":"result","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"response","type":"string"}],"name":"setGreeting","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setNone","outputs":[],"stateMutability":"nonpayable","type":"function"}]`, - `"github.com/stretchr/testify/require"`, - ` - testGreeting := "test" - packedGreeting, err := PackSetGreeting(testGreeting) - require.NoError(t, err) - // remove the first 4 bytes of the packed greeting - packedGreeting = packedGreeting[4:] - unpackedGreeting, err := UnpackSetGreetingInput(packedGreeting) - require.NoError(t, err) - require.Equal(t, testGreeting, unpackedGreeting) - `, - "", - false, - }, - { - `IEventer`, - ` - interface IEventer { - event test(address indexed addressTest, uint indexed intTest, bytes bytesTest); - event empty(); - event indexed(address addr, int8 indexed num); - event mixed(address indexed addr, int8 num); - event dynamic(string indexed idxStr, bytes indexed idxDat, string str, bytes dat); - event unnamed(uint8 indexed, uint8 indexed); - function eventTest() external view returns (string memory result); - } - `, - `[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"addressTest","type":"address"},{"indexed":true,"internalType":"uint8","name":"intTest","type":"uint8"},{"indexed":false,"internalType":"bytes","name":"bytesTest","type":"bytes"}],"name":"test","type":"event"},{"inputs":[],"name":"eventTest","outputs":[{"internalType":"string","name":"result","type":"string"}],"stateMutability":"view","type":"function"},{"type":"event","name":"empty","inputs":[]},{"type":"event","name":"indexed","inputs":[{"name":"addr","type":"address","indexed":true},{"name":"num","type":"int8","indexed":true}]},{"type":"event","name":"mixed","inputs":[{"name":"addr","type":"address","indexed":true},{"name":"num","type":"int8"}]},{"type":"event","name":"dynamic","inputs":[{"name":"idxStr","type":"string","indexed":true},{"name":"idxDat","type":"bytes","indexed":true},{"name":"str","type":"string"},{"name":"dat","type":"bytes"}]},{"type":"event","name":"unnamed","inputs":[{"name":"","type":"uint8","indexed":true},{"name":"","type":"uint8","indexed":true}]}]`, - `"github.com/stretchr/testify/require" - "github.com/ethereum/go-ethereum/common" - "github.com/ava-labs/subnet-evm/precompile/contract" - `, - ` - testAddr := common.Address{1} - testInt := int8(5) - testUint := uint8(5) - testBytes := []byte{1, 2, 3} - - testEventData := TestEventData{ - BytesTest: testBytes, - } - topics, data, err := PackTestEvent(testAddr, testUint, testEventData) - require.NoError(t, err) - eventID := IEventerABI.Events["test"].ID - require.Equal(t, eventID, topics[0]) - unpacked, err := UnpackTestEventData(data) - require.NoError(t, err) - require.Equal(t, testBytes, unpacked.BytesTest) - gasCost := GetTestEventGasCost(testEventData) - require.Equal(t, contract.LogGas + 3 * contract.LogTopicGas + contract.LogDataGas, gasCost) - - topics, data, err = PackEmptyEvent() - require.NoError(t, err) - eventID = IEventerABI.Events["empty"].ID - require.Len(t, topics, 1) - require.Equal(t, eventID, topics[0]) - require.Equal(t, 0, len(data)) - require.Equal(t, contract.LogGas, GetEmptyEventGasCost()) - - topics, data, err = PackIndexedEvent(testAddr, testInt) - require.NoError(t, err) - eventID = IEventerABI.Events["indexed"].ID - require.Len(t, topics, 3) - require.Equal(t, eventID, topics[0]) - require.Equal(t, common.BytesToHash(testAddr[:]), topics[1]) - require.Equal(t, 0, len(data)) - require.Equal(t, contract.LogGas + 3 * contract.LogTopicGas, GetIndexedEventGasCost()) - - testMixedData := MixedEventData{ - Num: testInt, - } - topics, data, err = PackMixedEvent(testAddr, testMixedData) - require.NoError(t, err) - eventID = IEventerABI.Events["mixed"].ID - require.Len(t, topics, 2) - require.Equal(t, eventID, topics[0]) - require.Equal(t, common.BytesToHash(testAddr[:]), topics[1]) - unpackedMixedData, err := UnpackMixedEventData(data) - require.NoError(t, err) - require.Equal(t, testMixedData, unpackedMixedData) - require.Equal(t, contract.LogGas + 2 * contract.LogTopicGas + contract.LogDataGas, GetMixedEventGasCost(testMixedData)) - - testDynamicData := DynamicEventData{ - Str: "test", - Dat: testBytes, - } - topics, data, err = PackDynamicEvent("test", testBytes, testDynamicData) - require.NoError(t, err) - eventID = IEventerABI.Events["dynamic"].ID - require.Len(t, topics, 3) - require.Equal(t, eventID, topics[0]) - unpackedDynamicData, err := UnpackDynamicEventData(data) - require.NoError(t, err) - require.Equal(t, testDynamicData, unpackedDynamicData) - require.Equal(t, contract.LogGas + 3 * contract.LogTopicGas + 2 * contract.LogDataGas, GetDynamicEventGasCost(testDynamicData)) - - topics, data, err = PackUnnamedEvent(testUint, testUint) - require.NoError(t, err) - eventID = IEventerABI.Events["unnamed"].ID - require.Len(t, topics, 3) - require.Equal(t, eventID, topics[0]) - require.Equal(t, 0, len(data)) - require.Equal(t, contract.LogGas + 3 * contract.LogTopicGas, GetUnnamedEventGasCost()) - `, - "", - false, - }, - { - `IEventerAnonymous`, - ` - interface IEventer { - event Anonymous(address indexed, uint indexed, bytes) anonymous; - function eventTest() external view returns (string memory result); - } - `, - `[{"anonymous":true,"inputs":[{"indexed":true,"internalType":"address","name":"","type":"address"},{"indexed":true,"internalType":"uint256","name":"","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"","type":"bytes"}],"name":"Anonymous","type":"event"},{"inputs":[],"name":"eventTest","outputs":[{"internalType":"string","name":"result","type":"string"}],"stateMutability":"view","type":"function"}]`, - ``, - ``, - errNoAnonymousEvent.Error(), - false, - }, -} - -// Tests that packages generated by the binder can be successfully compiled and -// the requested tester run against it. -func TestPrecompileBind(t *testing.T) { - // Skip the test if no Go command can be found - gocmd := runtime.GOROOT() + "/bin/go" - if !common.FileExist(gocmd) { - t.Skip("go sdk not found for testing") - } - // Create a temporary workspace for the test suite - ws := t.TempDir() - - pkg := filepath.Join(ws, "precompilebindtest") - if err := os.MkdirAll(pkg, 0o700); err != nil { - t.Fatalf("failed to create package: %v", err) - } - // Generate the test suite for all the contracts - for i, tt := range bindTests { - t.Run(tt.name, func(t *testing.T) { - types := []string{tt.name} - - // Generate the binding and create a Go source file in the workspace - bindedFiles, err := PrecompileBind(types, tt.abi, []string{""}, nil, tt.name, bind.LangGo, nil, nil, "contract.abi", true) - if tt.errMsg != "" { - require.ErrorContains(t, err, tt.errMsg) - return - } - if err != nil { - t.Fatalf("test %d: failed to generate binding: %v", i, err) - } - - precompilePath := filepath.Join(pkg, tt.name) - if err := os.MkdirAll(precompilePath, 0o700); err != nil { - t.Fatalf("failed to create package: %v", err) - } - for _, file := range bindedFiles { - switch file.FileName { - case ContractFileName: - // check if the allowlist functions are generated - if tt.expectAllowlist { - require.Contains(t, file.Content, "allowlist.CreateAllowListFunctions(", "generated contract does not contain AllowListFunctions") - } else { - require.NotContains(t, file.Content, "allowlist.CreateAllowListFunctions(", "generated contract contains AllowListFunctions") - } - case ModuleFileName: - // change address to a suitable one for testing - file.Content = strings.Replace(file.Content, `common.HexToAddress("{ASUITABLEHEXADDRESS}")`, `common.HexToAddress("0x03000000000000000000000000000000000000ff")`, 1) - } - if err = os.WriteFile(filepath.Join(precompilePath, file.FileName), []byte(file.Content), 0o600); err != nil { - t.Fatalf("test %d: failed to write binding: %v", i, err) - } - } - if err = os.WriteFile(filepath.Join(precompilePath, "contract.abi"), []byte(tt.abi), 0o600); err != nil { - t.Fatalf("test %d: failed to write binding: %v", i, err) - } - - // Generate the test file with the injected test code - code := fmt.Sprintf(` - package %s - - import ( - "testing" - %s - ) - - func Test%s(t *testing.T) { - %s - } - `, tt.name, tt.imports, tt.name, tt.tester) - if err := os.WriteFile(filepath.Join(precompilePath, strings.ToLower(tt.name)+"_test.go"), []byte(code), 0o600); err != nil { - t.Fatalf("test %d: failed to write tests: %v", i, err) - } - }) - } - - moder := exec.Command(gocmd, "mod", "init", "precompilebindtest") - moder.Dir = pkg - if out, err := moder.CombinedOutput(); err != nil { - t.Fatalf("failed to convert binding test to modules: %v\n%s", err, out) - } - pwd, _ := os.Getwd() - replacer := exec.Command(gocmd, "mod", "edit", "-x", "-require", "github.com/ava-labs/subnet-evm@v0.0.0", "-replace", "github.com/ava-labs/subnet-evm="+filepath.Join(pwd, "..", "..", "..", "..")) // Repo root - replacer.Dir = pkg - if out, err := replacer.CombinedOutput(); err != nil { - t.Fatalf("failed to replace binding test dependency to current source tree: %v\n%s", err, out) - } - tidier := exec.Command(gocmd, "mod", "tidy", "-compat=1.21") - tidier.Dir = pkg - if out, err := tidier.CombinedOutput(); err != nil { - t.Fatalf("failed to tidy Go module file: %v\n%s", err, out) - } - // Test the entire package and report any failures - cmd := exec.Command(gocmd, "test", "./...", "-v", "-count", "1") - cmd.Dir = pkg - if out, err := cmd.CombinedOutput(); err != nil { - t.Fatalf("failed to run binding test: %v\n%s", err, out) - } -} diff --git a/accounts/abi/bind/precompilebind/precompile_config_template.go b/accounts/abi/bind/precompilebind/precompile_config_template.go deleted file mode 100644 index 69c4fcbcc9..0000000000 --- a/accounts/abi/bind/precompilebind/precompile_config_template.go +++ /dev/null @@ -1,93 +0,0 @@ -// (c) 2019-2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. -package precompilebind - -// tmplSourcePrecompileConfigGo is the Go precompiled config source template. -const tmplSourcePrecompileConfigGo = ` -// Code generated -// This file is a generated precompile contract config with stubbed abstract functions. -// The file is generated by a template. Please inspect every code and comment in this file before use. - -package {{.Package}} - -import ( - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - {{- if .Contract.AllowList}} - "github.com/ava-labs/subnet-evm/precompile/allowlist" - - "github.com/ethereum/go-ethereum/common" - {{- end}} - -) - -var _ precompileconfig.Config = &Config{} - -// Config implements the precompileconfig.Config interface and -// adds specific configuration for {{.Contract.Type}}. -type Config struct { - {{- if .Contract.AllowList}} - allowlist.AllowListConfig - {{- end}} - precompileconfig.Upgrade - // CUSTOM CODE STARTS HERE - // Add your own custom fields for Config here -} - -// NewConfig returns a config for a network upgrade at [blockTimestamp] that enables -// {{.Contract.Type}} {{- if .Contract.AllowList}} with the given [admins], [enableds] and [managers] members of the allowlist {{end}}. -func NewConfig(blockTimestamp *uint64{{if .Contract.AllowList}}, admins []common.Address, enableds []common.Address, managers []common.Address{{end}}) *Config { - return &Config{ - {{- if .Contract.AllowList}} - AllowListConfig: allowlist.AllowListConfig{ - AdminAddresses: admins, - EnabledAddresses: enableds, - ManagerAddresses: managers, - }, - {{- end}} - Upgrade: precompileconfig.Upgrade{BlockTimestamp: blockTimestamp}, - } -} - -// NewDisableConfig returns config for a network upgrade at [blockTimestamp] -// that disables {{.Contract.Type}}. -func NewDisableConfig(blockTimestamp *uint64) *Config { - return &Config{ - Upgrade: precompileconfig.Upgrade{ - BlockTimestamp: blockTimestamp, - Disable: true, - }, - } -} - -// Key returns the key for the {{.Contract.Type}} precompileconfig. -// This should be the same key as used in the precompile module. -func (*Config) Key() string { return ConfigKey } - -// Verify tries to verify Config and returns an error accordingly. -func (c *Config) Verify(chainConfig precompileconfig.ChainConfig) error { - {{- if .Contract.AllowList}} - // Verify AllowList first - if err := c.AllowListConfig.Verify(chainConfig, c.Upgrade); err != nil { - return err - } - {{- end}} - // CUSTOM CODE STARTS HERE - // Add your own custom verify code for Config here - // and return an error accordingly - return nil -} - -// Equal returns true if [s] is a [*Config] and it has been configured identical to [c]. -func (c *Config) Equal(s precompileconfig.Config) bool { - // typecast before comparison - other, ok := (s).(*Config) - if !ok { - return false - } - // CUSTOM CODE STARTS HERE - // modify this boolean accordingly with your custom Config, to check if [other] and the current [c] are equal - // if Config contains only Upgrade {{- if .Contract.AllowList}} and AllowListConfig {{end}} you can skip modifying it. - equals := c.Upgrade.Equal(&other.Upgrade) {{- if .Contract.AllowList}} && c.AllowListConfig.Equal(&other.AllowListConfig) {{end}} - return equals -} -` diff --git a/accounts/abi/bind/precompilebind/precompile_config_test_template.go b/accounts/abi/bind/precompilebind/precompile_config_test_template.go deleted file mode 100644 index 3c03732fd4..0000000000 --- a/accounts/abi/bind/precompilebind/precompile_config_test_template.go +++ /dev/null @@ -1,107 +0,0 @@ -// (c) 2019-2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. -package precompilebind - -// tmplSourcePrecompileConfigGo is the Go precompiled config source template. -const tmplSourcePrecompileConfigTestGo = ` -// Code generated -// This file is a generated precompile config test with the skeleton of test functions. -// The file is generated by a template. Please inspect every code and comment in this file before use. - -package {{.Package}} - -import ( - "testing" - - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - "github.com/ava-labs/subnet-evm/precompile/testutils" - "github.com/ava-labs/subnet-evm/utils" - {{- if .Contract.AllowList}} - "github.com/ava-labs/subnet-evm/precompile/allowlist" - - "github.com/ethereum/go-ethereum/common" - {{- end}} - "go.uber.org/mock/gomock" -) - -// TestVerify tests the verification of Config. -func TestVerify(t *testing.T) { - {{- if .Contract.AllowList}} - admins := []common.Address{allowlist.TestAdminAddr} - enableds := []common.Address{allowlist.TestEnabledAddr} - managers := []common.Address{allowlist.TestManagerAddr} - {{- end}} - tests := map[string]testutils.ConfigVerifyTest{ - "valid config": { - Config: NewConfig(utils.NewUint64(3){{- if .Contract.AllowList}}, admins, enableds, managers{{- end}}), - ChainConfig: func() precompileconfig.ChainConfig { - config := precompileconfig.NewMockChainConfig(gomock.NewController(t)) - config.EXPECT().IsDurango(gomock.Any()).Return(true).AnyTimes() - return config - }(), - ExpectedError: "", - }, - // CUSTOM CODE STARTS HERE - // Add your own Verify tests here, e.g.: - // "your custom test name": { - // Config: NewConfig(utils.NewUint64(3), {{- if .Contract.AllowList}} admins, enableds, managers{{- end}}), - // ExpectedError: ErrYourCustomError.Error(), - // }, - } - {{- if .Contract.AllowList}} - // Verify the precompile with the allowlist. - // This adds allowlist verify tests to your custom tests - // and runs them all together. - // Even if you don't add any custom tests, keep this. This will still - // run the default allowlist verify tests. - allowlist.VerifyPrecompileWithAllowListTests(t, Module, tests) - {{- else}} - // Run verify tests. - testutils.RunVerifyTests(t, tests) - {{- end}} -} - -// TestEqual tests the equality of Config with other precompile configs. -func TestEqual(t *testing.T) { - {{- if .Contract.AllowList}} - admins := []common.Address{allowlist.TestAdminAddr} - enableds := []common.Address{allowlist.TestEnabledAddr} - managers := []common.Address{allowlist.TestManagerAddr} - {{- end}} - tests := map[string]testutils.ConfigEqualTest{ - "non-nil config and nil other": { - Config: NewConfig(utils.NewUint64(3){{- if .Contract.AllowList}}, admins, enableds,managers{{- end}}), - Other: nil, - Expected: false, - }, - "different type": { - Config: NewConfig(utils.NewUint64(3){{- if .Contract.AllowList}}, admins, enableds, managers{{- end}}), - Other: precompileconfig.NewMockConfig(gomock.NewController(t)), - Expected: false, - }, - "different timestamp": { - Config: NewConfig(utils.NewUint64(3){{- if .Contract.AllowList}}, admins, enableds, managers{{- end}}), - Other: NewConfig(utils.NewUint64(4){{- if .Contract.AllowList}}, admins, enableds, managers{{- end}}), - Expected: false, - }, - "same config": { - Config: NewConfig(utils.NewUint64(3){{- if .Contract.AllowList}}, admins, enableds, managers{{- end}}), - Other: NewConfig(utils.NewUint64(3){{- if .Contract.AllowList}}, admins, enableds, managers{{- end}}), - Expected: true, - }, - // CUSTOM CODE STARTS HERE - // Add your own Equal tests here - } - {{- if .Contract.AllowList}} - // Run allow list equal tests. - // This adds allowlist equal tests to your custom tests - // and runs them all together. - // Even if you don't add any custom tests, keep this. This will still - // run the default allowlist equal tests. - allowlist.EqualPrecompileWithAllowListTests(t, Module, tests) - {{- else}} - // Run equal tests. - testutils.RunEqualTests(t, tests) - {{- end}} - } -` diff --git a/accounts/abi/bind/precompilebind/precompile_contract_template.go b/accounts/abi/bind/precompilebind/precompile_contract_template.go deleted file mode 100644 index 8b637b0ec4..0000000000 --- a/accounts/abi/bind/precompilebind/precompile_contract_template.go +++ /dev/null @@ -1,363 +0,0 @@ -// (c) 2019-2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. -package precompilebind - -import "github.com/ava-labs/subnet-evm/accounts/abi/bind" - -// tmplPrecompileData is the data structure required to fill the binding template. -type tmplPrecompileData struct { - Package string - Contract *tmplPrecompileContract // The contract to generate into this file - Structs map[string]*bind.TmplStruct // Contract struct type definitions -} - -// tmplPrecompileContract contains the data needed to generate an individual contract binding. -type tmplPrecompileContract struct { - *bind.TmplContract - AllowList bool // Indicator whether the contract uses AllowList precompile - Funcs map[string]*bind.TmplMethod // Contract functions that include both Calls + Transacts in tmplContract - ABIFilename string // Path to the ABI file -} - -// tmplSourcePrecompileContractGo is the Go precompiled contract source template. -const tmplSourcePrecompileContractGo = ` -// Code generated -// This file is a generated precompile contract config with stubbed abstract functions. -// The file is generated by a template. Please inspect every code and comment in this file before use. - -package {{.Package}} - -import ( - "errors" - "fmt" - "math/big" - - "github.com/ava-labs/subnet-evm/accounts/abi" - {{- if .Contract.AllowList}} - "github.com/ava-labs/subnet-evm/precompile/allowlist" - {{- end}} - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ava-labs/subnet-evm/vmerrs" - - _ "embed" - - "github.com/ethereum/go-ethereum/common" -) -{{$contract := .Contract}} -const ( - // Gas costs for each function. These are set to 1 by default. - // You should set a gas cost for each function in your contract. - // Generally, you should not set gas costs very low as this may cause your network to be vulnerable to DoS attacks. - // There are some predefined gas costs in contract/utils.go that you can use. - {{- if .Contract.AllowList}} - // This contract also uses AllowList precompile. - // You should also increase gas costs of functions that read from AllowList storage. - {{- end}} - {{- range .Contract.Funcs}} - {{.Normalized.Name}}GasCost uint64 = 1 /* SET A GAS COST HERE */ {{if not .Original.IsConstant | and $contract.AllowList}} + allowlist.ReadAllowListGasCost {{end}} - {{- end}} - {{- if .Contract.Fallback}} - {{.Contract.Type}}FallbackGasCost uint64 = 1 // SET A GAS COST LESS THAN 2300 HERE - {{- end}} -) - -// CUSTOM CODE STARTS HERE -// Reference imports to suppress errors from unused imports. This code and any unnecessary imports can be removed. -var ( - _ = abi.JSON - _ = errors.New - _ = big.NewInt - _ = vmerrs.ErrOutOfGas - _ = common.Big0 -) - -// Singleton StatefulPrecompiledContract and signatures. -var ( - {{- range .Contract.Funcs}} - - {{- if not .Original.IsConstant | and $contract.AllowList}} - - ErrCannot{{.Normalized.Name}} = errors.New("non-enabled cannot call {{.Original.Name}}") - {{- end}} - {{- end}} - - {{- if .Contract.Fallback | and $contract.AllowList}} - Err{{.Contract.Type}}CannotFallback = errors.New("non-enabled cannot call fallback function") - {{- end}} - - // {{.Contract.Type}}RawABI contains the raw ABI of {{.Contract.Type}} contract. - {{- if .Contract.ABIFilename | eq ""}} - {{.Contract.Type}}RawABI = "{{.Contract.InputABI}}" - {{- else}} - //go:embed {{.Contract.ABIFilename}} - {{.Contract.Type}}RawABI string - {{- end}} - - {{.Contract.Type}}ABI = contract.ParseABI({{.Contract.Type}}RawABI) - - {{.Contract.Type}}Precompile = create{{.Contract.Type}}Precompile() -) - -{{$structs := .Structs}} -{{range $structs}} - // {{.Name}} is an auto generated low-level Go binding around an user-defined struct. - type {{.Name}} struct { - {{range $field := .Fields}} - {{$field.Name}} {{$field.Type}}{{end}} - } -{{- end}} - -{{- range .Contract.Funcs}} -{{ if len .Normalized.Inputs | lt 1}} -type {{capitalise .Normalized.Name}}Input struct{ -{{range .Normalized.Inputs}} {{capitalise .Name}} {{bindtype .Type $structs}}; {{end}} -} -{{- end}} -{{ if len .Normalized.Outputs | lt 1}} -type {{capitalise .Normalized.Name}}Output struct{ -{{range .Normalized.Outputs}} {{capitalise .Name}} {{bindtype .Type $structs}}; {{end}} -} -{{- end}} -{{- end}} - -{{if .Contract.AllowList}} -// Get{{.Contract.Type}}AllowListStatus returns the role of [address] for the {{.Contract.Type}} list. -func Get{{.Contract.Type}}AllowListStatus(stateDB contract.StateDB, address common.Address) allowlist.Role { - return allowlist.GetAllowListStatus(stateDB, ContractAddress, address) -} - -// Set{{.Contract.Type}}AllowListStatus sets the permissions of [address] to [role] for the -// {{.Contract.Type}} list. Assumes [role] has already been verified as valid. -// This stores the [role] in the contract storage with address [ContractAddress] -// and [address] hash. It means that any reusage of the [address] key for different value -// conflicts with the same slot [role] is stored. -// Precompile implementations must use a different key than [address] for their storage. -func Set{{.Contract.Type}}AllowListStatus(stateDB contract.StateDB, address common.Address, role allowlist.Role) { - allowlist.SetAllowListRole(stateDB, ContractAddress, address, role) -} -{{end}} - -{{range .Contract.Funcs}} -{{if len .Normalized.Inputs | lt 1}} -// Unpack{{capitalise .Normalized.Name}}Input attempts to unpack [input] as {{capitalise .Normalized.Name}}Input -// assumes that [input] does not include selector (omits first 4 func signature bytes) -func Unpack{{capitalise .Normalized.Name}}Input(input []byte) ({{capitalise .Normalized.Name}}Input, error) { - inputStruct := {{capitalise .Normalized.Name}}Input{} - err := {{$contract.Type}}ABI.UnpackInputIntoInterface(&inputStruct, "{{.Original.Name}}", input, false) - - return inputStruct, err -} - -// Pack{{.Normalized.Name}} packs [inputStruct] of type {{capitalise .Normalized.Name}}Input into the appropriate arguments for {{.Original.Name}}. -func Pack{{.Normalized.Name}}(inputStruct {{capitalise .Normalized.Name}}Input) ([]byte, error) { - return {{$contract.Type}}ABI.Pack("{{.Original.Name}}", {{range .Normalized.Inputs}} inputStruct.{{capitalise .Name}}, {{end}}) -} -{{else if len .Normalized.Inputs | eq 1 }} -{{$method := .}} -{{$input := index $method.Normalized.Inputs 0}} -{{$bindedType := bindtype $input.Type $structs}} -// Unpack{{capitalise .Normalized.Name}}Input attempts to unpack [input] into the {{$bindedType}} type argument -// assumes that [input] does not include selector (omits first 4 func signature bytes) -func Unpack{{capitalise .Normalized.Name}}Input(input []byte)({{$bindedType}}, error) { -res, err := {{$contract.Type}}ABI.UnpackInput("{{$method.Original.Name}}", input, false) -if err != nil { - return {{bindtypenew $input.Type $structs}}, err -} -unpacked := *abi.ConvertType(res[0], new({{$bindedType}})).(*{{$bindedType}}) -return unpacked, nil -} - -// Pack{{.Normalized.Name}} packs [{{decapitalise $input.Name}}] of type {{$bindedType}} into the appropriate arguments for {{$method.Original.Name}}. -// the packed bytes include selector (first 4 func signature bytes). -// This function is mostly used for tests. -func Pack{{$method.Normalized.Name}}( {{decapitalise $input.Name}} {{$bindedType}},) ([]byte, error) { - return {{$contract.Type}}ABI.Pack("{{$method.Original.Name}}", {{decapitalise $input.Name}},) -} -{{else}} -// Pack{{.Normalized.Name}} packs the include selector (first 4 func signature bytes). -// This function is mostly used for tests. -func Pack{{.Normalized.Name}}() ([]byte, error) { - return {{$contract.Type}}ABI.Pack("{{.Original.Name}}") -} -{{end}} - -{{if len .Normalized.Outputs | lt 1}} -// Pack{{capitalise .Normalized.Name}}Output attempts to pack given [outputStruct] of type {{capitalise .Normalized.Name}}Output -// to conform the ABI outputs. -func Pack{{capitalise .Normalized.Name}}Output (outputStruct {{capitalise .Normalized.Name}}Output) ([]byte, error) { - return {{$contract.Type}}ABI.PackOutput("{{.Original.Name}}", - {{- range .Normalized.Outputs}} - outputStruct.{{capitalise .Name}}, - {{- end}} - ) -} - -// Unpack{{capitalise .Normalized.Name}}Output attempts to unpack [output] as {{capitalise .Normalized.Name}}Output -// assumes that [output] does not include selector (omits first 4 func signature bytes) -func Unpack{{capitalise .Normalized.Name}}Output(output []byte) ({{capitalise .Normalized.Name}}Output, error) { - outputStruct := {{capitalise .Normalized.Name}}Output{} - err := {{$contract.Type}}ABI.UnpackIntoInterface(&outputStruct, "{{.Original.Name}}", output) - - return outputStruct, err -} - -{{else if len .Normalized.Outputs | eq 1 }} -{{$method := .}} -{{$output := index $method.Normalized.Outputs 0}} -{{$bindedType := bindtype $output.Type $structs}} -// Pack{{capitalise .Normalized.Name}}Output attempts to pack given {{decapitalise $output.Name}} of type {{$bindedType}} -// to conform the ABI outputs. -func Pack{{$method.Normalized.Name}}Output ({{decapitalise $output.Name}} {{$bindedType}}) ([]byte, error) { - return {{$contract.Type}}ABI.PackOutput("{{$method.Original.Name}}", {{decapitalise $output.Name}}) -} - -// Unpack{{capitalise .Normalized.Name}}Output attempts to unpack given [output] into the {{$bindedType}} type output -// assumes that [output] does not include selector (omits first 4 func signature bytes) -func Unpack{{capitalise .Normalized.Name}}Output(output []byte)({{$bindedType}}, error) { - res, err := {{$contract.Type}}ABI.Unpack("{{$method.Original.Name}}", output) - if err != nil { - return {{bindtypenew $output.Type $structs}}, err - } - unpacked := *abi.ConvertType(res[0], new({{$bindedType}})).(*{{$bindedType}}) - return unpacked, nil -} -{{end}} - -func {{decapitalise .Normalized.Name}}(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = contract.DeductGas(suppliedGas, {{.Normalized.Name}}GasCost); err != nil { - return nil, 0, err - } - - {{- if not .Original.IsConstant}} - if readOnly { - return nil, remainingGas, vmerrs.ErrWriteProtection - } - {{- end}} - - {{- if len .Normalized.Inputs | eq 0}} - // no input provided for this function - {{else}} - // attempts to unpack [input] into the arguments to the {{.Normalized.Name}}Input. - // Assumes that [input] does not include selector - // You can use unpacked [inputStruct] variable in your code - inputStruct, err := Unpack{{capitalise .Normalized.Name}}Input(input) - if err != nil{ - return nil, remainingGas, err - } - {{- end}} - - {{if not .Original.IsConstant | and $contract.AllowList}} - // Allow list is enabled and {{.Normalized.Name}} is a state-changer function. - // This part of the code restricts the function to be called only by enabled/admin addresses in the allow list. - // You can modify/delete this code if you don't want this function to be restricted by the allow list. - stateDB := accessibleState.GetStateDB() - // Verify that the caller is in the allow list and therefore has the right to call this function. - callerStatus := allowlist.GetAllowListStatus(stateDB, ContractAddress, caller) - if !callerStatus.IsEnabled() { - return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannot{{.Normalized.Name}}, caller) - } - // allow list code ends here. - {{end}} - // CUSTOM CODE STARTS HERE - {{- if len .Normalized.Inputs | ne 0}} - _ = inputStruct // CUSTOM CODE OPERATES ON INPUT - {{- end}} - - {{- if len .Normalized.Outputs | eq 0}} - // this function does not return an output, leave this one as is - packedOutput := []byte{} - {{- else}} - {{- if len .Normalized.Outputs | lt 1}} - var output {{capitalise .Normalized.Name}}Output // CUSTOM CODE FOR AN OUTPUT - {{- else }} - {{$output := index .Normalized.Outputs 0}} - var output {{bindtype $output.Type $structs}} // CUSTOM CODE FOR AN OUTPUT - {{- end}} - packedOutput, err := Pack{{.Normalized.Name}}Output(output) - if err != nil { - return nil, remainingGas, err - } - {{- end}} - - // Return the packed output and the remaining gas - return packedOutput, remainingGas, nil -} -{{end}} - -{{- if .Contract.Fallback}} -{{- with .Contract.Fallback}} -// {{decapitalise $contract.Type}}Fallback executed if a function identifier does not match any of the available functions in a smart contract. -// This function cannot take an input or return an output. -func {{decapitalise $contract.Type}}Fallback (accessibleState contract.AccessibleState, caller common.Address, addr common.Address, _ []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = contract.DeductGas(suppliedGas, {{$contract.Type}}FallbackGasCost); err != nil { - return nil, 0, err - } - - if readOnly { - return nil, remainingGas, vmerrs.ErrWriteProtection - } - - {{- if $contract.AllowList}} - // Allow list is enabled and {{.Normalized.Name}} is a state-changer function. - // This part of the code restricts the function to be called only by enabled/admin addresses in the allow list. - // You can modify/delete this code if you don't want this function to be restricted by the allow list. - stateDB := accessibleState.GetStateDB() - // Verify that the caller is in the allow list and therefore has the right to call this function. - callerStatus := allowlist.GetAllowListStatus(stateDB, ContractAddress, caller) - if !callerStatus.IsEnabled() { - return nil, remainingGas, fmt.Errorf("%w: %s", Err{{$contract.Type}}CannotFallback, caller) - } - // allow list code ends here. - {{- end}} - - // CUSTOM CODE STARTS HERE - - // Fallback can return data in output. - // The returned data will not be ABI-encoded. - // Instead it will be returned without modifications (not even padding). - output := []byte{} - // return raw output - return output, remainingGas, nil -} -{{- end}} -{{- end}} - -// create{{.Contract.Type}}Precompile returns a StatefulPrecompiledContract with getters and setters for the precompile. -{{if .Contract.AllowList}} // Access to the getters/setters is controlled by an allow list for ContractAddress.{{end}} -func create{{.Contract.Type}}Precompile() contract.StatefulPrecompiledContract { - var functions []*contract.StatefulPrecompileFunction - {{- if .Contract.AllowList}} - functions = append(functions, allowlist.CreateAllowListFunctions(ContractAddress)...) - {{- end}} - - abiFunctionMap := map[string]contract.RunStatefulPrecompileFunc{ - {{- range .Contract.Funcs}} - "{{.Original.Name}}": {{decapitalise .Normalized.Name}}, - {{- end}} - } - - for name, function := range abiFunctionMap { - method, ok := {{$contract.Type}}ABI.Methods[name] - if !ok { - panic(fmt.Errorf("given method (%s) does not exist in the ABI", name)) - } - functions = append(functions, contract.NewStatefulPrecompileFunction(method.ID, function)) - } - - {{- if .Contract.Fallback}} - // Construct the contract with the fallback function. - statefulContract, err := contract.NewStatefulPrecompileContract({{decapitalise $contract.Type}}Fallback, functions) - if err != nil { - panic(err) - } - return statefulContract - {{- else}} - // Construct the contract with no fallback function. - statefulContract, err := contract.NewStatefulPrecompileContract(nil, functions) - if err != nil { - panic(err) - } - return statefulContract - {{- end}} -} -` diff --git a/accounts/abi/bind/precompilebind/precompile_contract_test_template.go b/accounts/abi/bind/precompilebind/precompile_contract_test_template.go deleted file mode 100644 index 2e944d1033..0000000000 --- a/accounts/abi/bind/precompilebind/precompile_contract_test_template.go +++ /dev/null @@ -1,261 +0,0 @@ -// (c) 2019-2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. -package precompilebind - -// tmplSourcePrecompileConfigGo is the Go precompiled config source template. -const tmplSourcePrecompileContractTestGo = ` -// Code generated -// This file is a generated precompile contract test with the skeleton of test functions. -// The file is generated by a template. Please inspect every code and comment in this file before use. - -package {{.Package}} - -import ( - "testing" - "math/big" - - "github.com/ava-labs/subnet-evm/core/state" - {{- if .Contract.AllowList}} - "github.com/ava-labs/subnet-evm/precompile/allowlist" - {{- end}} - "github.com/ava-labs/subnet-evm/precompile/testutils" - "github.com/ava-labs/subnet-evm/vmerrs" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" -) - -var ( - _ = vmerrs.ErrOutOfGas - _ = big.NewInt - _ = common.Big0 - _ = require.New -) - -// These tests are run against the precompile contract directly with -// the given input and expected output. They're just a guide to -// help you write your own tests. These tests are for general cases like -// allowlist, readOnly behaviour, and gas cost. You should write your own -// tests for specific cases. -var( - tests = map[string]testutils.PrecompileTest{ - {{- $contract := .Contract}} - {{- $structs := .Structs}} - {{- range .Contract.Funcs}} - {{- $func := .}} - {{- if $contract.AllowList}} - {{- $roles := mkList "NoRole" "Enabled" "Manager" "Admin"}} - {{- range $role := $roles}} - {{- $fail := and (not $func.Original.IsConstant) (eq $role "NoRole")}} - "calling {{decapitalise $func.Normalized.Name}} from {{$role}} should {{- if $fail}} fail {{- else}} succeed{{- end}}": { - Caller: allowlist.Test{{$role}}Addr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - {{- if len $func.Normalized.Inputs | lt 1}} - // CUSTOM CODE STARTS HERE - // populate test input here - testInput := {{capitalise $func.Normalized.Name}}Input{} - input, err := Pack{{$func.Normalized.Name}}(testInput) - {{- else if len $func.Normalized.Inputs | eq 1 }} - {{- $input := index $func.Normalized.Inputs 0}} - // CUSTOM CODE STARTS HERE - // set test input to a value here - var testInput {{bindtype $input.Type $structs}} - testInput = {{bindtypenew $input.Type $structs}} - input, err := Pack{{$func.Normalized.Name}}(testInput) - {{- else}} - input, err := Pack{{$func.Normalized.Name}}() - {{- end}} - require.NoError(t, err) - return input - }, - {{- if not $fail}} - // This test is for a successful call. You can set the expected output here. - // CUSTOM CODE STARTS HERE - ExpectedRes: func() []byte{ - {{- if len $func.Normalized.Outputs | eq 0}} - // this function does not return an output, leave this one as is - packedOutput := []byte{} - {{- else}} - {{- if len $func.Normalized.Outputs | lt 1}} - var output {{capitalise $func.Normalized.Name}}Output // CUSTOM CODE FOR AN OUTPUT - {{- else }} - {{$output := index $func.Normalized.Outputs 0}} - var output {{bindtype $output.Type $structs}} // CUSTOM CODE FOR AN OUTPUT - output = {{bindtypenew $output.Type $structs}} // CUSTOM CODE FOR AN OUTPUT - {{- end}} - packedOutput, err := Pack{{$func.Normalized.Name}}Output(output) - if err != nil { - panic(err) - } - {{- end}} - return packedOutput - }(), - {{- end}} - SuppliedGas: {{$func.Normalized.Name}}GasCost, - ReadOnly: false, - ExpectedErr: {{if $fail}} ErrCannot{{$func.Normalized.Name}}.Error() {{- else}} "" {{- end}}, - }, - {{- end}} - {{- end}} - {{- if not $func.Original.IsConstant}} - "readOnly {{decapitalise $func.Normalized.Name}} should fail": { - Caller: common.Address{1}, - InputFn: func(t testing.TB) []byte { - {{- if len $func.Normalized.Inputs | lt 1}} - // CUSTOM CODE STARTS HERE - // populate test input here - testInput := {{capitalise $func.Normalized.Name}}Input{} - input, err := Pack{{$func.Normalized.Name}}(testInput) - {{- else if len $func.Normalized.Inputs | eq 1 }} - {{- $input := index $func.Normalized.Inputs 0}} - // CUSTOM CODE STARTS HERE - // set test input to a value here - var testInput {{bindtype $input.Type $structs}} - testInput = {{bindtypenew $input.Type $structs}} - input, err := Pack{{$func.Normalized.Name}}(testInput) - {{- else}} - input, err := Pack{{$func.Normalized.Name}}() - {{- end}} - require.NoError(t, err) - return input - }, - SuppliedGas: {{$func.Normalized.Name}}GasCost, - ReadOnly: true, - ExpectedErr: vmerrs.ErrWriteProtection.Error(), - }, - {{- end}} - "insufficient gas for {{decapitalise $func.Normalized.Name}} should fail": { - Caller: common.Address{1}, - InputFn: func(t testing.TB) []byte { - {{- if len $func.Normalized.Inputs | lt 1}} - // CUSTOM CODE STARTS HERE - // populate test input here - testInput := {{capitalise $func.Normalized.Name}}Input{} - input, err := Pack{{$func.Normalized.Name}}(testInput) - {{- else if len $func.Normalized.Inputs | eq 1 }} - {{- $input := index $func.Normalized.Inputs 0}} - // CUSTOM CODE STARTS HERE - // set test input to a value here - var testInput {{bindtype $input.Type $structs}} - testInput = {{bindtypenew $input.Type $structs}} - input, err := Pack{{$func.Normalized.Name}}(testInput) - {{- else}} - input, err := Pack{{$func.Normalized.Name}}() - {{- end}} - require.NoError(t, err) - return input - }, - SuppliedGas: {{$func.Normalized.Name}}GasCost - 1, - ReadOnly: false, - ExpectedErr: vmerrs.ErrOutOfGas.Error(), - }, - {{- end}} - {{- if .Contract.Fallback}} - "insufficient gas for fallback should fail": { - Caller: common.Address{1}, - Input: []byte{}, - SuppliedGas: {{.Contract.Type}}FallbackGasCost - 1, - ReadOnly: false, - ExpectedErr: vmerrs.ErrOutOfGas.Error(), - }, - "readOnly fallback should fail": { - Caller: common.Address{1}, - Input: []byte{}, - SuppliedGas: {{.Contract.Type}}FallbackGasCost, - ReadOnly: true, - ExpectedErr: vmerrs.ErrWriteProtection.Error(), - }, - "fallback should succeed": { - Caller: common.Address{1}, - Input: []byte{}, - SuppliedGas: {{.Contract.Type}}FallbackGasCost, - ReadOnly: false, - ExpectedErr: "", - // CUSTOM CODE STARTS HERE - // set expected output here - ExpectedRes: []byte{}, - }, - {{- end}} - } -) - -// Test{{.Contract.Type}}Run tests the Run function of the precompile contract. -func Test{{.Contract.Type}}Run(t *testing.T) { - {{- if .Contract.AllowList}} - // Run tests with allowlist tests. - // This adds allowlist tests to your custom tests - // and runs them all together. - // Even if you don't add any custom tests, keep this. This will still - // run the default allowlist tests. - allowlist.RunPrecompileWithAllowListTests(t, Module, state.NewTestStateDB, tests) - {{- else}} - // Run tests. - for name, test := range tests { - t.Run(name, func(t *testing.T) { - test.Run(t, Module, state.NewTestStateDB(t)) - }) - } - {{- end}} -} - -{{range .Contract.Events}} -{{$hasData := false}} -{{range .Normalized.Inputs}} -{{ if not .Indexed}} -{{ $hasData = true}} -{{end}} -{{end}} -{{ if $hasData}} -// TestPackUnpack{{.Normalized.Name}}EventData tests the Pack/Unpack{{.Normalized.Name}}EventData. -func TestPackUnpack{{.Normalized.Name}}EventData(t *testing.T) { - // CUSTOM CODE STARTS HERE - // set test inputs with proper values here - {{- range .Normalized.Inputs}} - {{if .Indexed}}var {{decapitalise .Name}}Input {{bindtype .Type $structs}} = {{bindtypenew .Type $structs}}{{end}} - {{- end}} - {{if $hasData}} dataInput := {{.Normalized.Name}}EventData{{end}}{ - {{- range .Normalized.Inputs}} - {{- if not .Indexed}} - {{capitalise .Name}}: {{bindtypenew .Type $structs}}, - {{- end}} - {{- end}} - } - - _, data, err := Pack{{.Normalized.Name}}Event( - {{- range .Normalized.Inputs}} - {{- if .Indexed}} - {{decapitalise .Name}}Input, - {{- end}} - {{- end}} - {{- if $hasData}} - dataInput, - {{- end}} - ) - require.NoError(t, err) - - unpacked, err := Unpack{{.Normalized.Name}}EventData(data) - require.NoError(t, err) - require.Equal(t, dataInput, unpacked) -} -{{end}} -{{end}} - -func Benchmark{{.Contract.Type}}(b *testing.B) { - {{- if .Contract.AllowList}} - // Benchmark tests with allowlist tests. - // This adds allowlist tests to your custom tests - // and benchmarks them all together. - // Even if you don't add any custom tests, keep this. This will still - // run the default allowlist tests. - allowlist.BenchPrecompileWithAllowList(b, Module, state.NewTestStateDB, tests) - {{- else}} - // Benchmark tests. - for name, test := range tests { - b.Run(name, func(b *testing.B) { - test.Bench(b, Module, state.NewTestStateDB(b)) - }) - } - {{- end}} -} - -` diff --git a/accounts/abi/bind/precompilebind/precompile_event_template.go b/accounts/abi/bind/precompilebind/precompile_event_template.go deleted file mode 100644 index 3a5582b35e..0000000000 --- a/accounts/abi/bind/precompilebind/precompile_event_template.go +++ /dev/null @@ -1,123 +0,0 @@ -// (c) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. -package precompilebind - -const tmplSourcePrecompileEventGo = ` -// Code generated -// This file is a generated precompile contract config with stubbed abstract functions. -// The file is generated by a template. Please inspect every code and comment in this file before use. - -package {{.Package}} - -import ( - "math/big" - - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ethereum/go-ethereum/common" -) - -// CUSTOM CODE STARTS HERE -// Reference imports to suppress errors from unused imports. This code and any unnecessary imports can be removed. -var ( - _ = big.NewInt - _ = common.Big0 - _ = contract.LogGas -) - -{{$structs := .Structs}} -{{$contract := .Contract}} -/* NOTE: Events can only be emitted in state-changing functions. So you cannot use events in read-only (view) functions. -Events are generally emitted at the end of a state-changing function with AddLog method of the StateDB. The AddLog method takes 4 arguments: - 1. Address of the contract that emitted the event. - 2. Topic hashes of the event. - 3. Encoded non-indexed data of the event. - 4. Block number at which the event was emitted. -The first argument is the address of the contract that emitted the event. -Topics can be at most 4 elements, the first topic is the hash of the event signature and the rest are the indexed event arguments. There can be at most 3 indexed arguments. -Topics cannot be fully unpacked into their original values since they're 32-bytes hashes. -The non-indexed arguments are encoded using the ABI encoding scheme. The non-indexed arguments can be unpacked into their original values. -Before packing the event, you need to calculate the gas cost of the event. The gas cost of an event is the base gas cost + the gas cost of the topics + the gas cost of the non-indexed data. -See Get{EvetName}EventGasCost functions for more details. -You can use the following code to emit an event in your state-changing precompile functions (generated packer might be different)): -topics, data, err := PackMyEvent( - topic1, - topic2, - data1, - data2, -) -if err != nil { - return nil, remainingGas, err -} -accessibleState.GetStateDB().AddLog( - ContractAddress, - topics, - data, - accessibleState.GetBlockContext().Number().Uint64(), -) -*/ -{{range .Contract.Events}} - {{$event := .}} - {{$createdDataStruct := false}} - {{$topicCount := 1}} - {{- range .Normalized.Inputs}} - {{- if .Indexed}} - {{$topicCount = add $topicCount 1}} - {{ continue }} - {{- end}} - {{- if not $createdDataStruct}} - {{$createdDataStruct = true}} - // {{$contract.Type}}{{$event.Normalized.Name}} represents a {{$event.Normalized.Name}} non-indexed event data raised by the {{$contract.Type}} contract. - type {{$event.Normalized.Name}}EventData struct { - {{- end}} - {{capitalise .Name}} {{bindtype .Type $structs}} - {{- end}} - {{- if $createdDataStruct}} - } - {{- end}} - - // Get{{.Normalized.Name}}EventGasCost returns the gas cost of the event. - // The gas cost of an event is the base gas cost + the gas cost of the topics + the gas cost of the non-indexed data. - // The base gas cost and the gas cost of per topics are fixed and can be found in the contract package. - // The gas cost of the non-indexed data depends on the data type and the data size. - func Get{{.Normalized.Name}}EventGasCost({{if $createdDataStruct}} data {{.Normalized.Name}}EventData{{end}}) uint64 { - gas := contract.LogGas // base gas cost - {{if $topicCount | lt 1}} - // Add topics gas cost ({{$topicCount}} topics) - // Topics always include the signature hash of the event. The rest are the indexed event arguments. - gas += contract.LogTopicGas * {{$topicCount}} - {{end}} - - {{range .Normalized.Inputs}} - {{- if not .Indexed}} - // CUSTOM CODE STARTS HERE - // TODO: calculate gas cost for packing the data.{{decapitalise .Name}} according to the type. - // Keep in mind that the data here will be encoded using the ABI encoding scheme. - // So the computation cost might change according to the data type + data size and should be charged accordingly. - // i.e gas += LogDataGas * uint64(len(data.{{decapitalise .Name}})) - gas += contract.LogDataGas // * ... - // CUSTOM CODE ENDS HERE - {{- end}} - {{- end}} - - // CUSTOM CODE STARTS HERE - // TODO: do any additional gas cost calculation here (only if needed) - return gas - } - - // Pack{{.Normalized.Name}}Event packs the event into the appropriate arguments for {{.Original.Name}}. - // It returns topic hashes and the encoded non-indexed data. - func Pack{{.Normalized.Name}}Event({{range .Normalized.Inputs}} {{if .Indexed}}{{decapitalise .Name}} {{bindtype .Type $structs}},{{end}}{{end}}{{if $createdDataStruct}} data {{.Normalized.Name}}EventData{{end}}) ([]common.Hash, []byte, error) { - return {{$contract.Type}}ABI.PackEvent("{{.Original.Name}}"{{range .Normalized.Inputs}},{{if .Indexed}}{{decapitalise .Name}}{{else}}data.{{capitalise .Name}}{{end}}{{end}}) - } - {{ if $createdDataStruct }} - // Unpack{{.Normalized.Name}}EventData attempts to unpack non-indexed [dataBytes]. - func Unpack{{.Normalized.Name}}EventData(dataBytes []byte) ({{.Normalized.Name}}EventData, error) { - eventData := {{.Normalized.Name}}EventData{} - err := {{$contract.Type}}ABI.UnpackIntoInterface(&eventData, "{{.Original.Name}}", dataBytes) - return eventData, err - } - {{else}} - // Unpack{{.Normalized.Name}}Event won't be generated because the event does not have any non-indexed data. - {{end}} -{{end}} -` diff --git a/accounts/abi/bind/precompilebind/precompile_module_template.go b/accounts/abi/bind/precompilebind/precompile_module_template.go deleted file mode 100644 index e9dd8e7275..0000000000 --- a/accounts/abi/bind/precompilebind/precompile_module_template.go +++ /dev/null @@ -1,80 +0,0 @@ -// (c) 2019-2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. -package precompilebind - -// tmplSourcePrecompileModuleGo is the Go precompiled module source template. -const tmplSourcePrecompileModuleGo = ` -// Code generated -// This file is a generated precompile contract config with stubbed abstract functions. -// The file is generated by a template. Please inspect every code and comment in this file before use. - -package {{.Package}} - -import ( - "fmt" - - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ava-labs/subnet-evm/precompile/modules" - - "github.com/ethereum/go-ethereum/common" -) - -var _ contract.Configurator = &configurator{} - -// ConfigKey is the key used in json config files to specify this precompile config. -// must be unique across all precompiles. -const ConfigKey = "{{decapitalise .Contract.Type}}Config" - -// ContractAddress is the defined address of the precompile contract. -// This should be unique across all precompile contracts. -// See precompile/registry/registry.go for registered precompile contracts and more information. -var ContractAddress = common.HexToAddress("{ASUITABLEHEXADDRESS}") // SET A SUITABLE HEX ADDRESS HERE - -// Module is the precompile module. It is used to register the precompile contract. -var Module = modules.Module{ - ConfigKey: ConfigKey, - Address: ContractAddress, - Contract: {{.Contract.Type}}Precompile, - Configurator: &configurator{}, -} - -type configurator struct{} - -func init() { - // Register the precompile module. - // Each precompile contract registers itself through [RegisterModule] function. - if err := modules.RegisterModule(Module); err != nil { - panic(err) - } -} - -// MakeConfig returns a new precompile config instance. -// This is required to Marshal/Unmarshal the precompile config. -func (*configurator) MakeConfig() precompileconfig.Config { - return new(Config) -} - -// Configure configures [state] with the given [cfg] precompileconfig. -// This function is called by the EVM once per precompile contract activation. -// You can use this function to set up your precompile contract's initial state, -// by using the [cfg] config and [state] stateDB. -func (*configurator) Configure(chainConfig precompileconfig.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, blockContext contract.ConfigurationBlockContext) error { - // CUSTOM CODE STARTS HERE - {{- if .Contract.AllowList}} - config, ok := cfg.(*Config) - if !ok { - return fmt.Errorf("expected config type %T, got %T: %v", &Config{}, cfg, cfg) - } - - // AllowList is activated for this precompile. Configuring allowlist addresses here. - return config.AllowListConfig.Configure(chainConfig, ContractAddress, state, blockContext) - {{- else}} - if _, ok := cfg.(*Config); !ok { - return fmt.Errorf("expected config type %T, got %T: %v", &Config{}, cfg, cfg) - } - - return nil - {{- end}} -} -` diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index a39311d774..0ee3946569 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -31,30 +31,30 @@ import "github.com/ava-labs/subnet-evm/accounts/abi" // tmplData is the data structure required to fill the binding template. type tmplData struct { Package string // Name of the package to place the generated file in - Contracts map[string]*TmplContract // List of contracts to generate into this file + Contracts map[string]*tmplContract // List of contracts to generate into this file Libraries map[string]string // Map the bytecode's link pattern to the library name - Structs map[string]*TmplStruct // Contract struct type definitions + Structs map[string]*tmplStruct // Contract struct type definitions } -// TmplContract contains the data needed to generate an individual contract binding. -type TmplContract struct { +// tmplContract contains the data needed to generate an individual contract binding. +type tmplContract struct { Type string // Type name of the main contract binding InputABI string // JSON ABI used as the input to generate the binding from InputBin string // Optional EVM bytecode used to generate deploy code from FuncSigs map[string]string // Optional map: string signature -> 4-byte signature Constructor abi.Method // Contract constructor for deploy parametrization - Calls map[string]*TmplMethod // Contract calls that only read state data - Transacts map[string]*TmplMethod // Contract calls that write state data - Fallback *TmplMethod // Additional special fallback function - Receive *TmplMethod // Additional special receive function + Calls map[string]*tmplMethod // Contract calls that only read state data + Transacts map[string]*tmplMethod // Contract calls that write state data + Fallback *tmplMethod // Additional special fallback function + Receive *tmplMethod // Additional special receive function Events map[string]*tmplEvent // Contract events accessors Libraries map[string]string // Same as tmplData, but filtered to only keep what the contract needs Library bool // Indicator whether the contract is a library } -// TmplMethod is a wrapper around an abi.Method that contains a few preprocessed +// tmplMethod is a wrapper around an abi.Method that contains a few preprocessed // and cached data fields. -type TmplMethod struct { +type tmplMethod struct { Original abi.Method // Original method as parsed by the abi package Normalized abi.Method // Normalized version of the parsed method (capitalized names, non-anonymous args/returns) Structured bool // Whether the returns should be accumulated into a struct @@ -75,9 +75,9 @@ type tmplField struct { SolKind abi.Type // Raw abi type information } -// TmplStruct is a wrapper around an abi.tuple and contains an auto-generated +// tmplStruct is a wrapper around an abi.tuple and contains an auto-generated // struct name. -type TmplStruct struct { +type tmplStruct struct { Name string // Auto-generated struct name(before solidity v0.5.11) or raw name. Fields []*tmplField // Struct fields definition depends on the binding language. } diff --git a/cmd/abigen/namefilter.go b/cmd/abigen/namefilter.go index e43cdf38e0..5fcf6e17a0 100644 --- a/cmd/abigen/namefilter.go +++ b/cmd/abigen/namefilter.go @@ -7,7 +7,6 @@ // original code from which it is derived. // // Much love to the original authors for their work. -// ********** package main import ( diff --git a/cmd/abigen/namefilter_test.go b/cmd/abigen/namefilter_test.go index 43dd28707e..d6c9a0a932 100644 --- a/cmd/abigen/namefilter_test.go +++ b/cmd/abigen/namefilter_test.go @@ -7,7 +7,6 @@ // original code from which it is derived. // // Much love to the original authors for their work. -// ********** package main import ( diff --git a/cmd/evm/README.md b/cmd/evm/README.md deleted file mode 100644 index 6c70e0dd3b..0000000000 --- a/cmd/evm/README.md +++ /dev/null @@ -1,625 +0,0 @@ -# EVM tool - -The EVM tool provides a few useful subcommands to facilitate testing at the EVM -layer. - -* transition tool (`t8n`) : a stateless state transition utility -* transaction tool (`t9n`) : a transaction validation utility -* block builder tool (`b11r`): a block assembler utility - -## State transition tool (`t8n`) - - -The `evm t8n` tool is a stateless state transition utility. It is a utility -which can - -1. Take a prestate, including - - Accounts, - - Block context information, - - Previous blockhashes (*optional) -2. Apply a set of transactions, -3. Apply a mining-reward (*optional), -4. And generate a post-state, including - - State root, transaction root, receipt root, - - Information about rejected transactions, - - Optionally: a full or partial post-state dump - -### Specification - -The idea is to specify the behaviour of this binary very _strict_, so that other -node implementors can build replicas based on their own state-machines, and the -state generators can swap between a \`geth\`-based implementation and a \`parityvm\`-based -implementation. - -#### Command line params - -Command line params that need to be supported are - -``` - --input.alloc value (default: "alloc.json") - --input.env value (default: "env.json") - --input.txs value (default: "txs.json") - --output.alloc value (default: "alloc.json") - --output.basedir value - --output.body value - --output.result value (default: "result.json") - --state.chainid value (default: 1) - --state.fork value (default: "GrayGlacier") - --state.reward value (default: 0) - --trace.memory (default: false) - --trace.nomemory (default: true) - --trace.noreturndata (default: true) - --trace.nostack (default: false) - --trace.returndata (default: false) -``` -#### Objects - -The transition tool uses JSON objects to read and write data related to the transition operation. The -following object definitions are required. - -##### `alloc` - -The `alloc` object defines the prestate that transition will begin with. - -```go -// Map of address to account definition. -type Alloc map[common.Address]Account -// Genesis account. Each field is optional. -type Account struct { - Code []byte `json:"code"` - Storage map[common.Hash]common.Hash `json:"storage"` - Balance *big.Int `json:"balance"` - Nonce uint64 `json:"nonce"` - SecretKey []byte `json:"secretKey"` -} -``` - -##### `env` - -The `env` object defines the environmental context in which the transition will -take place. - -```go -type Env struct { - // required - CurrentCoinbase common.Address `json:"currentCoinbase"` - CurrentGasLimit uint64 `json:"currentGasLimit"` - CurrentNumber uint64 `json:"currentNumber"` - CurrentTimestamp uint64 `json:"currentTimestamp"` - Withdrawals []*Withdrawal `json:"withdrawals"` - // optional - CurrentDifficulty *big.Int `json:"currentDifficulty"` - CurrentRandom *big.Int `json:"currentRandom"` - CurrentBaseFee *big.Int `json:"currentBaseFee"` - ParentDifficulty *big.Int `json:"parentDifficulty"` - ParentGasUsed uint64 `json:"parentGasUsed"` - ParentGasLimit uint64 `json:"parentGasLimit"` - ParentTimestamp uint64 `json:"parentTimestamp"` - BlockHashes map[uint64]common.Hash `json:"blockHashes"` - ParentUncleHash common.Hash `json:"parentUncleHash"` - Ommers []Ommer `json:"ommers"` -} -type Ommer struct { - Delta uint64 `json:"delta"` - Address common.Address `json:"address"` -} -type Withdrawal struct { - Index uint64 `json:"index"` - ValidatorIndex uint64 `json:"validatorIndex"` - Recipient common.Address `json:"recipient"` - Amount *big.Int `json:"amount"` -} -``` - -##### `txs` - -The `txs` object is an array of any of the transaction types: `LegacyTx`, -`AccessListTx`, or `DynamicFeeTx`. - -```go -type LegacyTx struct { - Nonce uint64 `json:"nonce"` - GasPrice *big.Int `json:"gasPrice"` - Gas uint64 `json:"gas"` - To *common.Address `json:"to"` - Value *big.Int `json:"value"` - Data []byte `json:"data"` - V *big.Int `json:"v"` - R *big.Int `json:"r"` - S *big.Int `json:"s"` - SecretKey *common.Hash `json:"secretKey"` -} -type AccessList []AccessTuple -type AccessTuple struct { - Address common.Address `json:"address" gencodec:"required"` - StorageKeys []common.Hash `json:"storageKeys" gencodec:"required"` -} -type AccessListTx struct { - ChainID *big.Int `json:"chainId"` - Nonce uint64 `json:"nonce"` - GasPrice *big.Int `json:"gasPrice"` - Gas uint64 `json:"gas"` - To *common.Address `json:"to"` - Value *big.Int `json:"value"` - Data []byte `json:"data"` - AccessList AccessList `json:"accessList"` - V *big.Int `json:"v"` - R *big.Int `json:"r"` - S *big.Int `json:"s"` - SecretKey *common.Hash `json:"secretKey"` -} -type DynamicFeeTx struct { - ChainID *big.Int `json:"chainId"` - Nonce uint64 `json:"nonce"` - GasTipCap *big.Int `json:"maxPriorityFeePerGas"` - GasFeeCap *big.Int `json:"maxFeePerGas"` - Gas uint64 `json:"gas"` - To *common.Address `json:"to"` - Value *big.Int `json:"value"` - Data []byte `json:"data"` - AccessList AccessList `json:"accessList"` - V *big.Int `json:"v"` - R *big.Int `json:"r"` - S *big.Int `json:"s"` - SecretKey *common.Hash `json:"secretKey"` -} -``` - -##### `result` - -The `result` object is output after a transition is executed. It includes -information about the post-transition environment. - -```go -type ExecutionResult struct { - StateRoot common.Hash `json:"stateRoot"` - TxRoot common.Hash `json:"txRoot"` - ReceiptRoot common.Hash `json:"receiptsRoot"` - LogsHash common.Hash `json:"logsHash"` - Bloom types.Bloom `json:"logsBloom"` - Receipts types.Receipts `json:"receipts"` - Rejected []*rejectedTx `json:"rejected,omitempty"` - Difficulty *big.Int `json:"currentDifficulty"` - GasUsed uint64 `json:"gasUsed"` - BaseFee *big.Int `json:"currentBaseFee,omitempty"` -} -``` - -#### Error codes and output - -All logging should happen against the `stderr`. -There are a few (not many) errors that can occur, those are defined below. - -##### EVM-based errors (`2` to `9`) - -- Other EVM error. Exit code `2` -- Failed configuration: when a non-supported or invalid fork was specified. Exit code `3`. -- Block history is not supplied, but needed for a `BLOCKHASH` operation. If `BLOCKHASH` - is invoked targeting a block which history has not been provided for, the program will - exit with code `4`. - -##### IO errors (`10`-`20`) - -- Invalid input json: the supplied data could not be marshalled. - The program will exit with code `10` -- IO problems: failure to load or save files, the program will exit with code `11` - -``` -# This should exit with 3 -./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --state.fork=Frontier+1346 2>/dev/null -exitcode:3 OK -``` -#### Forks -### Basic usage - -The chain configuration to be used for a transition is specified via the -`--state.fork` CLI flag. A list of possible values and configurations can be -found in [`tests/init.go`](tests/init.go). - -#### Examples -##### Basic usage - -Invoking it with the provided example files -``` -./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --state.fork=Berlin -``` -Two resulting files: - -`alloc.json`: -```json -{ - "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": { - "balance": "0xfeed1a9d", - "nonce": "0x1" - }, - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "balance": "0x5ffd4878be161d74", - "nonce": "0xac" - }, - "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "balance": "0xa410" - } -} -``` -`result.json`: -```json -{ - "stateRoot": "0x84208a19bc2b46ada7445180c1db162be5b39b9abc8c0a54b05d32943eae4e13", - "txRoot": "0xc4761fd7b87ff2364c7c60b6c5c8d02e522e815328aaea3f20e3b7b7ef52c42d", - "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", - "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "receipts": [ - { - "root": "0x", - "status": "0x1", - "cumulativeGasUsed": "0x5208", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "logs": null, - "transactionHash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", - "contractAddress": "0x0000000000000000000000000000000000000000", - "gasUsed": "0x5208", - "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "transactionIndex": "0x0" - } - ], - "rejected": [ - { - "index": 1, - "error": "nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" - } - ], - "currentDifficulty": "0x20000", - "gasUsed": "0x5208" -} -``` - -We can make them spit out the data to e.g. `stdout` like this: -``` -./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --output.result=stdout --output.alloc=stdout --state.fork=Berlin -``` -Output: -```json -{ - "alloc": { - "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": { - "balance": "0xfeed1a9d", - "nonce": "0x1" - }, - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "balance": "0x5ffd4878be161d74", - "nonce": "0xac" - }, - "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "balance": "0xa410" - } - }, - "result": { - "stateRoot": "0x84208a19bc2b46ada7445180c1db162be5b39b9abc8c0a54b05d32943eae4e13", - "txRoot": "0xc4761fd7b87ff2364c7c60b6c5c8d02e522e815328aaea3f20e3b7b7ef52c42d", - "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", - "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "receipts": [ - { - "root": "0x", - "status": "0x1", - "cumulativeGasUsed": "0x5208", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "logs": null, - "transactionHash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", - "contractAddress": "0x0000000000000000000000000000000000000000", - "gasUsed": "0x5208", - "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "transactionIndex": "0x0" - } - ], - "rejected": [ - { - "index": 1, - "error": "nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" - } - ], - "currentDifficulty": "0x20000", - "gasUsed": "0x5208" - } -} -``` - -#### About Ommers - -Mining rewards and ommer rewards might need to be added. This is how those are applied: - -- `block_reward` is the block mining reward for the miner (`0xaa`), of a block at height `N`. -- For each ommer (mined by `0xbb`), with blocknumber `N-delta` - - (where `delta` is the difference between the current block and the ommer) - - The account `0xbb` (ommer miner) is awarded `(8-delta)/ 8 * block_reward` - - The account `0xaa` (block miner) is awarded `block_reward / 32` - -To make `t8n` apply these, the following inputs are required: - -- `--state.reward` - - For ethash, it is `5000000000000000000` `wei`, - - If this is not defined, mining rewards are not applied, - - A value of `0` is valid, and causes accounts to be 'touched'. -- For each ommer, the tool needs to be given an `address\` and a `delta`. This - is done via the `ommers` field in `env`. - -Note: the tool does not verify that e.g. the normal uncle rules apply, -and allows e.g two uncles at the same height, or the uncle-distance. This means that -the tool allows for negative uncle reward (distance > 8) - -Example: -`./testdata/5/env.json`: -```json -{ - "currentCoinbase": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "currentDifficulty": "0x20000", - "currentGasLimit": "0x750a163df65e8a", - "currentNumber": "1", - "currentTimestamp": "1000", - "ommers": [ - {"delta": 1, "address": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" }, - {"delta": 2, "address": "0xcccccccccccccccccccccccccccccccccccccccc" } - ] -} -``` -When applying this, using a reward of `0x08` -Output: -```json -{ - "alloc": { - "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": { - "balance": "0x88" - }, - "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb": { - "balance": "0x70" - }, - "0xcccccccccccccccccccccccccccccccccccccccc": { - "balance": "0x60" - } - } -} -``` -#### Future EIPS - -It is also possible to experiment with future eips that are not yet defined in a hard fork. -Example, putting EIP-1344 into Frontier: -``` -./evm t8n --state.fork=Frontier+1344 --input.pre=./testdata/1/pre.json --input.txs=./testdata/1/txs.json --input.env=/testdata/1/env.json -``` - -#### Block history - -The `BLOCKHASH` opcode requires blockhashes to be provided by the caller, inside the `env`. -If a required blockhash is not provided, the exit code should be `4`: -Example where blockhashes are provided: -``` -./evm t8n --input.alloc=./testdata/3/alloc.json --input.txs=./testdata/3/txs.json --input.env=./testdata/3/env.json --trace --state.fork=Berlin - -``` - -``` -cat trace-0-0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81.jsonl | grep BLOCKHASH -C2 -``` -``` -{"pc":0,"op":96,"gas":"0x5f58ef8","gasCost":"0x3","memSize":0,"stack":[],"depth":1,"refund":0,"opName":"PUSH1"} -{"pc":2,"op":64,"gas":"0x5f58ef5","gasCost":"0x14","memSize":0,"stack":["0x1"],"depth":1,"refund":0,"opName":"BLOCKHASH"} -{"pc":3,"op":0,"gas":"0x5f58ee1","gasCost":"0x0","memSize":0,"stack":["0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"],"depth":1,"refund":0,"opName":"STOP"} -{"output":"","gasUsed":"0x17"} -``` - -In this example, the caller has not provided the required blockhash: -``` -./evm t8n --input.alloc=./testdata/4/alloc.json --input.txs=./testdata/4/txs.json --input.env=./testdata/4/env.json --trace --state.fork=Berlin -ERROR(4): getHash(3) invoked, blockhash for that block not provided -``` -Error code: 4 - -#### Chaining - -Another thing that can be done, is to chain invocations: -``` -./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --state.fork=Berlin --output.alloc=stdout | ./evm t8n --input.alloc=stdin --input.env=./testdata/1/env.json --input.txs=./testdata/1/txs.json --state.fork=Berlin - -``` -What happened here, is that we first applied two identical transactions, so the second one was rejected. -Then, taking the poststate alloc as the input for the next state, we tried again to include -the same two transactions: this time, both failed due to too low nonce. - -In order to meaningfully chain invocations, one would need to provide meaningful new `env`, otherwise the -actual blocknumber (exposed to the EVM) would not increase. - -#### Transactions in RLP form - -It is possible to provide already-signed transactions as input to, using an `input.txs` which ends with the `rlp` suffix. -The input format for RLP-form transactions is _identical_ to the _output_ format for block bodies. Therefore, it's fully possible -to use the evm to go from `json` input to `rlp` input. - -The following command takes **json** the transactions in `./testdata/13/txs.json` and signs them. After execution, they are output to `signed_txs.rlp`.: -``` -./evm t8n --state.fork=London --input.alloc=./testdata/13/alloc.json --input.txs=./testdata/13/txs.json --input.env=./testdata/13/env.json --output.result=alloc_jsontx.json --output.body=signed_txs.rlp -INFO [12-27|09:25:11.102] Trie dumping started root=e4b924..6aef61 -INFO [12-27|09:25:11.102] Trie dumping complete accounts=3 elapsed="275.66Âĩs" -INFO [12-27|09:25:11.102] Wrote file file=alloc.json -INFO [12-27|09:25:11.103] Wrote file file=alloc_jsontx.json -INFO [12-27|09:25:11.103] Wrote file file=signed_txs.rlp -``` - -The `output.body` is the rlp-list of transactions, encoded in hex and placed in a string a'la `json` encoding rules: -``` -cat signed_txs.rlp -"0xf8d2b86702f864010180820fa08284d09411111111111111111111111111111111111111118080c001a0b7dfab36232379bb3d1497a4f91c1966b1f932eae3ade107bf5d723b9cb474e0a06261c359a10f2132f126d250485b90cf20f30340801244a08ef6142ab33d1904b86702f864010280820fa08284d09411111111111111111111111111111111111111118080c080a0d4ec563b6568cd42d998fc4134b36933c6568d01533b5adf08769270243c6c7fa072bf7c21eac6bbeae5143371eef26d5e279637f3bd73482b55979d76d935b1e9" -``` - -We can use `rlpdump` to check what the contents are: -``` -rlpdump -hex $(cat signed_txs.rlp | jq -r ) -[ - 02f864010180820fa08284d09411111111111111111111111111111111111111118080c001a0b7dfab36232379bb3d1497a4f91c1966b1f932eae3ade107bf5d723b9cb474e0a06261c359a10f2132f126d250485b90cf20f30340801244a08ef6142ab33d1904, - 02f864010280820fa08284d09411111111111111111111111111111111111111118080c080a0d4ec563b6568cd42d998fc4134b36933c6568d01533b5adf08769270243c6c7fa072bf7c21eac6bbeae5143371eef26d5e279637f3bd73482b55979d76d935b1e9, -] -``` -Now, we can now use those (or any other already signed transactions), as input, like so: -``` -./evm t8n --state.fork=London --input.alloc=./testdata/13/alloc.json --input.txs=./signed_txs.rlp --input.env=./testdata/13/env.json --output.result=alloc_rlptx.json -INFO [12-27|09:25:11.187] Trie dumping started root=e4b924..6aef61 -INFO [12-27|09:25:11.187] Trie dumping complete accounts=3 elapsed="123.676Âĩs" -INFO [12-27|09:25:11.187] Wrote file file=alloc.json -INFO [12-27|09:25:11.187] Wrote file file=alloc_rlptx.json -``` -You might have noticed that the results from these two invocations were stored in two separate files. -And we can now finally check that they match. -``` -cat alloc_jsontx.json | jq .stateRoot && cat alloc_rlptx.json | jq .stateRoot -"0xe4b924a6adb5959fccf769d5b7bb2f6359e26d1e76a2443c5a91a36d826aef61" -"0xe4b924a6adb5959fccf769d5b7bb2f6359e26d1e76a2443c5a91a36d826aef61" -``` - -## Transaction tool - -The transaction tool is used to perform static validity checks on transactions such as: -* intrinsic gas calculation -* max values on integers -* fee semantics, such as `maxFeePerGas < maxPriorityFeePerGas` -* newer tx types on old forks - -### Examples - -``` -./evm t9n --state.fork Homestead --input.txs testdata/15/signed_txs.rlp -[ - { - "error": "transaction type not supported", - "hash": "0xa98a24882ea90916c6a86da650fbc6b14238e46f0af04a131ce92be897507476" - }, - { - "error": "transaction type not supported", - "hash": "0x36bad80acce7040c45fd32764b5c2b2d2e6f778669fb41791f73f546d56e739a" - } -] -``` -``` -./evm t9n --state.fork London --input.txs testdata/15/signed_txs.rlp -[ - { - "address": "0xd02d72e067e77158444ef2020ff2d325f929b363", - "hash": "0xa98a24882ea90916c6a86da650fbc6b14238e46f0af04a131ce92be897507476", - "intrinsicGas": "0x5208" - }, - { - "address": "0xd02d72e067e77158444ef2020ff2d325f929b363", - "hash": "0x36bad80acce7040c45fd32764b5c2b2d2e6f778669fb41791f73f546d56e739a", - "intrinsicGas": "0x5208" - } -] -``` -## Block builder tool (b11r) - -The `evm b11r` tool is used to assemble and seal full block rlps. - -### Specification - -#### Command line params - -Command line params that need to be supported are: - -``` - --input.header value `stdin` or file name of where to find the block header to use. (default: "header.json") - --input.ommers value `stdin` or file name of where to find the list of ommer header RLPs to use. - --input.txs value `stdin` or file name of where to find the transactions list in RLP form. (default: "txs.rlp") - --output.basedir value Specifies where output files are placed. Will be created if it does not exist. - --output.block value Determines where to put the alloc of the post-state. (default: "block.json") - - into the file - `stdout` - into the stdout output - `stderr` - into the stderr output - --seal.clique value Seal block with Clique. `stdin` or file name of where to find the Clique sealing data. - --seal.ethash Seal block with ethash. (default: false) - --seal.ethash.dir value Path to ethash DAG. If none exists, a new DAG will be generated. - --seal.ethash.mode value Defines the type and amount of PoW verification an ethash engine makes. (default: "normal") - --verbosity value Sets the verbosity level. (default: 3) -``` - -#### Objects - -##### `header` - -The `header` object is a consensus header. - -```go= -type Header struct { - ParentHash common.Hash `json:"parentHash"` - OmmerHash *common.Hash `json:"sha3Uncles"` - Coinbase *common.Address `json:"miner"` - Root common.Hash `json:"stateRoot" gencodec:"required"` - TxHash *common.Hash `json:"transactionsRoot"` - ReceiptHash *common.Hash `json:"receiptsRoot"` - Bloom types.Bloom `json:"logsBloom"` - Difficulty *big.Int `json:"difficulty"` - Number *big.Int `json:"number" gencodec:"required"` - GasLimit uint64 `json:"gasLimit" gencodec:"required"` - GasUsed uint64 `json:"gasUsed"` - Time uint64 `json:"timestamp" gencodec:"required"` - Extra []byte `json:"extraData"` - MixDigest common.Hash `json:"mixHash"` - Nonce *types.BlockNonce `json:"nonce"` - BaseFee *big.Int `json:"baseFeePerGas"` -} -``` -#### `ommers` - -The `ommers` object is a list of RLP-encoded ommer blocks in hex -representation. - -```go= -type Ommers []string -``` - -#### `txs` - -The `txs` object is a list of RLP-encoded transactions in hex representation. - -```go= -type Txs []string -``` - -#### `clique` - -The `clique` object provides the necessary information to complete a clique -seal of the block. - -```go= -var CliqueInfo struct { - Key *common.Hash `json:"secretKey"` - Voted *common.Address `json:"voted"` - Authorize *bool `json:"authorize"` - Vanity common.Hash `json:"vanity"` -} -``` - -#### `output` - -The `output` object contains two values, the block RLP and the block hash. - -```go= -type BlockInfo struct { - Rlp []byte `json:"rlp"` - Hash common.Hash `json:"hash"` -} -``` - -## A Note on Encoding - -The encoding of values for `evm` utility attempts to be relatively flexible. It -generally supports hex-encoded or decimal-encoded numeric values, and -hex-encoded byte values (like `common.Address`, `common.Hash`, etc). When in -doubt, the [`execution-apis`](https://github.com/ethereum/execution-apis) way -of encoding should always be accepted. - -## Testing - -There are many test cases in the [`cmd/evm/testdata`](./testdata) directory. -These fixtures are used to power the `t8n` tests in -[`t8n_test.go`](./t8n_test.go). The best way to verify correctness of new `evm` -implementations is to execute these and verify the output and error codes match -the expected values. diff --git a/cmd/evm/compiler.go b/cmd/evm/compiler.go deleted file mode 100644 index 4c341767fd..0000000000 --- a/cmd/evm/compiler.go +++ /dev/null @@ -1,65 +0,0 @@ -// (c) 2023, Ava Labs, Inc. -// -// This file is a derived work, based on the go-ethereum library whose original -// notices appear below. -// -// It is distributed under a license compatible with the licensing terms of the -// original code from which it is derived. -// -// Much love to the original authors for their work. -// ********** -// Copyright 2017 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "errors" - "fmt" - "os" - - "github.com/ava-labs/subnet-evm/cmd/evm/internal/compiler" - - "github.com/urfave/cli/v2" -) - -var compileCommand = &cli.Command{ - Action: compileCmd, - Name: "compile", - Usage: "Compiles easm source to evm binary", - ArgsUsage: "", -} - -func compileCmd(ctx *cli.Context) error { - debug := ctx.Bool(DebugFlag.Name) - - if len(ctx.Args().First()) == 0 { - return errors.New("filename required") - } - - fn := ctx.Args().First() - src, err := os.ReadFile(fn) - if err != nil { - return err - } - - bin, err := compiler.Compile(fn, src, debug) - if err != nil { - return err - } - fmt.Println(bin) - return nil -} diff --git a/cmd/evm/disasm.go b/cmd/evm/disasm.go deleted file mode 100644 index f227e90a2c..0000000000 --- a/cmd/evm/disasm.go +++ /dev/null @@ -1,65 +0,0 @@ -// (c) 2023, Ava Labs, Inc. -// -// This file is a derived work, based on the go-ethereum library whose original -// notices appear below. -// -// It is distributed under a license compatible with the licensing terms of the -// original code from which it is derived. -// -// Much love to the original authors for their work. -// ********** -// Copyright 2017 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "errors" - "fmt" - "os" - "strings" - - "github.com/ethereum/go-ethereum/core/asm" - "github.com/urfave/cli/v2" -) - -var disasmCommand = &cli.Command{ - Action: disasmCmd, - Name: "disasm", - Usage: "Disassembles evm binary", - ArgsUsage: "", -} - -func disasmCmd(ctx *cli.Context) error { - var in string - switch { - case len(ctx.Args().First()) > 0: - fn := ctx.Args().First() - input, err := os.ReadFile(fn) - if err != nil { - return err - } - in = string(input) - case ctx.IsSet(InputFlag.Name): - in = ctx.String(InputFlag.Name) - default: - return errors.New("missing filename or --input value") - } - - code := strings.TrimSpace(in) - fmt.Printf("%v\n", code) - return asm.PrintDisassembled(code) -} diff --git a/cmd/evm/internal/compiler/compiler.go b/cmd/evm/internal/compiler/compiler.go deleted file mode 100644 index ba72065e43..0000000000 --- a/cmd/evm/internal/compiler/compiler.go +++ /dev/null @@ -1,49 +0,0 @@ -// (c) 2023, Ava Labs, Inc. -// -// This file is a derived work, based on the go-ethereum library whose original -// notices appear below. -// -// It is distributed under a license compatible with the licensing terms of the -// original code from which it is derived. -// -// Much love to the original authors for their work. -// ********** -// Copyright 2017 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package compiler - -import ( - "errors" - "fmt" - - "github.com/ethereum/go-ethereum/core/asm" -) - -func Compile(fn string, src []byte, debug bool) (string, error) { - compiler := asm.NewCompiler(debug) - compiler.Feed(asm.Lex(src, debug)) - - bin, compileErrors := compiler.Compile() - if len(compileErrors) > 0 { - // report errors - for _, err := range compileErrors { - fmt.Printf("%s:%v\n", fn, err) - } - return "", errors.New("compiling failed") - } - return bin, nil -} diff --git a/cmd/evm/internal/t8ntool/block.go b/cmd/evm/internal/t8ntool/block.go deleted file mode 100644 index c245f0e45b..0000000000 --- a/cmd/evm/internal/t8ntool/block.go +++ /dev/null @@ -1,305 +0,0 @@ -// (c) 2023, Ava Labs, Inc. -// -// This file is a derived work, based on the go-ethereum library whose original -// notices appear below. -// -// It is distributed under a license compatible with the licensing terms of the -// original code from which it is derived. -// -// Much love to the original authors for their work. -// ********** -// Copyright 2021 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package t8ntool - -import ( - "crypto/ecdsa" - "encoding/json" - "errors" - "fmt" - "math/big" - "os" - - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" - "github.com/urfave/cli/v2" -) - -//go:generate go run github.com/fjl/gencodec -type header -field-override headerMarshaling -out gen_header.go -type header struct { - ParentHash common.Hash `json:"parentHash"` - OmmerHash *common.Hash `json:"sha3Uncles"` - Coinbase *common.Address `json:"miner"` - Root common.Hash `json:"stateRoot" gencodec:"required"` - TxHash *common.Hash `json:"transactionsRoot"` - ReceiptHash *common.Hash `json:"receiptsRoot"` - Bloom types.Bloom `json:"logsBloom"` - Difficulty *big.Int `json:"difficulty"` - Number *big.Int `json:"number" gencodec:"required"` - GasLimit uint64 `json:"gasLimit" gencodec:"required"` - GasUsed uint64 `json:"gasUsed"` - Time uint64 `json:"timestamp" gencodec:"required"` - Extra []byte `json:"extraData"` - MixDigest common.Hash `json:"mixHash"` - Nonce *types.BlockNonce `json:"nonce"` - BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"` - BlobGasUsed *uint64 `json:"blobGasUsed" rlp:"optional"` - ExcessBlobGas *uint64 `json:"excessBlobGas" rlp:"optional"` - ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` -} - -type headerMarshaling struct { - Difficulty *math.HexOrDecimal256 - Number *math.HexOrDecimal256 - GasLimit math.HexOrDecimal64 - GasUsed math.HexOrDecimal64 - Time math.HexOrDecimal64 - Extra hexutil.Bytes - BaseFee *math.HexOrDecimal256 - BlobGasUsed *math.HexOrDecimal64 - ExcessBlobGas *math.HexOrDecimal64 -} - -type bbInput struct { - Header *header `json:"header,omitempty"` - OmmersRlp []string `json:"ommers,omitempty"` - TxRlp string `json:"txs,omitempty"` - Clique *cliqueInput `json:"clique,omitempty"` - - Ethash bool `json:"-"` - Txs []*types.Transaction `json:"-"` - Ommers []*types.Header `json:"-"` -} - -type cliqueInput struct { - Key *ecdsa.PrivateKey - Voted *common.Address - Authorize *bool - Vanity common.Hash -} - -// UnmarshalJSON implements json.Unmarshaler interface. -func (c *cliqueInput) UnmarshalJSON(input []byte) error { - var x struct { - Key *common.Hash `json:"secretKey"` - Voted *common.Address `json:"voted"` - Authorize *bool `json:"authorize"` - Vanity common.Hash `json:"vanity"` - } - if err := json.Unmarshal(input, &x); err != nil { - return err - } - if x.Key == nil { - return errors.New("missing required field 'secretKey' for cliqueInput") - } - if ecdsaKey, err := crypto.ToECDSA(x.Key[:]); err != nil { - return err - } else { - c.Key = ecdsaKey - } - c.Voted = x.Voted - c.Authorize = x.Authorize - c.Vanity = x.Vanity - return nil -} - -// ToBlock converts i into a *types.Block -func (i *bbInput) ToBlock() *types.Block { - header := &types.Header{ - ParentHash: i.Header.ParentHash, - UncleHash: types.EmptyUncleHash, - Coinbase: common.Address{}, - Root: i.Header.Root, - TxHash: types.EmptyTxsHash, - ReceiptHash: types.EmptyReceiptsHash, - Bloom: i.Header.Bloom, - Difficulty: common.Big0, - Number: i.Header.Number, - GasLimit: i.Header.GasLimit, - GasUsed: i.Header.GasUsed, - Time: i.Header.Time, - Extra: i.Header.Extra, - MixDigest: i.Header.MixDigest, - BaseFee: i.Header.BaseFee, - BlobGasUsed: i.Header.BlobGasUsed, - ExcessBlobGas: i.Header.ExcessBlobGas, - ParentBeaconRoot: i.Header.ParentBeaconBlockRoot, - } - - // Fill optional values. - if i.Header.OmmerHash != nil { - header.UncleHash = *i.Header.OmmerHash - } else if len(i.Ommers) != 0 { - // Calculate the ommer hash if none is provided and there are ommers to hash - header.UncleHash = types.CalcUncleHash(i.Ommers) - } - if i.Header.Coinbase != nil { - header.Coinbase = *i.Header.Coinbase - } - if i.Header.TxHash != nil { - header.TxHash = *i.Header.TxHash - } - if i.Header.ReceiptHash != nil { - header.ReceiptHash = *i.Header.ReceiptHash - } - if i.Header.Nonce != nil { - header.Nonce = *i.Header.Nonce - } - if i.Header.Difficulty != nil { - header.Difficulty = i.Header.Difficulty - } - return types.NewBlockWithHeader(header).WithBody(i.Txs, i.Ommers) -} - -// SealBlock seals the given block using the configured engine. -func (i *bbInput) SealBlock(block *types.Block) (*types.Block, error) { - switch { - case i.Clique != nil: - return i.sealClique(block) - default: - return block, nil - } -} - -// sealClique seals the given block using clique. -func (i *bbInput) sealClique(block *types.Block) (*types.Block, error) { - // NOTE: this has been removed - return block, nil -} - -// BuildBlock constructs a block from the given inputs. -func BuildBlock(ctx *cli.Context) error { - baseDir, err := createBasedir(ctx) - if err != nil { - return NewError(ErrorIO, fmt.Errorf("failed creating output basedir: %v", err)) - } - inputData, err := readInput(ctx) - if err != nil { - return err - } - block := inputData.ToBlock() - block, err = inputData.SealBlock(block) - if err != nil { - return err - } - return dispatchBlock(ctx, baseDir, block) -} - -func readInput(ctx *cli.Context) (*bbInput, error) { - var ( - headerStr = ctx.String(InputHeaderFlag.Name) - ommersStr = ctx.String(InputOmmersFlag.Name) - txsStr = ctx.String(InputTxsRlpFlag.Name) - cliqueStr = ctx.String(SealCliqueFlag.Name) - inputData = &bbInput{} - ) - if headerStr == stdinSelector || ommersStr == stdinSelector || txsStr == stdinSelector || cliqueStr == stdinSelector { - decoder := json.NewDecoder(os.Stdin) - if err := decoder.Decode(inputData); err != nil { - return nil, NewError(ErrorJson, fmt.Errorf("failed unmarshaling stdin: %v", err)) - } - } - if cliqueStr != stdinSelector && cliqueStr != "" { - var clique cliqueInput - if err := readFile(cliqueStr, "clique", &clique); err != nil { - return nil, err - } - inputData.Clique = &clique - } - if headerStr != stdinSelector { - var env header - if err := readFile(headerStr, "header", &env); err != nil { - return nil, err - } - inputData.Header = &env - } - if ommersStr != stdinSelector && ommersStr != "" { - var ommers []string - if err := readFile(ommersStr, "ommers", &ommers); err != nil { - return nil, err - } - inputData.OmmersRlp = ommers - } - if txsStr != stdinSelector { - var txs string - if err := readFile(txsStr, "txs", &txs); err != nil { - return nil, err - } - inputData.TxRlp = txs - } - // Deserialize rlp txs and ommers - var ( - ommers = []*types.Header{} - txs = []*types.Transaction{} - ) - if inputData.TxRlp != "" { - if err := rlp.DecodeBytes(common.FromHex(inputData.TxRlp), &txs); err != nil { - return nil, NewError(ErrorRlp, fmt.Errorf("unable to decode transaction from rlp data: %v", err)) - } - inputData.Txs = txs - } - for _, str := range inputData.OmmersRlp { - type extblock struct { - Header *types.Header - Txs []*types.Transaction - Ommers []*types.Header - } - var ommer *extblock - if err := rlp.DecodeBytes(common.FromHex(str), &ommer); err != nil { - return nil, NewError(ErrorRlp, fmt.Errorf("unable to decode ommer from rlp data: %v", err)) - } - ommers = append(ommers, ommer.Header) - } - inputData.Ommers = ommers - - return inputData, nil -} - -// dispatchBlock writes the output data to either stderr or stdout, or to the specified -// files -func dispatchBlock(ctx *cli.Context, baseDir string, block *types.Block) error { - raw, _ := rlp.EncodeToBytes(block) - type blockInfo struct { - Rlp hexutil.Bytes `json:"rlp"` - Hash common.Hash `json:"hash"` - } - enc := blockInfo{ - Rlp: raw, - Hash: block.Hash(), - } - b, err := json.MarshalIndent(enc, "", " ") - if err != nil { - return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) - } - switch dest := ctx.String(OutputBlockFlag.Name); dest { - case "stdout": - os.Stdout.Write(b) - os.Stdout.WriteString("\n") - case "stderr": - os.Stderr.Write(b) - os.Stderr.WriteString("\n") - default: - if err := saveFile(baseDir, dest, enc); err != nil { - return err - } - } - return nil -} diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go deleted file mode 100644 index ea4bf549dc..0000000000 --- a/cmd/evm/internal/t8ntool/execution.go +++ /dev/null @@ -1,376 +0,0 @@ -// (c) 2023, Ava Labs, Inc. -// -// This file is a derived work, based on the go-ethereum library whose original -// notices appear below. -// -// It is distributed under a license compatible with the licensing terms of the -// original code from which it is derived. -// -// Much love to the original authors for their work. -// ********** -// Copyright 2020 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package t8ntool - -import ( - "fmt" - "math/big" - - "github.com/ava-labs/subnet-evm/consensus/misc/eip4844" - "github.com/ava-labs/subnet-evm/core" - "github.com/ava-labs/subnet-evm/core/rawdb" - "github.com/ava-labs/subnet-evm/core/state" - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/core/vm" - "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/trie" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" - "golang.org/x/crypto/sha3" -) - -type Prestate struct { - Env stEnv `json:"env"` - Pre core.GenesisAlloc `json:"pre"` -} - -// ExecutionResult contains the execution status after running a state test, any -// error that might have occurred and a dump of the final state if requested. -type ExecutionResult struct { - StateRoot common.Hash `json:"stateRoot"` - TxRoot common.Hash `json:"txRoot"` - ReceiptRoot common.Hash `json:"receiptsRoot"` - LogsHash common.Hash `json:"logsHash"` - Bloom types.Bloom `json:"logsBloom" gencodec:"required"` - Receipts types.Receipts `json:"receipts"` - Rejected []*rejectedTx `json:"rejected,omitempty"` - Difficulty *math.HexOrDecimal256 `json:"currentDifficulty" gencodec:"required"` - GasUsed math.HexOrDecimal64 `json:"gasUsed"` - BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"` - CurrentExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas,omitempty"` - CurrentBlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed,omitempty"` -} - -type ommer struct { - Delta uint64 `json:"delta"` - Address common.Address `json:"address"` -} - -//go:generate go run github.com/fjl/gencodec -type stEnv -field-override stEnvMarshaling -out gen_stenv.go -type stEnv struct { - Coinbase common.Address `json:"currentCoinbase" gencodec:"required"` - Difficulty *big.Int `json:"currentDifficulty"` - Random *big.Int `json:"currentRandom"` - ParentDifficulty *big.Int `json:"parentDifficulty"` - ParentBaseFee *big.Int `json:"parentBaseFee,omitempty"` - ParentGasUsed uint64 `json:"parentGasUsed,omitempty"` - ParentGasLimit uint64 `json:"parentGasLimit,omitempty"` - MinBaseFee *big.Int `json:"minBaseFee,omitempty"` - GasLimit uint64 `json:"currentGasLimit" gencodec:"required"` - Number uint64 `json:"currentNumber" gencodec:"required"` - Timestamp uint64 `json:"currentTimestamp" gencodec:"required"` - ParentTimestamp uint64 `json:"parentTimestamp,omitempty"` - BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` - Ommers []ommer `json:"ommers,omitempty"` - BaseFee *big.Int `json:"currentBaseFee,omitempty"` - ParentUncleHash common.Hash `json:"parentUncleHash"` - ExcessBlobGas *uint64 `json:"currentExcessBlobGas,omitempty"` - ParentExcessBlobGas *uint64 `json:"parentExcessBlobGas,omitempty"` - ParentBlobGasUsed *uint64 `json:"parentBlobGasUsed,omitempty"` - ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"` -} - -type stEnvMarshaling struct { - Coinbase common.UnprefixedAddress - Difficulty *math.HexOrDecimal256 - Random *math.HexOrDecimal256 - ParentDifficulty *math.HexOrDecimal256 - ParentBaseFee *math.HexOrDecimal256 - ParentGasUsed math.HexOrDecimal64 - ParentGasLimit math.HexOrDecimal64 - MinBaseFee *math.HexOrDecimal256 - GasLimit math.HexOrDecimal64 - Number math.HexOrDecimal64 - Timestamp math.HexOrDecimal64 - ParentTimestamp math.HexOrDecimal64 - BaseFee *math.HexOrDecimal256 - ExcessBlobGas *math.HexOrDecimal64 - ParentExcessBlobGas *math.HexOrDecimal64 - ParentBlobGasUsed *math.HexOrDecimal64 -} - -type rejectedTx struct { - Index int `json:"index"` - Err string `json:"error"` -} - -// Apply applies a set of transactions to a pre-state -func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, - txIt txIterator, miningReward int64, - getTracerFn func(txIndex int, txHash common.Hash) (vm.EVMLogger, error)) (*state.StateDB, *ExecutionResult, []byte, error) { - // Capture errors for BLOCKHASH operation, if we haven't been supplied the - // required blockhashes - var hashError error - getHash := func(num uint64) common.Hash { - if pre.Env.BlockHashes == nil { - hashError = fmt.Errorf("getHash(%d) invoked, no blockhashes provided", num) - return common.Hash{} - } - h, ok := pre.Env.BlockHashes[math.HexOrDecimal64(num)] - if !ok { - hashError = fmt.Errorf("getHash(%d) invoked, blockhash for that block not provided", num) - } - return h - } - var ( - statedb = MakePreState(rawdb.NewMemoryDatabase(), pre.Pre) - signer = types.MakeSigner(chainConfig, new(big.Int).SetUint64(pre.Env.Number), pre.Env.Timestamp) - gaspool = new(core.GasPool) - blockHash = common.Hash{0x13, 0x37} - rejectedTxs []*rejectedTx - includedTxs types.Transactions - gasUsed = uint64(0) - receipts = make(types.Receipts, 0) - txIndex = 0 - ) - gaspool.AddGas(pre.Env.GasLimit) - vmContext := vm.BlockContext{ - CanTransfer: core.CanTransfer, - Transfer: core.Transfer, - Coinbase: pre.Env.Coinbase, - BlockNumber: new(big.Int).SetUint64(pre.Env.Number), - Time: pre.Env.Timestamp, - Difficulty: pre.Env.Difficulty, - GasLimit: pre.Env.GasLimit, - GetHash: getHash, - } - // If currentBaseFee is defined, add it to the vmContext. - if pre.Env.BaseFee != nil { - vmContext.BaseFee = new(big.Int).Set(pre.Env.BaseFee) - } - // NOTE: this has been removed - // If random is defined, add it to the vmContext. - // if pre.Env.Random != nil { - // rnd := common.BigToHash(pre.Env.Random) - // vmContext.Random = &rnd - // } - // Calculate the BlobBaseFee - var excessBlobGas uint64 - if pre.Env.ExcessBlobGas != nil { - excessBlobGas := *pre.Env.ExcessBlobGas - vmContext.BlobBaseFee = eip4844.CalcBlobFee(excessBlobGas) - } else { - // If it is not explicitly defined, but we have the parent values, we try - // to calculate it ourselves. - parentExcessBlobGas := pre.Env.ParentExcessBlobGas - parentBlobGasUsed := pre.Env.ParentBlobGasUsed - if parentExcessBlobGas != nil && parentBlobGasUsed != nil { - excessBlobGas = eip4844.CalcExcessBlobGas(*parentExcessBlobGas, *parentBlobGasUsed) - vmContext.BlobBaseFee = eip4844.CalcBlobFee(excessBlobGas) - } - } - // If DAO is supported/enabled, we need to handle it here. In geth 'proper', it's - // done in StateProcessor.Process(block, ...), right before transactions are applied. - // if chainConfig.DAOForkSupport && - // chainConfig.DAOForkBlock != nil && - // chainConfig.DAOForkBlock.Cmp(new(big.Int).SetUint64(pre.Env.Number)) == 0 { - // misc.ApplyDAOHardFork(statedb) - // } - if beaconRoot := pre.Env.ParentBeaconBlockRoot; beaconRoot != nil { - evm := vm.NewEVM(vmContext, vm.TxContext{}, statedb, chainConfig, vmConfig) - core.ProcessBeaconBlockRoot(*beaconRoot, evm, statedb) - } - var blobGasUsed uint64 - - for i := 0; txIt.Next(); i++ { - tx, err := txIt.Tx() - if err != nil { - log.Warn("rejected tx", "index", i, "error", err) - rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()}) - continue - } - if tx.Type() == types.BlobTxType && vmContext.BlobBaseFee == nil { - errMsg := "blob tx used but field env.ExcessBlobGas missing" - log.Warn("rejected tx", "index", i, "hash", tx.Hash(), "error", errMsg) - rejectedTxs = append(rejectedTxs, &rejectedTx{i, errMsg}) - continue - } - msg, err := core.TransactionToMessage(tx, signer, pre.Env.BaseFee) - if err != nil { - log.Warn("rejected tx", "index", i, "hash", tx.Hash(), "error", err) - rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()}) - continue - } - if tx.Type() == types.BlobTxType { - txBlobGas := uint64(params.BlobTxBlobGasPerBlob * len(tx.BlobHashes())) - if used, max := blobGasUsed+txBlobGas, uint64(params.MaxBlobGasPerBlock); used > max { - err := fmt.Errorf("blob gas (%d) would exceed maximum allowance %d", used, max) - log.Warn("rejected tx", "index", i, "err", err) - rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()}) - continue - } - blobGasUsed += txBlobGas - } - tracer, err := getTracerFn(txIndex, tx.Hash()) - if err != nil { - return nil, nil, nil, err - } - vmConfig.Tracer = tracer - statedb.SetTxContext(tx.Hash(), txIndex) - - var ( - txContext = core.NewEVMTxContext(msg) - snapshot = statedb.Snapshot() - prevGas = gaspool.Gas() - ) - evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig) - - // (ret []byte, usedGas uint64, failed bool, err error) - msgResult, err := core.ApplyMessage(evm, msg, gaspool) - if err != nil { - statedb.RevertToSnapshot(snapshot) - log.Info("rejected tx", "index", i, "hash", tx.Hash(), "from", msg.From, "error", err) - rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()}) - gaspool.SetGas(prevGas) - continue - } - includedTxs = append(includedTxs, tx) - if hashError != nil { - return nil, nil, nil, NewError(ErrorMissingBlockhash, hashError) - } - gasUsed += msgResult.UsedGas - - // Receipt: - { - var root []byte - if chainConfig.IsByzantium(vmContext.BlockNumber) { - statedb.Finalise(true) - } else { - root = statedb.IntermediateRoot(chainConfig.IsEIP158(vmContext.BlockNumber)).Bytes() - } - - // Create a new receipt for the transaction, storing the intermediate root and - // gas used by the tx. - receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: gasUsed} - if msgResult.Failed() { - receipt.Status = types.ReceiptStatusFailed - } else { - receipt.Status = types.ReceiptStatusSuccessful - } - receipt.TxHash = tx.Hash() - receipt.GasUsed = msgResult.UsedGas - - // If the transaction created a contract, store the creation address in the receipt. - if msg.To == nil { - receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce()) - } - - // Set the receipt logs and create the bloom filter. - receipt.Logs = statedb.GetLogs(tx.Hash(), vmContext.BlockNumber.Uint64(), blockHash) - receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) - // These three are non-consensus fields: - //receipt.BlockHash - //receipt.BlockNumber - receipt.TransactionIndex = uint(txIndex) - receipts = append(receipts, receipt) - } - - txIndex++ - } - statedb.IntermediateRoot(chainConfig.IsEIP158(vmContext.BlockNumber)) - // Add mining reward? (-1 means rewards are disabled) - if miningReward >= 0 { - // Add mining reward. The mining reward may be `0`, which only makes a difference in the cases - // where - // - the coinbase self-destructed, or - // - there are only 'bad' transactions, which aren't executed. In those cases, - // the coinbase gets no txfee, so isn't created, and thus needs to be touched - var ( - blockReward = big.NewInt(miningReward) - minerReward = new(big.Int).Set(blockReward) - perOmmer = new(big.Int).Div(blockReward, big.NewInt(32)) - ) - for _, ommer := range pre.Env.Ommers { - // Add 1/32th for each ommer included - minerReward.Add(minerReward, perOmmer) - // Add (8-delta)/8 - reward := big.NewInt(8) - reward.Sub(reward, new(big.Int).SetUint64(ommer.Delta)) - reward.Mul(reward, blockReward) - reward.Div(reward, big.NewInt(8)) - statedb.AddBalance(ommer.Address, reward) - } - statedb.AddBalance(pre.Env.Coinbase, minerReward) - } - // Commit block - root, err := statedb.Commit(vmContext.BlockNumber.Uint64(), chainConfig.IsEIP158(vmContext.BlockNumber), false) - if err != nil { - return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not commit state: %v", err)) - } - execRs := &ExecutionResult{ - StateRoot: root, - TxRoot: types.DeriveSha(includedTxs, trie.NewStackTrie(nil)), - ReceiptRoot: types.DeriveSha(receipts, trie.NewStackTrie(nil)), - Bloom: types.CreateBloom(receipts), - LogsHash: rlpHash(statedb.Logs()), - Receipts: receipts, - Rejected: rejectedTxs, - Difficulty: (*math.HexOrDecimal256)(vmContext.Difficulty), - GasUsed: (math.HexOrDecimal64)(gasUsed), - BaseFee: (*math.HexOrDecimal256)(vmContext.BaseFee), - } - if vmContext.BlobBaseFee != nil { - execRs.CurrentExcessBlobGas = (*math.HexOrDecimal64)(&excessBlobGas) - execRs.CurrentBlobGasUsed = (*math.HexOrDecimal64)(&blobGasUsed) - } - // Re-create statedb instance with new root upon the updated database - // for accessing latest states. - statedb, err = state.New(root, statedb.Database(), nil) - if err != nil { - return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("could not reopen state: %v", err)) - } - body, _ := rlp.EncodeToBytes(includedTxs) - return statedb, execRs, body, nil -} - -func MakePreState(db ethdb.Database, accounts core.GenesisAlloc) *state.StateDB { - sdb := state.NewDatabaseWithConfig(db, &trie.Config{Preimages: true}) - statedb, _ := state.New(types.EmptyRootHash, sdb, nil) - for addr, a := range accounts { - statedb.SetCode(addr, a.Code) - statedb.SetNonce(addr, a.Nonce) - statedb.SetBalance(addr, a.Balance) - for k, v := range a.Storage { - statedb.SetState(addr, k, v) - } - } - // Commit and re-open to start with a clean state. - root, _ := statedb.Commit(0, false, false) - statedb, _ = state.New(root, sdb, nil) - return statedb -} - -func rlpHash(x interface{}) (h common.Hash) { - hw := sha3.NewLegacyKeccak256() - rlp.Encode(hw, x) - hw.Sum(h[:0]) - return h -} diff --git a/cmd/evm/internal/t8ntool/flags.go b/cmd/evm/internal/t8ntool/flags.go deleted file mode 100644 index ff70eb6a58..0000000000 --- a/cmd/evm/internal/t8ntool/flags.go +++ /dev/null @@ -1,159 +0,0 @@ -// (c) 2023, Ava Labs, Inc. -// -// This file is a derived work, based on the go-ethereum library whose original -// notices appear below. -// -// It is distributed under a license compatible with the licensing terms of the -// original code from which it is derived. -// -// Much love to the original authors for their work. -// ********** -// Copyright 2020 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package t8ntool - -import ( - "fmt" - "strings" - - "github.com/ava-labs/subnet-evm/core/vm" - "github.com/ava-labs/subnet-evm/tests" - "github.com/urfave/cli/v2" -) - -var ( - TraceFlag = &cli.BoolFlag{ - Name: "trace", - Usage: "Configures the use of the JSON opcode tracer. This tracer emits traces to files as trace--.jsonl", - } - TraceTracerFlag = &cli.StringFlag{ - Name: "trace.tracer", - Usage: "Configures the use of a custom tracer, e.g native or js tracers. Examples are callTracer and 4byteTracer. These tracers emit results into files as trace--.json", - } - TraceTracerConfigFlag = &cli.StringFlag{ - Name: "trace.jsonconfig", - Usage: "The configurations for the custom tracer specified by --trace.tracer. If provided, must be in JSON format", - } - TraceEnableMemoryFlag = &cli.BoolFlag{ - Name: "trace.memory", - Usage: "Enable full memory dump in traces", - } - TraceDisableStackFlag = &cli.BoolFlag{ - Name: "trace.nostack", - Usage: "Disable stack output in traces", - } - TraceEnableReturnDataFlag = &cli.BoolFlag{ - Name: "trace.returndata", - Usage: "Enable return data output in traces", - } - OutputBasedir = &cli.StringFlag{ - Name: "output.basedir", - Usage: "Specifies where output files are placed. Will be created if it does not exist.", - Value: "", - } - OutputBodyFlag = &cli.StringFlag{ - Name: "output.body", - Usage: "If set, the RLP of the transactions (block body) will be written to this file.", - Value: "", - } - OutputAllocFlag = &cli.StringFlag{ - Name: "output.alloc", - Usage: "Determines where to put the `alloc` of the post-state.\n" + - "\t`stdout` - into the stdout output\n" + - "\t`stderr` - into the stderr output\n" + - "\t - into the file ", - Value: "alloc.json", - } - OutputResultFlag = &cli.StringFlag{ - Name: "output.result", - Usage: "Determines where to put the `result` (stateroot, txroot etc) of the post-state.\n" + - "\t`stdout` - into the stdout output\n" + - "\t`stderr` - into the stderr output\n" + - "\t - into the file ", - Value: "result.json", - } - OutputBlockFlag = &cli.StringFlag{ - Name: "output.block", - Usage: "Determines where to put the `block` after building.\n" + - "\t`stdout` - into the stdout output\n" + - "\t`stderr` - into the stderr output\n" + - "\t - into the file ", - Value: "block.json", - } - InputAllocFlag = &cli.StringFlag{ - Name: "input.alloc", - Usage: "`stdin` or file name of where to find the prestate alloc to use.", - Value: "alloc.json", - } - InputEnvFlag = &cli.StringFlag{ - Name: "input.env", - Usage: "`stdin` or file name of where to find the prestate env to use.", - Value: "env.json", - } - InputTxsFlag = &cli.StringFlag{ - Name: "input.txs", - Usage: "`stdin` or file name of where to find the transactions to apply. " + - "If the file extension is '.rlp', then the data is interpreted as an RLP list of signed transactions." + - "The '.rlp' format is identical to the output.body format.", - Value: "txs.json", - } - InputHeaderFlag = &cli.StringFlag{ - Name: "input.header", - Usage: "`stdin` or file name of where to find the block header to use.", - Value: "header.json", - } - InputOmmersFlag = &cli.StringFlag{ - Name: "input.ommers", - Usage: "`stdin` or file name of where to find the list of ommer header RLPs to use.", - } - InputTxsRlpFlag = &cli.StringFlag{ - Name: "input.txs", - Usage: "`stdin` or file name of where to find the transactions list in RLP form.", - Value: "txs.rlp", - } - SealCliqueFlag = &cli.StringFlag{ - Name: "seal.clique", - Usage: "Seal block with Clique. `stdin` or file name of where to find the Clique sealing data.", - } - RewardFlag = &cli.Int64Flag{ - Name: "state.reward", - Usage: "Mining reward. Set to -1 to disable", - Value: 0, - } - ChainIDFlag = &cli.Int64Flag{ - Name: "state.chainid", - Usage: "ChainID to use", - Value: 1, - } - ForknameFlag = &cli.StringFlag{ - Name: "state.fork", - Usage: fmt.Sprintf("Name of ruleset to use."+ - "\n\tAvailable forknames:"+ - "\n\t %v"+ - "\n\tAvailable extra eips:"+ - "\n\t %v"+ - "\n\tSyntax (+ExtraEip)", - strings.Join(tests.AvailableForks(), "\n\t "), - strings.Join(vm.ActivateableEips(), ", ")), - Value: "GrayGlacier", - } - VerbosityFlag = &cli.IntFlag{ - Name: "verbosity", - Usage: "sets the verbosity level", - Value: 3, - } -) diff --git a/cmd/evm/internal/t8ntool/gen_header.go b/cmd/evm/internal/t8ntool/gen_header.go deleted file mode 100644 index 6ace0fb35d..0000000000 --- a/cmd/evm/internal/t8ntool/gen_header.go +++ /dev/null @@ -1,153 +0,0 @@ -// Code generated by github.com/fjl/gencodec. DO NOT EDIT. - -package t8ntool - -import ( - "encoding/json" - "errors" - "math/big" - - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/common/math" -) - -var _ = (*headerMarshaling)(nil) - -// MarshalJSON marshals as JSON. -func (h header) MarshalJSON() ([]byte, error) { - type header struct { - ParentHash common.Hash `json:"parentHash"` - OmmerHash *common.Hash `json:"sha3Uncles"` - Coinbase *common.Address `json:"miner"` - Root common.Hash `json:"stateRoot" gencodec:"required"` - TxHash *common.Hash `json:"transactionsRoot"` - ReceiptHash *common.Hash `json:"receiptsRoot"` - Bloom types.Bloom `json:"logsBloom"` - Difficulty *math.HexOrDecimal256 `json:"difficulty"` - Number *math.HexOrDecimal256 `json:"number" gencodec:"required"` - GasLimit math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"` - GasUsed math.HexOrDecimal64 `json:"gasUsed"` - Time math.HexOrDecimal64 `json:"timestamp" gencodec:"required"` - Extra hexutil.Bytes `json:"extraData"` - MixDigest common.Hash `json:"mixHash"` - Nonce *types.BlockNonce `json:"nonce"` - BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas" rlp:"optional"` - BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed" rlp:"optional"` - ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas" rlp:"optional"` - ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` - } - var enc header - enc.ParentHash = h.ParentHash - enc.OmmerHash = h.OmmerHash - enc.Coinbase = h.Coinbase - enc.Root = h.Root - enc.TxHash = h.TxHash - enc.ReceiptHash = h.ReceiptHash - enc.Bloom = h.Bloom - enc.Difficulty = (*math.HexOrDecimal256)(h.Difficulty) - enc.Number = (*math.HexOrDecimal256)(h.Number) - enc.GasLimit = math.HexOrDecimal64(h.GasLimit) - enc.GasUsed = math.HexOrDecimal64(h.GasUsed) - enc.Time = math.HexOrDecimal64(h.Time) - enc.Extra = h.Extra - enc.MixDigest = h.MixDigest - enc.Nonce = h.Nonce - enc.BaseFee = (*math.HexOrDecimal256)(h.BaseFee) - enc.BlobGasUsed = (*math.HexOrDecimal64)(h.BlobGasUsed) - enc.ExcessBlobGas = (*math.HexOrDecimal64)(h.ExcessBlobGas) - enc.ParentBeaconBlockRoot = h.ParentBeaconBlockRoot - return json.Marshal(&enc) -} - -// UnmarshalJSON unmarshals from JSON. -func (h *header) UnmarshalJSON(input []byte) error { - type header struct { - ParentHash *common.Hash `json:"parentHash"` - OmmerHash *common.Hash `json:"sha3Uncles"` - Coinbase *common.Address `json:"miner"` - Root *common.Hash `json:"stateRoot" gencodec:"required"` - TxHash *common.Hash `json:"transactionsRoot"` - ReceiptHash *common.Hash `json:"receiptsRoot"` - Bloom *types.Bloom `json:"logsBloom"` - Difficulty *math.HexOrDecimal256 `json:"difficulty"` - Number *math.HexOrDecimal256 `json:"number" gencodec:"required"` - GasLimit *math.HexOrDecimal64 `json:"gasLimit" gencodec:"required"` - GasUsed *math.HexOrDecimal64 `json:"gasUsed"` - Time *math.HexOrDecimal64 `json:"timestamp" gencodec:"required"` - Extra *hexutil.Bytes `json:"extraData"` - MixDigest *common.Hash `json:"mixHash"` - Nonce *types.BlockNonce `json:"nonce"` - BaseFee *math.HexOrDecimal256 `json:"baseFeePerGas" rlp:"optional"` - BlobGasUsed *math.HexOrDecimal64 `json:"blobGasUsed" rlp:"optional"` - ExcessBlobGas *math.HexOrDecimal64 `json:"excessBlobGas" rlp:"optional"` - ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` - } - var dec header - if err := json.Unmarshal(input, &dec); err != nil { - return err - } - if dec.ParentHash != nil { - h.ParentHash = *dec.ParentHash - } - if dec.OmmerHash != nil { - h.OmmerHash = dec.OmmerHash - } - if dec.Coinbase != nil { - h.Coinbase = dec.Coinbase - } - if dec.Root == nil { - return errors.New("missing required field 'stateRoot' for header") - } - h.Root = *dec.Root - if dec.TxHash != nil { - h.TxHash = dec.TxHash - } - if dec.ReceiptHash != nil { - h.ReceiptHash = dec.ReceiptHash - } - if dec.Bloom != nil { - h.Bloom = *dec.Bloom - } - if dec.Difficulty != nil { - h.Difficulty = (*big.Int)(dec.Difficulty) - } - if dec.Number == nil { - return errors.New("missing required field 'number' for header") - } - h.Number = (*big.Int)(dec.Number) - if dec.GasLimit == nil { - return errors.New("missing required field 'gasLimit' for header") - } - h.GasLimit = uint64(*dec.GasLimit) - if dec.GasUsed != nil { - h.GasUsed = uint64(*dec.GasUsed) - } - if dec.Time == nil { - return errors.New("missing required field 'timestamp' for header") - } - h.Time = uint64(*dec.Time) - if dec.Extra != nil { - h.Extra = *dec.Extra - } - if dec.MixDigest != nil { - h.MixDigest = *dec.MixDigest - } - if dec.Nonce != nil { - h.Nonce = dec.Nonce - } - if dec.BaseFee != nil { - h.BaseFee = (*big.Int)(dec.BaseFee) - } - if dec.BlobGasUsed != nil { - h.BlobGasUsed = (*uint64)(dec.BlobGasUsed) - } - if dec.ExcessBlobGas != nil { - h.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas) - } - if dec.ParentBeaconBlockRoot != nil { - h.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot - } - return nil -} diff --git a/cmd/evm/internal/t8ntool/gen_stenv.go b/cmd/evm/internal/t8ntool/gen_stenv.go deleted file mode 100644 index fcd3431480..0000000000 --- a/cmd/evm/internal/t8ntool/gen_stenv.go +++ /dev/null @@ -1,157 +0,0 @@ -// Code generated by github.com/fjl/gencodec. DO NOT EDIT. - -package t8ntool - -import ( - "encoding/json" - "errors" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/math" -) - -var _ = (*stEnvMarshaling)(nil) - -// MarshalJSON marshals as JSON. -func (s stEnv) MarshalJSON() ([]byte, error) { - type stEnv struct { - Coinbase common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"` - Difficulty *math.HexOrDecimal256 `json:"currentDifficulty"` - Random *math.HexOrDecimal256 `json:"currentRandom"` - ParentDifficulty *math.HexOrDecimal256 `json:"parentDifficulty"` - ParentBaseFee *math.HexOrDecimal256 `json:"parentBaseFee,omitempty"` - ParentGasUsed math.HexOrDecimal64 `json:"parentGasUsed,omitempty"` - ParentGasLimit math.HexOrDecimal64 `json:"parentGasLimit,omitempty"` - MinBaseFee *math.HexOrDecimal256 `json:"minBaseFee,omitempty"` - GasLimit math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"` - Number math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"` - Timestamp math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"` - ParentTimestamp math.HexOrDecimal64 `json:"parentTimestamp,omitempty"` - BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` - Ommers []ommer `json:"ommers,omitempty"` - BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"` - ParentUncleHash common.Hash `json:"parentUncleHash"` - ExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas,omitempty"` - ParentExcessBlobGas *math.HexOrDecimal64 `json:"parentExcessBlobGas,omitempty"` - ParentBlobGasUsed *math.HexOrDecimal64 `json:"parentBlobGasUsed,omitempty"` - ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"` - } - var enc stEnv - enc.Coinbase = common.UnprefixedAddress(s.Coinbase) - enc.Difficulty = (*math.HexOrDecimal256)(s.Difficulty) - enc.Random = (*math.HexOrDecimal256)(s.Random) - enc.ParentDifficulty = (*math.HexOrDecimal256)(s.ParentDifficulty) - enc.ParentBaseFee = (*math.HexOrDecimal256)(s.ParentBaseFee) - enc.ParentGasUsed = math.HexOrDecimal64(s.ParentGasUsed) - enc.ParentGasLimit = math.HexOrDecimal64(s.ParentGasLimit) - enc.MinBaseFee = (*math.HexOrDecimal256)(s.MinBaseFee) - enc.GasLimit = math.HexOrDecimal64(s.GasLimit) - enc.Number = math.HexOrDecimal64(s.Number) - enc.Timestamp = math.HexOrDecimal64(s.Timestamp) - enc.ParentTimestamp = math.HexOrDecimal64(s.ParentTimestamp) - enc.BlockHashes = s.BlockHashes - enc.Ommers = s.Ommers - enc.BaseFee = (*math.HexOrDecimal256)(s.BaseFee) - enc.ParentUncleHash = s.ParentUncleHash - enc.ExcessBlobGas = (*math.HexOrDecimal64)(s.ExcessBlobGas) - enc.ParentExcessBlobGas = (*math.HexOrDecimal64)(s.ParentExcessBlobGas) - enc.ParentBlobGasUsed = (*math.HexOrDecimal64)(s.ParentBlobGasUsed) - enc.ParentBeaconBlockRoot = s.ParentBeaconBlockRoot - return json.Marshal(&enc) -} - -// UnmarshalJSON unmarshals from JSON. -func (s *stEnv) UnmarshalJSON(input []byte) error { - type stEnv struct { - Coinbase *common.UnprefixedAddress `json:"currentCoinbase" gencodec:"required"` - Difficulty *math.HexOrDecimal256 `json:"currentDifficulty"` - Random *math.HexOrDecimal256 `json:"currentRandom"` - ParentDifficulty *math.HexOrDecimal256 `json:"parentDifficulty"` - ParentBaseFee *math.HexOrDecimal256 `json:"parentBaseFee,omitempty"` - ParentGasUsed *math.HexOrDecimal64 `json:"parentGasUsed,omitempty"` - ParentGasLimit *math.HexOrDecimal64 `json:"parentGasLimit,omitempty"` - MinBaseFee *math.HexOrDecimal256 `json:"minBaseFee,omitempty"` - GasLimit *math.HexOrDecimal64 `json:"currentGasLimit" gencodec:"required"` - Number *math.HexOrDecimal64 `json:"currentNumber" gencodec:"required"` - Timestamp *math.HexOrDecimal64 `json:"currentTimestamp" gencodec:"required"` - ParentTimestamp *math.HexOrDecimal64 `json:"parentTimestamp,omitempty"` - BlockHashes map[math.HexOrDecimal64]common.Hash `json:"blockHashes,omitempty"` - Ommers []ommer `json:"ommers,omitempty"` - BaseFee *math.HexOrDecimal256 `json:"currentBaseFee,omitempty"` - ParentUncleHash *common.Hash `json:"parentUncleHash"` - ExcessBlobGas *math.HexOrDecimal64 `json:"currentExcessBlobGas,omitempty"` - ParentExcessBlobGas *math.HexOrDecimal64 `json:"parentExcessBlobGas,omitempty"` - ParentBlobGasUsed *math.HexOrDecimal64 `json:"parentBlobGasUsed,omitempty"` - ParentBeaconBlockRoot *common.Hash `json:"parentBeaconBlockRoot"` - } - var dec stEnv - if err := json.Unmarshal(input, &dec); err != nil { - return err - } - if dec.Coinbase == nil { - return errors.New("missing required field 'currentCoinbase' for stEnv") - } - s.Coinbase = common.Address(*dec.Coinbase) - if dec.Difficulty != nil { - s.Difficulty = (*big.Int)(dec.Difficulty) - } - if dec.Random != nil { - s.Random = (*big.Int)(dec.Random) - } - if dec.ParentDifficulty != nil { - s.ParentDifficulty = (*big.Int)(dec.ParentDifficulty) - } - if dec.ParentBaseFee != nil { - s.ParentBaseFee = (*big.Int)(dec.ParentBaseFee) - } - if dec.ParentGasUsed != nil { - s.ParentGasUsed = uint64(*dec.ParentGasUsed) - } - if dec.ParentGasLimit != nil { - s.ParentGasLimit = uint64(*dec.ParentGasLimit) - } - if dec.MinBaseFee != nil { - s.MinBaseFee = (*big.Int)(dec.MinBaseFee) - } - if dec.GasLimit == nil { - return errors.New("missing required field 'currentGasLimit' for stEnv") - } - s.GasLimit = uint64(*dec.GasLimit) - if dec.Number == nil { - return errors.New("missing required field 'currentNumber' for stEnv") - } - s.Number = uint64(*dec.Number) - if dec.Timestamp == nil { - return errors.New("missing required field 'currentTimestamp' for stEnv") - } - s.Timestamp = uint64(*dec.Timestamp) - if dec.ParentTimestamp != nil { - s.ParentTimestamp = uint64(*dec.ParentTimestamp) - } - if dec.BlockHashes != nil { - s.BlockHashes = dec.BlockHashes - } - if dec.Ommers != nil { - s.Ommers = dec.Ommers - } - if dec.BaseFee != nil { - s.BaseFee = (*big.Int)(dec.BaseFee) - } - if dec.ParentUncleHash != nil { - s.ParentUncleHash = *dec.ParentUncleHash - } - if dec.ExcessBlobGas != nil { - s.ExcessBlobGas = (*uint64)(dec.ExcessBlobGas) - } - if dec.ParentExcessBlobGas != nil { - s.ParentExcessBlobGas = (*uint64)(dec.ParentExcessBlobGas) - } - if dec.ParentBlobGasUsed != nil { - s.ParentBlobGasUsed = (*uint64)(dec.ParentBlobGasUsed) - } - if dec.ParentBeaconBlockRoot != nil { - s.ParentBeaconBlockRoot = dec.ParentBeaconBlockRoot - } - return nil -} diff --git a/cmd/evm/internal/t8ntool/tracewriter.go b/cmd/evm/internal/t8ntool/tracewriter.go deleted file mode 100644 index cfdab261c8..0000000000 --- a/cmd/evm/internal/t8ntool/tracewriter.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2020 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package t8ntool - -import ( - "encoding/json" - "io" - "math/big" - - "github.com/ava-labs/subnet-evm/core/vm" - "github.com/ava-labs/subnet-evm/eth/tracers" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" -) - -// traceWriter is an vm.EVMLogger which also holds an inner logger/tracer. -// When the TxEnd event happens, the inner tracer result is written to the file, and -// the file is closed. -type traceWriter struct { - inner vm.EVMLogger - f io.WriteCloser -} - -// Compile-time interface check -var _ = vm.EVMLogger((*traceWriter)(nil)) - -func (t *traceWriter) CaptureTxEnd(restGas uint64) { - t.inner.CaptureTxEnd(restGas) - defer t.f.Close() - - if tracer, ok := t.inner.(tracers.Tracer); ok { - result, err := tracer.GetResult() - if err != nil { - log.Warn("Error in tracer", "err", err) - return - } - err = json.NewEncoder(t.f).Encode(result) - if err != nil { - log.Warn("Error writing tracer output", "err", err) - return - } - } -} - -func (t *traceWriter) CaptureTxStart(gasLimit uint64) { t.inner.CaptureTxStart(gasLimit) } -func (t *traceWriter) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { - t.inner.CaptureStart(env, from, to, create, input, gas, value) -} - -func (t *traceWriter) CaptureEnd(output []byte, gasUsed uint64, err error) { - t.inner.CaptureEnd(output, gasUsed, err) -} - -func (t *traceWriter) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { - t.inner.CaptureEnter(typ, from, to, input, gas, value) -} - -func (t *traceWriter) CaptureExit(output []byte, gasUsed uint64, err error) { - t.inner.CaptureExit(output, gasUsed, err) -} - -func (t *traceWriter) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) { - t.inner.CaptureState(pc, op, gas, cost, scope, rData, depth, err) -} -func (t *traceWriter) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) { - t.inner.CaptureFault(pc, op, gas, cost, scope, depth, err) -} diff --git a/cmd/evm/internal/t8ntool/transaction.go b/cmd/evm/internal/t8ntool/transaction.go deleted file mode 100644 index 9aeaa1bd70..0000000000 --- a/cmd/evm/internal/t8ntool/transaction.go +++ /dev/null @@ -1,187 +0,0 @@ -// (c) 2023, Ava Labs, Inc. -// -// This file is a derived work, based on the go-ethereum library whose original -// notices appear below. -// -// It is distributed under a license compatible with the licensing terms of the -// original code from which it is derived. -// -// Much love to the original authors for their work. -// ********** -// Copyright 2021 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package t8ntool - -import ( - "encoding/json" - "errors" - "fmt" - "math/big" - "os" - "strings" - - "github.com/ava-labs/subnet-evm/core" - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/tests" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/rlp" - "github.com/urfave/cli/v2" -) - -type result struct { - Error error - Address common.Address - Hash common.Hash - IntrinsicGas uint64 -} - -// MarshalJSON marshals as JSON with a hash. -func (r *result) MarshalJSON() ([]byte, error) { - type xx struct { - Error string `json:"error,omitempty"` - Address *common.Address `json:"address,omitempty"` - Hash *common.Hash `json:"hash,omitempty"` - IntrinsicGas hexutil.Uint64 `json:"intrinsicGas,omitempty"` - } - var out xx - if r.Error != nil { - out.Error = r.Error.Error() - } - if r.Address != (common.Address{}) { - out.Address = &r.Address - } - if r.Hash != (common.Hash{}) { - out.Hash = &r.Hash - } - out.IntrinsicGas = hexutil.Uint64(r.IntrinsicGas) - return json.Marshal(out) -} - -func Transaction(ctx *cli.Context) error { - var ( - err error - ) - // We need to load the transactions. May be either in stdin input or in files. - // Check if anything needs to be read from stdin - var ( - txStr = ctx.String(InputTxsFlag.Name) - inputData = &input{} - chainConfig *params.ChainConfig - ) - // Construct the chainconfig - if cConf, _, err := tests.GetChainConfig(ctx.String(ForknameFlag.Name)); err != nil { - return NewError(ErrorConfig, fmt.Errorf("failed constructing chain configuration: %v", err)) - } else { - chainConfig = cConf - } - // Set the chain id - chainConfig.ChainID = big.NewInt(ctx.Int64(ChainIDFlag.Name)) - var body hexutil.Bytes - if txStr == stdinSelector { - decoder := json.NewDecoder(os.Stdin) - if err := decoder.Decode(inputData); err != nil { - return NewError(ErrorJson, fmt.Errorf("failed unmarshaling stdin: %v", err)) - } - // Decode the body of already signed transactions - body = common.FromHex(inputData.TxRlp) - } else { - // Read input from file - inFile, err := os.Open(txStr) - if err != nil { - return NewError(ErrorIO, fmt.Errorf("failed reading txs file: %v", err)) - } - defer inFile.Close() - decoder := json.NewDecoder(inFile) - if strings.HasSuffix(txStr, ".rlp") { - if err := decoder.Decode(&body); err != nil { - return err - } - } else { - return NewError(ErrorIO, errors.New("only rlp supported")) - } - } - signer := types.MakeSigner(chainConfig, new(big.Int), 0) - // We now have the transactions in 'body', which is supposed to be an - // rlp list of transactions - it, err := rlp.NewListIterator([]byte(body)) - if err != nil { - return err - } - var results []result - for it.Next() { - if err := it.Err(); err != nil { - return NewError(ErrorIO, err) - } - var tx types.Transaction - err := rlp.DecodeBytes(it.Value(), &tx) - if err != nil { - results = append(results, result{Error: err}) - continue - } - r := result{Hash: tx.Hash()} - if sender, err := types.Sender(signer, &tx); err != nil { - r.Error = err - results = append(results, r) - continue - } else { - r.Address = sender - } - // Check intrinsic gas - rules := chainConfig.Rules(new(big.Int), 0) - if gas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.To() == nil, rules); err != nil { - r.Error = err - results = append(results, r) - continue - } else { - r.IntrinsicGas = gas - if tx.Gas() < gas { - r.Error = fmt.Errorf("%w: have %d, want %d", core.ErrIntrinsicGas, tx.Gas(), gas) - results = append(results, r) - continue - } - } - // Validate <256bit fields - switch { - case tx.Nonce()+1 < tx.Nonce(): - r.Error = errors.New("nonce exceeds 2^64-1") - case tx.Value().BitLen() > 256: - r.Error = errors.New("value exceeds 256 bits") - case tx.GasPrice().BitLen() > 256: - r.Error = errors.New("gasPrice exceeds 256 bits") - case tx.GasTipCap().BitLen() > 256: - r.Error = errors.New("maxPriorityFeePerGas exceeds 256 bits") - case tx.GasFeeCap().BitLen() > 256: - r.Error = errors.New("maxFeePerGas exceeds 256 bits") - case tx.GasFeeCap().Cmp(tx.GasTipCap()) < 0: - r.Error = errors.New("maxFeePerGas < maxPriorityFeePerGas") - case new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas())).BitLen() > 256: - r.Error = errors.New("gas * gasPrice exceeds 256 bits") - case new(big.Int).Mul(tx.GasFeeCap(), new(big.Int).SetUint64(tx.Gas())).BitLen() > 256: - r.Error = errors.New("gas * maxFeePerGas exceeds 256 bits") - } - // Check whether the init code size has been exceeded. - if chainConfig.IsDurango(0) && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize { - r.Error = errors.New("max initcode size exceeded") - } - results = append(results, r) - } - out, err := json.MarshalIndent(results, "", " ") - fmt.Println(string(out)) - return err -} diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go deleted file mode 100644 index 5bb33c1e91..0000000000 --- a/cmd/evm/internal/t8ntool/transition.go +++ /dev/null @@ -1,339 +0,0 @@ -// (c) 2023, Ava Labs, Inc. -// -// This file is a derived work, based on the go-ethereum library whose original -// notices appear below. -// -// It is distributed under a license compatible with the licensing terms of the -// original code from which it is derived. -// -// Much love to the original authors for their work. -// ********** -// Copyright 2020 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package t8ntool - -import ( - "encoding/json" - "errors" - "fmt" - "math/big" - "os" - "path" - - "github.com/ava-labs/subnet-evm/consensus/dummy" - "github.com/ava-labs/subnet-evm/core" - "github.com/ava-labs/subnet-evm/core/state" - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/core/vm" - "github.com/ava-labs/subnet-evm/eth/tracers" - "github.com/ava-labs/subnet-evm/eth/tracers/logger" - "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/tests" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/log" - "github.com/urfave/cli/v2" -) - -const ( - ErrorEVM = 2 - ErrorConfig = 3 - ErrorMissingBlockhash = 4 - - ErrorJson = 10 - ErrorIO = 11 - ErrorRlp = 12 - - stdinSelector = "stdin" -) - -type NumberedError struct { - errorCode int - err error -} - -func NewError(errorCode int, err error) *NumberedError { - return &NumberedError{errorCode, err} -} - -func (n *NumberedError) Error() string { - return fmt.Sprintf("ERROR(%d): %v", n.errorCode, n.err.Error()) -} - -func (n *NumberedError) ExitCode() int { - return n.errorCode -} - -// compile-time conformance test -var ( - _ cli.ExitCoder = (*NumberedError)(nil) -) - -type input struct { - Alloc core.GenesisAlloc `json:"alloc,omitempty"` - Env *stEnv `json:"env,omitempty"` - Txs []*txWithKey `json:"txs,omitempty"` - TxRlp string `json:"txsRlp,omitempty"` -} - -func Transition(ctx *cli.Context) error { - var getTracer = func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) { return nil, nil } - - baseDir, err := createBasedir(ctx) - if err != nil { - return NewError(ErrorIO, fmt.Errorf("failed creating output basedir: %v", err)) - } - - if ctx.Bool(TraceFlag.Name) { // JSON opcode tracing - // Configure the EVM logger - logConfig := &logger.Config{ - DisableStack: ctx.Bool(TraceDisableStackFlag.Name), - EnableMemory: ctx.Bool(TraceEnableMemoryFlag.Name), - EnableReturnData: ctx.Bool(TraceEnableReturnDataFlag.Name), - Debug: true, - } - getTracer = func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) { - traceFile, err := os.Create(path.Join(baseDir, fmt.Sprintf("trace-%d-%v.jsonl", txIndex, txHash.String()))) - if err != nil { - return nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err)) - } - return &traceWriter{logger.NewJSONLogger(logConfig, traceFile), traceFile}, nil - } - } else if ctx.IsSet(TraceTracerFlag.Name) { - var config json.RawMessage - if ctx.IsSet(TraceTracerConfigFlag.Name) { - config = []byte(ctx.String(TraceTracerConfigFlag.Name)) - } - getTracer = func(txIndex int, txHash common.Hash) (vm.EVMLogger, error) { - traceFile, err := os.Create(path.Join(baseDir, fmt.Sprintf("trace-%d-%v.json", txIndex, txHash.String()))) - if err != nil { - return nil, NewError(ErrorIO, fmt.Errorf("failed creating trace-file: %v", err)) - } - tracer, err := tracers.DefaultDirectory.New(ctx.String(TraceTracerFlag.Name), nil, config) - if err != nil { - return nil, NewError(ErrorConfig, fmt.Errorf("failed instantiating tracer: %w", err)) - } - return &traceWriter{tracer, traceFile}, nil - } - } - // We need to load three things: alloc, env and transactions. May be either in - // stdin input or in files. - // Check if anything needs to be read from stdin - var ( - prestate Prestate - txIt txIterator // txs to apply - allocStr = ctx.String(InputAllocFlag.Name) - - envStr = ctx.String(InputEnvFlag.Name) - txStr = ctx.String(InputTxsFlag.Name) - inputData = &input{} - ) - // Figure out the prestate alloc - if allocStr == stdinSelector || envStr == stdinSelector || txStr == stdinSelector { - decoder := json.NewDecoder(os.Stdin) - if err := decoder.Decode(inputData); err != nil { - return NewError(ErrorJson, fmt.Errorf("failed unmarshaling stdin: %v", err)) - } - } - if allocStr != stdinSelector { - if err := readFile(allocStr, "alloc", &inputData.Alloc); err != nil { - return err - } - } - prestate.Pre = inputData.Alloc - - // Set the block environment - if envStr != stdinSelector { - var env stEnv - if err := readFile(envStr, "env", &env); err != nil { - return err - } - inputData.Env = &env - } - prestate.Env = *inputData.Env - - vmConfig := vm.Config{} - // Construct the chainconfig - var chainConfig *params.ChainConfig - if cConf, extraEips, err := tests.GetChainConfig(ctx.String(ForknameFlag.Name)); err != nil { - return NewError(ErrorConfig, fmt.Errorf("failed constructing chain configuration: %v", err)) - } else { - chainConfig = cConf - vmConfig.ExtraEips = extraEips - } - // Set the chain id - chainConfig.ChainID = big.NewInt(ctx.Int64(ChainIDFlag.Name)) - - if txIt, err = loadTransactions(txStr, inputData, prestate.Env, chainConfig); err != nil { - return err - } - if err := applyLondonChecks(&prestate.Env, chainConfig); err != nil { - return err - } - // NOTE: Removed isMerged logic here. - if prestate.Env.Random != nil { - // NOTE: subnet-evm continues to return the difficulty value for the RANDOM opcode, - // so for testing if Random is set in the environment, we copy it to difficulty instead. - prestate.Env.Difficulty = prestate.Env.Random - } - if err := applyCancunChecks(&prestate.Env, chainConfig); err != nil { - return err - } - // Run the test and aggregate the result - s, result, body, err := prestate.Apply(vmConfig, chainConfig, txIt, ctx.Int64(RewardFlag.Name), getTracer) - if err != nil { - return err - } - // Dump the excution result - collector := make(Alloc) - s.DumpToCollector(collector, nil) - return dispatchOutput(ctx, baseDir, result, collector, body) -} - -func applyLondonChecks(env *stEnv, chainConfig *params.ChainConfig) error { - // NOTE: IsLondon replaced with IsSubnetEVM here - if !chainConfig.IsSubnetEVM(env.Timestamp) { - return nil - } - // Sanity check, to not `panic` in state_transition - if env.BaseFee != nil { - // Already set, base fee has precedent over parent base fee. - return nil - } - if env.ParentBaseFee == nil || env.Number == 0 { - return NewError(ErrorConfig, errors.New("EIP-1559 config but missing 'currentBaseFee' in env section")) - } - parent := &types.Header{ - Number: new(big.Int).SetUint64(env.Number - 1), - Time: env.ParentTimestamp, - BaseFee: env.ParentBaseFee, - GasUsed: env.ParentGasUsed, - GasLimit: env.ParentGasLimit, - Extra: make([]byte, params.DynamicFeeExtraDataSize), // TODO: consider passing extra through env - } - feeConfig := params.DefaultFeeConfig - if env.MinBaseFee != nil { - // Override the default min base fee if it's set in the env - feeConfig.MinBaseFee = env.MinBaseFee - } - var err error - _, env.BaseFee, err = dummy.CalcBaseFee(chainConfig, feeConfig, parent, env.Timestamp) - if err != nil { - return NewError(ErrorConfig, fmt.Errorf("failed calculating base fee: %v", err)) - } - return nil -} - -func applyCancunChecks(env *stEnv, chainConfig *params.ChainConfig) error { - if !chainConfig.IsCancun(big.NewInt(int64(env.Number)), env.Timestamp) { - env.ParentBeaconBlockRoot = nil // un-set it if it has been set too early - return nil - } - // Post-cancun - // We require EIP-4788 beacon root to be set in the env - if env.ParentBeaconBlockRoot == nil { - return NewError(ErrorConfig, errors.New("post-cancun env requires parentBeaconBlockRoot to be set")) - } - return nil -} - -type Alloc map[common.Address]core.GenesisAccount - -func (g Alloc) OnRoot(common.Hash) {} - -func (g Alloc) OnAccount(addr *common.Address, dumpAccount state.DumpAccount) { - if addr == nil { - return - } - balance, _ := new(big.Int).SetString(dumpAccount.Balance, 10) - var storage map[common.Hash]common.Hash - if dumpAccount.Storage != nil { - storage = make(map[common.Hash]common.Hash) - for k, v := range dumpAccount.Storage { - storage[k] = common.HexToHash(v) - } - } - genesisAccount := core.GenesisAccount{ - Code: dumpAccount.Code, - Storage: storage, - Balance: balance, - Nonce: dumpAccount.Nonce, - } - g[*addr] = genesisAccount -} - -// saveFile marshals the object to the given file -func saveFile(baseDir, filename string, data interface{}) error { - b, err := json.MarshalIndent(data, "", " ") - if err != nil { - return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) - } - location := path.Join(baseDir, filename) - if err = os.WriteFile(location, b, 0644); err != nil { - return NewError(ErrorIO, fmt.Errorf("failed writing output: %v", err)) - } - log.Info("Wrote file", "file", location) - return nil -} - -// dispatchOutput writes the output data to either stderr or stdout, or to the specified -// files -func dispatchOutput(ctx *cli.Context, baseDir string, result *ExecutionResult, alloc Alloc, body hexutil.Bytes) error { - stdOutObject := make(map[string]interface{}) - stdErrObject := make(map[string]interface{}) - dispatch := func(baseDir, fName, name string, obj interface{}) error { - switch fName { - case "stdout": - stdOutObject[name] = obj - case "stderr": - stdErrObject[name] = obj - case "": - // don't save - default: // save to file - if err := saveFile(baseDir, fName, obj); err != nil { - return err - } - } - return nil - } - if err := dispatch(baseDir, ctx.String(OutputAllocFlag.Name), "alloc", alloc); err != nil { - return err - } - if err := dispatch(baseDir, ctx.String(OutputResultFlag.Name), "result", result); err != nil { - return err - } - if err := dispatch(baseDir, ctx.String(OutputBodyFlag.Name), "body", body); err != nil { - return err - } - if len(stdOutObject) > 0 { - b, err := json.MarshalIndent(stdOutObject, "", " ") - if err != nil { - return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) - } - os.Stdout.Write(b) - os.Stdout.WriteString("\n") - } - if len(stdErrObject) > 0 { - b, err := json.MarshalIndent(stdErrObject, "", " ") - if err != nil { - return NewError(ErrorJson, fmt.Errorf("failed marshalling output: %v", err)) - } - os.Stderr.Write(b) - os.Stderr.WriteString("\n") - } - return nil -} diff --git a/cmd/evm/internal/t8ntool/tx_iterator.go b/cmd/evm/internal/t8ntool/tx_iterator.go deleted file mode 100644 index 6a7d909a56..0000000000 --- a/cmd/evm/internal/t8ntool/tx_iterator.go +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright 2023 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package t8ntool - -import ( - "bytes" - "crypto/ecdsa" - "encoding/json" - "fmt" - "io" - "os" - "strings" - - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/params" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" -) - -// txWithKey is a helper-struct, to allow us to use the types.Transaction along with -// a `secretKey`-field, for input -type txWithKey struct { - key *ecdsa.PrivateKey - tx *types.Transaction - protected bool -} - -func (t *txWithKey) UnmarshalJSON(input []byte) error { - // Read the metadata, if present - type txMetadata struct { - Key *common.Hash `json:"secretKey"` - Protected *bool `json:"protected"` - } - var data txMetadata - if err := json.Unmarshal(input, &data); err != nil { - return err - } - if data.Key != nil { - k := data.Key.Hex()[2:] - if ecdsaKey, err := crypto.HexToECDSA(k); err != nil { - return err - } else { - t.key = ecdsaKey - } - } - if data.Protected != nil { - t.protected = *data.Protected - } else { - t.protected = true - } - // Now, read the transaction itself - var tx types.Transaction - if err := json.Unmarshal(input, &tx); err != nil { - return err - } - t.tx = &tx - return nil -} - -// signUnsignedTransactions converts the input txs to canonical transactions. -// -// The transactions can have two forms, either -// 1. unsigned or -// 2. signed -// -// For (1), r, s, v, need so be zero, and the `secretKey` needs to be set. -// If so, we sign it here and now, with the given `secretKey` -// If the condition above is not met, then it's considered a signed transaction. -// -// To manage this, we read the transactions twice, first trying to read the secretKeys, -// and secondly to read them with the standard tx json format -func signUnsignedTransactions(txs []*txWithKey, signer types.Signer) (types.Transactions, error) { - var signedTxs []*types.Transaction - for i, tx := range txs { - var ( - v, r, s = tx.tx.RawSignatureValues() - signed *types.Transaction - err error - ) - if tx.key == nil || v.BitLen()+r.BitLen()+s.BitLen() != 0 { - // Already signed - signedTxs = append(signedTxs, tx.tx) - continue - } - // This transaction needs to be signed - if tx.protected { - signed, err = types.SignTx(tx.tx, signer, tx.key) - } else { - signed, err = types.SignTx(tx.tx, types.FrontierSigner{}, tx.key) - } - if err != nil { - return nil, NewError(ErrorJson, fmt.Errorf("tx %d: failed to sign tx: %v", i, err)) - } - signedTxs = append(signedTxs, signed) - } - return signedTxs, nil -} - -func loadTransactions(txStr string, inputData *input, env stEnv, chainConfig *params.ChainConfig) (txIterator, error) { - var txsWithKeys []*txWithKey - if txStr != stdinSelector { - data, err := os.ReadFile(txStr) - if err != nil { - return nil, NewError(ErrorIO, fmt.Errorf("failed reading txs file: %v", err)) - } - if strings.HasSuffix(txStr, ".rlp") { // A file containing an rlp list - var body hexutil.Bytes - if err := json.Unmarshal(data, &body); err != nil { - return nil, err - } - return newRlpTxIterator(body), nil - } - if err := json.Unmarshal(data, &txsWithKeys); err != nil { - return nil, NewError(ErrorJson, fmt.Errorf("failed unmarshaling txs-file: %v", err)) - } - } else { - if len(inputData.TxRlp) > 0 { - // Decode the body of already signed transactions - return newRlpTxIterator(common.FromHex(inputData.TxRlp)), nil - } - // JSON encoded transactions - txsWithKeys = inputData.Txs - } - // We may have to sign the transactions. - signer := types.LatestSignerForChainID(chainConfig.ChainID) - txs, err := signUnsignedTransactions(txsWithKeys, signer) - return newSliceTxIterator(txs), err -} - -type txIterator interface { - // Next returns true until EOF - Next() bool - // Tx returns the next transaction, OR an error. - Tx() (*types.Transaction, error) -} - -type sliceTxIterator struct { - idx int - txs []*types.Transaction -} - -func newSliceTxIterator(transactions types.Transactions) txIterator { - return &sliceTxIterator{0, transactions} -} - -func (ait *sliceTxIterator) Next() bool { - return ait.idx < len(ait.txs) -} - -func (ait *sliceTxIterator) Tx() (*types.Transaction, error) { - if ait.idx < len(ait.txs) { - ait.idx++ - return ait.txs[ait.idx-1], nil - } - return nil, io.EOF -} - -type rlpTxIterator struct { - in *rlp.Stream -} - -func newRlpTxIterator(rlpData []byte) txIterator { - in := rlp.NewStream(bytes.NewBuffer(rlpData), 1024*1024) - in.List() - return &rlpTxIterator{in} -} - -func (it *rlpTxIterator) Next() bool { - return it.in.MoreDataInList() -} - -func (it *rlpTxIterator) Tx() (*types.Transaction, error) { - var a types.Transaction - if err := it.in.Decode(&a); err != nil { - return nil, err - } - return &a, nil -} diff --git a/cmd/evm/internal/t8ntool/utils.go b/cmd/evm/internal/t8ntool/utils.go deleted file mode 100644 index 6a73913eb4..0000000000 --- a/cmd/evm/internal/t8ntool/utils.go +++ /dev/null @@ -1,64 +0,0 @@ -// (c) 2023, Ava Labs, Inc. -// -// This file is a derived work, based on the go-ethereum library whose original -// notices appear below. -// -// It is distributed under a license compatible with the licensing terms of the -// original code from which it is derived. -// -// Much love to the original authors for their work. -// ********** -// Copyright 2021 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package t8ntool - -import ( - "encoding/json" - "fmt" - "os" - - "github.com/urfave/cli/v2" -) - -// readFile reads the json-data in the provided path and marshals into dest. -func readFile(path, desc string, dest interface{}) error { - inFile, err := os.Open(path) - if err != nil { - return NewError(ErrorIO, fmt.Errorf("failed reading %s file: %v", desc, err)) - } - defer inFile.Close() - decoder := json.NewDecoder(inFile) - if err := decoder.Decode(dest); err != nil { - return NewError(ErrorJson, fmt.Errorf("failed unmarshaling %s file: %v", desc, err)) - } - return nil -} - -// createBasedir makes sure the basedir exists, if user specified one. -func createBasedir(ctx *cli.Context) (string, error) { - baseDir := "" - if ctx.IsSet(OutputBasedir.Name) { - if base := ctx.String(OutputBasedir.Name); len(base) > 0 { - err := os.MkdirAll(base, 0755) // //rw-r--r-- - if err != nil { - return "", err - } - baseDir = base - } - } - return baseDir, nil -} diff --git a/cmd/evm/main.go b/cmd/evm/main.go deleted file mode 100644 index 99c7f740d8..0000000000 --- a/cmd/evm/main.go +++ /dev/null @@ -1,265 +0,0 @@ -// (c) 2023, Ava Labs, Inc. -// -// This file is a derived work, based on the go-ethereum library whose original -// notices appear below. -// -// It is distributed under a license compatible with the licensing terms of the -// original code from which it is derived. -// -// Much love to the original authors for their work. -// ********** -// Copyright 2014 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -// evm executes EVM code snippets. -package main - -import ( - "fmt" - "math/big" - "os" - - "github.com/ava-labs/subnet-evm/cmd/evm/internal/t8ntool" - "github.com/ava-labs/subnet-evm/internal/debug" - "github.com/ava-labs/subnet-evm/internal/flags" - "github.com/urfave/cli/v2" - - // Force-load the tracer engines to trigger registration - _ "github.com/ava-labs/subnet-evm/eth/tracers/js" - _ "github.com/ava-labs/subnet-evm/eth/tracers/native" -) - -var ( - DebugFlag = &cli.BoolFlag{ - Name: "debug", - Usage: "output full trace logs", - Category: flags.VMCategory, - } - StatDumpFlag = &cli.BoolFlag{ - Name: "statdump", - Usage: "displays stack and heap memory information", - Category: flags.VMCategory, - } - CodeFlag = &cli.StringFlag{ - Name: "code", - Usage: "EVM code", - Category: flags.VMCategory, - } - CodeFileFlag = &cli.StringFlag{ - Name: "codefile", - Usage: "File containing EVM code. If '-' is specified, code is read from stdin ", - Category: flags.VMCategory, - } - GasFlag = &cli.Uint64Flag{ - Name: "gas", - Usage: "gas limit for the evm", - Value: 10000000000, - Category: flags.VMCategory, - } - PriceFlag = &flags.BigFlag{ - Name: "price", - Usage: "price set for the evm", - Value: new(big.Int), - Category: flags.VMCategory, - } - ValueFlag = &flags.BigFlag{ - Name: "value", - Usage: "value set for the evm", - Value: new(big.Int), - Category: flags.VMCategory, - } - DumpFlag = &cli.BoolFlag{ - Name: "dump", - Usage: "dumps the state after the run", - Category: flags.VMCategory, - } - InputFlag = &cli.StringFlag{ - Name: "input", - Usage: "input for the EVM", - Category: flags.VMCategory, - } - InputFileFlag = &cli.StringFlag{ - Name: "inputfile", - Usage: "file containing input for the EVM", - Category: flags.VMCategory, - } - BenchFlag = &cli.BoolFlag{ - Name: "bench", - Usage: "benchmark the execution", - Category: flags.VMCategory, - } - CreateFlag = &cli.BoolFlag{ - Name: "create", - Usage: "indicates the action should be create rather than call", - Category: flags.VMCategory, - } - GenesisFlag = &cli.StringFlag{ - Name: "prestate", - Usage: "JSON file with prestate (genesis) config", - Category: flags.VMCategory, - } - MachineFlag = &cli.BoolFlag{ - Name: "json", - Usage: "output trace logs in machine readable format (json)", - Category: flags.VMCategory, - } - SenderFlag = &cli.StringFlag{ - Name: "sender", - Usage: "The transaction origin", - Category: flags.VMCategory, - } - ReceiverFlag = &cli.StringFlag{ - Name: "receiver", - Usage: "The transaction receiver (execution context)", - Category: flags.VMCategory, - } - DisableMemoryFlag = &cli.BoolFlag{ - Name: "nomemory", - Value: true, - Usage: "disable memory output", - Category: flags.VMCategory, - } - DisableStackFlag = &cli.BoolFlag{ - Name: "nostack", - Usage: "disable stack output", - Category: flags.VMCategory, - } - DisableStorageFlag = &cli.BoolFlag{ - Name: "nostorage", - Usage: "disable storage output", - Category: flags.VMCategory, - } - DisableReturnDataFlag = &cli.BoolFlag{ - Name: "noreturndata", - Value: true, - Usage: "enable return data output", - Category: flags.VMCategory, - } -) - -var stateTransitionCommand = &cli.Command{ - Name: "transition", - Aliases: []string{"t8n"}, - Usage: "Executes a full state transition", - Action: t8ntool.Transition, - Flags: []cli.Flag{ - t8ntool.TraceFlag, - t8ntool.TraceTracerFlag, - t8ntool.TraceTracerConfigFlag, - t8ntool.TraceEnableMemoryFlag, - t8ntool.TraceDisableStackFlag, - t8ntool.TraceEnableReturnDataFlag, - t8ntool.OutputBasedir, - t8ntool.OutputAllocFlag, - t8ntool.OutputResultFlag, - t8ntool.OutputBodyFlag, - t8ntool.InputAllocFlag, - t8ntool.InputEnvFlag, - t8ntool.InputTxsFlag, - t8ntool.ForknameFlag, - t8ntool.ChainIDFlag, - t8ntool.RewardFlag, - }, -} - -var transactionCommand = &cli.Command{ - Name: "transaction", - Aliases: []string{"t9n"}, - Usage: "Performs transaction validation", - Action: t8ntool.Transaction, - Flags: []cli.Flag{ - t8ntool.InputTxsFlag, - t8ntool.ChainIDFlag, - t8ntool.ForknameFlag, - }, -} - -var blockBuilderCommand = &cli.Command{ - Name: "block-builder", - Aliases: []string{"b11r"}, - Usage: "Builds a block", - Action: t8ntool.BuildBlock, - Flags: []cli.Flag{ - t8ntool.OutputBasedir, - t8ntool.OutputBlockFlag, - t8ntool.InputHeaderFlag, - t8ntool.InputOmmersFlag, - t8ntool.InputTxsRlpFlag, - t8ntool.SealCliqueFlag, - }, -} - -// vmFlags contains flags related to running the EVM. -var vmFlags = []cli.Flag{ - CodeFlag, - CodeFileFlag, - CreateFlag, - GasFlag, - PriceFlag, - ValueFlag, - InputFlag, - InputFileFlag, - GenesisFlag, - SenderFlag, - ReceiverFlag, -} - -// traceFlags contains flags that configure tracing output. -var traceFlags = []cli.Flag{ - BenchFlag, - DebugFlag, - DumpFlag, - MachineFlag, - StatDumpFlag, - DisableMemoryFlag, - DisableStackFlag, - DisableStorageFlag, - DisableReturnDataFlag, -} - -var app = flags.NewApp("the evm command line interface") - -func init() { - app.Flags = flags.Merge(vmFlags, traceFlags, debug.Flags) - app.Commands = []*cli.Command{ - compileCommand, - disasmCommand, - runCommand, - stateTestCommand, - stateTransitionCommand, - transactionCommand, - blockBuilderCommand, - } - app.Before = func(ctx *cli.Context) error { - flags.MigrateGlobalFlags(ctx) - return debug.Setup(ctx) - } - app.After = func(ctx *cli.Context) error { - debug.Exit() - return nil - } -} - -func main() { - if err := app.Run(os.Args); err != nil { - code := 1 - if ec, ok := err.(*t8ntool.NumberedError); ok { - code = ec.ExitCode() - } - fmt.Fprintln(os.Stderr, err) - os.Exit(code) - } -} diff --git a/cmd/evm/runner.go b/cmd/evm/runner.go deleted file mode 100644 index d182715ca4..0000000000 --- a/cmd/evm/runner.go +++ /dev/null @@ -1,312 +0,0 @@ -// (c) 2023, Ava Labs, Inc. -// -// This file is a derived work, based on the go-ethereum library whose original -// notices appear below. -// -// It is distributed under a license compatible with the licensing terms of the -// original code from which it is derived. -// -// Much love to the original authors for their work. -// ********** -// Copyright 2017 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "math/big" - "os" - goruntime "runtime" - "testing" - "time" - - "github.com/ava-labs/subnet-evm/cmd/evm/internal/compiler" - "github.com/ava-labs/subnet-evm/cmd/utils" - "github.com/ava-labs/subnet-evm/core" - "github.com/ava-labs/subnet-evm/core/rawdb" - "github.com/ava-labs/subnet-evm/core/state" - "github.com/ava-labs/subnet-evm/core/vm" - "github.com/ava-labs/subnet-evm/core/vm/runtime" - "github.com/ava-labs/subnet-evm/eth/tracers/logger" - "github.com/ava-labs/subnet-evm/internal/flags" - "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/trie" - "github.com/ava-labs/subnet-evm/trie/triedb/hashdb" - "github.com/ethereum/go-ethereum/common" - "github.com/urfave/cli/v2" -) - -var runCommand = &cli.Command{ - Action: runCmd, - Name: "run", - Usage: "Run arbitrary evm binary", - ArgsUsage: "", - Description: `The run command runs arbitrary EVM code.`, - Flags: flags.Merge(vmFlags, traceFlags), -} - -// readGenesis will read the given JSON format genesis file and return -// the initialized Genesis structure -func readGenesis(genesisPath string) *core.Genesis { - // Make sure we have a valid genesis JSON - //genesisPath := ctx.Args().First() - if len(genesisPath) == 0 { - utils.Fatalf("Must supply path to genesis JSON file") - } - file, err := os.Open(genesisPath) - if err != nil { - utils.Fatalf("Failed to read genesis file: %v", err) - } - defer file.Close() - - genesis := new(core.Genesis) - if err := json.NewDecoder(file).Decode(genesis); err != nil { - utils.Fatalf("invalid genesis file: %v", err) - } - return genesis -} - -type execStats struct { - time time.Duration // The execution time. - allocs int64 // The number of heap allocations during execution. - bytesAllocated int64 // The cumulative number of bytes allocated during execution. -} - -func timedExec(bench bool, execFunc func() ([]byte, uint64, error)) (output []byte, gasLeft uint64, stats execStats, err error) { - if bench { - result := testing.Benchmark(func(b *testing.B) { - for i := 0; i < b.N; i++ { - output, gasLeft, err = execFunc() - } - }) - - // Get the average execution time from the benchmarking result. - // There are other useful stats here that could be reported. - stats.time = time.Duration(result.NsPerOp()) - stats.allocs = result.AllocsPerOp() - stats.bytesAllocated = result.AllocedBytesPerOp() - } else { - var memStatsBefore, memStatsAfter goruntime.MemStats - goruntime.ReadMemStats(&memStatsBefore) - startTime := time.Now() - output, gasLeft, err = execFunc() - stats.time = time.Since(startTime) - goruntime.ReadMemStats(&memStatsAfter) - stats.allocs = int64(memStatsAfter.Mallocs - memStatsBefore.Mallocs) - stats.bytesAllocated = int64(memStatsAfter.TotalAlloc - memStatsBefore.TotalAlloc) - } - - return output, gasLeft, stats, err -} - -func runCmd(ctx *cli.Context) error { - logconfig := &logger.Config{ - EnableMemory: !ctx.Bool(DisableMemoryFlag.Name), - DisableStack: ctx.Bool(DisableStackFlag.Name), - DisableStorage: ctx.Bool(DisableStorageFlag.Name), - EnableReturnData: !ctx.Bool(DisableReturnDataFlag.Name), - Debug: ctx.Bool(DebugFlag.Name), - } - - var ( - tracer vm.EVMLogger - debugLogger *logger.StructLogger - statedb *state.StateDB - chainConfig *params.ChainConfig - sender = common.BytesToAddress([]byte("sender")) - receiver = common.BytesToAddress([]byte("receiver")) - preimages = ctx.Bool(DumpFlag.Name) - blobHashes []common.Hash // TODO (MariusVanDerWijden) implement blob hashes in state tests - blobBaseFee = new(big.Int) // TODO (MariusVanDerWijden) implement blob fee in state tests - ) - if ctx.Bool(MachineFlag.Name) { - tracer = logger.NewJSONLogger(logconfig, os.Stdout) - } else if ctx.Bool(DebugFlag.Name) { - debugLogger = logger.NewStructLogger(logconfig) - tracer = debugLogger - } else { - debugLogger = logger.NewStructLogger(logconfig) - } - - initialGas := ctx.Uint64(GasFlag.Name) - genesisConfig := new(core.Genesis) - genesisConfig.GasLimit = initialGas - if ctx.String(GenesisFlag.Name) != "" { - genesisConfig = readGenesis(ctx.String(GenesisFlag.Name)) - if genesisConfig.GasLimit != 0 { - initialGas = genesisConfig.GasLimit - } - } else { - genesisConfig.Config = params.TestSubnetEVMChainConfig - } - - db := rawdb.NewMemoryDatabase() - triedb := trie.NewDatabase(db, &trie.Config{ - Preimages: preimages, - HashDB: hashdb.Defaults, - }) - defer triedb.Close() - genesis := genesisConfig.MustCommit(db, triedb) - sdb := state.NewDatabaseWithNodeDB(db, triedb) - statedb, _ = state.New(genesis.Root(), sdb, nil) - chainConfig = genesisConfig.Config - - if ctx.String(SenderFlag.Name) != "" { - sender = common.HexToAddress(ctx.String(SenderFlag.Name)) - } - statedb.CreateAccount(sender) - - if ctx.String(ReceiverFlag.Name) != "" { - receiver = common.HexToAddress(ctx.String(ReceiverFlag.Name)) - } - - var code []byte - codeFileFlag := ctx.String(CodeFileFlag.Name) - codeFlag := ctx.String(CodeFlag.Name) - - // The '--code' or '--codefile' flag overrides code in state - if codeFileFlag != "" || codeFlag != "" { - var hexcode []byte - if codeFileFlag != "" { - var err error - // If - is specified, it means that code comes from stdin - if codeFileFlag == "-" { - //Try reading from stdin - if hexcode, err = io.ReadAll(os.Stdin); err != nil { - fmt.Printf("Could not load code from stdin: %v\n", err) - os.Exit(1) - } - } else { - // Codefile with hex assembly - if hexcode, err = os.ReadFile(codeFileFlag); err != nil { - fmt.Printf("Could not load code from file: %v\n", err) - os.Exit(1) - } - } - } else { - hexcode = []byte(codeFlag) - } - hexcode = bytes.TrimSpace(hexcode) - if len(hexcode)%2 != 0 { - fmt.Printf("Invalid input length for hex data (%d)\n", len(hexcode)) - os.Exit(1) - } - code = common.FromHex(string(hexcode)) - } else if fn := ctx.Args().First(); len(fn) > 0 { - // EASM-file to compile - src, err := os.ReadFile(fn) - if err != nil { - return err - } - bin, err := compiler.Compile(fn, src, false) - if err != nil { - return err - } - code = common.Hex2Bytes(bin) - } - runtimeConfig := runtime.Config{ - Origin: sender, - State: statedb, - GasLimit: initialGas, - GasPrice: flags.GlobalBig(ctx, PriceFlag.Name), - Value: flags.GlobalBig(ctx, ValueFlag.Name), - Difficulty: genesisConfig.Difficulty, - Time: genesisConfig.Timestamp, - Coinbase: genesisConfig.Coinbase, - BlockNumber: new(big.Int).SetUint64(genesisConfig.Number), - BlobHashes: blobHashes, - BlobBaseFee: blobBaseFee, - EVMConfig: vm.Config{ - Tracer: tracer, - }, - } - - if chainConfig != nil { - runtimeConfig.ChainConfig = chainConfig - } else { - runtimeConfig.ChainConfig = params.SubnetEVMDefaultChainConfig // NOTE: Replaced AllEthashProtocolChanges with SubnetEVMDefaultChainConfig here - } - - var hexInput []byte - if inputFileFlag := ctx.String(InputFileFlag.Name); inputFileFlag != "" { - var err error - if hexInput, err = os.ReadFile(inputFileFlag); err != nil { - fmt.Printf("could not load input from file: %v\n", err) - os.Exit(1) - } - } else { - hexInput = []byte(ctx.String(InputFlag.Name)) - } - hexInput = bytes.TrimSpace(hexInput) - if len(hexInput)%2 != 0 { - fmt.Println("input length must be even") - os.Exit(1) - } - input := common.FromHex(string(hexInput)) - - var execFunc func() ([]byte, uint64, error) - if ctx.Bool(CreateFlag.Name) { - input = append(code, input...) - execFunc = func() ([]byte, uint64, error) { - output, _, gasLeft, err := runtime.Create(input, &runtimeConfig) - return output, gasLeft, err - } - } else { - if len(code) > 0 { - statedb.SetCode(receiver, code) - } - execFunc = func() ([]byte, uint64, error) { - return runtime.Call(receiver, input, &runtimeConfig) - } - } - - bench := ctx.Bool(BenchFlag.Name) - output, leftOverGas, stats, err := timedExec(bench, execFunc) - - if ctx.Bool(DumpFlag.Name) { - statedb.Commit(genesisConfig.Number, true, false) - fmt.Println(string(statedb.Dump(nil))) - } - - if ctx.Bool(DebugFlag.Name) { - if debugLogger != nil { - fmt.Fprintln(os.Stderr, "#### TRACE ####") - logger.WriteTrace(os.Stderr, debugLogger.StructLogs()) - } - fmt.Fprintln(os.Stderr, "#### LOGS ####") - logger.WriteLogs(os.Stderr, statedb.Logs()) - } - - if bench || ctx.Bool(StatDumpFlag.Name) { - fmt.Fprintf(os.Stderr, `EVM gas used: %d -execution time: %v -allocations: %d -allocated bytes: %d -`, initialGas-leftOverGas, stats.time, stats.allocs, stats.bytesAllocated) - } - if tracer == nil { - fmt.Printf("%#x\n", output) - if err != nil { - fmt.Printf(" error: %v\n", err) - } - } - - return nil -} diff --git a/cmd/evm/staterunner.go b/cmd/evm/staterunner.go deleted file mode 100644 index db8aca19e8..0000000000 --- a/cmd/evm/staterunner.go +++ /dev/null @@ -1,138 +0,0 @@ -// (c) 2023, Ava Labs, Inc. -// -// This file is a derived work, based on the go-ethereum library whose original -// notices appear below. -// -// It is distributed under a license compatible with the licensing terms of the -// original code from which it is derived. -// -// Much love to the original authors for their work. -// ********** -// Copyright 2017 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "bufio" - "encoding/json" - "fmt" - "os" - - "github.com/ava-labs/subnet-evm/core/rawdb" - "github.com/ava-labs/subnet-evm/core/state" - "github.com/ava-labs/subnet-evm/core/state/snapshot" - "github.com/ava-labs/subnet-evm/core/vm" - "github.com/ava-labs/subnet-evm/eth/tracers/logger" - "github.com/ava-labs/subnet-evm/tests" - "github.com/ethereum/go-ethereum/common" - "github.com/urfave/cli/v2" -) - -var stateTestCommand = &cli.Command{ - Action: stateTestCmd, - Name: "statetest", - Usage: "Executes the given state tests. Filenames can be fed via standard input (batch mode) or as an argument (one-off execution).", - ArgsUsage: "", -} - -// StatetestResult contains the execution status after running a state test, any -// error that might have occurred and a dump of the final state if requested. -type StatetestResult struct { - Name string `json:"name"` - Pass bool `json:"pass"` - Root *common.Hash `json:"stateRoot,omitempty"` - Fork string `json:"fork"` - Error string `json:"error,omitempty"` - State *state.Dump `json:"state,omitempty"` -} - -func stateTestCmd(ctx *cli.Context) error { - // Configure the EVM logger - config := &logger.Config{ - EnableMemory: !ctx.Bool(DisableMemoryFlag.Name), - DisableStack: ctx.Bool(DisableStackFlag.Name), - DisableStorage: ctx.Bool(DisableStorageFlag.Name), - EnableReturnData: !ctx.Bool(DisableReturnDataFlag.Name), - } - var cfg vm.Config - switch { - case ctx.Bool(MachineFlag.Name): - cfg.Tracer = logger.NewJSONLogger(config, os.Stderr) - - case ctx.Bool(DebugFlag.Name): - cfg.Tracer = logger.NewStructLogger(config) - } - // Load the test content from the input file - if len(ctx.Args().First()) != 0 { - return runStateTest(ctx.Args().First(), cfg, ctx.Bool(MachineFlag.Name), ctx.Bool(DumpFlag.Name)) - } - // Read filenames from stdin and execute back-to-back - scanner := bufio.NewScanner(os.Stdin) - for scanner.Scan() { - fname := scanner.Text() - if len(fname) == 0 { - return nil - } - if err := runStateTest(fname, cfg, ctx.Bool(MachineFlag.Name), ctx.Bool(DumpFlag.Name)); err != nil { - return err - } - } - return nil -} - -// runStateTest loads the state-test given by fname, and executes the test. -func runStateTest(fname string, cfg vm.Config, jsonOut, dump bool) error { - src, err := os.ReadFile(fname) - if err != nil { - return err - } - var tests map[string]tests.StateTest - if err := json.Unmarshal(src, &tests); err != nil { - return err - } - // Iterate over all the tests, run them and aggregate the results - results := make([]StatetestResult, 0, len(tests)) - for key, test := range tests { - for _, st := range test.Subtests() { - // Run the test and aggregate the result - result := &StatetestResult{Name: key, Fork: st.Fork, Pass: true} - test.Run(st, cfg, false, rawdb.HashScheme, func(err error, snaps *snapshot.Tree, statedb *state.StateDB) { - var root common.Hash - if statedb != nil { - root = statedb.IntermediateRoot(false) - result.Root = &root - if jsonOut { - fmt.Fprintf(os.Stderr, "{\"stateRoot\": \"%#x\"}\n", root) - } - if dump { // Dump any state to aid debugging - cpy, _ := state.New(root, statedb.Database(), nil) - dump := cpy.RawDump(nil) - result.State = &dump - } - } - if err != nil { - // Test failed, mark as so - result.Pass, result.Error = false, err.Error() - } - }) - results = append(results, *result) - } - } - out, _ := json.MarshalIndent(results, "", " ") - fmt.Println(string(out)) - return nil -} diff --git a/cmd/evm/t8n_test.go b/cmd/evm/t8n_test.go deleted file mode 100644 index 2304a54af6..0000000000 --- a/cmd/evm/t8n_test.go +++ /dev/null @@ -1,615 +0,0 @@ -// (c) 2023, Ava Labs, Inc. -// -// This file is a derived work, based on the go-ethereum library whose original -// notices appear below. -// -// It is distributed under a license compatible with the licensing terms of the -// original code from which it is derived. -// -// Much love to the original authors for their work. -// ********** -// Copyright 2021 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "encoding/json" - "fmt" - "os" - "reflect" - "strings" - "testing" - - "github.com/ava-labs/subnet-evm/cmd/evm/internal/t8ntool" - "github.com/ava-labs/subnet-evm/internal/cmdtest" - "github.com/ava-labs/subnet-evm/internal/reexec" -) - -func TestMain(m *testing.M) { - // Run the app if we've been exec'd as "ethkey-test" in runEthkey. - reexec.Register("evm-test", func() { - if err := app.Run(os.Args); err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - os.Exit(0) - }) - // check if we have been reexec'd - if reexec.Init() { - return - } - os.Exit(m.Run()) -} - -type testT8n struct { - *cmdtest.TestCmd -} - -type t8nInput struct { - inAlloc string - inTxs string - inEnv string - stFork string - stReward string -} - -func (args *t8nInput) get(base string) []string { - var out []string - if opt := args.inAlloc; opt != "" { - out = append(out, "--input.alloc") - out = append(out, fmt.Sprintf("%v/%v", base, opt)) - } - if opt := args.inTxs; opt != "" { - out = append(out, "--input.txs") - out = append(out, fmt.Sprintf("%v/%v", base, opt)) - } - if opt := args.inEnv; opt != "" { - out = append(out, "--input.env") - out = append(out, fmt.Sprintf("%v/%v", base, opt)) - } - if opt := args.stFork; opt != "" { - out = append(out, "--state.fork", opt) - } - if opt := args.stReward; opt != "" { - out = append(out, "--state.reward", opt) - } - return out -} - -type t8nOutput struct { - alloc bool - result bool - body bool -} - -func (args *t8nOutput) get() (out []string) { - if args.body { - out = append(out, "--output.body", "stdout") - } else { - out = append(out, "--output.body", "") // empty means ignore - } - if args.result { - out = append(out, "--output.result", "stdout") - } else { - out = append(out, "--output.result", "") - } - if args.alloc { - out = append(out, "--output.alloc", "stdout") - } else { - out = append(out, "--output.alloc", "") - } - return out -} - -func TestT8n(t *testing.T) { - t.Parallel() - tt := new(testT8n) - tt.TestCmd = cmdtest.NewTestCmd(t, tt) - for i, tc := range []struct { - base string - input t8nInput - output t8nOutput - expExitCode int - expOut string - }{ - { // Test exit (3) on bad config - base: "./testdata/1", - input: t8nInput{ - "alloc.json", "txs.json", "env.json", "Frontier+1346", "", - }, - output: t8nOutput{alloc: true, result: true}, - expExitCode: 3, - }, - { - base: "./testdata/1", - input: t8nInput{ - "alloc.json", "txs.json", "env.json", "Byzantium", "", - }, - output: t8nOutput{alloc: true, result: true}, - expOut: "exp.json", - }, - { // blockhash test - base: "./testdata/3", - input: t8nInput{ - "alloc.json", "txs.json", "env.json", "Berlin", "", - }, - output: t8nOutput{alloc: true, result: true}, - expOut: "exp.json", - }, - { // missing blockhash test - base: "./testdata/4", - input: t8nInput{ - "alloc.json", "txs.json", "env.json", "Berlin", "", - }, - output: t8nOutput{alloc: true, result: true}, - expExitCode: 4, - }, - { // Uncle test - base: "./testdata/5", - input: t8nInput{ - "alloc.json", "txs.json", "env.json", "Byzantium", "0x80", - }, - output: t8nOutput{alloc: true, result: true}, - expOut: "exp.json", - }, - { // Sign json transactions - base: "./testdata/13", - input: t8nInput{ - "alloc.json", "txs.json", "env.json", "London", "", - }, - output: t8nOutput{body: true}, - expOut: "exp.json", - }, - { // Already signed transactions - base: "./testdata/13", - input: t8nInput{ - "alloc.json", "signed_txs.rlp", "env.json", "London", "", - }, - output: t8nOutput{result: true}, - expOut: "exp2.json", - }, - { // Difficulty calculation - no uncles - base: "./testdata/14", - input: t8nInput{ - "alloc.json", "txs.json", "env.json", "London", "", - }, - output: t8nOutput{result: true}, - expOut: "exp.json", - }, - { // Difficulty calculation - with uncles - base: "./testdata/14", - input: t8nInput{ - "alloc.json", "txs.json", "env.uncles.json", "London", "", - }, - output: t8nOutput{result: true}, - expOut: "exp2.json", - }, - { // Difficulty calculation - with ommers + Berlin - base: "./testdata/14", - input: t8nInput{ - "alloc.json", "txs.json", "env.uncles.json", "Berlin", "", - }, - output: t8nOutput{result: true}, - expOut: "exp_berlin.json", - }, - { // Difficulty calculation on arrow glacier - base: "./testdata/19", - input: t8nInput{ - "alloc.json", "txs.json", "env.json", "London", "", - }, - output: t8nOutput{result: true}, - expOut: "exp_london.json", - }, - { // Difficulty calculation on arrow glacier - base: "./testdata/19", - input: t8nInput{ - "alloc.json", "txs.json", "env.json", "ArrowGlacier", "", - }, - output: t8nOutput{result: true}, - expOut: "exp_arrowglacier.json", - }, - { // Difficulty calculation on gray glacier - base: "./testdata/19", - input: t8nInput{ - "alloc.json", "txs.json", "env.json", "GrayGlacier", "", - }, - output: t8nOutput{result: true}, - expOut: "exp_grayglacier.json", - }, - { // Sign unprotected (pre-EIP155) transaction - base: "./testdata/23", - input: t8nInput{ - "alloc.json", "txs.json", "env.json", "Berlin", "", - }, - output: t8nOutput{result: true}, - expOut: "exp.json", - }, - { // Test post-merge transition - base: "./testdata/24", - input: t8nInput{ - "alloc.json", "txs.json", "env.json", "Merge", "", - }, - output: t8nOutput{alloc: true, result: true}, - expOut: "exp.json", - }, - // NOTE: we don't use this test because it is testing the behavior of a missing - // RANDOM env for Merged. - // { // Test post-merge transition where input is missing random - // base: "./testdata/24", - // input: t8nInput{ - // "alloc.json", "txs.json", "env-missingrandom.json", "Merge", "", - // }, - // output: t8nOutput{alloc: false, result: false}, - // expExitCode: 3, - // }, - // NOTE: this test was modified to test a non-trivial calculation - // of dynamic Subnet-EVM fees (instead of the original EIP-1559 - // [misc.CalcBaseFee] calculation). - { // Test base fee calculation - base: "./testdata/25", - input: t8nInput{ - "alloc.json", "txs.json", "env.json", "Merge", "", - }, - output: t8nOutput{alloc: true, result: true}, - expOut: "exp.json", - }, - // NOTE: we don't use this test because it is testing the behavior of a missing - // withrawals env for Shanghai. - // { // Test withdrawals transition - // base: "./testdata/26", - // input: t8nInput{ - // "alloc.json", "txs.json", "env.json", "Shanghai", "", - // }, - // output: t8nOutput{alloc: true, result: true}, - // expOut: "exp.json", - // }, - { // Cancun tests - base: "./testdata/28", - input: t8nInput{ - "alloc.json", "txs.rlp", "env.json", "Cancun", "", - }, - output: t8nOutput{alloc: true, result: true}, - expOut: "exp.json", - }, - { // More cancun tests - base: "./testdata/29", - input: t8nInput{ - "alloc.json", "txs.json", "env.json", "Cancun", "", - }, - output: t8nOutput{alloc: true, result: true}, - expOut: "exp.json", - }, - { // More cancun test, plus example of rlp-transaction that cannot be decoded properly - base: "./testdata/30", - input: t8nInput{ - "alloc.json", "txs_more.rlp", "env.json", "Cancun", "", - }, - output: t8nOutput{alloc: true, result: true}, - expOut: "exp.json", - }, - } { - args := []string{"t8n"} - args = append(args, tc.output.get()...) - args = append(args, tc.input.get(tc.base)...) - var qArgs []string // quoted args for debugging purposes - for _, arg := range args { - if len(arg) == 0 { - qArgs = append(qArgs, `""`) - } else { - qArgs = append(qArgs, arg) - } - } - tt.Logf("args: %v\n", strings.Join(qArgs, " ")) - tt.Run("evm-test", args...) - // Compare the expected output, if provided - if tc.expOut != "" { - file := fmt.Sprintf("%v/%v", tc.base, tc.expOut) - want, err := os.ReadFile(file) - if err != nil { - t.Fatalf("test %d: could not read expected output: %v", i, err) - } - have := tt.Output() - ok, err := cmpJson(have, want) - switch { - case err != nil: - t.Fatalf("test %d, file %v: json parsing failed: %v", i, file, err) - case !ok: - t.Fatalf("test %d, file %v: output wrong, have \n%v\nwant\n%v\n", i, file, string(have), string(want)) - } - } - tt.WaitExit() - if have, want := tt.ExitStatus(), tc.expExitCode; have != want { - t.Fatalf("test %d: wrong exit code, have %d, want %d", i, have, want) - } - } -} - -type t9nInput struct { - inTxs string - stFork string -} - -func (args *t9nInput) get(base string) []string { - var out []string - if opt := args.inTxs; opt != "" { - out = append(out, "--input.txs") - out = append(out, fmt.Sprintf("%v/%v", base, opt)) - } - if opt := args.stFork; opt != "" { - out = append(out, "--state.fork", opt) - } - return out -} - -func TestT9n(t *testing.T) { - t.Parallel() - tt := new(testT8n) - tt.TestCmd = cmdtest.NewTestCmd(t, tt) - for i, tc := range []struct { - base string - input t9nInput - expExitCode int - expOut string - }{ - { // London txs on homestead - base: "./testdata/15", - input: t9nInput{ - inTxs: "signed_txs.rlp", - stFork: "Homestead", - }, - expOut: "exp.json", - }, - { // London txs on London - base: "./testdata/15", - input: t9nInput{ - inTxs: "signed_txs.rlp", - stFork: "London", - }, - expOut: "exp2.json", - }, - { // An RLP list (a blockheader really) - base: "./testdata/15", - input: t9nInput{ - inTxs: "blockheader.rlp", - stFork: "London", - }, - expOut: "exp3.json", - }, - { // Transactions with too low gas - base: "./testdata/16", - input: t9nInput{ - inTxs: "signed_txs.rlp", - stFork: "London", - }, - expOut: "exp.json", - }, - { // Transactions with value exceeding 256 bits - base: "./testdata/17", - input: t9nInput{ - inTxs: "signed_txs.rlp", - stFork: "London", - }, - expOut: "exp.json", - }, - { // Invalid RLP - base: "./testdata/18", - input: t9nInput{ - inTxs: "invalid.rlp", - stFork: "London", - }, - expExitCode: t8ntool.ErrorIO, - }, - } { - args := []string{"t9n"} - args = append(args, tc.input.get(tc.base)...) - - tt.Run("evm-test", args...) - tt.Logf("args:\n go run . %v\n", strings.Join(args, " ")) - // Compare the expected output, if provided - if tc.expOut != "" { - want, err := os.ReadFile(fmt.Sprintf("%v/%v", tc.base, tc.expOut)) - if err != nil { - t.Fatalf("test %d: could not read expected output: %v", i, err) - } - have := tt.Output() - ok, err := cmpJson(have, want) - switch { - case err != nil: - t.Logf(string(have)) - t.Fatalf("test %d, json parsing failed: %v", i, err) - case !ok: - t.Fatalf("test %d: output wrong, have \n%v\nwant\n%v\n", i, string(have), string(want)) - } - } - tt.WaitExit() - if have, want := tt.ExitStatus(), tc.expExitCode; have != want { - t.Fatalf("test %d: wrong exit code, have %d, want %d", i, have, want) - } - } -} - -type b11rInput struct { - inEnv string - inOmmersRlp string - inTxsRlp string - inClique string - ethash bool - ethashMode string - ethashDir string -} - -func (args *b11rInput) get(base string) []string { - var out []string - if opt := args.inEnv; opt != "" { - out = append(out, "--input.header") - out = append(out, fmt.Sprintf("%v/%v", base, opt)) - } - if opt := args.inOmmersRlp; opt != "" { - out = append(out, "--input.ommers") - out = append(out, fmt.Sprintf("%v/%v", base, opt)) - } - if opt := args.inTxsRlp; opt != "" { - out = append(out, "--input.txs") - out = append(out, fmt.Sprintf("%v/%v", base, opt)) - } - if opt := args.inClique; opt != "" { - out = append(out, "--seal.clique") - out = append(out, fmt.Sprintf("%v/%v", base, opt)) - } - if args.ethash { - out = append(out, "--seal.ethash") - } - if opt := args.ethashMode; opt != "" { - out = append(out, "--seal.ethash.mode") - out = append(out, fmt.Sprintf("%v/%v", base, opt)) - } - if opt := args.ethashDir; opt != "" { - out = append(out, "--seal.ethash.dir") - out = append(out, fmt.Sprintf("%v/%v", base, opt)) - } - out = append(out, "--output.block") - out = append(out, "stdout") - return out -} - -func TestB11r(t *testing.T) { - t.Parallel() - tt := new(testT8n) - tt.TestCmd = cmdtest.NewTestCmd(t, tt) - for i, tc := range []struct { - base string - input b11rInput - expExitCode int - expOut string - }{ - { // unsealed block - base: "./testdata/20", - input: b11rInput{ - inEnv: "header.json", - inOmmersRlp: "ommers.json", - inTxsRlp: "txs.rlp", - }, - expOut: "exp.json", - }, - // NOTE: we ignore these tests since subnet-evm does not do - // ethash or clique sealing. - // { // ethash test seal - // base: "./testdata/21", - // input: b11rInput{ - // inEnv: "header.json", - // inOmmersRlp: "ommers.json", - // inTxsRlp: "txs.rlp", - // }, - // expOut: "exp.json", - // }, - // { // clique test seal - // base: "./testdata/21", - // input: b11rInput{ - // inEnv: "header.json", - // inOmmersRlp: "ommers.json", - // inTxsRlp: "txs.rlp", - // inClique: "clique.json", - // }, - // expOut: "exp-clique.json", - // }, - { // block with ommers - base: "./testdata/22", - input: b11rInput{ - inEnv: "header.json", - inOmmersRlp: "ommers.json", - inTxsRlp: "txs.rlp", - }, - expOut: "exp.json", - }, - //{ // block with withdrawals - // base: "./testdata/27", - // input: b11rInput{ - // inEnv: "header.json", - // inOmmersRlp: "ommers.json", - // inWithdrawals: "withdrawals.json", - // inTxsRlp: "txs.rlp", - // }, - // expOut: "exp.json", - //}, - } { - args := []string{"b11r"} - args = append(args, tc.input.get(tc.base)...) - - tt.Run("evm-test", args...) - tt.Logf("args:\n go run . %v\n", strings.Join(args, " ")) - // Compare the expected output, if provided - if tc.expOut != "" { - want, err := os.ReadFile(fmt.Sprintf("%v/%v", tc.base, tc.expOut)) - if err != nil { - t.Fatalf("test %d: could not read expected output: %v", i, err) - } - have := tt.Output() - ok, err := cmpJson(have, want) - switch { - case err != nil: - t.Logf(string(have)) - t.Fatalf("test %d, json parsing failed: %v", i, err) - case !ok: - t.Fatalf("test %d: output wrong, have \n%v\nwant\n%v\n", i, string(have), string(want)) - } - } - tt.WaitExit() - if have, want := tt.ExitStatus(), tc.expExitCode; have != want { - t.Fatalf("test %d: wrong exit code, have %d, want %d", i, have, want) - } - } -} - -// cmpJson compares the JSON in two byte slices. -func cmpJson(a, b []byte) (bool, error) { - var j, j2 interface{} - if err := json.Unmarshal(a, &j); err != nil { - return false, err - } - if err := json.Unmarshal(b, &j2); err != nil { - return false, err - } - // NOTE: In subnet-evm the difficulty for each block is 1 where in go-ethereum - // (pre-merge) this depends on the block. We ignore this field to avoid having - // to modify all the test inputs. - ignoreKeys := []string{"currentDifficulty"} - delKeysRecursive(j, ignoreKeys) - delKeysRecursive(j2, ignoreKeys) - return reflect.DeepEqual(j2, j), nil -} - -func delKeysRecursive(v interface{}, keys []string) { - // Handle list case recursively - if l, ok := v.([]interface{}); ok { - for _, v := range l { - delKeysRecursive(v, keys) - } - return - } - - // Handle map case recursively - m, ok := v.(map[string]interface{}) - if !ok { - return - } - for _, key := range keys { - delete(m, key) - } - for _, v := range m { - delKeysRecursive(v, keys) - } -} diff --git a/cmd/evm/testdata/1/alloc.json b/cmd/evm/testdata/1/alloc.json deleted file mode 100644 index cef1a25ff0..0000000000 --- a/cmd/evm/testdata/1/alloc.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "balance": "0x5ffd4878be161d74", - "code": "0x", - "nonce": "0xac", - "storage": {} - }, - "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192":{ - "balance": "0xfeedbead", - "nonce" : "0x00" - } -} \ No newline at end of file diff --git a/cmd/evm/testdata/1/env.json b/cmd/evm/testdata/1/env.json deleted file mode 100644 index dd60abd205..0000000000 --- a/cmd/evm/testdata/1/env.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b", - "currentDifficulty": "0x20000", - "currentGasLimit": "0x750a163df65e8a", - "currentNumber": "1", - "currentTimestamp": "1000" -} \ No newline at end of file diff --git a/cmd/evm/testdata/1/exp.json b/cmd/evm/testdata/1/exp.json deleted file mode 100644 index d1351e5b76..0000000000 --- a/cmd/evm/testdata/1/exp.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "alloc": { - "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": { - "balance": "0xfeed1a9d", - "nonce": "0x1" - }, - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "balance": "0x5ffd4878be161d74", - "nonce": "0xac" - }, - "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "balance": "0xa410" - } - }, - "result": { - "stateRoot": "0x84208a19bc2b46ada7445180c1db162be5b39b9abc8c0a54b05d32943eae4e13", - "txRoot": "0xc4761fd7b87ff2364c7c60b6c5c8d02e522e815328aaea3f20e3b7b7ef52c42d", - "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", - "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "receipts": [ - { - "root": "0x", - "status": "0x1", - "cumulativeGasUsed": "0x5208", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "logs": null, - "transactionHash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", - "contractAddress": "0x0000000000000000000000000000000000000000", - "gasUsed": "0x5208", - "effectiveGasPrice": null, - "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "transactionIndex": "0x0" - } - ], - "rejected": [ - { - "index": 1, - "error": "nonce too low: address 0x8A8eAFb1cf62BfBeb1741769DAE1a9dd47996192, tx: 0 state: 1" - } - ], - "currentDifficulty": "0x20000", - "gasUsed": "0x5208" - } -} diff --git a/cmd/evm/testdata/1/txs.json b/cmd/evm/testdata/1/txs.json deleted file mode 100644 index 50b31ff31b..0000000000 --- a/cmd/evm/testdata/1/txs.json +++ /dev/null @@ -1,26 +0,0 @@ -[ - { - "gas": "0x5208", - "gasPrice": "0x2", - "hash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", - "input": "0x", - "nonce": "0x0", - "r": "0x9500e8ba27d3c33ca7764e107410f44cbd8c19794bde214d694683a7aa998cdb", - "s": "0x7235ae07e4bd6e0206d102b1f8979d6adab280466b6a82d2208ee08951f1f600", - "to": "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192", - "v": "0x1b", - "value": "0x1" - }, - { - "gas": "0x5208", - "gasPrice": "0x2", - "hash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", - "input": "0x", - "nonce": "0x0", - "r": "0x9500e8ba27d3c33ca7764e107410f44cbd8c19794bde214d694683a7aa998cdb", - "s": "0x7235ae07e4bd6e0206d102b1f8979d6adab280466b6a82d2208ee08951f1f600", - "to": "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192", - "v": "0x1b", - "value": "0x1" - } -] diff --git a/cmd/evm/testdata/13/alloc.json b/cmd/evm/testdata/13/alloc.json deleted file mode 100644 index 6e98e7513c..0000000000 --- a/cmd/evm/testdata/13/alloc.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "0x1111111111111111111111111111111111111111" : { - "balance" : "0x010000000000", - "code" : "0xfe", - "nonce" : "0x01", - "storage" : { - } - }, - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { - "balance" : "0x010000000000", - "code" : "0x", - "nonce" : "0x01", - "storage" : { - } - }, - "0xd02d72e067e77158444ef2020ff2d325f929b363" : { - "balance" : "0x01000000000000", - "code" : "0x", - "nonce" : "0x01", - "storage" : { - } - } -} \ No newline at end of file diff --git a/cmd/evm/testdata/13/env.json b/cmd/evm/testdata/13/env.json deleted file mode 100644 index 3a82d46a77..0000000000 --- a/cmd/evm/testdata/13/env.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", - "currentDifficulty" : "0x020000", - "currentNumber" : "0x01", - "currentTimestamp" : "0x079e", - "previousHash" : "0xcb23ee65a163121f640673b41788ee94633941405f95009999b502eedfbbfd4f", - "currentGasLimit" : "0x40000000", - "currentBaseFee" : "0x036b", - "blockHashes" : { - "0" : "0xcb23ee65a163121f640673b41788ee94633941405f95009999b502eedfbbfd4f" - } -} \ No newline at end of file diff --git a/cmd/evm/testdata/13/exp.json b/cmd/evm/testdata/13/exp.json deleted file mode 100644 index 2b049dfb29..0000000000 --- a/cmd/evm/testdata/13/exp.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "body": "0xf8d2b86702f864010180820fa08284d09411111111111111111111111111111111111111118080c001a0b7dfab36232379bb3d1497a4f91c1966b1f932eae3ade107bf5d723b9cb474e0a06261c359a10f2132f126d250485b90cf20f30340801244a08ef6142ab33d1904b86702f864010280820fa08284d09411111111111111111111111111111111111111118080c080a0d4ec563b6568cd42d998fc4134b36933c6568d01533b5adf08769270243c6c7fa072bf7c21eac6bbeae5143371eef26d5e279637f3bd73482b55979d76d935b1e9" -} diff --git a/cmd/evm/testdata/13/exp2.json b/cmd/evm/testdata/13/exp2.json deleted file mode 100644 index c10d2277b0..0000000000 --- a/cmd/evm/testdata/13/exp2.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "result": { - "stateRoot": "0x17228ad68f0ed80a362f0fe66b9307b96b115d57641f699931a0b7c3a04d1636", - "txRoot": "0x013509c8563d41c0ae4bf38f2d6d19fc6512a1d0d6be045079c8c9f68bf45f9d", - "receiptsRoot": "0xa532a08aa9f62431d6fe5d924951b8efb86ed3c54d06fee77788c3767dd13420", - "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "receipts": [ - { - "type": "0x2", - "root": "0x", - "status": "0x0", - "cumulativeGasUsed": "0x84d0", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "logs": null, - "transactionHash": "0xa98a24882ea90916c6a86da650fbc6b14238e46f0af04a131ce92be897507476", - "contractAddress": "0x0000000000000000000000000000000000000000", - "gasUsed": "0x84d0", - "effectiveGasPrice": null, - "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "transactionIndex": "0x0" - }, - { - "type": "0x2", - "root": "0x", - "status": "0x0", - "cumulativeGasUsed": "0x109a0", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "logs": null, - "transactionHash": "0x36bad80acce7040c45fd32764b5c2b2d2e6f778669fb41791f73f546d56e739a", - "contractAddress": "0x0000000000000000000000000000000000000000", - "gasUsed": "0x84d0", - "effectiveGasPrice": null, - "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "transactionIndex": "0x1" - } - ], - "currentDifficulty": "0x20000", - "gasUsed": "0x109a0", - "currentBaseFee": "0x36b" - } -} diff --git a/cmd/evm/testdata/13/exp2.json.diff b/cmd/evm/testdata/13/exp2.json.diff deleted file mode 100644 index 09e46a8780..0000000000 --- a/cmd/evm/testdata/13/exp2.json.diff +++ /dev/null @@ -1,12 +0,0 @@ -State root differs because of SubnetEVM's fee calculations. - ---- a/cmd/evm/testdata/13/exp2.json 2023-08-25 07:34:20 -+++ b/cmd/evm/testdata/13/exp2.json 2023-08-24 14:17:32 -@@ -1,6 +1,6 @@ - { - "result": { -- "stateRoot": "0xe4b924a6adb5959fccf769d5b7bb2f6359e26d1e76a2443c5a91a36d826aef61", -+ "stateRoot": "0x17228ad68f0ed80a362f0fe66b9307b96b115d57641f699931a0b7c3a04d1636", - "txRoot": "0x013509c8563d41c0ae4bf38f2d6d19fc6512a1d0d6be045079c8c9f68bf45f9d", - "receiptsRoot": "0xa532a08aa9f62431d6fe5d924951b8efb86ed3c54d06fee77788c3767dd13420", - "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", diff --git a/cmd/evm/testdata/13/readme.md b/cmd/evm/testdata/13/readme.md deleted file mode 100644 index 889975d47e..0000000000 --- a/cmd/evm/testdata/13/readme.md +++ /dev/null @@ -1,4 +0,0 @@ -## Input transactions in RLP form - -This testdata folder is used to exemplify how transaction input can be provided in rlp form. -Please see the README in `evm` folder for how this is performed. \ No newline at end of file diff --git a/cmd/evm/testdata/13/signed_txs.rlp b/cmd/evm/testdata/13/signed_txs.rlp deleted file mode 100644 index 9d1157ea45..0000000000 --- a/cmd/evm/testdata/13/signed_txs.rlp +++ /dev/null @@ -1 +0,0 @@ -"0xf8d2b86702f864010180820fa08284d09411111111111111111111111111111111111111118080c001a0b7dfab36232379bb3d1497a4f91c1966b1f932eae3ade107bf5d723b9cb474e0a06261c359a10f2132f126d250485b90cf20f30340801244a08ef6142ab33d1904b86702f864010280820fa08284d09411111111111111111111111111111111111111118080c080a0d4ec563b6568cd42d998fc4134b36933c6568d01533b5adf08769270243c6c7fa072bf7c21eac6bbeae5143371eef26d5e279637f3bd73482b55979d76d935b1e9" \ No newline at end of file diff --git a/cmd/evm/testdata/13/txs.json b/cmd/evm/testdata/13/txs.json deleted file mode 100644 index c45ef1e13d..0000000000 --- a/cmd/evm/testdata/13/txs.json +++ /dev/null @@ -1,34 +0,0 @@ -[ - { - "input" : "0x", - "gas" : "0x84d0", - "nonce" : "0x1", - "to" : "0x1111111111111111111111111111111111111111", - "value" : "0x0", - "v" : "0x0", - "r" : "0x0", - "s" : "0x0", - "secretKey" : "0x41f6e321b31e72173f8ff2e292359e1862f24fba42fe6f97efaf641980eff298", - "chainId" : "0x1", - "type" : "0x2", - "maxFeePerGas" : "0xfa0", - "maxPriorityFeePerGas" : "0x0", - "accessList" : [] - }, - { - "input" : "0x", - "gas" : "0x84d0", - "nonce" : "0x2", - "to" : "0x1111111111111111111111111111111111111111", - "value" : "0x0", - "v" : "0x0", - "r" : "0x0", - "s" : "0x0", - "secretKey" : "0x41f6e321b31e72173f8ff2e292359e1862f24fba42fe6f97efaf641980eff298", - "chainId" : "0x1", - "type" : "0x2", - "maxFeePerGas" : "0xfa0", - "maxPriorityFeePerGas" : "0x0", - "accessList" : [] - } -] \ No newline at end of file diff --git a/cmd/evm/testdata/14/alloc.json b/cmd/evm/testdata/14/alloc.json deleted file mode 100644 index cef1a25ff0..0000000000 --- a/cmd/evm/testdata/14/alloc.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "balance": "0x5ffd4878be161d74", - "code": "0x", - "nonce": "0xac", - "storage": {} - }, - "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192":{ - "balance": "0xfeedbead", - "nonce" : "0x00" - } -} \ No newline at end of file diff --git a/cmd/evm/testdata/14/env.json b/cmd/evm/testdata/14/env.json deleted file mode 100644 index 0bf1c5cf48..0000000000 --- a/cmd/evm/testdata/14/env.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b", - "currentGasLimit": "0x750a163df65e8a", - "currentBaseFee": "0x500", - "currentNumber": "12800000", - "currentTimestamp": "100015", - "parentTimestamp" : "99999", - "parentDifficulty" : "0x2000000000000" -} diff --git a/cmd/evm/testdata/14/env.uncles.json b/cmd/evm/testdata/14/env.uncles.json deleted file mode 100644 index 83811b95ec..0000000000 --- a/cmd/evm/testdata/14/env.uncles.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b", - "currentGasLimit": "0x750a163df65e8a", - "currentBaseFee": "0x500", - "currentNumber": "12800000", - "currentTimestamp": "100035", - "parentTimestamp" : "99999", - "parentDifficulty" : "0x2000000000000", - "parentUncleHash" : "0x000000000000000000000000000000000000000000000000000000000000beef" -} diff --git a/cmd/evm/testdata/14/exp.json b/cmd/evm/testdata/14/exp.json deleted file mode 100644 index 26d49173ce..0000000000 --- a/cmd/evm/testdata/14/exp.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "result": { - "stateRoot": "0x6f058887ca01549716789c380ede95aecc510e6d1fdc4dbf67d053c7c07f4bdc", - "txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "currentDifficulty": "0x2000020000000", - "receipts": [], - "gasUsed": "0x0", - "currentBaseFee": "0x500" - } -} diff --git a/cmd/evm/testdata/14/exp2.json b/cmd/evm/testdata/14/exp2.json deleted file mode 100644 index cd75b47d5a..0000000000 --- a/cmd/evm/testdata/14/exp2.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "result": { - "stateRoot": "0x6f058887ca01549716789c380ede95aecc510e6d1fdc4dbf67d053c7c07f4bdc", - "txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "receipts": [], - "currentDifficulty": "0x1ff8020000000", - "gasUsed": "0x0", - "currentBaseFee": "0x500" - } -} diff --git a/cmd/evm/testdata/14/exp_berlin.json b/cmd/evm/testdata/14/exp_berlin.json deleted file mode 100644 index 5c00ef130a..0000000000 --- a/cmd/evm/testdata/14/exp_berlin.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "result": { - "stateRoot": "0x6f058887ca01549716789c380ede95aecc510e6d1fdc4dbf67d053c7c07f4bdc", - "txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "receipts": [], - "currentDifficulty": "0x1ff9000000000", - "gasUsed": "0x0", - "currentBaseFee": "0x500" - } -} diff --git a/cmd/evm/testdata/14/readme.md b/cmd/evm/testdata/14/readme.md deleted file mode 100644 index 40dd75486e..0000000000 --- a/cmd/evm/testdata/14/readme.md +++ /dev/null @@ -1,45 +0,0 @@ -## Difficulty calculation - -This test shows how the `evm t8n` can be used to calculate the (ethash) difficulty, if none is provided by the caller. - -Calculating it (with an empty set of txs) using `London` rules (and no provided unclehash for the parent block): -``` -[user@work evm]$ ./evm t8n --input.alloc=./testdata/14/alloc.json --input.txs=./testdata/14/txs.json --input.env=./testdata/14/env.json --output.result=stdout --state.fork=London -INFO [03-09|10:43:57.070] Trie dumping started root=6f0588..7f4bdc -INFO [03-09|10:43:57.070] Trie dumping complete accounts=2 elapsed="214.663Âĩs" -INFO [03-09|10:43:57.071] Wrote file file=alloc.json -{ - "result": { - "stateRoot": "0x6f058887ca01549716789c380ede95aecc510e6d1fdc4dbf67d053c7c07f4bdc", - "txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "receipts": [], - "currentDifficulty": "0x2000020000000", - "gasUsed": "0x0", - "currentBaseFee": "0x500" - } -} -``` -Same thing, but this time providing a non-empty (and non-`emptyKeccak`) unclehash, which leads to a slightly different result: -``` -[user@work evm]$ ./evm t8n --input.alloc=./testdata/14/alloc.json --input.txs=./testdata/14/txs.json --input.env=./testdata/14/env.uncles.json --output.result=stdout --state.fork=London -INFO [03-09|10:44:20.511] Trie dumping started root=6f0588..7f4bdc -INFO [03-09|10:44:20.511] Trie dumping complete accounts=2 elapsed="184.319Âĩs" -INFO [03-09|10:44:20.512] Wrote file file=alloc.json -{ - "result": { - "stateRoot": "0x6f058887ca01549716789c380ede95aecc510e6d1fdc4dbf67d053c7c07f4bdc", - "txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "receipts": [], - "currentDifficulty": "0x1ff8020000000", - "gasUsed": "0x0", - "currentBaseFee": "0x500" - } -} -``` - diff --git a/cmd/evm/testdata/14/txs.json b/cmd/evm/testdata/14/txs.json deleted file mode 100644 index fe51488c70..0000000000 --- a/cmd/evm/testdata/14/txs.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/cmd/evm/testdata/15/blockheader.rlp b/cmd/evm/testdata/15/blockheader.rlp deleted file mode 100644 index 1124e8e2da..0000000000 --- a/cmd/evm/testdata/15/blockheader.rlp +++ /dev/null @@ -1 +0,0 @@ -"0xf901f0a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000940000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b0101020383010203a00000000000000000000000000000000000000000000000000000000000000000880000000000000000" \ No newline at end of file diff --git a/cmd/evm/testdata/15/exp.json b/cmd/evm/testdata/15/exp.json deleted file mode 100644 index 1893fdfc08..0000000000 --- a/cmd/evm/testdata/15/exp.json +++ /dev/null @@ -1,10 +0,0 @@ -[ - { - "error": "transaction type not supported", - "hash": "0xa98a24882ea90916c6a86da650fbc6b14238e46f0af04a131ce92be897507476" - }, - { - "error": "transaction type not supported", - "hash": "0x36bad80acce7040c45fd32764b5c2b2d2e6f778669fb41791f73f546d56e739a" - } -] \ No newline at end of file diff --git a/cmd/evm/testdata/15/exp2.json b/cmd/evm/testdata/15/exp2.json deleted file mode 100644 index dd5e8a358c..0000000000 --- a/cmd/evm/testdata/15/exp2.json +++ /dev/null @@ -1,12 +0,0 @@ -[ - { - "address": "0xd02d72e067e77158444ef2020ff2d325f929b363", - "hash": "0xa98a24882ea90916c6a86da650fbc6b14238e46f0af04a131ce92be897507476", - "intrinsicGas": "0x5208" - }, - { - "address": "0xd02d72e067e77158444ef2020ff2d325f929b363", - "hash": "0x36bad80acce7040c45fd32764b5c2b2d2e6f778669fb41791f73f546d56e739a", - "intrinsicGas": "0x5208" - } -] diff --git a/cmd/evm/testdata/15/exp3.json b/cmd/evm/testdata/15/exp3.json deleted file mode 100644 index d7606a2073..0000000000 --- a/cmd/evm/testdata/15/exp3.json +++ /dev/null @@ -1,47 +0,0 @@ -[ - { - "error": "transaction type not supported" - }, - { - "error": "transaction type not supported" - }, - { - "error": "transaction type not supported" - }, - { - "error": "transaction type not supported" - }, - { - "error": "transaction type not supported" - }, - { - "error": "transaction type not supported" - }, - { - "error": "transaction type not supported" - }, - { - "error": "typed transaction too short" - }, - { - "error": "typed transaction too short" - }, - { - "error": "typed transaction too short" - }, - { - "error": "typed transaction too short" - }, - { - "error": "typed transaction too short" - }, - { - "error": "rlp: expected input list for types.AccessListTx" - }, - { - "error": "transaction type not supported" - }, - { - "error": "transaction type not supported" - } -] diff --git a/cmd/evm/testdata/15/signed_txs.rlp b/cmd/evm/testdata/15/signed_txs.rlp deleted file mode 100644 index 9d1157ea45..0000000000 --- a/cmd/evm/testdata/15/signed_txs.rlp +++ /dev/null @@ -1 +0,0 @@ -"0xf8d2b86702f864010180820fa08284d09411111111111111111111111111111111111111118080c001a0b7dfab36232379bb3d1497a4f91c1966b1f932eae3ade107bf5d723b9cb474e0a06261c359a10f2132f126d250485b90cf20f30340801244a08ef6142ab33d1904b86702f864010280820fa08284d09411111111111111111111111111111111111111118080c080a0d4ec563b6568cd42d998fc4134b36933c6568d01533b5adf08769270243c6c7fa072bf7c21eac6bbeae5143371eef26d5e279637f3bd73482b55979d76d935b1e9" \ No newline at end of file diff --git a/cmd/evm/testdata/15/signed_txs.rlp.json b/cmd/evm/testdata/15/signed_txs.rlp.json deleted file mode 100644 index 187f40f24a..0000000000 --- a/cmd/evm/testdata/15/signed_txs.rlp.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "txsRlp" : "0xf8d2b86702f864010180820fa08284d09411111111111111111111111111111111111111118080c001a0b7dfab36232379bb3d1497a4f91c1966b1f932eae3ade107bf5d723b9cb474e0a06261c359a10f2132f126d250485b90cf20f30340801244a08ef6142ab33d1904b86702f864010280820fa08284d09411111111111111111111111111111111111111118080c080a0d4ec563b6568cd42d998fc4134b36933c6568d01533b5adf08769270243c6c7fa072bf7c21eac6bbeae5143371eef26d5e279637f3bd73482b55979d76d935b1e9" -} - diff --git a/cmd/evm/testdata/16/exp.json b/cmd/evm/testdata/16/exp.json deleted file mode 100644 index 137ade6513..0000000000 --- a/cmd/evm/testdata/16/exp.json +++ /dev/null @@ -1,13 +0,0 @@ -[ - { - "address": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", - "hash": "0x7cc3d1a8540a44736750f03bb4d85c0113be4b3472a71bf82241a3b261b479e6", - "intrinsicGas": "0x5208" - }, - { - "error": "intrinsic gas too low: have 82, want 21000", - "address": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", - "hash": "0x3b2d2609e4361562edb9169314f4c05afc6dbf5d706bf9dda5abe242ab76a22b", - "intrinsicGas": "0x5208" - } -] \ No newline at end of file diff --git a/cmd/evm/testdata/16/signed_txs.rlp b/cmd/evm/testdata/16/signed_txs.rlp deleted file mode 100644 index 952ced2130..0000000000 --- a/cmd/evm/testdata/16/signed_txs.rlp +++ /dev/null @@ -1 +0,0 @@ -"0xf8cab86401f8610180018252089411111111111111111111111111111111111111112080c001a0937f65ef1deece46c473b99962678fb7c38425cf303d1e8fa9717eb4b9d012b5a01940c5a5647c4940217ffde1051a5fd92ec8551e275c1787f81f50a2ad84de43b86201f85f018001529411111111111111111111111111111111111111112080c001a0241c3aec732205542a87fef8c76346741e85480bce5a42d05a9a73dac892f84ca04f52e2dfce57f3a02ed10e085e1a154edf38a726da34127c85fc53b4921759c8" \ No newline at end of file diff --git a/cmd/evm/testdata/16/unsigned_txs.json b/cmd/evm/testdata/16/unsigned_txs.json deleted file mode 100644 index f619589406..0000000000 --- a/cmd/evm/testdata/16/unsigned_txs.json +++ /dev/null @@ -1,34 +0,0 @@ -[ - { - "input" : "0x", - "gas" : "0x5208", - "nonce" : "0x0", - "to" : "0x1111111111111111111111111111111111111111", - "value" : "0x20", - "v" : "0x0", - "r" : "0x0", - "s" : "0x0", - "secretKey" : "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", - "chainId" : "0x1", - "type" : "0x1", - "gasPrice": "0x1", - "accessList" : [ - ] - }, - { - "input" : "0x", - "gas" : "0x52", - "nonce" : "0x0", - "to" : "0x1111111111111111111111111111111111111111", - "value" : "0x20", - "v" : "0x0", - "r" : "0x0", - "s" : "0x0", - "secretKey" : "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", - "chainId" : "0x1", - "type" : "0x1", - "gasPrice": "0x1", - "accessList" : [ - ] - } -] diff --git a/cmd/evm/testdata/17/exp.json b/cmd/evm/testdata/17/exp.json deleted file mode 100644 index 485906041b..0000000000 --- a/cmd/evm/testdata/17/exp.json +++ /dev/null @@ -1,22 +0,0 @@ - [ - { - "error": "value exceeds 256 bits", - "address": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", - "hash": "0xfbd91685dcbf8172f0e8c53e2ddbb4d26707840da6b51a74371f62a33868fd82", - "intrinsicGas": "0x5208" - }, - { - "error": "gasPrice exceeds 256 bits", - "address": "0x1b57ccef1fe5fb73f1e64530fb4ebd9cf1655964", - "hash": "0x45dc05035cada83748e4c1fe617220106b331eca054f44c2304d5654a9fb29d5", - "intrinsicGas": "0x5208" - }, - { - "error": "invalid transaction v, r, s values", - "hash": "0xf06691c2a803ab7f3c81d06a0c0a896f80f311105c599fc59a9fdbc669356d35" - }, - { - "error": "invalid transaction v, r, s values", - "hash": "0x84703b697ad5b0db25e4f1f98fb6b1adce85b9edb2232eeba9cedd8c6601694b" - } -] \ No newline at end of file diff --git a/cmd/evm/testdata/17/rlpdata.txt b/cmd/evm/testdata/17/rlpdata.txt deleted file mode 100644 index 874461fd76..0000000000 --- a/cmd/evm/testdata/17/rlpdata.txt +++ /dev/null @@ -1,46 +0,0 @@ -[ - [ - "", - "d", - 5208, - d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0, - 010000000000000000000000000000000000000000000000000000000000000001, - "", - 1b, - c16787a8e25e941d67691954642876c08f00996163ae7dfadbbfd6cd436f549d, - 6180e5626cae31590f40641fe8f63734316c4bfeb4cdfab6714198c1044d2e28, - ], - [ - "", - 010000000000000000000000000000000000000000000000000000000000000001, - 5208, - d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0, - 11, - "", - 1b, - c16787a8e25e941d67691954642876c08f00996163ae7dfadbbfd6cd436f549d, - 6180e5626cae31590f40641fe8f63734316c4bfeb4cdfab6714198c1044d2e28, - ], - [ - "", - 11, - 5208, - d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0, - 11, - "", - 1b, - c16787a8e25e941d67691954642876c08f00996163ae7dfadbbfd6cd436f549daa, - 6180e5626cae31590f40641fe8f63734316c4bfeb4cdfab6714198c1044d2e28, - ], - [ - "", - 11, - 5208, - d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0, - 11, - "", - 1b, - c16787a8e25e941d67691954642876c08f00996163ae7dfadbbfd6cd436f549d, - 6180e5626cae31590f40641fe8f63734316c4bfeb4cdfab6714198c1044d2e28bb, - ], -] diff --git a/cmd/evm/testdata/17/signed_txs.rlp b/cmd/evm/testdata/17/signed_txs.rlp deleted file mode 100644 index 0e351fb03c..0000000000 --- a/cmd/evm/testdata/17/signed_txs.rlp +++ /dev/null @@ -1 +0,0 @@ -"0xf901c8f880806482520894d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0a1010000000000000000000000000000000000000000000000000000000000000001801ba0c16787a8e25e941d67691954642876c08f00996163ae7dfadbbfd6cd436f549da06180e5626cae31590f40641fe8f63734316c4bfeb4cdfab6714198c1044d2e28f88080a101000000000000000000000000000000000000000000000000000000000000000182520894d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d011801ba0c16787a8e25e941d67691954642876c08f00996163ae7dfadbbfd6cd436f549da06180e5626cae31590f40641fe8f63734316c4bfeb4cdfab6714198c1044d2e28f860801182520894d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d011801ba1c16787a8e25e941d67691954642876c08f00996163ae7dfadbbfd6cd436f549daaa06180e5626cae31590f40641fe8f63734316c4bfeb4cdfab6714198c1044d2e28f860801182520894d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d011801ba0c16787a8e25e941d67691954642876c08f00996163ae7dfadbbfd6cd436f549da16180e5626cae31590f40641fe8f63734316c4bfeb4cdfab6714198c1044d2e28bb" \ No newline at end of file diff --git a/cmd/evm/testdata/18/README.md b/cmd/evm/testdata/18/README.md deleted file mode 100644 index 360a9bba01..0000000000 --- a/cmd/evm/testdata/18/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# Invalid rlp - -This folder contains a sample of invalid RLP, and it's expected -that the t9n handles this properly: - -``` -$ go run . t9n --input.txs=./testdata/18/invalid.rlp --state.fork=London -ERROR(11): rlp: value size exceeds available input length -``` \ No newline at end of file diff --git a/cmd/evm/testdata/18/invalid.rlp b/cmd/evm/testdata/18/invalid.rlp deleted file mode 100644 index 7ff2824caf..0000000000 --- a/cmd/evm/testdata/18/invalid.rlp +++ /dev/null @@ -1 +0,0 @@ -"0xf852328001825208870b9331677e6ebf0a801ca098ff921201554726367d2be8c804a7ff89ccf285ebc57dff8ae4c44b9c19ac4aa03887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a3" \ No newline at end of file diff --git a/cmd/evm/testdata/19/alloc.json b/cmd/evm/testdata/19/alloc.json deleted file mode 100644 index cef1a25ff0..0000000000 --- a/cmd/evm/testdata/19/alloc.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "balance": "0x5ffd4878be161d74", - "code": "0x", - "nonce": "0xac", - "storage": {} - }, - "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192":{ - "balance": "0xfeedbead", - "nonce" : "0x00" - } -} \ No newline at end of file diff --git a/cmd/evm/testdata/19/env.json b/cmd/evm/testdata/19/env.json deleted file mode 100644 index 0c64392aff..0000000000 --- a/cmd/evm/testdata/19/env.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b", - "currentGasLimit": "0x750a163df65e8a", - "currentBaseFee": "0x500", - "currentNumber": "13000000", - "currentTimestamp": "100015", - "parentTimestamp" : "99999", - "parentDifficulty" : "0x2000000000000" -} diff --git a/cmd/evm/testdata/19/exp_arrowglacier.json b/cmd/evm/testdata/19/exp_arrowglacier.json deleted file mode 100644 index dd49f7d02e..0000000000 --- a/cmd/evm/testdata/19/exp_arrowglacier.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "result": { - "stateRoot": "0x6f058887ca01549716789c380ede95aecc510e6d1fdc4dbf67d053c7c07f4bdc", - "txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "currentDifficulty": "0x2000000200000", - "receipts": [], - "gasUsed": "0x0", - "currentBaseFee": "0x500" - } -} diff --git a/cmd/evm/testdata/19/exp_grayglacier.json b/cmd/evm/testdata/19/exp_grayglacier.json deleted file mode 100644 index 86fd8e6c13..0000000000 --- a/cmd/evm/testdata/19/exp_grayglacier.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "result": { - "stateRoot": "0x6f058887ca01549716789c380ede95aecc510e6d1fdc4dbf67d053c7c07f4bdc", - "txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "receipts": [], - "currentDifficulty": "0x2000000004000", - "gasUsed": "0x0", - "currentBaseFee": "0x500" - } -} \ No newline at end of file diff --git a/cmd/evm/testdata/19/exp_london.json b/cmd/evm/testdata/19/exp_london.json deleted file mode 100644 index 9e9a17da90..0000000000 --- a/cmd/evm/testdata/19/exp_london.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "result": { - "stateRoot": "0x6f058887ca01549716789c380ede95aecc510e6d1fdc4dbf67d053c7c07f4bdc", - "txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "currentDifficulty": "0x2000080000000", - "receipts": [], - "gasUsed": "0x0", - "currentBaseFee": "0x500" - } -} diff --git a/cmd/evm/testdata/19/readme.md b/cmd/evm/testdata/19/readme.md deleted file mode 100644 index 9c7c4b3656..0000000000 --- a/cmd/evm/testdata/19/readme.md +++ /dev/null @@ -1,25 +0,0 @@ -## Difficulty calculation - -This test shows how the `evm t8n` can be used to calculate the (ethash) difficulty, if none is provided by the caller, -this time on `GrayGlacier` (Eip 5133). - -Calculating it (with an empty set of txs) using `GrayGlacier` rules (and no provided unclehash for the parent block): -``` -[user@work evm]$ ./evm t8n --input.alloc=./testdata/19/alloc.json --input.txs=./testdata/19/txs.json --input.env=./testdata/19/env.json --output.result=stdout --state.fork=GrayGlacier -INFO [03-09|10:45:26.777] Trie dumping started root=6f0588..7f4bdc -INFO [03-09|10:45:26.777] Trie dumping complete accounts=2 elapsed="176.471Âĩs" -INFO [03-09|10:45:26.777] Wrote file file=alloc.json -{ - "result": { - "stateRoot": "0x6f058887ca01549716789c380ede95aecc510e6d1fdc4dbf67d053c7c07f4bdc", - "txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "receipts": [], - "currentDifficulty": "0x2000000004000", - "gasUsed": "0x0", - "currentBaseFee": "0x500" - } -} -``` \ No newline at end of file diff --git a/cmd/evm/testdata/19/txs.json b/cmd/evm/testdata/19/txs.json deleted file mode 100644 index fe51488c70..0000000000 --- a/cmd/evm/testdata/19/txs.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/cmd/evm/testdata/20/exp.json b/cmd/evm/testdata/20/exp.json deleted file mode 100644 index 7bec6cefd6..0000000000 --- a/cmd/evm/testdata/20/exp.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "rlp": "0xf902d9f90211a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794e997a23b159e2e2a5ce72333262972374b15425ca0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e99476574682f76312e302e312f6c696e75782f676f312e342e32a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf8897435673d874f7c8f8c2f85f8002825208948a8eafb1cf62bfbeb1741769dae1a9dd4799619201801ba09500e8ba27d3c33ca7764e107410f44cbd8c19794bde214d694683a7aa998cdba07235ae07e4bd6e0206d102b1f8979d6adab280466b6a82d2208ee08951f1f600f85f8002825208948a8eafb1cf62bfbeb1741769dae1a9dd4799619201801ba09500e8ba27d3c33ca7764e107410f44cbd8c19794bde214d694683a7aa998cdba07235ae07e4bd6e0206d102b1f8979d6adab280466b6a82d2208ee08951f1f600c0", - "hash": "0xaba9a3b6a4e96e9ecffcadaa5a2ae0589359455617535cd86589fe1dd26fe899" -} diff --git a/cmd/evm/testdata/20/header.json b/cmd/evm/testdata/20/header.json deleted file mode 100644 index fb9b7fc563..0000000000 --- a/cmd/evm/testdata/20/header.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "parentHash": "0xd6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34e", - "miner": "0xe997a23b159e2e2a5ce72333262972374b15425c", - "stateRoot": "0x325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2e", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "difficulty": "0x1000", - "number": "0xc3be", - "gasLimit": "0x50785", - "gasUsed": "0x0", - "timestamp": "0x55c5277e", - "extraData": "0x476574682f76312e302e312f6c696e75782f676f312e342e32", - "mixHash": "0x5865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf", - "nonce": "0x97435673d874f7c8" -} diff --git a/cmd/evm/testdata/20/ommers.json b/cmd/evm/testdata/20/ommers.json deleted file mode 100644 index fe51488c70..0000000000 --- a/cmd/evm/testdata/20/ommers.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/cmd/evm/testdata/20/readme.md b/cmd/evm/testdata/20/readme.md deleted file mode 100644 index 2c448a96e6..0000000000 --- a/cmd/evm/testdata/20/readme.md +++ /dev/null @@ -1,11 +0,0 @@ -# Block building - -This test shows how `b11r` can be used to assemble an unsealed block. - -```console -$ go run . b11r --input.header=testdata/20/header.json --input.txs=testdata/20/txs.rlp --input.ommers=testdata/20/ommers.json --output.block=stdout -{ - "rlp": "0xf90216f90211a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794e997a23b159e2e2a5ce72333262972374b15425ca0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e99476574682f76312e302e312f6c696e75782f676f312e342e32a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf8897435673d874f7c8c0c0", - "hash": "0xaba9a3b6a4e96e9ecffcadaa5a2ae0589359455617535cd86589fe1dd26fe899" -} -``` diff --git a/cmd/evm/testdata/20/txs.rlp b/cmd/evm/testdata/20/txs.rlp deleted file mode 100644 index 3599ff0654..0000000000 --- a/cmd/evm/testdata/20/txs.rlp +++ /dev/null @@ -1 +0,0 @@ -"0xf8c2f85f8002825208948a8eafb1cf62bfbeb1741769dae1a9dd4799619201801ba09500e8ba27d3c33ca7764e107410f44cbd8c19794bde214d694683a7aa998cdba07235ae07e4bd6e0206d102b1f8979d6adab280466b6a82d2208ee08951f1f600f85f8002825208948a8eafb1cf62bfbeb1741769dae1a9dd4799619201801ba09500e8ba27d3c33ca7764e107410f44cbd8c19794bde214d694683a7aa998cdba07235ae07e4bd6e0206d102b1f8979d6adab280466b6a82d2208ee08951f1f600" \ No newline at end of file diff --git a/cmd/evm/testdata/22/exp-clique.json b/cmd/evm/testdata/22/exp-clique.json deleted file mode 100644 index c990ba8aa6..0000000000 --- a/cmd/evm/testdata/22/exp-clique.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "rlp": "0xf9025ff9025aa0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347942adc25665018aa1fe0e6bc666dac8fc2697ff9baa0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277eb861aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac540a67aaee364005841da84f488f6b6d0116dfb5103d091402c81a163d5f66666595e37f56f196d8c5c98da714dbfae68d6b7e1790cc734a20ec6ce52213ad800a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf88ffffffffffffffffc0c0", - "hash": "0x71c59102cc805dbe8741e1210ebe229a321eff144ac7276006fefe39e8357dc7" -} diff --git a/cmd/evm/testdata/22/exp.json b/cmd/evm/testdata/22/exp.json deleted file mode 100644 index 14fd81997d..0000000000 --- a/cmd/evm/testdata/22/exp.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "rlp": "0xf905f5f901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea06eb9f0c3cd68c9e97134e6725d12b1f1d8f0644458da6870a37ff84c908fb1e7940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000c0f903f6f901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000f901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000", - "hash": "0xd9a81c8fcd57a7f2a0d2c375eff6ad192c30c3729a271303f0a9a7e1b357e755" -} diff --git a/cmd/evm/testdata/22/header.json b/cmd/evm/testdata/22/header.json deleted file mode 100644 index 62abe3cc2c..0000000000 --- a/cmd/evm/testdata/22/header.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "parentHash": "0xd6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34e", - "stateRoot": "0x325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2e", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "difficulty": "0x1000", - "number": "0xc3be", - "gasLimit": "0x50785", - "gasUsed": "0x0", - "timestamp": "0x55c5277e", - "mixHash": "0x5865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf" -} diff --git a/cmd/evm/testdata/22/ommers.json b/cmd/evm/testdata/22/ommers.json deleted file mode 100644 index 997015b3ce..0000000000 --- a/cmd/evm/testdata/22/ommers.json +++ /dev/null @@ -1 +0,0 @@ -["0xf901fdf901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000c0c0","0xf901fdf901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000c0c0"] diff --git a/cmd/evm/testdata/22/readme.md b/cmd/evm/testdata/22/readme.md deleted file mode 100644 index 2cac8a2434..0000000000 --- a/cmd/evm/testdata/22/readme.md +++ /dev/null @@ -1,11 +0,0 @@ -# Building blocks with ommers - -This test shows how `b11r` can chain together ommer assembles into a canonical block. - -```console -$ echo "{ \"ommers\": [`go run . b11r --input.header=testdata/22/header.json --input.txs=testdata/22/txs.rlp --output.block=stdout | jq '.[\"rlp\"]'`,`go run . b11r --input.header=testdata/22/header.json --input.txs=testdata/22/txs.rlp --output.block=stdout | jq '.[\"rlp\"]'`]}" | go run . b11r --input.header=testdata/22/header.json --input.txs=testdata/22/txs.rlp --input.ommers=stdin --output.block=stdout -{ - "rlp": "0xf905f5f901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea06eb9f0c3cd68c9e97134e6725d12b1f1d8f0644458da6870a37ff84c908fb1e7940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000c0f903f6f901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000f901f8a0d6d785d33cbecf30f30d07e00e226af58f72efdf385d46bc3e6326c23b11e34ea01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a0325aea6db48e9d737cddf59034843e99f05bec269453be83c9b9a981a232cc2ea056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000082100082c3be83050785808455c5277e80a05865e417635a26db6d1d39ac70d1abf373e5398b3c6fd506acd038fa1334eedf880000000000000000", - "hash": "0xd9a81c8fcd57a7f2a0d2c375eff6ad192c30c3729a271303f0a9a7e1b357e755" -} -``` diff --git a/cmd/evm/testdata/22/txs.rlp b/cmd/evm/testdata/22/txs.rlp deleted file mode 100644 index e815397b33..0000000000 --- a/cmd/evm/testdata/22/txs.rlp +++ /dev/null @@ -1 +0,0 @@ -"c0" diff --git a/cmd/evm/testdata/23/alloc.json b/cmd/evm/testdata/23/alloc.json deleted file mode 100644 index 239b3553f9..0000000000 --- a/cmd/evm/testdata/23/alloc.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "0x095e7baea6a6c7c4c2dfeb977efac326af552d87" : { - "balance" : "0x0de0b6b3a7640000", - "code" : "0x6001", - "nonce" : "0x00", - "storage" : { - } - }, - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { - "balance" : "0x0de0b6b3a7640000", - "code" : "0x", - "nonce" : "0x00", - "storage" : { - } - } -} diff --git a/cmd/evm/testdata/23/env.json b/cmd/evm/testdata/23/env.json deleted file mode 100644 index 1b46321512..0000000000 --- a/cmd/evm/testdata/23/env.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", - "currentDifficulty" : "0x020000", - "currentGasLimit" : "0x3b9aca00", - "currentNumber" : "0x05", - "currentTimestamp" : "0x03e8" -} diff --git a/cmd/evm/testdata/23/exp.json b/cmd/evm/testdata/23/exp.json deleted file mode 100644 index 22dde0a27c..0000000000 --- a/cmd/evm/testdata/23/exp.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "result": { - "stateRoot": "0x65334305e4accfa18352deb24f007b837b5036425b0712cf0e65a43bfa95154d", - "txRoot": "0x75e61774a2ff58cbe32653420256c7f44bc715715a423b0b746d5c622979af6b", - "receiptsRoot": "0xf951f9396af203499cc7d379715a9110323de73967c5700e2f424725446a3c76", - "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "receipts": [ - { - "root": "0x", - "status": "0x1", - "cumulativeGasUsed": "0x520b", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "logs": null, - "transactionHash": "0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81", - "contractAddress": "0x0000000000000000000000000000000000000000", - "gasUsed": "0x520b", - "effectiveGasPrice": null, - "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "transactionIndex": "0x0" - } - ], - "currentDifficulty": "0x20000", - "gasUsed": "0x520b" - } -} diff --git a/cmd/evm/testdata/23/readme.md b/cmd/evm/testdata/23/readme.md deleted file mode 100644 index f31b64de2f..0000000000 --- a/cmd/evm/testdata/23/readme.md +++ /dev/null @@ -1 +0,0 @@ -These files exemplify how to sign a transaction using the pre-EIP155 scheme. diff --git a/cmd/evm/testdata/23/txs.json b/cmd/evm/testdata/23/txs.json deleted file mode 100644 index 22f3840f84..0000000000 --- a/cmd/evm/testdata/23/txs.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - { - "input" : "0x", - "gas" : "0x5f5e100", - "gasPrice" : "0x1", - "nonce" : "0x0", - "to" : "0x095e7baea6a6c7c4c2dfeb977efac326af552d87", - "value" : "0x186a0", - "v" : "0x0", - "r" : "0x0", - "s" : "0x0", - "secretKey" : "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", - "protected": false - } -] diff --git a/cmd/evm/testdata/24/alloc.json b/cmd/evm/testdata/24/alloc.json deleted file mode 100644 index 73a9a03c0b..0000000000 --- a/cmd/evm/testdata/24/alloc.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "balance": "0x5ffd4878be161d74", - "code": "0x", - "nonce": "0xac", - "storage": {} - }, - "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192":{ - "balance": "0xfeedbead", - "nonce" : "0x00", - "code" : "0x44600055", - "_comment": "The code is 'sstore(0, random)'" - } -} \ No newline at end of file diff --git a/cmd/evm/testdata/24/env.json b/cmd/evm/testdata/24/env.json deleted file mode 100644 index 262cc2528c..0000000000 --- a/cmd/evm/testdata/24/env.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b", - "currentDifficulty": null, - "currentRandom": "0xdeadc0de", - "currentGasLimit": "0x750a163df65e8a", - "currentBaseFee": "0x500", - "currentNumber": "1", - "currentTimestamp": "1000" -} diff --git a/cmd/evm/testdata/24/exp.json b/cmd/evm/testdata/24/exp.json deleted file mode 100644 index b295099ab5..0000000000 --- a/cmd/evm/testdata/24/exp.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "alloc": { - "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": { - "code": "0x44600055", - "storage": { - "0x0000000000000000000000000000000000000000000000000000000000000000": "0x00000000000000000000000000000000000000000000000000000000deadc0de" - }, - "balance": "0xfeedbeaf" - }, - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "balance": "0x5ffd4878b803f972", - "nonce": "0xae" - }, - "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "balance": "0x6122400" - } - }, - "result": { - "stateRoot": "0xba04fd7f80a33bfb4b0bc5c8dc1178b05b67b3e95aeca01f516db3c93e6838e2", - "txRoot": "0x16cd3a7daa6686ceebadf53b7af2bc6919eccb730907f0e74a95a4423c209593", - "receiptsRoot": "0x22b85cda738345a9880260b2a71e144aab1ca9485f5db4fd251008350fc124c8", - "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "receipts": [ - { - "root": "0x", - "status": "0x1", - "cumulativeGasUsed": "0xa861", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "logs": null, - "transactionHash": "0x92ea4a28224d033afb20e0cc2b290d4c7c2d61f6a4800a680e4e19ac962ee941", - "contractAddress": "0x0000000000000000000000000000000000000000", - "gasUsed": "0xa861", - "effectiveGasPrice": null, - "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "transactionIndex": "0x0" - }, - { - "root": "0x", - "status": "0x1", - "cumulativeGasUsed": "0x10306", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "logs": null, - "transactionHash": "0x16b1d912f1d664f3f60f4e1b5f296f3c82a64a1a253117b4851d18bc03c4f1da", - "contractAddress": "0x0000000000000000000000000000000000000000", - "gasUsed": "0x5aa5", - "effectiveGasPrice": null, - "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "transactionIndex": "0x1" - } - ], - "currentDifficulty": "0x0", - "gasUsed": "0x10306", - "currentBaseFee": "0x500" - } -} diff --git a/cmd/evm/testdata/24/exp.json.diff b/cmd/evm/testdata/24/exp.json.diff deleted file mode 100644 index 996b8c251b..0000000000 --- a/cmd/evm/testdata/24/exp.json.diff +++ /dev/null @@ -1,30 +0,0 @@ -State root and balance differ because of SubnetEVM's fee calculations. - -CurrentDifficulty is 0 instead of null because the test overrides the -difficulty if currentRandom is provided in the environment. - ---- a/cmd/evm/testdata/24/exp.json 2023-08-25 07:34:20 -+++ b/cmd/evm/testdata/24/exp.json 2023-08-24 14:17:32 -@@ -12,11 +12,11 @@ - "nonce": "0xae" - }, - "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": { -- "balance": "0x1030600" -+ "balance": "0x6122400" - } - }, - "result": { -- "stateRoot": "0x9e4224c6bba343d5b0fdbe9200cc66a7ef2068240d901ae516e634c45a043c15", -+ "stateRoot": "0xba04fd7f80a33bfb4b0bc5c8dc1178b05b67b3e95aeca01f516db3c93e6838e2", - "txRoot": "0x16cd3a7daa6686ceebadf53b7af2bc6919eccb730907f0e74a95a4423c209593", - "receiptsRoot": "0x22b85cda738345a9880260b2a71e144aab1ca9485f5db4fd251008350fc124c8", - "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", -@@ -49,7 +49,7 @@ - "transactionIndex": "0x1" - } - ], -- "currentDifficulty": null, -+ "currentDifficulty": "0x0", - "gasUsed": "0x10306", - "currentBaseFee": "0x500" - } diff --git a/cmd/evm/testdata/24/txs.json b/cmd/evm/testdata/24/txs.json deleted file mode 100644 index 99c2068f1a..0000000000 --- a/cmd/evm/testdata/24/txs.json +++ /dev/null @@ -1,28 +0,0 @@ -[ - { - "gas": "0x186a0", - "gasPrice": "0x600", - "hash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", - "input": "0x", - "nonce": "0xac", - "to": "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192", - "value": "0x1", - "v" : "0x0", - "r" : "0x0", - "s" : "0x0", - "secretKey" : "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" - }, - { - "gas": "0x186a0", - "gasPrice": "0x600", - "hash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", - "input": "0x", - "nonce": "0xad", - "to": "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192", - "value": "0x1", - "v" : "0x0", - "r" : "0x0", - "s" : "0x0", - "secretKey" : "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" - } -] diff --git a/cmd/evm/testdata/25/alloc.json b/cmd/evm/testdata/25/alloc.json deleted file mode 100644 index d66366718e..0000000000 --- a/cmd/evm/testdata/25/alloc.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "balance": "0x5ffd4878be161d74", - "code": "0x", - "nonce": "0xac", - "storage": {} - } -} diff --git a/cmd/evm/testdata/25/env.json b/cmd/evm/testdata/25/env.json deleted file mode 100644 index 34daed6329..0000000000 --- a/cmd/evm/testdata/25/env.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "currentCoinbase": "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b", - "currentDifficulty": null, - "currentRandom": "0xdeadc0de", - "currentGasLimit": "0x750a163df65e8a", - "parentBaseFee": "0x500", - "parentGasUsed": "0x0", - "parentGasLimit": "0x750a163df65e8a", - "currentNumber": "2", - "currentTimestamp": "1", - "minBaseFee": "0x1" -} diff --git a/cmd/evm/testdata/25/env.json.diff b/cmd/evm/testdata/25/env.json.diff deleted file mode 100644 index e5cf2b08b2..0000000000 --- a/cmd/evm/testdata/25/env.json.diff +++ /dev/null @@ -1,18 +0,0 @@ -This test is modified so we use a meaningful base fee calculation that shows -fee adjustment. If we use block number 1, the parent will be genesis and the -current base fee will be minBaseFee. -The ability to set minBaseFee was added to the test, since the default minBaseFee -is too high for the test tx to work. - ---- a/cmd/evm/testdata/25/env.json 2023-08-25 07:34:20 -+++ b/cmd/evm/testdata/25/env.json 2023-08-24 14:17:32 -@@ -6,6 +6,7 @@ - "parentBaseFee": "0x500", - "parentGasUsed": "0x0", - "parentGasLimit": "0x750a163df65e8a", -- "currentNumber": "1", -- "currentTimestamp": "1000" -+ "currentNumber": "2", -+ "currentTimestamp": "1", -+ "minBaseFee": "0x1" - } diff --git a/cmd/evm/testdata/25/exp.json b/cmd/evm/testdata/25/exp.json deleted file mode 100644 index e518f47dda..0000000000 --- a/cmd/evm/testdata/25/exp.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "alloc": { - "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192": { - "balance": "0x1" - }, - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "balance": "0x5ffd4878bc29ed73", - "nonce": "0xad" - }, - "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "balance": "0x1ec3000" - } - }, - "result": { - "stateRoot": "0xb056800260ffcf459b9acdfd9b213fce174bdfa53cfeaf505f0cfa9f411db860", - "txRoot": "0x572690baf4898c2972446e56ecf0aa2a027c08a863927d2dce34472f0c5496fe", - "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", - "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "receipts": [ - { - "root": "0x", - "status": "0x1", - "cumulativeGasUsed": "0x5208", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "logs": null, - "transactionHash": "0x92ea4a28224d033afb20e0cc2b290d4c7c2d61f6a4800a680e4e19ac962ee941", - "contractAddress": "0x0000000000000000000000000000000000000000", - "gasUsed": "0x5208", - "effectiveGasPrice": null, - "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "transactionIndex": "0x0" - } - ], - "currentDifficulty": "0xdeadc0de", - "gasUsed": "0x5208", - "currentBaseFee": "0x4dd" - } -} diff --git a/cmd/evm/testdata/25/exp.json.diff b/cmd/evm/testdata/25/exp.json.diff deleted file mode 100644 index b6f95ed08e..0000000000 --- a/cmd/evm/testdata/25/exp.json.diff +++ /dev/null @@ -1,32 +0,0 @@ -State root and balance differ because of SubnetEVM's fee calculations. - -CurrentDifficulty is 0xdeadc0de instead of null because the test overrides the -difficulty if currentRandom is provided in the environment. - ---- a/cmd/evm/testdata/25/exp.json 2023-08-25 07:34:20 -+++ b/cmd/evm/testdata/25/exp.json 2023-08-24 14:17:32 -@@ -8,11 +8,11 @@ - "nonce": "0xad" - }, - "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b": { -- "balance": "0x854d00" -+ "balance": "0x1ec3000" - } - }, - "result": { -- "stateRoot": "0x5139609e39f4d158a7d1ad1800908eb0349cea9b500a8273a6cf0a7e4392639b", -+ "stateRoot": "0xb056800260ffcf459b9acdfd9b213fce174bdfa53cfeaf505f0cfa9f411db860", - "txRoot": "0x572690baf4898c2972446e56ecf0aa2a027c08a863927d2dce34472f0c5496fe", - "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", - "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", -@@ -32,8 +32,8 @@ - "transactionIndex": "0x0" - } - ], -- "currentDifficulty": null, -+ "currentDifficulty": "0xdeadc0de", - "gasUsed": "0x5208", -- "currentBaseFee": "0x460" -+ "currentBaseFee": "0x4dd" - } - } diff --git a/cmd/evm/testdata/25/txs.json b/cmd/evm/testdata/25/txs.json deleted file mode 100644 index acb4035fd1..0000000000 --- a/cmd/evm/testdata/25/txs.json +++ /dev/null @@ -1,15 +0,0 @@ -[ - { - "gas": "0x186a0", - "gasPrice": "0x600", - "hash": "0x0557bacce3375c98d806609b8d5043072f0b6a8bae45ae5a67a00d3a1a18d673", - "input": "0x", - "nonce": "0xac", - "to": "0x8a8eafb1cf62bfbeb1741769dae1a9dd47996192", - "value": "0x1", - "v" : "0x0", - "r" : "0x0", - "s" : "0x0", - "secretKey" : "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" - } -] diff --git a/cmd/evm/testdata/28/alloc.json b/cmd/evm/testdata/28/alloc.json deleted file mode 100644 index 680a89f4ed..0000000000 --- a/cmd/evm/testdata/28/alloc.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { - "balance" : "0x016345785d8a0000", - "code" : "0x", - "nonce" : "0x00", - "storage" : { - } - }, - "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { - "balance" : "0x016345785d8a0000", - "code" : "0x60004960015500", - "nonce" : "0x00", - "storage" : { - } - } -} \ No newline at end of file diff --git a/cmd/evm/testdata/28/env.json b/cmd/evm/testdata/28/env.json deleted file mode 100644 index 804689b43a..0000000000 --- a/cmd/evm/testdata/28/env.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "minBaseFee" : "0x9", - "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", - "currentNumber" : "0x01", - "currentTimestamp" : "0x079e", - "currentGasLimit" : "0x7fffffffffffffff", - "previousHash" : "0x3a9b485972e7353edd9152712492f0c58d89ef80623686b6bf947a4a6dce6cb6", - "currentBlobGasUsed" : "0x00", - "parentTimestamp" : "0x03b6", - "parentDifficulty" : "0x00", - "parentUncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "currentRandom" : "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "withdrawals" : [], - "parentBaseFee" : "0x0a", - "parentGasUsed" : "0x00", - "parentGasLimit" : "0x7fffffffffffffff", - "parentExcessBlobGas" : "0x00", - "parentBlobGasUsed" : "0x00", - "blockHashes" : { - "0" : "0x3a9b485972e7353edd9152712492f0c58d89ef80623686b6bf947a4a6dce6cb6" - }, - "parentBeaconBlockRoot": "0x0000beac00beac00beac00beac00beac00beac00beac00beac00beac00beac00" -} diff --git a/cmd/evm/testdata/28/exp.json b/cmd/evm/testdata/28/exp.json deleted file mode 100644 index 7282904cdb..0000000000 --- a/cmd/evm/testdata/28/exp.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "alloc": { - "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba": { - "balance": "0x73c57" - }, - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "balance": "0x16345785d80c3a9", - "nonce": "0x1" - }, - "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "code": "0x60004960015500", - "storage": { - "0x0000000000000000000000000000000000000000000000000000000000000001": "0x01a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8" - }, - "balance": "0x16345785d8a0000" - } - }, - "result": { - "stateRoot": "0xabcbb1d3be8aee044a219dd181fe6f2c2482749b9da95d15358ba7af9b43c372", - "txRoot": "0x4409cc4b699384ba5f8248d92b784713610c5ff9c1de51e9239da0dac76de9ce", - "receiptsRoot": "0xbff643da765981266133094092d98c81d2ac8e9a83a7bbda46c3d736f1f874ac", - "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "receipts": [ - { - "type": "0x3", - "root": "0x", - "status": "0x1", - "cumulativeGasUsed": "0xa865", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "logs": null, - "transactionHash": "0x7508d7139d002a4b3a26a4f12dec0d87cb46075c78bf77a38b569a133b509262", - "contractAddress": "0x0000000000000000000000000000000000000000", - "gasUsed": "0xa865", - "effectiveGasPrice": null, - "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "transactionIndex": "0x0" - } - ], - "currentDifficulty": null, - "gasUsed": "0xa865", - "currentBaseFee": "0x9", - "currentExcessBlobGas": "0x0", - "blobGasUsed": "0x20000" - } -} diff --git a/cmd/evm/testdata/28/txs.rlp b/cmd/evm/testdata/28/txs.rlp deleted file mode 100644 index 8df20e3aa2..0000000000 --- a/cmd/evm/testdata/28/txs.rlp +++ /dev/null @@ -1 +0,0 @@ -"0xf88bb88903f8860180026483061a8094b94f5374fce5edbc8e2a8697c15331677e6ebf0b8080c00ae1a001a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d801a025e16bb498552165016751911c3608d79000ab89dc3100776e729e6ea13091c7a03acacff7fc0cff6eda8a927dec93ca17765e1ee6cbc06c5954ce102e097c01d2" \ No newline at end of file diff --git a/cmd/evm/testdata/29/alloc.json b/cmd/evm/testdata/29/alloc.json deleted file mode 100644 index d2c879a45c..0000000000 --- a/cmd/evm/testdata/29/alloc.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { - "balance" : "0x016345785d8a0000", - "code" : "0x", - "nonce" : "0x00", - "storage" : { - } - }, - "0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02" : { - "balance" : "0x1", - "code" : "0x3373fffffffffffffffffffffffffffffffffffffffe14604457602036146024575f5ffd5b620180005f350680545f35146037575f5ffd5b6201800001545f5260205ff35b6201800042064281555f359062018000015500", - "nonce" : "0x00", - "storage" : { - } - } -} \ No newline at end of file diff --git a/cmd/evm/testdata/29/env.json b/cmd/evm/testdata/29/env.json deleted file mode 100644 index c0e4192564..0000000000 --- a/cmd/evm/testdata/29/env.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "minBaseFee" : "0x9", - "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", - "currentNumber" : "0x01", - "currentTimestamp" : "0x079e", - "currentGasLimit" : "0x7fffffffffffffff", - "previousHash" : "0x3a9b485972e7353edd9152712492f0c58d89ef80623686b6bf947a4a6dce6cb6", - "currentBlobGasUsed" : "0x00", - "parentTimestamp" : "0x03b6", - "parentDifficulty" : "0x00", - "parentUncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "currentRandom" : "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "withdrawals" : [ - ], - "parentBaseFee" : "0x0a", - "parentGasUsed" : "0x00", - "parentGasLimit" : "0x7fffffffffffffff", - "parentExcessBlobGas" : "0x00", - "parentBlobGasUsed" : "0x00", - "parentBeaconBlockRoot": "0x0000beac00beac00beac00beac00beac00beac00beac00beac00beac00beac00" -} \ No newline at end of file diff --git a/cmd/evm/testdata/29/exp.json b/cmd/evm/testdata/29/exp.json deleted file mode 100644 index eac577cc8b..0000000000 --- a/cmd/evm/testdata/29/exp.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "alloc": { - "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba": { - "balance": "0x2e248" - }, - "0x000f3df6d732807ef1319fb7b8bb8522d0beac02": { - "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604457602036146024575f5ffd5b620180005f350680545f35146037575f5ffd5b6201800001545f5260205ff35b6201800042064281555f359062018000015500", - "storage": { - "0x000000000000000000000000000000000000000000000000000000000000079e": "0x000000000000000000000000000000000000000000000000000000000000079e", - "0x000000000000000000000000000000000000000000000000000000000001879e": "0x0000beac00beac00beac00beac00beac00beac00beac00beac00beac00beac00" - }, - "balance": "0x1" - }, - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "balance": "0x16345785d871db8", - "nonce": "0x1" - } - }, - "result": { - "stateRoot": "0xbad33754200872b417eb005c29ab6d8df97f9814044a24020fccb0e4946c2c73", - "txRoot": "0x248074fabe112f7d93917f292b64932394f835bb98da91f21501574d58ec92ab", - "receiptsRoot": "0xf78dfb743fbd92ade140711c8bbc542b5e307f0ab7984eff35d751969fe57efa", - "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "receipts": [ - { - "type": "0x2", - "root": "0x", - "status": "0x1", - "cumulativeGasUsed": "0x5208", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "logs": null, - "transactionHash": "0x84f70aba406a55628a0620f26d260f90aeb6ccc55fed6ec2ac13dd4f727032ed", - "contractAddress": "0x0000000000000000000000000000000000000000", - "gasUsed": "0x5208", - "effectiveGasPrice": null, - "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "transactionIndex": "0x0" - } - ], - "currentDifficulty": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "gasUsed": "0x5208", - "currentBaseFee": "0x9", - "currentExcessBlobGas": "0x0", - "blobGasUsed": "0x0" - } -} diff --git a/cmd/evm/testdata/29/readme.md b/cmd/evm/testdata/29/readme.md deleted file mode 100644 index ab02ce9cf8..0000000000 --- a/cmd/evm/testdata/29/readme.md +++ /dev/null @@ -1,29 +0,0 @@ -## EIP 4788 - -This test contains testcases for EIP-4788. The 4788-contract is -located at address `0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02`, and this test executes a simple transaction. It also -implicitly invokes the system tx, which sets calls the contract and sets the -storage values - -``` -$ dir=./testdata/29/ && go run . t8n --state.fork=Cancun --input.alloc=$dir/alloc.json --input.txs=$dir/txs.json --input.env=$dir/env.json --output.alloc=stdout -INFO [09-27|15:34:53.049] Trie dumping started root=19a4f8..01573c -INFO [09-27|15:34:53.049] Trie dumping complete accounts=2 elapsed="192.759Âĩs" -INFO [09-27|15:34:53.050] Wrote file file=result.json -{ - "alloc": { - "0x000f3df6d732807ef1319fb7b8bb8522d0beac02": { - "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604457602036146024575f5ffd5b620180005f350680545f35146037575f5ffd5b6201800001545f5260205ff35b6201800042064281555f359062018000015500", - "storage": { - "0x000000000000000000000000000000000000000000000000000000000000079e": "0x000000000000000000000000000000000000000000000000000000000000079e", - "0x000000000000000000000000000000000000000000000000000000000001879e": "0x0000beac00beac00beac00beac00beac00beac00beac00beac00beac00beac00" - }, - "balance": "0x1" - }, - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "balance": "0x16345785d871db8", - "nonce": "0x1" - } - } -} -``` diff --git a/cmd/evm/testdata/29/txs.json b/cmd/evm/testdata/29/txs.json deleted file mode 100644 index d6743cc4d2..0000000000 --- a/cmd/evm/testdata/29/txs.json +++ /dev/null @@ -1,19 +0,0 @@ -[ - { - "input" : "0x", - "gas" : "0x10000000", - "nonce" : "0x0", - "to" : "0x1111111111111111111111111111111111111111", - "value" : "0x0", - "secretKey" : "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", - "chainId" : "0x1", - "type" : "0x2", - "v": "0x0", - "r": "0x0", - "s": "0x0", - "maxFeePerGas" : "0xfa0", - "maxPriorityFeePerGas" : "0x0", - "accessList" : [ - ] - } -] \ No newline at end of file diff --git a/cmd/evm/testdata/3/alloc.json b/cmd/evm/testdata/3/alloc.json deleted file mode 100644 index dca318ee54..0000000000 --- a/cmd/evm/testdata/3/alloc.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "0x095e7baea6a6c7c4c2dfeb977efac326af552d87" : { - "balance" : "0x0de0b6b3a7640000", - "code" : "0x600140", - "nonce" : "0x00", - "storage" : { - } - }, - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { - "balance" : "0x0de0b6b3a7640000", - "code" : "0x", - "nonce" : "0x00", - "storage" : { - } - } -} \ No newline at end of file diff --git a/cmd/evm/testdata/3/env.json b/cmd/evm/testdata/3/env.json deleted file mode 100644 index e283eff461..0000000000 --- a/cmd/evm/testdata/3/env.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", - "currentDifficulty" : "0x020000", - "currentGasLimit" : "0x3b9aca00", - "currentNumber" : "0x05", - "currentTimestamp" : "0x03e8", - "blockHashes" : { "1" : "0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"} -} \ No newline at end of file diff --git a/cmd/evm/testdata/3/exp.json b/cmd/evm/testdata/3/exp.json deleted file mode 100644 index 7230dca2cf..0000000000 --- a/cmd/evm/testdata/3/exp.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "alloc": { - "0x095e7baea6a6c7c4c2dfeb977efac326af552d87": { - "code": "0x600140", - "balance": "0xde0b6b3a76586a0" - }, - "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba": { - "balance": "0x521f" - }, - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "balance": "0xde0b6b3a7622741", - "nonce": "0x1" - } - }, - "result": { - "stateRoot": "0xb7341da3f9f762a6884eaa186c32942734c146b609efee11c4b0214c44857ea1", - "txRoot": "0x75e61774a2ff58cbe32653420256c7f44bc715715a423b0b746d5c622979af6b", - "receiptsRoot": "0xd0d26df80374a327c025d405ebadc752b1bbd089d864801ae78ab704bcad8086", - "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "receipts": [ - { - "root": "0x", - "status": "0x1", - "cumulativeGasUsed": "0x521f", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "logs": null, - "transactionHash": "0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81", - "contractAddress": "0x0000000000000000000000000000000000000000", - "gasUsed": "0x521f", - "effectiveGasPrice": null, - "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "transactionIndex": "0x0" - } - ], - "currentDifficulty": "0x20000", - "gasUsed": "0x521f" - } -} diff --git a/cmd/evm/testdata/3/readme.md b/cmd/evm/testdata/3/readme.md deleted file mode 100644 index 246c58ef3b..0000000000 --- a/cmd/evm/testdata/3/readme.md +++ /dev/null @@ -1,2 +0,0 @@ -These files exemplify a transition where a transaction (executed on block 5) requests -the blockhash for block `1`. diff --git a/cmd/evm/testdata/3/txs.json b/cmd/evm/testdata/3/txs.json deleted file mode 100644 index 3044458588..0000000000 --- a/cmd/evm/testdata/3/txs.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "input" : "0x", - "gas" : "0x5f5e100", - "gasPrice" : "0x1", - "nonce" : "0x0", - "to" : "0x095e7baea6a6c7c4c2dfeb977efac326af552d87", - "value" : "0x186a0", - "v" : "0x1b", - "r" : "0x88544c93a564b4c28d2ffac2074a0c55fdd4658fe0d215596ed2e32e3ef7f56b", - "s" : "0x7fb4075d54190f825d7c47bb820284757b34fd6293904a93cddb1d3aa961ac28", - "hash" : "0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81" - } -] \ No newline at end of file diff --git a/cmd/evm/testdata/30/README.txt b/cmd/evm/testdata/30/README.txt deleted file mode 100644 index 84c92de853..0000000000 --- a/cmd/evm/testdata/30/README.txt +++ /dev/null @@ -1,77 +0,0 @@ -This example comes from https://github.com/ethereum/go-ethereum/issues/27730. -The input transactions contain three transactions, number `0` and `2` are taken from -`testdata/13`, whereas number `1` is taken from #27730. - -The problematic second transaction cannot be RLP-decoded, and the expectation is -that that particular transaction should be rejected, but number `0` and `1` should -still be accepted. - -``` -$ go run . t8n --input.alloc=./testdata/30/alloc.json --input.txs=./testdata/30/txs_more.rlp --input.env=./testdata/30/env.json --output.result=stdout --output.alloc=stdout --state.fork=Cancun -WARN [10-22|15:38:03.283] rejected tx index=1 error="rlp: input string too short for common.Address, decoding into (types.Transaction)(types.BlobTx).To" -INFO [10-22|15:38:03.284] Trie dumping started root=348312..915c93 -INFO [10-22|15:38:03.284] Trie dumping complete accounts=3 elapsed="160.831Âĩs" -{ - "alloc": { - "0x095e7baea6a6c7c4c2dfeb977efac326af552d87": { - "code": "0x60004960005500", - "balance": "0xde0b6b3a7640000" - }, - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "balance": "0xde0b6b3a7640000" - }, - "0xd02d72e067e77158444ef2020ff2d325f929b363": { - "balance": "0xfffffffb8390", - "nonce": "0x3" - } - }, - "result": { - "stateRoot": "0x3483124b6710486c9fb3e07975669c66924697c88cccdcc166af5e1218915c93", - "txRoot": "0x013509c8563d41c0ae4bf38f2d6d19fc6512a1d0d6be045079c8c9f68bf45f9d", - "receiptsRoot": "0x75308898d571eafb5cd8cde8278bf5b3d13c5f6ec074926de3bb895b519264e1", - "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "receipts": [ - { - "type": "0x2", - "root": "0x", - "status": "0x1", - "cumulativeGasUsed": "0x5208", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "logs": null, - "transactionHash": "0xa98a24882ea90916c6a86da650fbc6b14238e46f0af04a131ce92be897507476", - "contractAddress": "0x0000000000000000000000000000000000000000", - "gasUsed": "0x5208", - "effectiveGasPrice": null, - "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "transactionIndex": "0x0" - }, - { - "type": "0x2", - "root": "0x", - "status": "0x1", - "cumulativeGasUsed": "0xa410", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "logs": null, - "transactionHash": "0x36bad80acce7040c45fd32764b5c2b2d2e6f778669fb41791f73f546d56e739a", - "contractAddress": "0x0000000000000000000000000000000000000000", - "gasUsed": "0x5208", - "effectiveGasPrice": null, - "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "transactionIndex": "0x1" - } - ], - "rejected": [ - { - "index": 1, - "error": "rlp: input string too short for common.Address, decoding into (types.Transaction)(types.BlobTx).To" - } - ], - "currentDifficulty": null, - "gasUsed": "0xa410", - "currentBaseFee": "0x7", - "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" - } -} - -``` \ No newline at end of file diff --git a/cmd/evm/testdata/30/alloc.json b/cmd/evm/testdata/30/alloc.json deleted file mode 100644 index 6bc93d2552..0000000000 --- a/cmd/evm/testdata/30/alloc.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "0x095e7baea6a6c7c4c2dfeb977efac326af552d87" : { - "balance" : "0x0de0b6b3a7640000", - "code" : "0x60004960005500", - "nonce" : "0x00", - "storage" : { - } - }, - "0xd02d72e067e77158444ef2020ff2d325f929b363" : { - "balance": "0x01000000000000", - "code": "0x", - "nonce": "0x01", - "storage": { - } - }, - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { - "balance" : "0x0de0b6b3a7640000", - "code" : "0x", - "nonce" : "0x00", - "storage" : { - } - } -} \ No newline at end of file diff --git a/cmd/evm/testdata/30/env.json b/cmd/evm/testdata/30/env.json deleted file mode 100644 index da07d60acc..0000000000 --- a/cmd/evm/testdata/30/env.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", - "currentNumber" : "0x01", - "currentTimestamp" : "0x03e8", - "currentGasLimit" : "0x1000000000", - "previousHash" : "0xe4e2a30b340bec696242b67584264f878600dce98354ae0b6328740fd4ff18da", - "currentDataGasUsed" : "0x2000", - "parentTimestamp" : "0x00", - "parentDifficulty" : "0x00", - "parentUncleHash" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "parentBeaconBlockRoot" : "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "currentRandom" : "0x0000000000000000000000000000000000000000000000000000000000020000", - "withdrawals" : [ - ], - "parentBaseFee" : "0x08", - "parentGasUsed" : "0x00", - "parentGasLimit" : "0x1000000000", - "parentExcessBlobGas" : "0x1000", - "parentBlobGasUsed" : "0x2000", - "blockHashes" : { - "0" : "0xe4e2a30b340bec696242b67584264f878600dce98354ae0b6328740fd4ff18da" - }, - "minBaseFee": "0x1" -} \ No newline at end of file diff --git a/cmd/evm/testdata/30/exp.json b/cmd/evm/testdata/30/exp.json deleted file mode 100644 index e6ec4f2746..0000000000 --- a/cmd/evm/testdata/30/exp.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "alloc": { - "0x095e7baea6a6c7c4c2dfeb977efac326af552d87": { - "code": "0x60004960005500", - "balance": "0xde0b6b3a7640000" - }, - "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba": { - "balance": "0xa410" - }, - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b": { - "balance": "0xde0b6b3a7640000" - }, - "0xd02d72e067e77158444ef2020ff2d325f929b363": { - "balance": "0xffffffff5bf0", - "nonce": "0x3" - } - }, - "result": { - "stateRoot": "0x8b295ea0347ce22ffd1c9f9e5068f2eb1bd1a72c11fe00771c6c9f30695ccf90", - "txRoot": "0x013509c8563d41c0ae4bf38f2d6d19fc6512a1d0d6be045079c8c9f68bf45f9d", - "receiptsRoot": "0x75308898d571eafb5cd8cde8278bf5b3d13c5f6ec074926de3bb895b519264e1", - "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "receipts": [ - { - "type": "0x2", - "root": "0x", - "status": "0x1", - "cumulativeGasUsed": "0x5208", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "logs": null, - "transactionHash": "0xa98a24882ea90916c6a86da650fbc6b14238e46f0af04a131ce92be897507476", - "contractAddress": "0x0000000000000000000000000000000000000000", - "gasUsed": "0x5208", - "effectiveGasPrice": null, - "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "transactionIndex": "0x0" - }, - { - "type": "0x2", - "root": "0x", - "status": "0x1", - "cumulativeGasUsed": "0xa410", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "logs": null, - "transactionHash": "0x36bad80acce7040c45fd32764b5c2b2d2e6f778669fb41791f73f546d56e739a", - "contractAddress": "0x0000000000000000000000000000000000000000", - "gasUsed": "0x5208", - "effectiveGasPrice": null, - "blockHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "transactionIndex": "0x1" - } - ], - "rejected": [ - { - "index": 1, - "error": "rlp: input string too short for common.Address, decoding into (types.Transaction)(types.BlobTx).To" - } - ], - "currentDifficulty": "0x20000", - "gasUsed": "0xa410", - "currentBaseFee": "0x1", - "currentExcessBlobGas": "0x0", - "blobGasUsed": "0x0" - } -} \ No newline at end of file diff --git a/cmd/evm/testdata/30/txs.rlp b/cmd/evm/testdata/30/txs.rlp deleted file mode 100644 index 620c1a13ac..0000000000 --- a/cmd/evm/testdata/30/txs.rlp +++ /dev/null @@ -1 +0,0 @@ -"0xf8dbb8d903f8d601800285012a05f200833d090080830186a000f85bf85994095e7baea6a6c7c4c2dfeb977efac326af552d87f842a00000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010ae1a001a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d880a0fc12b67159a3567f8bdbc49e0be369a2e20e09d57a51c41310543a4128409464a02de0cfe5495c4f58ff60645ceda0afd67a4c90a70bc89fe207269435b35e5b67" \ No newline at end of file diff --git a/cmd/evm/testdata/30/txs_more.rlp b/cmd/evm/testdata/30/txs_more.rlp deleted file mode 100644 index 35af8d1f23..0000000000 --- a/cmd/evm/testdata/30/txs_more.rlp +++ /dev/null @@ -1 +0,0 @@ -"0xf901adb86702f864010180820fa08284d09411111111111111111111111111111111111111118080c001a0b7dfab36232379bb3d1497a4f91c1966b1f932eae3ade107bf5d723b9cb474e0a06261c359a10f2132f126d250485b90cf20f30340801244a08ef6142ab33d1904b8d903f8d601800285012a05f200833d090080830186a000f85bf85994095e7baea6a6c7c4c2dfeb977efac326af552d87f842a00000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000010ae1a001a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d880a0fc12b67159a3567f8bdbc49e0be369a2e20e09d57a51c41310543a4128409464a02de0cfe5495c4f58ff60645ceda0afd67a4c90a70bc89fe207269435b35e5b67b86702f864010280820fa08284d09411111111111111111111111111111111111111118080c080a0d4ec563b6568cd42d998fc4134b36933c6568d01533b5adf08769270243c6c7fa072bf7c21eac6bbeae5143371eef26d5e279637f3bd73482b55979d76d935b1e9" \ No newline at end of file diff --git a/cmd/evm/testdata/4/alloc.json b/cmd/evm/testdata/4/alloc.json deleted file mode 100644 index fadf2bdc4e..0000000000 --- a/cmd/evm/testdata/4/alloc.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "0x095e7baea6a6c7c4c2dfeb977efac326af552d87" : { - "balance" : "0x0de0b6b3a7640000", - "code" : "0x600340", - "nonce" : "0x00", - "storage" : { - } - }, - "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { - "balance" : "0x0de0b6b3a7640000", - "code" : "0x", - "nonce" : "0x00", - "storage" : { - } - } -} \ No newline at end of file diff --git a/cmd/evm/testdata/4/env.json b/cmd/evm/testdata/4/env.json deleted file mode 100644 index e283eff461..0000000000 --- a/cmd/evm/testdata/4/env.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "currentCoinbase" : "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", - "currentDifficulty" : "0x020000", - "currentGasLimit" : "0x3b9aca00", - "currentNumber" : "0x05", - "currentTimestamp" : "0x03e8", - "blockHashes" : { "1" : "0xdac58aa524e50956d0c0bae7f3f8bb9d35381365d07804dd5b48a5a297c06af4"} -} \ No newline at end of file diff --git a/cmd/evm/testdata/4/readme.md b/cmd/evm/testdata/4/readme.md deleted file mode 100644 index eede41a9fd..0000000000 --- a/cmd/evm/testdata/4/readme.md +++ /dev/null @@ -1,3 +0,0 @@ -These files exemplify a transition where a transaction (executed on block 5) requests -the blockhash for block `4`, but where the hash for that block is missing. -It's expected that executing these should cause `exit` with errorcode `4`. diff --git a/cmd/evm/testdata/4/txs.json b/cmd/evm/testdata/4/txs.json deleted file mode 100644 index 3044458588..0000000000 --- a/cmd/evm/testdata/4/txs.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "input" : "0x", - "gas" : "0x5f5e100", - "gasPrice" : "0x1", - "nonce" : "0x0", - "to" : "0x095e7baea6a6c7c4c2dfeb977efac326af552d87", - "value" : "0x186a0", - "v" : "0x1b", - "r" : "0x88544c93a564b4c28d2ffac2074a0c55fdd4658fe0d215596ed2e32e3ef7f56b", - "s" : "0x7fb4075d54190f825d7c47bb820284757b34fd6293904a93cddb1d3aa961ac28", - "hash" : "0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81" - } -] \ No newline at end of file diff --git a/cmd/evm/testdata/5/alloc.json b/cmd/evm/testdata/5/alloc.json deleted file mode 100644 index 9e26dfeeb6..0000000000 --- a/cmd/evm/testdata/5/alloc.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/cmd/evm/testdata/5/env.json b/cmd/evm/testdata/5/env.json deleted file mode 100644 index 1085f63e62..0000000000 --- a/cmd/evm/testdata/5/env.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "currentCoinbase": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", - "currentDifficulty": "0x20000", - "currentGasLimit": "0x750a163df65e8a", - "currentNumber": "1", - "currentTimestamp": "1000", - "ommers": [ - {"delta": 1, "address": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" }, - {"delta": 2, "address": "0xcccccccccccccccccccccccccccccccccccccccc" } - ] -} \ No newline at end of file diff --git a/cmd/evm/testdata/5/exp.json b/cmd/evm/testdata/5/exp.json deleted file mode 100644 index 7d715672c5..0000000000 --- a/cmd/evm/testdata/5/exp.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "alloc": { - "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": { - "balance": "0x88" - }, - "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb": { - "balance": "0x70" - }, - "0xcccccccccccccccccccccccccccccccccccccccc": { - "balance": "0x60" - } - }, - "result": { - "stateRoot": "0xa7312add33811645c6aa65d928a1a4f49d65d448801912c069a0aa8fe9c1f393", - "txRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", - "logsHash": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "receipts": [], - "currentDifficulty": "0x20000", - "gasUsed": "0x0" - } -} diff --git a/cmd/evm/testdata/5/readme.md b/cmd/evm/testdata/5/readme.md deleted file mode 100644 index 1a84afaab6..0000000000 --- a/cmd/evm/testdata/5/readme.md +++ /dev/null @@ -1 +0,0 @@ -These files exemplify a transition where there are no transactions, two ommers, at block `N-1` (delta 1) and `N-2` (delta 2). \ No newline at end of file diff --git a/cmd/evm/testdata/5/txs.json b/cmd/evm/testdata/5/txs.json deleted file mode 100644 index fe51488c70..0000000000 --- a/cmd/evm/testdata/5/txs.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/cmd/evm/transition-test.sh b/cmd/evm/transition-test.sh deleted file mode 100755 index 52fe01e25f..0000000000 --- a/cmd/evm/transition-test.sh +++ /dev/null @@ -1,518 +0,0 @@ -#!/usr/bin/env bash -ticks="\`\`\`" - -function showjson(){ - echo "\`$1\`:" - echo "${ticks}json" - cat $1 - echo "" - echo "$ticks" -} -function demo(){ - echo "$ticks" - echo "$1" - $1 - echo "" - echo "$ticks" - echo "" -} -function tick(){ - echo "$ticks" -} - -function code(){ - echo "$ticks$1" -} - -cat << "EOF" -# EVM tool - -The EVM tool provides a few useful subcommands to facilitate testing at the EVM -layer. - -* transition tool (`t8n`) : a stateless state transition utility -* transaction tool (`t9n`) : a transaction validation utility -* block builder tool (`b11r`): a block assembler utility - -## State transition tool (`t8n`) - - -The `evm t8n` tool is a stateless state transition utility. It is a utility -which can - -1. Take a prestate, including - - Accounts, - - Block context information, - - Previous blockshashes (*optional) -2. Apply a set of transactions, -3. Apply a mining-reward (*optional), -4. And generate a post-state, including - - State root, transaction root, receipt root, - - Information about rejected transactions, - - Optionally: a full or partial post-state dump - -### Specification - -The idea is to specify the behaviour of this binary very _strict_, so that other -node implementors can build replicas based on their own state-machines, and the -state generators can swap between a \`geth\`-based implementation and a \`parityvm\`-based -implementation. - -#### Command line params - -Command line params that need to be supported are - -``` -EOF -./evm t8n -h | grep "\-\-trace\.\|\-\-output\.\|\-\-state\.\|\-\-input" -cat << "EOF" -``` -#### Objects - -The transition tool uses JSON objects to read and write data related to the transition operation. The -following object definitions are required. - -##### `alloc` - -The `alloc` object defines the prestate that transition will begin with. - -```go -// Map of address to account definition. -type Alloc map[common.Address]Account -// Genesis account. Each field is optional. -type Account struct { - Code []byte `json:"code"` - Storage map[common.Hash]common.Hash `json:"storage"` - Balance *big.Int `json:"balance"` - Nonce uint64 `json:"nonce"` - SecretKey []byte `json:"secretKey"` -} -``` - -##### `env` - -The `env` object defines the environmental context in which the transition will -take place. - -```go -type Env struct { - // required - CurrentCoinbase common.Address `json:"currentCoinbase"` - CurrentGasLimit uint64 `json:"currentGasLimit"` - CurrentNumber uint64 `json:"currentNumber"` - CurrentTimestamp uint64 `json:"currentTimestamp"` - Withdrawals []*Withdrawal `json:"withdrawals"` - // optional - CurrentDifficulty *big.Int `json:"currentDifficuly"` - CurrentRandom *big.Int `json:"currentRandom"` - CurrentBaseFee *big.Int `json:"currentBaseFee"` - ParentDifficulty *big.Int `json:"parentDifficulty"` - ParentGasUsed uint64 `json:"parentGasUsed"` - ParentGasLimit uint64 `json:"parentGasLimit"` - ParentTimestamp uint64 `json:"parentTimestamp"` - BlockHashes map[uint64]common.Hash `json:"blockHashes"` - ParentUncleHash common.Hash `json:"parentUncleHash"` - Ommers []Ommer `json:"ommers"` -} -type Ommer struct { - Delta uint64 `json:"delta"` - Address common.Address `json:"address"` -} -type Withdrawal struct { - Index uint64 `json:"index"` - ValidatorIndex uint64 `json:"validatorIndex"` - Recipient common.Address `json:"recipient"` - Amount *big.Int `json:"amount"` -} -``` - -##### `txs` - -The `txs` object is an array of any of the transaction types: `LegacyTx`, -`AccessListTx`, or `DynamicFeeTx`. - -```go -type LegacyTx struct { - Nonce uint64 `json:"nonce"` - GasPrice *big.Int `json:"gasPrice"` - Gas uint64 `json:"gas"` - To *common.Address `json:"to"` - Value *big.Int `json:"value"` - Data []byte `json:"data"` - V *big.Int `json:"v"` - R *big.Int `json:"r"` - S *big.Int `json:"s"` - SecretKey *common.Hash `json:"secretKey"` -} -type AccessList []AccessTuple -type AccessTuple struct { - Address common.Address `json:"address" gencodec:"required"` - StorageKeys []common.Hash `json:"storageKeys" gencodec:"required"` -} -type AccessListTx struct { - ChainID *big.Int `json:"chainId"` - Nonce uint64 `json:"nonce"` - GasPrice *big.Int `json:"gasPrice"` - Gas uint64 `json:"gas"` - To *common.Address `json:"to"` - Value *big.Int `json:"value"` - Data []byte `json:"data"` - AccessList AccessList `json:"accessList"` - V *big.Int `json:"v"` - R *big.Int `json:"r"` - S *big.Int `json:"s"` - SecretKey *common.Hash `json:"secretKey"` -} -type DynamicFeeTx struct { - ChainID *big.Int `json:"chainId"` - Nonce uint64 `json:"nonce"` - GasTipCap *big.Int `json:"maxPriorityFeePerGas"` - GasFeeCap *big.Int `json:"maxFeePerGas"` - Gas uint64 `json:"gas"` - To *common.Address `json:"to"` - Value *big.Int `json:"value"` - Data []byte `json:"data"` - AccessList AccessList `json:"accessList"` - V *big.Int `json:"v"` - R *big.Int `json:"r"` - S *big.Int `json:"s"` - SecretKey *common.Hash `json:"secretKey"` -} -``` - -##### `result` - -The `result` object is output after a transition is executed. It includes -information about the post-transition environment. - -```go -type ExecutionResult struct { - StateRoot common.Hash `json:"stateRoot"` - TxRoot common.Hash `json:"txRoot"` - ReceiptRoot common.Hash `json:"receiptsRoot"` - LogsHash common.Hash `json:"logsHash"` - Bloom types.Bloom `json:"logsBloom"` - Receipts types.Receipts `json:"receipts"` - Rejected []*rejectedTx `json:"rejected,omitempty"` - Difficulty *big.Int `json:"currentDifficulty"` - GasUsed uint64 `json:"gasUsed"` - BaseFee *big.Int `json:"currentBaseFee,omitempty"` -} -``` - -#### Error codes and output - -All logging should happen against the `stderr`. -There are a few (not many) errors that can occur, those are defined below. - -##### EVM-based errors (`2` to `9`) - -- Other EVM error. Exit code `2` -- Failed configuration: when a non-supported or invalid fork was specified. Exit code `3`. -- Block history is not supplied, but needed for a `BLOCKHASH` operation. If `BLOCKHASH` - is invoked targeting a block which history has not been provided for, the program will - exit with code `4`. - -##### IO errors (`10`-`20`) - -- Invalid input json: the supplied data could not be marshalled. - The program will exit with code `10` -- IO problems: failure to load or save files, the program will exit with code `11` - -``` -# This should exit with 3 -./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --state.fork=Frontier+1346 2>/dev/null -EOF -./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --state.fork=Frontier+1346 2>/dev/null -exitcode=$? -if [ $exitcode != 3 ]; then - echo "Failed, exitcode should be 3,was $exitcode" -else - echo "exitcode:$exitcode OK" -fi -cat << "EOF" -``` -#### Forks -### Basic usage - -The chain configuration to be used for a transition is specified via the -`--state.fork` CLI flag. A list of possible values and configurations can be -found in [`tests/init.go`](tests/init.go). - -#### Examples -##### Basic usage - -Invoking it with the provided example files -EOF -cmd="./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --state.fork=Berlin" -tick;echo "$cmd"; tick -$cmd 2>/dev/null -echo "Two resulting files:" -echo "" -showjson alloc.json -showjson result.json -echo "" - -echo "We can make them spit out the data to e.g. \`stdout\` like this:" -cmd="./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --output.result=stdout --output.alloc=stdout --state.fork=Berlin" -tick;echo "$cmd"; tick -output=`$cmd 2>/dev/null` -echo "Output:" -echo "${ticks}json" -echo "$output" -echo "$ticks" - -cat << "EOF" - -#### About Ommers - -Mining rewards and ommer rewards might need to be added. This is how those are applied: - -- `block_reward` is the block mining reward for the miner (`0xaa`), of a block at height `N`. -- For each ommer (mined by `0xbb`), with blocknumber `N-delta` - - (where `delta` is the difference between the current block and the ommer) - - The account `0xbb` (ommer miner) is awarded `(8-delta)/ 8 * block_reward` - - The account `0xaa` (block miner) is awarded `block_reward / 32` - -To make `t8n` apply these, the following inputs are required: - -- `--state.reward` - - For ethash, it is `5000000000000000000` `wei`, - - If this is not defined, mining rewards are not applied, - - A value of `0` is valid, and causes accounts to be 'touched'. -- For each ommer, the tool needs to be given an `address\` and a `delta`. This - is done via the `ommers` field in `env`. - -Note: the tool does not verify that e.g. the normal uncle rules apply, -and allows e.g two uncles at the same height, or the uncle-distance. This means that -the tool allows for negative uncle reward (distance > 8) - -Example: -EOF - -showjson ./testdata/5/env.json - -echo "When applying this, using a reward of \`0x08\`" -cmd="./evm t8n --input.alloc=./testdata/5/alloc.json -input.txs=./testdata/5/txs.json --input.env=./testdata/5/env.json --output.alloc=stdout --state.reward=0x80 --state.fork=Berlin" -output=`$cmd 2>/dev/null` -echo "Output:" -echo "${ticks}json" -echo "$output" -echo "$ticks" - -echo "#### Future EIPS" -echo "" -echo "It is also possible to experiment with future eips that are not yet defined in a hard fork." -echo "Example, putting EIP-1344 into Frontier: " -cmd="./evm t8n --state.fork=Frontier+1344 --input.pre=./testdata/1/pre.json --input.txs=./testdata/1/txs.json --input.env=/testdata/1/env.json" -tick;echo "$cmd"; tick -echo "" - -echo "#### Block history" -echo "" -echo "The \`BLOCKHASH\` opcode requires blockhashes to be provided by the caller, inside the \`env\`." -echo "If a required blockhash is not provided, the exit code should be \`4\`:" -echo "Example where blockhashes are provided: " -demo "./evm t8n --input.alloc=./testdata/3/alloc.json --input.txs=./testdata/3/txs.json --input.env=./testdata/3/env.json --trace --state.fork=Berlin" -cmd="cat trace-0-0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81.jsonl | grep BLOCKHASH -C2" -tick && echo $cmd && tick -echo "$ticks" -cat trace-0-0x72fadbef39cd251a437eea619cfeda752271a5faaaa2147df012e112159ffb81.jsonl | grep BLOCKHASH -C2 -echo "$ticks" -echo "" - -echo "In this example, the caller has not provided the required blockhash:" -cmd="./evm t8n --input.alloc=./testdata/4/alloc.json --input.txs=./testdata/4/txs.json --input.env=./testdata/4/env.json --trace --state.fork=Berlin" -tick && echo $cmd && $cmd 2>&1 -errc=$? -tick -echo "Error code: $errc" -echo "" - -echo "#### Chaining" -echo "" -echo "Another thing that can be done, is to chain invocations:" -cmd1="./evm t8n --input.alloc=./testdata/1/alloc.json --input.txs=./testdata/1/txs.json --input.env=./testdata/1/env.json --state.fork=Berlin --output.alloc=stdout" -cmd2="./evm t8n --input.alloc=stdin --input.env=./testdata/1/env.json --input.txs=./testdata/1/txs.json --state.fork=Berlin" -echo "$ticks" -echo "$cmd1 | $cmd2" -output=$($cmd1 | $cmd2 ) -echo $output -echo "$ticks" -echo "What happened here, is that we first applied two identical transactions, so the second one was rejected. " -echo "Then, taking the poststate alloc as the input for the next state, we tried again to include" -echo "the same two transactions: this time, both failed due to too low nonce." -echo "" -echo "In order to meaningfully chain invocations, one would need to provide meaningful new \`env\`, otherwise the" -echo "actual blocknumber (exposed to the EVM) would not increase." -echo "" - -echo "#### Transactions in RLP form" -echo "" -echo "It is possible to provide already-signed transactions as input to, using an \`input.txs\` which ends with the \`rlp\` suffix." -echo "The input format for RLP-form transactions is _identical_ to the _output_ format for block bodies. Therefore, it's fully possible" -echo "to use the evm to go from \`json\` input to \`rlp\` input." -echo "" -echo "The following command takes **json** the transactions in \`./testdata/13/txs.json\` and signs them. After execution, they are output to \`signed_txs.rlp\`.:" -cmd="./evm t8n --state.fork=London --input.alloc=./testdata/13/alloc.json --input.txs=./testdata/13/txs.json --input.env=./testdata/13/env.json --output.result=alloc_jsontx.json --output.body=signed_txs.rlp" -echo "$ticks" -echo $cmd -$cmd 2>&1 -echo "$ticks" -echo "" -echo "The \`output.body\` is the rlp-list of transactions, encoded in hex and placed in a string a'la \`json\` encoding rules:" -demo "cat signed_txs.rlp" -echo "We can use \`rlpdump\` to check what the contents are: " -echo "$ticks" -echo "rlpdump -hex \$(cat signed_txs.rlp | jq -r )" -rlpdump -hex $(cat signed_txs.rlp | jq -r ) -echo "$ticks" -echo "Now, we can now use those (or any other already signed transactions), as input, like so: " -cmd="./evm t8n --state.fork=London --input.alloc=./testdata/13/alloc.json --input.txs=./signed_txs.rlp --input.env=./testdata/13/env.json --output.result=alloc_rlptx.json" -echo "$ticks" -echo $cmd -$cmd 2>&1 -echo "$ticks" -echo "You might have noticed that the results from these two invocations were stored in two separate files. " -echo "And we can now finally check that they match." -echo "$ticks" -echo "cat alloc_jsontx.json | jq .stateRoot && cat alloc_rlptx.json | jq .stateRoot" -cat alloc_jsontx.json | jq .stateRoot && cat alloc_rlptx.json | jq .stateRoot -echo "$ticks" - -cat << "EOF" - -## Transaction tool - -The transaction tool is used to perform static validity checks on transactions such as: -* intrinsic gas calculation -* max values on integers -* fee semantics, such as `maxFeePerGas < maxPriorityFeePerGas` -* newer tx types on old forks - -### Examples - -EOF - -cmd="./evm t9n --state.fork Homestead --input.txs testdata/15/signed_txs.rlp" -tick;echo "$cmd"; -$cmd 2>/dev/null -tick - -cmd="./evm t9n --state.fork London --input.txs testdata/15/signed_txs.rlp" -tick;echo "$cmd"; -$cmd 2>/dev/null -tick - -cat << "EOF" -## Block builder tool (b11r) - -The `evm b11r` tool is used to assemble and seal full block rlps. - -### Specification - -#### Command line params - -Command line params that need to be supported are: - -``` - --input.header value `stdin` or file name of where to find the block header to use. (default: "header.json") - --input.ommers value `stdin` or file name of where to find the list of ommer header RLPs to use. - --input.txs value `stdin` or file name of where to find the transactions list in RLP form. (default: "txs.rlp") - --output.basedir value Specifies where output files are placed. Will be created if it does not exist. - --output.block value Determines where to put the alloc of the post-state. (default: "block.json") - - into the file - `stdout` - into the stdout output - `stderr` - into the stderr output - --seal.clique value Seal block with Clique. `stdin` or file name of where to find the Clique sealing data. - --seal.ethash Seal block with ethash. (default: false) - --seal.ethash.dir value Path to ethash DAG. If none exists, a new DAG will be generated. - --seal.ethash.mode value Defines the type and amount of PoW verification an ethash engine makes. (default: "normal") - --verbosity value Sets the verbosity level. (default: 3) -``` - -#### Objects - -##### `header` - -The `header` object is a consensus header. - -```go= -type Header struct { - ParentHash common.Hash `json:"parentHash"` - OmmerHash *common.Hash `json:"sha3Uncles"` - Coinbase *common.Address `json:"miner"` - Root common.Hash `json:"stateRoot" gencodec:"required"` - TxHash *common.Hash `json:"transactionsRoot"` - ReceiptHash *common.Hash `json:"receiptsRoot"` - Bloom types.Bloom `json:"logsBloom"` - Difficulty *big.Int `json:"difficulty"` - Number *big.Int `json:"number" gencodec:"required"` - GasLimit uint64 `json:"gasLimit" gencodec:"required"` - GasUsed uint64 `json:"gasUsed"` - Time uint64 `json:"timestamp" gencodec:"required"` - Extra []byte `json:"extraData"` - MixDigest common.Hash `json:"mixHash"` - Nonce *types.BlockNonce `json:"nonce"` - BaseFee *big.Int `json:"baseFeePerGas"` -} -``` -#### `ommers` - -The `ommers` object is a list of RLP-encoded ommer blocks in hex -representation. - -```go= -type Ommers []string -``` - -#### `txs` - -The `txs` object is a list of RLP-encoded transactions in hex representation. - -```go= -type Txs []string -``` - -#### `clique` - -The `clique` object provides the necessary information to complete a clique -seal of the block. - -```go= -var CliqueInfo struct { - Key *common.Hash `json:"secretKey"` - Voted *common.Address `json:"voted"` - Authorize *bool `json:"authorize"` - Vanity common.Hash `json:"vanity"` -} -``` - -#### `output` - -The `output` object contains two values, the block RLP and the block hash. - -```go= -type BlockInfo struct { - Rlp []byte `json:"rlp"` - Hash common.Hash `json:"hash"` -} -``` - -## A Note on Encoding - -The encoding of values for `evm` utility attempts to be relatively flexible. It -generally supports hex-encoded or decimal-encoded numeric values, and -hex-encoded byte values (like `common.Address`, `common.Hash`, etc). When in -doubt, the [`execution-apis`](https://github.com/ethereum/execution-apis) way -of encoding should always be accepted. - -## Testing - -There are many test cases in the [`cmd/evm/testdata`](./testdata) directory. -These fixtures are used to power the `t8n` tests in -[`t8n_test.go`](./t8n_test.go). The best way to verify correctness of new `evm` -implementations is to execute these and verify the output and error codes match -the expected values. - -EOF diff --git a/cmd/precompilegen/main.go b/cmd/precompilegen/main.go deleted file mode 100644 index 35328f4fcd..0000000000 --- a/cmd/precompilegen/main.go +++ /dev/null @@ -1,198 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. -// -// This file is a derived work, based on the go-ethereum library whose original -// notices appear below. -// -// It is distributed under a license compatible with the licensing terms of the -// original code from which it is derived. -// -// Much love to the original authors for their work. -// ********** -// Copyright 2016 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "fmt" - "io" - "os" - "path/filepath" - "strings" - - _ "embed" - - "github.com/ava-labs/subnet-evm/accounts/abi/bind" - "github.com/ava-labs/subnet-evm/accounts/abi/bind/precompilebind" - "github.com/ava-labs/subnet-evm/cmd/utils" - "github.com/ava-labs/subnet-evm/internal/flags" - "github.com/ethereum/go-ethereum/log" - "github.com/urfave/cli/v2" -) - -//go:embed template-readme.md -var readme string - -var ( - // Flags needed by abigen - abiFlag = &cli.StringFlag{ - Name: "abi", - Usage: "Path to the contract ABI json to generate, - for STDIN", - } - typeFlag = &cli.StringFlag{ - Name: "type", - Usage: "Struct name for the precompile (default = {abi file name})", - } - pkgFlag = &cli.StringFlag{ - Name: "pkg", - Usage: "Go package name to generate the precompile into (default = {type})", - } - outFlag = &cli.StringFlag{ - Name: "out", - Usage: "Output folder for the generated precompile files, - for STDOUT (default = ./precompile/contracts/{pkg}). Test files won't be generated if STDOUT is used", - } -) - -var app = flags.NewApp("subnet-evm precompile generator tool") - -func init() { - app.Name = "precompilegen" - app.Flags = []cli.Flag{ - abiFlag, - outFlag, - pkgFlag, - typeFlag, - } - app.Action = precompilegen -} - -func precompilegen(c *cli.Context) error { - outFlagStr := c.String(outFlag.Name) - isOutStdout := outFlagStr == "-" - - if isOutStdout && !c.IsSet(typeFlag.Name) { - utils.Fatalf("type (--type) should be set explicitly for STDOUT ") - } - lang := bind.LangGo - // If the entire solidity code was specified, build and bind based on that - var ( - bins []string - types []string - sigs []map[string]string - libs = make(map[string]string) - aliases = make(map[string]string) - ) - if c.String(abiFlag.Name) == "" { - utils.Fatalf("no abi path is specified (--abi)") - } - // Load up the ABI - var ( - abi []byte - err error - ) - - input := c.String(abiFlag.Name) - if input == "-" { - abi, err = io.ReadAll(os.Stdin) - } else { - abi, err = os.ReadFile(input) - } - if err != nil { - utils.Fatalf("Failed to read input ABI: %v", err) - } - - bins = append(bins, "") - - kind := c.String(typeFlag.Name) - if kind == "" { - fn := filepath.Base(input) - kind = strings.TrimSuffix(fn, filepath.Ext(fn)) - kind = strings.TrimSpace(kind) - } - types = append(types, kind) - - pkg := c.String(pkgFlag.Name) - if pkg == "" { - pkg = strings.ToLower(kind) - } - - if outFlagStr == "" { - outFlagStr = filepath.Join("./precompile/contracts", pkg) - } - - abifilename := "" - abipath := "" - // we should not generate the abi file if output is set to stdout - if !isOutStdout { - // get file name from the output path - abifilename = "contract.abi" - abipath = filepath.Join(outFlagStr, abifilename) - } - // if output is set to stdout, we should not generate the test codes - generateTests := !isOutStdout - - // Generate the contract precompile - bindedFiles, err := precompilebind.PrecompileBind(types, string(abi), bins, sigs, pkg, lang, libs, aliases, abifilename, generateTests) - if err != nil { - utils.Fatalf("Failed to generate precompile: %v", err) - } - - // Either flush it out to a file or display on the standard output - // Skip displaying test codes here. - if isOutStdout { - for _, file := range bindedFiles { - if !file.IsTest { - fmt.Printf("-----file: %s-----\n", file.FileName) - fmt.Printf("%s\n", file.Content) - } - } - return nil - } - - if _, err := os.Stat(outFlagStr); os.IsNotExist(err) { - os.MkdirAll(outFlagStr, 0o700) // Create your file - } - - for _, file := range bindedFiles { - outputPath := filepath.Join(outFlagStr, file.FileName) - if err := os.WriteFile(outputPath, []byte(file.Content), 0o600); err != nil { - utils.Fatalf("Failed to write generated file %s: %v", file.FileName, err) - } - } - - // Write the ABI to the output folder - if err := os.WriteFile(abipath, abi, 0o600); err != nil { - utils.Fatalf("Failed to write ABI: %v", err) - } - - // Write the README to the output folder - readmeOut := filepath.Join(outFlagStr, "README.md") - if err := os.WriteFile(readmeOut, []byte(readme), 0o600); err != nil { - utils.Fatalf("Failed to write README: %v", err) - } - - fmt.Println("Precompile files generated successfully at: ", outFlagStr) - return nil -} - -func main() { - log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelInfo, true))) - - if err := app.Run(os.Args); err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } -} diff --git a/cmd/precompilegen/template-readme.md b/cmd/precompilegen/template-readme.md deleted file mode 100644 index 09aa152658..0000000000 --- a/cmd/precompilegen/template-readme.md +++ /dev/null @@ -1,25 +0,0 @@ -There are some must-be-done changes waiting in the generated file. Each area requiring you to add your code is marked with CUSTOM CODE to make them easy to find and modify. -Additionally there are other files you need to edit to activate your precompile. -These areas are highlighted with comments "ADD YOUR PRECOMPILE HERE". -For testing take a look at other precompile tests in contract_test.go and config_test.go in other precompile folders. -See the tutorial in for more information about precompile development. - -General guidelines for precompile development: - -1- Set a suitable config key in generated module.go. E.g: "yourPrecompileConfig" -2- Read the comment and set a suitable contract address in generated module.go. E.g: -ContractAddress = common.HexToAddress("ASUITABLEHEXADDRESS") -3- It is recommended to only modify code in the highlighted areas marked with "CUSTOM CODE STARTS HERE". Typically, custom codes are required in only those areas. -Modifying code outside of these areas should be done with caution and with a deep understanding of how these changes may impact the EVM. -4- If you have any event defined in your precompile, review the generated event.go file and set your event gas costs. You should also emit your event in your function in the contract.go file. -5- Set gas costs in generated contract.go -6- Force import your precompile package in precompile/registry/registry.go -7- Add your config unit tests under generated package config_test.go -8- Add your contract unit tests under generated package contract_test.go -9- Additionally you can add a full-fledged VM test for your precompile under plugin/vm/vm_test.go. See existing precompile tests for examples. -10- Add your solidity interface and test contract to contracts/contracts -11- Write solidity contract tests for your precompile in contracts/contracts/test -12- Write TypeScript DS-Test counterparts for your solidity tests in contracts/test -13- Create your genesis with your precompile enabled in tests/precompile/genesis/ -14- Create e2e test for your solidity test in tests/precompile/solidity/suites.go -15- Run your e2e precompile Solidity tests with './scripts/run_ginkgo.sh` diff --git a/cmd/simulator/.simulator/keys/0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC b/cmd/simulator/.simulator/keys/0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC deleted file mode 100644 index 289ffc12f2..0000000000 --- a/cmd/simulator/.simulator/keys/0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC +++ /dev/null @@ -1 +0,0 @@ -56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027 diff --git a/cmd/simulator/README.md b/cmd/simulator/README.md deleted file mode 100644 index f05304c079..0000000000 --- a/cmd/simulator/README.md +++ /dev/null @@ -1,62 +0,0 @@ -# Load Simulator - -When building developing your own blockchain using `subnet-evm`, you may want to analyze how your fee parameterization behaves and/or how many resources your VM uses under different load patterns. For this reason, we developed `cmd/simulator`. `cmd/simulator` lets you drive arbitrary load across any number of [endpoints] with a user-specified `keys` directory (insecure) `timeout`, `workers`, `max-fee-cap`, and `max-tip-cap`. - -## Building the Load Simulator - -To build the load simulator, navigate to the base of the simulator directory: - -```bash -cd $GOPATH/src/github.com/ava-labs/subnet-evm/cmd/simulator -``` - -Build the simulator: - -```bash -go build -o ./simulator main/*.go -``` - -To confirm that you built successfully, run the simulator and print the version: - -```bash -./simulator --version -``` - -This should give the following output: - -``` -v0.1.0 -``` - -To run the load simulator, you must first start an EVM based network. The load simulator works on both the C-Chain and Subnet-EVM, so we will start a single node network and run the load simulator on the C-Chain. - -To start a single node network, follow the instructions from the AvalancheGo [README](https://github.com/ava-labs/avalanchego#building-avalanchego) to build from source. - -Once you've built AvalancheGo, open the AvalancheGo directory in a separate terminal window and run a single node non-staking network with the following command: - -```bash -./build/avalanchego --sybil-protection-enabled=false --network-id=local -``` - -WARNING: - -The staking-enabled flag is only for local testing. Disabling staking serves two functions explicitly for testing purposes: - -1. Ignore stake weight on the P-Chain and count each connected peer as having a stake weight of 1 -2. Automatically opts in to validate every Subnet - -Once you have AvalancheGo running locally, it will be running an HTTP Server on the default port `9650`. This means that the RPC Endpoint for the C-Chain will be http://127.0.0.1:9650/ext/bc/C/rpc and ws://127.0.0.1:9650/ext/bc/C/ws for WebSocket connections. - -Now, we can run the simulator command to simulate some load on the local C-Chain for 30s: - -```bash -./simulator --timeout=1m --workers=1 --max-fee-cap=300 --max-tip-cap=10 --txs-per-worker=50 -``` - -## Command Line Flags - -To see all of the command line flag options, run - -```bash -./simulator --help -``` diff --git a/cmd/simulator/config/flags.go b/cmd/simulator/config/flags.go deleted file mode 100644 index 8fb3d3c5fd..0000000000 --- a/cmd/simulator/config/flags.go +++ /dev/null @@ -1,129 +0,0 @@ -// (c) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package config - -import ( - "errors" - "fmt" - "strings" - "time" - - "github.com/spf13/pflag" - "github.com/spf13/viper" -) - -const Version = "v0.1.1" - -const ( - ConfigFilePathKey = "config-file" - LogLevelKey = "log-level" - EndpointsKey = "endpoints" - MaxFeeCapKey = "max-fee-cap" - MaxTipCapKey = "max-tip-cap" - WorkersKey = "workers" - TxsPerWorkerKey = "txs-per-worker" - KeyDirKey = "key-dir" - VersionKey = "version" - TimeoutKey = "timeout" - BatchSizeKey = "batch-size" - MetricsPortKey = "metrics-port" - MetricsOutputKey = "metrics-output" -) - -var ( - ErrNoEndpoints = errors.New("must specify at least one endpoint") - ErrNoWorkers = errors.New("must specify non-zero number of workers") - ErrNoTxs = errors.New("must specify non-zero number of txs-per-worker") -) - -type Config struct { - Endpoints []string `json:"endpoints"` - MaxFeeCap int64 `json:"max-fee-cap"` - MaxTipCap int64 `json:"max-tip-cap"` - Workers int `json:"workers"` - TxsPerWorker uint64 `json:"txs-per-worker"` - KeyDir string `json:"key-dir"` - Timeout time.Duration `json:"timeout"` - BatchSize uint64 `json:"batch-size"` - MetricsPort uint64 `json:"metrics-port"` - MetricsOutput string `json:"metrics-output"` -} - -func BuildConfig(v *viper.Viper) (Config, error) { - c := Config{ - Endpoints: v.GetStringSlice(EndpointsKey), - MaxFeeCap: v.GetInt64(MaxFeeCapKey), - MaxTipCap: v.GetInt64(MaxTipCapKey), - Workers: v.GetInt(WorkersKey), - TxsPerWorker: v.GetUint64(TxsPerWorkerKey), - KeyDir: v.GetString(KeyDirKey), - Timeout: v.GetDuration(TimeoutKey), - BatchSize: v.GetUint64(BatchSizeKey), - MetricsPort: v.GetUint64(MetricsPortKey), - MetricsOutput: v.GetString(MetricsOutputKey), - } - if len(c.Endpoints) == 0 { - return c, ErrNoEndpoints - } - if c.Workers == 0 { - return c, ErrNoWorkers - } - if c.TxsPerWorker == 0 { - return c, ErrNoTxs - } - // Note: it's technically valid for the fee/tip cap to be 0, but cannot - // be less than 0. - if c.MaxFeeCap < 0 { - return c, fmt.Errorf("invalid max fee cap %d < 0", c.MaxFeeCap) - } - if c.MaxTipCap < 0 { - return c, fmt.Errorf("invalid max tip cap %d <= 0", c.MaxTipCap) - } - return c, nil -} - -func BuildViper(fs *pflag.FlagSet, args []string) (*viper.Viper, error) { - if err := fs.Parse(args); err != nil { - return nil, err - } - - v := viper.New() - v.AutomaticEnv() - v.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) - v.SetEnvPrefix("evm_simulator") - if err := v.BindPFlags(fs); err != nil { - return nil, err - } - - if v.IsSet(ConfigFilePathKey) { - v.SetConfigFile(v.GetString(ConfigFilePathKey)) - if err := v.ReadInConfig(); err != nil { - return nil, err - } - } - return v, nil -} - -// BuildFlagSet returns a complete set of flags for simulator -func BuildFlagSet() *pflag.FlagSet { - fs := pflag.NewFlagSet("simulator", pflag.ContinueOnError) - addSimulatorFlags(fs) - return fs -} - -func addSimulatorFlags(fs *pflag.FlagSet) { - fs.Bool(VersionKey, false, "Print the version and exit") - fs.String(ConfigFilePathKey, "", "Specify the config path to use to load a YAML config for the simulator") - fs.StringSlice(EndpointsKey, []string{"ws://127.0.0.1:9650/ext/bc/C/ws"}, "Specify a comma separated list of RPC Websocket Endpoints (minimum of 1 endpoint)") - fs.Int64(MaxFeeCapKey, 50, "Specify the maximum fee cap to use for transactions denominated in GWei (must be > 0)") - fs.Int64(MaxTipCapKey, 1, "Specify the max tip cap for transactions denominated in GWei (must be >= 0)") - fs.Uint64(TxsPerWorkerKey, 100, "Specify the number of transactions to create per worker (must be > 0)") - fs.Int(WorkersKey, 1, "Specify the number of workers to create for the simulator (must be > 0)") - fs.String(KeyDirKey, ".simulator/keys", "Specify the directory to save private keys in (INSECURE: only use for testing)") - fs.Duration(TimeoutKey, 5*time.Minute, "Specify the timeout for the simulator to complete (0 indicates no timeout)") - fs.String(LogLevelKey, "info", "Specify the log level to use in the simulator") - fs.Uint64(BatchSizeKey, 100, "Specify the batchsize for the worker to issue and confirm txs") - fs.Uint64(MetricsPortKey, 8082, "Specify the port to use for the metrics server") - fs.String(MetricsOutputKey, "", "Specify the file to write metrics in json format, or empy to write to stdout (defaults to stdout)") -} diff --git a/cmd/simulator/key/key.go b/cmd/simulator/key/key.go deleted file mode 100644 index a1a247b84f..0000000000 --- a/cmd/simulator/key/key.go +++ /dev/null @@ -1,85 +0,0 @@ -// (c) 2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package key - -import ( - "context" - "crypto/ecdsa" - "fmt" - "os" - "path/filepath" - - "github.com/ethereum/go-ethereum/common" - ethcrypto "github.com/ethereum/go-ethereum/crypto" -) - -type Key struct { - PrivKey *ecdsa.PrivateKey - Address common.Address -} - -func CreateKey(pk *ecdsa.PrivateKey) *Key { - return &Key{pk, ethcrypto.PubkeyToAddress(pk.PublicKey)} -} - -// Load attempts to open a [Key] stored at [file]. -func Load(file string) (*Key, error) { - pk, err := ethcrypto.LoadECDSA(file) - if err != nil { - return nil, fmt.Errorf("problem loading private key from %s: %w", file, err) - } - return CreateKey(pk), nil -} - -// LoadAll loads all keys in [dir]. -func LoadAll(ctx context.Context, dir string) ([]*Key, error) { - if _, err := os.Stat(dir); os.IsNotExist(err) { - if err := os.MkdirAll(dir, 0755); err != nil { - return nil, fmt.Errorf("unable to create %s: %w", dir, err) - } - - return nil, nil - } - - var files []string - - err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - if path == dir { - return nil - } - - files = append(files, path) - return nil - }) - if err != nil { - return nil, fmt.Errorf("could not walk %s: %w", dir, err) - } - - ks := make([]*Key, len(files)) - for i, file := range files { - k, err := Load(file) - if err != nil { - return nil, fmt.Errorf("could not load key at %s: %w", file, err) - } - - ks[i] = k - } - return ks, nil -} - -// Save persists a [Key] to [dir] (where the filename is the hex-encoded -// address). -func (k *Key) Save(dir string) error { - fp := filepath.Join(dir, k.Address.Hex()) - return ethcrypto.SaveECDSA(fp, k.PrivKey) -} - -// Generate creates a new [Key] and returns it. -func Generate() (*Key, error) { - pk, err := ethcrypto.GenerateKey() - if err != nil { - return nil, fmt.Errorf("%w: cannot generate key", err) - } - return CreateKey(pk), nil -} diff --git a/cmd/simulator/load/funder.go b/cmd/simulator/load/funder.go deleted file mode 100644 index cda5722a28..0000000000 --- a/cmd/simulator/load/funder.go +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package load - -import ( - "context" - "crypto/ecdsa" - "fmt" - "math/big" - - "github.com/ava-labs/subnet-evm/cmd/simulator/key" - "github.com/ava-labs/subnet-evm/cmd/simulator/metrics" - "github.com/ava-labs/subnet-evm/cmd/simulator/txs" - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/ethclient" - "github.com/ava-labs/subnet-evm/params" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" -) - -// DistributeFunds ensures that each address in keys has at least [minFundsPerAddr] by sending funds -// from the key with the highest starting balance. -// This function returns a set of at least [numKeys] keys, each having a minimum balance [minFundsPerAddr]. -func DistributeFunds(ctx context.Context, client ethclient.Client, keys []*key.Key, numKeys int, minFundsPerAddr *big.Int, m *metrics.Metrics) ([]*key.Key, error) { - if len(keys) < numKeys { - return nil, fmt.Errorf("insufficient number of keys %d < %d", len(keys), numKeys) - } - fundedKeys := make([]*key.Key, 0, numKeys) - // TODO: clean up fund distribution. - needFundsKeys := make([]*key.Key, 0) - needFundsAddrs := make([]common.Address, 0) - - maxFundsKey := keys[0] - maxFundsBalance := common.Big0 - log.Info("Checking balance of each key to distribute funds") - for _, key := range keys { - balance, err := client.BalanceAt(ctx, key.Address, nil) - if err != nil { - return nil, fmt.Errorf("failed to fetch balance for addr %s: %w", key.Address, err) - } - - if balance.Cmp(minFundsPerAddr) < 0 { - needFundsKeys = append(needFundsKeys, key) - needFundsAddrs = append(needFundsAddrs, key.Address) - } else { - fundedKeys = append(fundedKeys, key) - } - - if balance.Cmp(maxFundsBalance) > 0 { - maxFundsKey = key - maxFundsBalance = balance - } - } - requiredFunds := new(big.Int).Mul(minFundsPerAddr, big.NewInt(int64(numKeys))) - if maxFundsBalance.Cmp(requiredFunds) < 0 { - return nil, fmt.Errorf("insufficient funds to distribute %d < %d", maxFundsBalance, requiredFunds) - } - log.Info("Found max funded key", "address", maxFundsKey.Address, "balance", maxFundsBalance, "numFundAddrs", len(needFundsAddrs)) - if len(fundedKeys) >= numKeys { - return fundedKeys[:numKeys], nil - } - - // If there are not enough funded keys, cut [needFundsAddrs] to the number of keys that - // must be funded to reach [numKeys] required. - fundKeysCutLen := numKeys - len(fundedKeys) - needFundsKeys = needFundsKeys[:fundKeysCutLen] - needFundsAddrs = needFundsAddrs[:fundKeysCutLen] - - chainID, err := client.ChainID(ctx) - if err != nil { - return nil, fmt.Errorf("failed to fetch chainID: %w", err) - } - gasFeeCap, err := client.EstimateBaseFee(ctx) - if err != nil { - return nil, fmt.Errorf("failed to fetch estimated base fee: %w", err) - } - gasTipCap, err := client.SuggestGasTipCap(ctx) - if err != nil { - return nil, fmt.Errorf("failed to fetch suggested gas tip: %w", err) - } - signer := types.LatestSignerForChainID(chainID) - - // Generate a sequence of transactions to distribute the required funds. - log.Info("Generating distribution transactions...") - i := 0 - txGenerator := func(key *ecdsa.PrivateKey, nonce uint64) (*types.Transaction, error) { - tx, err := types.SignNewTx(key, signer, &types.DynamicFeeTx{ - ChainID: chainID, - Nonce: nonce, - GasTipCap: gasTipCap, - GasFeeCap: gasFeeCap, - Gas: params.TxGas, - To: &needFundsAddrs[i], - Data: nil, - Value: requiredFunds, - }) - if err != nil { - return nil, err - } - i++ - return tx, nil - } - - numTxs := uint64(len(needFundsAddrs)) - txSequence, err := txs.GenerateTxSequence(ctx, txGenerator, client, maxFundsKey.PrivKey, numTxs, false) - if err != nil { - return nil, fmt.Errorf("failed to generate fund distribution sequence from %s of length %d", maxFundsKey.Address, len(needFundsAddrs)) - } - worker := NewSingleAddressTxWorker(ctx, client, maxFundsKey.Address) - txFunderAgent := txs.NewIssueNAgent[*types.Transaction](txSequence, worker, numTxs, m) - - if err := txFunderAgent.Execute(ctx); err != nil { - return nil, err - } - for _, addr := range needFundsAddrs { - balance, err := client.BalanceAt(ctx, addr, nil) - if err != nil { - return nil, fmt.Errorf("failed to fetch balance for addr %s: %w", addr, err) - } - log.Info("Funded address has balance", "addr", addr, "balance", balance) - } - fundedKeys = append(fundedKeys, needFundsKeys...) - return fundedKeys, nil -} diff --git a/cmd/simulator/load/loader.go b/cmd/simulator/load/loader.go deleted file mode 100644 index 0dffe80bb7..0000000000 --- a/cmd/simulator/load/loader.go +++ /dev/null @@ -1,247 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package load - -import ( - "context" - "crypto/ecdsa" - "fmt" - "math/big" - "os" - "os/signal" - "strconv" - "syscall" - "time" - - "github.com/ava-labs/subnet-evm/cmd/simulator/config" - "github.com/ava-labs/subnet-evm/cmd/simulator/key" - "github.com/ava-labs/subnet-evm/cmd/simulator/metrics" - "github.com/ava-labs/subnet-evm/cmd/simulator/txs" - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/ethclient" - "github.com/ava-labs/subnet-evm/params" - "github.com/ethereum/go-ethereum/common" - ethcrypto "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" - "golang.org/x/sync/errgroup" -) - -const ( - MetricsEndpoint = "/metrics" // Endpoint for the Prometheus Metrics Server -) - -// Loader executes a series of worker/tx sequence pairs. -// Each worker/txSequence pair issues [batchSize] transactions, confirms all -// of them as accepted, and then moves to the next batch until the txSequence -// is exhausted. -type Loader[T txs.THash] struct { - clients []txs.Worker[T] - txSequences []txs.TxSequence[T] - batchSize uint64 - metrics *metrics.Metrics -} - -func New[T txs.THash]( - clients []txs.Worker[T], - txSequences []txs.TxSequence[T], - batchSize uint64, - metrics *metrics.Metrics, -) *Loader[T] { - return &Loader[T]{ - clients: clients, - txSequences: txSequences, - batchSize: batchSize, - metrics: metrics, - } -} - -func (l *Loader[T]) Execute(ctx context.Context) error { - log.Info("Constructing tx agents...", "numAgents", len(l.txSequences)) - agents := make([]txs.Agent[T], 0, len(l.txSequences)) - for i := 0; i < len(l.txSequences); i++ { - agents = append(agents, txs.NewIssueNAgent(l.txSequences[i], l.clients[i], l.batchSize, l.metrics)) - } - - log.Info("Starting tx agents...") - eg := errgroup.Group{} - for _, agent := range agents { - agent := agent - eg.Go(func() error { - return agent.Execute(ctx) - }) - } - - log.Info("Waiting for tx agents...") - if err := eg.Wait(); err != nil { - return err - } - log.Info("Tx agents completed successfully.") - return nil -} - -// ConfirmReachedTip finds the max height any client has reached and then ensures every client -// reaches at least that height. -// -// This allows the network to continue to roll forward and creates a synchronization point to ensure -// that every client in the loader has reached at least the max height observed of any client at -// the time this function was called. -func (l *Loader[T]) ConfirmReachedTip(ctx context.Context) error { - maxHeight := uint64(0) - for i, client := range l.clients { - latestHeight, err := client.LatestHeight(ctx) - if err != nil { - return fmt.Errorf("client %d failed to get latest height: %w", i, err) - } - if latestHeight > maxHeight { - maxHeight = latestHeight - } - } - - eg := errgroup.Group{} - for i, client := range l.clients { - i := i - client := client - eg.Go(func() error { - for { - latestHeight, err := client.LatestHeight(ctx) - if err != nil { - return fmt.Errorf("failed to get latest height from client %d: %w", i, err) - } - if latestHeight >= maxHeight { - return nil - } - select { - case <-ctx.Done(): - return fmt.Errorf("failed to get latest height from client %d: %w", i, ctx.Err()) - case <-time.After(time.Second): - } - } - }) - } - - return eg.Wait() -} - -// ExecuteLoader creates txSequences from [config] and has txAgents execute the specified simulation. -func ExecuteLoader(ctx context.Context, config config.Config) error { - if config.Timeout > 0 { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, config.Timeout) - defer cancel() - } - - // Create buffered sigChan to receive SIGINT notifications - sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, syscall.SIGINT) - - // Create context with cancel - ctx, cancel := context.WithCancel(ctx) - - go func() { - // Blocks until we receive a SIGINT notification or if parent context is done - select { - case <-sigChan: - case <-ctx.Done(): - } - - // Cancel the child context and end all processes - cancel() - }() - - m := metrics.NewDefaultMetrics() - metricsCtx := context.Background() - ms := m.Serve(metricsCtx, strconv.Itoa(int(config.MetricsPort)), MetricsEndpoint) - defer ms.Shutdown() - - // Construct the arguments for the load simulator - clients := make([]ethclient.Client, 0, len(config.Endpoints)) - for i := 0; i < config.Workers; i++ { - clientURI := config.Endpoints[i%len(config.Endpoints)] - client, err := ethclient.Dial(clientURI) - if err != nil { - return fmt.Errorf("failed to dial client at %s: %w", clientURI, err) - } - clients = append(clients, client) - } - - keys, err := key.LoadAll(ctx, config.KeyDir) - if err != nil { - return err - } - // Ensure there are at least [config.Workers] keys and save any newly generated ones. - if len(keys) < config.Workers { - for i := 0; len(keys) < config.Workers; i++ { - newKey, err := key.Generate() - if err != nil { - return fmt.Errorf("failed to generate %d new key: %w", i, err) - } - if err := newKey.Save(config.KeyDir); err != nil { - return fmt.Errorf("failed to save %d new key: %w", i, err) - } - keys = append(keys, newKey) - } - } - - // Each address needs: params.GWei * MaxFeeCap * params.TxGas * TxsPerWorker total wei - // to fund gas for all of their transactions. - maxFeeCap := new(big.Int).Mul(big.NewInt(params.GWei), big.NewInt(config.MaxFeeCap)) - minFundsPerAddr := new(big.Int).Mul(maxFeeCap, big.NewInt(int64(config.TxsPerWorker*params.TxGas))) - fundStart := time.Now() - log.Info("Distributing funds", "numTxsPerWorker", config.TxsPerWorker, "minFunds", minFundsPerAddr) - keys, err = DistributeFunds(ctx, clients[0], keys, config.Workers, minFundsPerAddr, m) - if err != nil { - return err - } - log.Info("Distributed funds successfully", "time", time.Since(fundStart)) - - pks := make([]*ecdsa.PrivateKey, 0, len(keys)) - senders := make([]common.Address, 0, len(keys)) - for _, key := range keys { - pks = append(pks, key.PrivKey) - senders = append(senders, key.Address) - } - - bigGwei := big.NewInt(params.GWei) - gasTipCap := new(big.Int).Mul(bigGwei, big.NewInt(config.MaxTipCap)) - gasFeeCap := new(big.Int).Mul(bigGwei, big.NewInt(config.MaxFeeCap)) - client := clients[0] - chainID, err := client.ChainID(ctx) - if err != nil { - return fmt.Errorf("failed to fetch chainID: %w", err) - } - signer := types.LatestSignerForChainID(chainID) - - log.Info("Creating transaction sequences...") - txGenerator := func(key *ecdsa.PrivateKey, nonce uint64) (*types.Transaction, error) { - addr := ethcrypto.PubkeyToAddress(key.PublicKey) - return types.SignNewTx(key, signer, &types.DynamicFeeTx{ - ChainID: chainID, - Nonce: nonce, - GasTipCap: gasTipCap, - GasFeeCap: gasFeeCap, - Gas: params.TxGas, - To: &addr, - Data: nil, - Value: common.Big0, - }) - } - txSequenceStart := time.Now() - txSequences, err := txs.GenerateTxSequences(ctx, txGenerator, clients[0], pks, config.TxsPerWorker, false) - if err != nil { - return err - } - log.Info("Created transaction sequences successfully", "time", time.Since(txSequenceStart)) - - workers := make([]txs.Worker[*types.Transaction], 0, len(clients)) - for i, client := range clients { - workers = append(workers, NewSingleAddressTxWorker(ctx, client, ethcrypto.PubkeyToAddress(pks[i].PublicKey))) - } - loader := New(workers, txSequences, config.BatchSize, m) - err = loader.Execute(ctx) - prerr := m.Print(config.MetricsOutput) // Print regardless of execution error - if prerr != nil { - log.Warn("Failed to print metrics", "error", prerr) - } - return err -} diff --git a/cmd/simulator/load/worker.go b/cmd/simulator/load/worker.go deleted file mode 100644 index 6794127015..0000000000 --- a/cmd/simulator/load/worker.go +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package load - -import ( - "context" - "fmt" - "time" - - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/ethclient" - "github.com/ava-labs/subnet-evm/interfaces" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" -) - -type ethereumTxWorker struct { - client ethclient.Client - - acceptedNonce uint64 - address common.Address - - sub interfaces.Subscription - newHeads chan *types.Header -} - -// NewSingleAddressTxWorker creates and returns a new ethereumTxWorker that confirms transactions by checking the latest -// nonce of [address] and assuming any transaction with a lower nonce was already accepted. -func NewSingleAddressTxWorker(ctx context.Context, client ethclient.Client, address common.Address) *ethereumTxWorker { - newHeads := make(chan *types.Header) - tw := ðereumTxWorker{ - client: client, - address: address, - newHeads: newHeads, - } - - sub, err := client.SubscribeNewHead(ctx, newHeads) - if err != nil { - log.Debug("failed to subscribe new heads, falling back to polling", "err", err) - } else { - tw.sub = sub - } - - return tw -} - -// NewTxReceiptWorker creates and returns a new ethereumTxWorker that confirms transactions by checking for the -// corresponding transaction receipt. -func NewTxReceiptWorker(ctx context.Context, client ethclient.Client) *ethereumTxWorker { - newHeads := make(chan *types.Header) - tw := ðereumTxWorker{ - client: client, - newHeads: newHeads, - } - - sub, err := client.SubscribeNewHead(ctx, newHeads) - if err != nil { - log.Debug("failed to subscribe new heads, falling back to polling", "err", err) - } else { - tw.sub = sub - } - - return tw -} - -func (tw *ethereumTxWorker) IssueTx(ctx context.Context, tx *types.Transaction) error { - return tw.client.SendTransaction(ctx, tx) -} - -func (tw *ethereumTxWorker) ConfirmTx(ctx context.Context, tx *types.Transaction) error { - if tw.address == (common.Address{}) { - return tw.confirmTxByReceipt(ctx, tx) - } - return tw.confirmTxByNonce(ctx, tx) -} - -func (tw *ethereumTxWorker) confirmTxByNonce(ctx context.Context, tx *types.Transaction) error { - txNonce := tx.Nonce() - - for { - acceptedNonce, err := tw.client.NonceAt(ctx, tw.address, nil) - if err != nil { - return fmt.Errorf("failed to await tx %s nonce %d: %w", tx.Hash(), txNonce, err) - } - tw.acceptedNonce = acceptedNonce - - log.Debug("confirming tx", "txHash", tx.Hash(), "txNonce", txNonce, "acceptedNonce", tw.acceptedNonce) - // If the is less than what has already been accepted, the transaction is confirmed - if txNonce < tw.acceptedNonce { - return nil - } - - select { - case <-tw.newHeads: - case <-time.After(time.Second): - case <-ctx.Done(): - return fmt.Errorf("failed to await tx %s nonce %d: %w", tx.Hash(), txNonce, ctx.Err()) - } - } -} - -func (tw *ethereumTxWorker) confirmTxByReceipt(ctx context.Context, tx *types.Transaction) error { - for { - _, err := tw.client.TransactionReceipt(ctx, tx.Hash()) - if err == nil { - return nil - } - log.Debug("no tx receipt", "txHash", tx.Hash(), "nonce", tx.Nonce(), "err", err) - - select { - case <-tw.newHeads: - case <-time.After(time.Second): - case <-ctx.Done(): - return fmt.Errorf("failed to await tx %s nonce %d: %w", tx.Hash(), tx.Nonce(), ctx.Err()) - } - } -} - -func (tw *ethereumTxWorker) LatestHeight(ctx context.Context) (uint64, error) { - return tw.client.BlockNumber(ctx) -} diff --git a/cmd/simulator/main/main.go b/cmd/simulator/main/main.go deleted file mode 100644 index 5b6603a420..0000000000 --- a/cmd/simulator/main/main.go +++ /dev/null @@ -1,58 +0,0 @@ -// (c) 2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package main - -import ( - "context" - "errors" - "fmt" - "os" - - "github.com/ava-labs/subnet-evm/cmd/simulator/config" - "github.com/ava-labs/subnet-evm/cmd/simulator/load" - "github.com/ava-labs/subnet-evm/log" - gethlog "github.com/ethereum/go-ethereum/log" - "github.com/spf13/pflag" -) - -func main() { - fs := config.BuildFlagSet() - v, err := config.BuildViper(fs, os.Args[1:]) - if errors.Is(err, pflag.ErrHelp) { - os.Exit(0) - } - - if err != nil { - fmt.Printf("couldn't build viper: %s\n", err) - os.Exit(1) - } - - if err != nil { - fmt.Printf("couldn't configure flags: %s\n", err) - os.Exit(1) - } - - if v.GetBool(config.VersionKey) { - fmt.Printf("%s\n", config.Version) - os.Exit(0) - } - - logLevel, err := log.LvlFromString(v.GetString(config.LogLevelKey)) - if err != nil { - fmt.Printf("couldn't parse log level: %s\n", err) - os.Exit(1) - } - gethLogger := gethlog.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, logLevel, true)) - gethlog.SetDefault(gethLogger) - - config, err := config.BuildConfig(v) - if err != nil { - fmt.Printf("%s\n", err) - os.Exit(1) - } - if err := load.ExecuteLoader(context.Background(), config); err != nil { - fmt.Printf("load execution failed: %s\n", err) - os.Exit(1) - } -} diff --git a/cmd/simulator/metrics/metrics.go b/cmd/simulator/metrics/metrics.go deleted file mode 100644 index f9d44bbbcb..0000000000 --- a/cmd/simulator/metrics/metrics.go +++ /dev/null @@ -1,139 +0,0 @@ -// (c) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package metrics - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "net/http" - "os" - - "github.com/ethereum/go-ethereum/log" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promhttp" -) - -type Metrics struct { - reg *prometheus.Registry - // Summary of the quantiles of Individual Issuance Tx Times - IssuanceTxTimes prometheus.Summary - // Summary of the quantiles of Individual Confirmation Tx Times - ConfirmationTxTimes prometheus.Summary - // Summary of the quantiles of Individual Issuance To Confirmation Tx Times - IssuanceToConfirmationTxTimes prometheus.Summary -} - -func NewDefaultMetrics() *Metrics { - registry := prometheus.NewRegistry() - return NewMetrics(registry) -} - -// NewMetrics creates and returns a Metrics and registers it with a Collector -func NewMetrics(reg *prometheus.Registry) *Metrics { - m := &Metrics{ - reg: reg, - IssuanceTxTimes: prometheus.NewSummary(prometheus.SummaryOpts{ - Name: "tx_issuance_time", - Help: "Individual Tx Issuance Times for a Load Test", - Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, - }), - ConfirmationTxTimes: prometheus.NewSummary(prometheus.SummaryOpts{ - Name: "tx_confirmation_time", - Help: "Individual Tx Confirmation Times for a Load Test", - Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, - }), - IssuanceToConfirmationTxTimes: prometheus.NewSummary(prometheus.SummaryOpts{ - Name: "tx_issuance_to_confirmation_time", - Help: "Individual Tx Issuance To Confirmation Times for a Load Test", - Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, - }), - } - reg.MustRegister(m.IssuanceTxTimes) - reg.MustRegister(m.ConfirmationTxTimes) - reg.MustRegister(m.IssuanceToConfirmationTxTimes) - return m -} - -type MetricsServer struct { - metricsPort string - metricsEndpoint string - - cancel context.CancelFunc - stopCh chan struct{} -} - -func (m *Metrics) Serve(ctx context.Context, metricsPort string, metricsEndpoint string) *MetricsServer { - ctx, cancel := context.WithCancel(ctx) - // Create a prometheus server to expose individual tx metrics - server := &http.Server{ - Addr: fmt.Sprintf(":%s", metricsPort), - } - - // Start up go routine to listen for SIGINT notifications to gracefully shut down server - go func() { - // Blocks until signal is received - <-ctx.Done() - - if err := server.Shutdown(ctx); err != nil { - log.Error("Metrics server error: %v", err) - } - log.Info("Received a SIGINT signal: Gracefully shutting down metrics server") - }() - - // Start metrics server - ms := &MetricsServer{ - metricsPort: metricsPort, - metricsEndpoint: metricsEndpoint, - stopCh: make(chan struct{}), - cancel: cancel, - } - go func() { - defer close(ms.stopCh) - - http.Handle(metricsEndpoint, promhttp.HandlerFor(m.reg, promhttp.HandlerOpts{Registry: m.reg})) - log.Info(fmt.Sprintf("Metrics Server: localhost:%s%s", metricsPort, metricsEndpoint)) - if err := server.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { - log.Error("Metrics server error: %v", err) - } - }() - - return ms -} - -func (ms *MetricsServer) Shutdown() { - ms.cancel() - <-ms.stopCh -} - -func (m *Metrics) Print(outputFile string) error { - metrics, err := m.reg.Gather() - if err != nil { - return err - } - - if outputFile == "" { - // Printout to stdout - fmt.Println("*** Metrics ***") - for _, mf := range metrics { - for _, m := range mf.GetMetric() { - fmt.Printf("Type: %s, Name: %s, Description: %s, Values: %s\n", mf.GetType().String(), mf.GetName(), mf.GetHelp(), m.String()) - } - } - fmt.Println("***************") - } else { - jsonFile, err := os.Create(outputFile) - if err != nil { - return err - } - defer jsonFile.Close() - - if err := json.NewEncoder(jsonFile).Encode(metrics); err != nil { - return err - } - } - - return nil -} diff --git a/cmd/simulator/txs/agent.go b/cmd/simulator/txs/agent.go deleted file mode 100644 index db7259bcad..0000000000 --- a/cmd/simulator/txs/agent.go +++ /dev/null @@ -1,142 +0,0 @@ -// (c) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package txs - -import ( - "context" - "errors" - "fmt" - "time" - - "github.com/ava-labs/subnet-evm/cmd/simulator/metrics" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/log" -) - -type THash interface { - Hash() common.Hash -} - -// TxSequence provides an interface to return a channel of transactions. -// The sequence is responsible for closing the channel when there are no further -// transactions. -type TxSequence[T THash] interface { - Chan() <-chan T -} - -// Worker defines the interface for issuance and confirmation of transactions. -// The caller is responsible for calling Close to cleanup resources used by the -// worker at the end of the simulation. -type Worker[T THash] interface { - IssueTx(ctx context.Context, tx T) error - ConfirmTx(ctx context.Context, tx T) error - LatestHeight(ctx context.Context) (uint64, error) -} - -// Execute the work of the given agent. -type Agent[T THash] interface { - Execute(ctx context.Context) error -} - -// issueNAgent issues and confirms a batch of N transactions at a time. -type issueNAgent[T THash] struct { - sequence TxSequence[T] - worker Worker[T] - n uint64 - metrics *metrics.Metrics -} - -// NewIssueNAgent creates a new issueNAgent -func NewIssueNAgent[T THash](sequence TxSequence[T], worker Worker[T], n uint64, metrics *metrics.Metrics) Agent[T] { - return &issueNAgent[T]{ - sequence: sequence, - worker: worker, - n: n, - metrics: metrics, - } -} - -// Execute issues txs in batches of N and waits for them to confirm -func (a issueNAgent[T]) Execute(ctx context.Context) error { - if a.n == 0 { - return errors.New("batch size n cannot be equal to 0") - } - - txChan := a.sequence.Chan() - confirmedCount := 0 - batchI := 0 - m := a.metrics - txMap := make(map[common.Hash]time.Time) - - // Tracks the total amount of time waiting for issuing and confirming txs - var ( - totalIssuedTime time.Duration - totalConfirmedTime time.Duration - ) - - // Start time for execution - start := time.Now() - for { - var ( - txs = make([]T, 0, a.n) - tx T - moreTxs bool - ) - // Start issuance batch - issuedStart := time.Now() - L: - for i := uint64(0); i < a.n; i++ { - select { - case <-ctx.Done(): - return ctx.Err() - case tx, moreTxs = <-txChan: - if !moreTxs { - break L - } - issuanceIndividualStart := time.Now() - txMap[tx.Hash()] = issuanceIndividualStart - if err := a.worker.IssueTx(ctx, tx); err != nil { - return fmt.Errorf("failed to issue transaction %d: %w", len(txs), err) - } - issuanceIndividualDuration := time.Since(issuanceIndividualStart) - m.IssuanceTxTimes.Observe(issuanceIndividualDuration.Seconds()) - txs = append(txs, tx) - } - } - // Get the batch's issuance time and add it to totalIssuedTime - issuedDuration := time.Since(issuedStart) - log.Info("Issuance Batch Done", "batch", batchI, "time", issuedDuration.Seconds()) - totalIssuedTime += issuedDuration - - // Wait for txs in this batch to confirm - confirmedStart := time.Now() - for i, tx := range txs { - confirmedIndividualStart := time.Now() - if err := a.worker.ConfirmTx(ctx, tx); err != nil { - return fmt.Errorf("failed to await transaction %d: %w", i, err) - } - confirmationIndividualDuration := time.Since(confirmedIndividualStart) - issuanceToConfirmationIndividualDuration := time.Since(txMap[tx.Hash()]) - m.ConfirmationTxTimes.Observe(confirmationIndividualDuration.Seconds()) - m.IssuanceToConfirmationTxTimes.Observe(issuanceToConfirmationIndividualDuration.Seconds()) - delete(txMap, tx.Hash()) - confirmedCount++ - } - // Get the batch's confirmation time and add it to totalConfirmedTime - confirmedDuration := time.Since(confirmedStart) - log.Info("Confirmed Batch Done", "batch", batchI, "time", confirmedDuration.Seconds()) - totalConfirmedTime += confirmedDuration - - // Check if this is the last batch, if so write the final log and return - if !moreTxs { - totalTime := time.Since(start).Seconds() - log.Info("Execution complete", "totalTxs", confirmedCount, "totalTime", totalTime, "TPS", float64(confirmedCount)/totalTime, - "issuanceTime", totalIssuedTime.Seconds(), "confirmedTime", totalConfirmedTime.Seconds()) - - return nil - } - - batchI++ - } -} diff --git a/cmd/simulator/txs/tx_generator.go b/cmd/simulator/txs/tx_generator.go deleted file mode 100644 index b75672c0f9..0000000000 --- a/cmd/simulator/txs/tx_generator.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package txs - -import ( - "context" - "crypto/ecdsa" - "fmt" - - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/ethclient" - ethcrypto "github.com/ethereum/go-ethereum/crypto" -) - -var _ TxSequence[*types.Transaction] = (*txSequence)(nil) - -type CreateTx func(key *ecdsa.PrivateKey, nonce uint64) (*types.Transaction, error) - -func GenerateTxSequence(ctx context.Context, generator CreateTx, client ethclient.Client, key *ecdsa.PrivateKey, numTxs uint64, async bool) (TxSequence[*types.Transaction], error) { - sequence := &txSequence{ - txChan: make(chan *types.Transaction, numTxs), - } - - if async { - go func() { - defer close(sequence.txChan) - - if err := addTxs(ctx, sequence, generator, client, key, numTxs); err != nil { - panic(err) - } - }() - } else { - if err := addTxs(ctx, sequence, generator, client, key, numTxs); err != nil { - return nil, err - } - close(sequence.txChan) - } - - return sequence, nil -} - -func GenerateTxSequences(ctx context.Context, generator CreateTx, client ethclient.Client, keys []*ecdsa.PrivateKey, txsPerKey uint64, async bool) ([]TxSequence[*types.Transaction], error) { - txSequences := make([]TxSequence[*types.Transaction], len(keys)) - for i, key := range keys { - txs, err := GenerateTxSequence(ctx, generator, client, key, txsPerKey, async) - if err != nil { - return nil, fmt.Errorf("failed to generate tx sequence at index %d: %w", i, err) - } - txSequences[i] = txs - } - return txSequences, nil -} - -func addTxs(ctx context.Context, txSequence *txSequence, generator CreateTx, client ethclient.Client, key *ecdsa.PrivateKey, numTxs uint64) error { - address := ethcrypto.PubkeyToAddress(key.PublicKey) - startingNonce, err := client.NonceAt(ctx, address, nil) - if err != nil { - return err - } - for i := uint64(0); i < numTxs; i++ { - tx, err := generator(key, startingNonce+i) - if err != nil { - return err - } - txSequence.txChan <- tx - } - - return nil -} - -type txSequence struct { - txChan chan *types.Transaction -} - -func ConvertTxSliceToSequence(txs []*types.Transaction) TxSequence[*types.Transaction] { - txChan := make(chan *types.Transaction, len(txs)) - for _, tx := range txs { - txChan <- tx - } - close(txChan) - - return &txSequence{ - txChan: txChan, - } -} - -func (t *txSequence) Chan() <-chan *types.Transaction { - return t.txChan -} diff --git a/commontype/fee_config.go b/commontype/fee_config.go deleted file mode 100644 index 3089df5d9c..0000000000 --- a/commontype/fee_config.go +++ /dev/null @@ -1,151 +0,0 @@ -// (c) 2019-2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package commontype - -import ( - "fmt" - "math/big" - - "github.com/ava-labs/subnet-evm/utils" - "github.com/ethereum/go-ethereum/common" -) - -// FeeConfig specifies the parameters for the dynamic fee algorithm, which determines the gas limit, base fee, and block gas cost of blocks -// on the network. -// -// The dynamic fee algorithm simply increases fees when the network is operating at a utilization level above the target and decreases fees -// when the network is operating at a utilization level below the target. -// This struct is used by Genesis and Fee Manager precompile. -// Any modification of this struct has direct affect on the precompiled contract -// and changes should be carefully handled in the precompiled contract code. -type FeeConfig struct { - // GasLimit sets the max amount of gas consumed per block. - GasLimit *big.Int `json:"gasLimit,omitempty"` - - // TargetBlockRate sets the target rate of block production in seconds. - // A target of 2 will target producing a block every 2 seconds. - TargetBlockRate uint64 `json:"targetBlockRate,omitempty"` - - // The minimum base fee sets a lower bound on the EIP-1559 base fee of a block. - // Since the block's base fee sets the minimum gas price for any transaction included in that block, this effectively sets a minimum - // gas price for any transaction. - MinBaseFee *big.Int `json:"minBaseFee,omitempty"` - - // When the dynamic fee algorithm observes that network activity is above/below the [TargetGas], it increases/decreases the base fee proportionally to - // how far above/below the target actual network activity is. - - // TargetGas specifies the targeted amount of gas (including block gas cost) to consume within a rolling 10s window. - TargetGas *big.Int `json:"targetGas,omitempty"` - // The BaseFeeChangeDenominator divides the difference between actual and target utilization to determine how much to increase/decrease the base fee. - // This means that a larger denominator indicates a slower changing, stickier base fee, while a lower denominator will allow the base fee to adjust - // more quickly. - BaseFeeChangeDenominator *big.Int `json:"baseFeeChangeDenominator,omitempty"` - - // MinBlockGasCost sets the minimum amount of gas to charge for the production of a block. - MinBlockGasCost *big.Int `json:"minBlockGasCost,omitempty"` - // MaxBlockGasCost sets the maximum amount of gas to charge for the production of a block. - MaxBlockGasCost *big.Int `json:"maxBlockGasCost,omitempty"` - // BlockGasCostStep determines how much to increase/decrease the block gas cost depending on the amount of time elapsed since the previous block. - // If the block is produced at the target rate, the block gas cost will stay the same as the block gas cost for the parent block. - // If it is produced faster/slower, the block gas cost will be increased/decreased by the step value for each second faster/slower than the target - // block rate accordingly. - // Note: if the BlockGasCostStep is set to a very large number, it effectively requires block production to go no faster than the TargetBlockRate. - // - // Ex: if a block is produced two seconds faster than the target block rate, the block gas cost will increase by 2 * BlockGasCostStep. - BlockGasCostStep *big.Int `json:"blockGasCostStep,omitempty"` -} - -// represents an empty fee config without any field -var EmptyFeeConfig = FeeConfig{} - -// Verify checks fields of this config to ensure a valid fee configuration is provided. -func (f *FeeConfig) Verify() error { - switch { - case f.GasLimit == nil: - return fmt.Errorf("gasLimit cannot be nil") - case f.MinBaseFee == nil: - return fmt.Errorf("minBaseFee cannot be nil") - case f.TargetGas == nil: - return fmt.Errorf("targetGas cannot be nil") - case f.BaseFeeChangeDenominator == nil: - return fmt.Errorf("baseFeeChangeDenominator cannot be nil") - case f.MinBlockGasCost == nil: - return fmt.Errorf("minBlockGasCost cannot be nil") - case f.MaxBlockGasCost == nil: - return fmt.Errorf("maxBlockGasCost cannot be nil") - case f.BlockGasCostStep == nil: - return fmt.Errorf("blockGasCostStep cannot be nil") - } - - switch { - case f.GasLimit.Cmp(common.Big0) != 1: - return fmt.Errorf("gasLimit = %d cannot be less than or equal to 0", f.GasLimit) - case f.TargetBlockRate <= 0: - return fmt.Errorf("targetBlockRate = %d cannot be less than or equal to 0", f.TargetBlockRate) - case f.MinBaseFee.Cmp(common.Big0) == -1: - return fmt.Errorf("minBaseFee = %d cannot be less than 0", f.MinBaseFee) - case f.TargetGas.Cmp(common.Big0) != 1: - return fmt.Errorf("targetGas = %d cannot be less than or equal to 0", f.TargetGas) - case f.BaseFeeChangeDenominator.Cmp(common.Big0) != 1: - return fmt.Errorf("baseFeeChangeDenominator = %d cannot be less than or equal to 0", f.BaseFeeChangeDenominator) - case f.MinBlockGasCost.Cmp(common.Big0) == -1: - return fmt.Errorf("minBlockGasCost = %d cannot be less than 0", f.MinBlockGasCost) - case f.MinBlockGasCost.Cmp(f.MaxBlockGasCost) == 1: - return fmt.Errorf("minBlockGasCost = %d cannot be greater than maxBlockGasCost = %d", f.MinBlockGasCost, f.MaxBlockGasCost) - case f.BlockGasCostStep.Cmp(common.Big0) == -1: - return fmt.Errorf("blockGasCostStep = %d cannot be less than 0", f.BlockGasCostStep) - } - return f.checkByteLens() -} - -// Equal checks if given [other] is same with this FeeConfig. -func (f *FeeConfig) Equal(other *FeeConfig) bool { - if other == nil { - return false - } - - return utils.BigNumEqual(f.GasLimit, other.GasLimit) && - f.TargetBlockRate == other.TargetBlockRate && - utils.BigNumEqual(f.MinBaseFee, other.MinBaseFee) && - utils.BigNumEqual(f.TargetGas, other.TargetGas) && - utils.BigNumEqual(f.BaseFeeChangeDenominator, other.BaseFeeChangeDenominator) && - utils.BigNumEqual(f.MinBlockGasCost, other.MinBlockGasCost) && - utils.BigNumEqual(f.MaxBlockGasCost, other.MaxBlockGasCost) && - utils.BigNumEqual(f.BlockGasCostStep, other.BlockGasCostStep) -} - -// checkByteLens checks byte lengths against common.HashLen (32 bytes) and returns error -func (f *FeeConfig) checkByteLens() error { - if isBiggerThanHashLen(f.GasLimit) { - return fmt.Errorf("gasLimit exceeds %d bytes", common.HashLength) - } - if isBiggerThanHashLen(new(big.Int).SetUint64(f.TargetBlockRate)) { - return fmt.Errorf("targetBlockRate exceeds %d bytes", common.HashLength) - } - if isBiggerThanHashLen(f.MinBaseFee) { - return fmt.Errorf("minBaseFee exceeds %d bytes", common.HashLength) - } - if isBiggerThanHashLen(f.TargetGas) { - return fmt.Errorf("targetGas exceeds %d bytes", common.HashLength) - } - if isBiggerThanHashLen(f.BaseFeeChangeDenominator) { - return fmt.Errorf("baseFeeChangeDenominator exceeds %d bytes", common.HashLength) - } - if isBiggerThanHashLen(f.MinBlockGasCost) { - return fmt.Errorf("minBlockGasCost exceeds %d bytes", common.HashLength) - } - if isBiggerThanHashLen(f.MaxBlockGasCost) { - return fmt.Errorf("maxBlockGasCost exceeds %d bytes", common.HashLength) - } - if isBiggerThanHashLen(f.BlockGasCostStep) { - return fmt.Errorf("blockGasCostStep exceeds %d bytes", common.HashLength) - } - return nil -} - -func isBiggerThanHashLen(bigint *big.Int) bool { - buf := bigint.Bytes() - isBigger := len(buf) > common.HashLength - return isBigger -} diff --git a/commontype/fee_config_test.go b/commontype/fee_config_test.go deleted file mode 100644 index c0e26c4ced..0000000000 --- a/commontype/fee_config_test.go +++ /dev/null @@ -1,143 +0,0 @@ -// (c) 2019-2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package commontype - -import ( - "math/big" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestVerify(t *testing.T) { - tests := []struct { - name string - config *FeeConfig - expectedError string - }{ - { - name: "nil gasLimit in FeeConfig", - config: &FeeConfig{ - // GasLimit: big.NewInt(8_000_000) - TargetBlockRate: 2, // in seconds - - MinBaseFee: big.NewInt(25_000_000_000), - TargetGas: big.NewInt(15_000_000), - BaseFeeChangeDenominator: big.NewInt(36), - - MinBlockGasCost: big.NewInt(0), - MaxBlockGasCost: big.NewInt(1_000_000), - BlockGasCostStep: big.NewInt(200_000), - }, - expectedError: "gasLimit cannot be nil", - }, - { - name: "invalid GasLimit in FeeConfig", - config: func() *FeeConfig { c := ValidTestFeeConfig; c.GasLimit = big.NewInt(0); return &c }(), - expectedError: "gasLimit = 0 cannot be less than or equal to 0", - }, - { - name: "invalid TargetBlockRate in FeeConfig", - config: func() *FeeConfig { c := ValidTestFeeConfig; c.TargetBlockRate = 0; return &c }(), - expectedError: "targetBlockRate = 0 cannot be less than or equal to 0", - }, - { - name: "invalid MinBaseFee in FeeConfig", - config: func() *FeeConfig { c := ValidTestFeeConfig; c.MinBaseFee = big.NewInt(-1); return &c }(), - expectedError: "minBaseFee = -1 cannot be less than 0", - }, - { - name: "invalid TargetGas in FeeConfig", - config: func() *FeeConfig { c := ValidTestFeeConfig; c.TargetGas = big.NewInt(0); return &c }(), - expectedError: "targetGas = 0 cannot be less than or equal to 0", - }, - { - name: "invalid BaseFeeChangeDenominator in FeeConfig", - config: func() *FeeConfig { c := ValidTestFeeConfig; c.BaseFeeChangeDenominator = big.NewInt(0); return &c }(), - expectedError: "baseFeeChangeDenominator = 0 cannot be less than or equal to 0", - }, - { - name: "invalid MinBlockGasCost in FeeConfig", - config: func() *FeeConfig { c := ValidTestFeeConfig; c.MinBlockGasCost = big.NewInt(-1); return &c }(), - expectedError: "minBlockGasCost = -1 cannot be less than 0", - }, - { - name: "valid FeeConfig", - config: &ValidTestFeeConfig, - expectedError: "", - }, - { - name: "MinBlockGasCost bigger than MaxBlockGasCost in FeeConfig", - config: func() *FeeConfig { - c := ValidTestFeeConfig - c.MinBlockGasCost = big.NewInt(2) - c.MaxBlockGasCost = big.NewInt(1) - return &c - }(), - expectedError: "minBlockGasCost = 2 cannot be greater than maxBlockGasCost = 1", - }, - { - name: "invalid BlockGasCostStep in FeeConfig", - config: func() *FeeConfig { c := ValidTestFeeConfig; c.BlockGasCostStep = big.NewInt(-1); return &c }(), - expectedError: "blockGasCostStep = -1 cannot be less than 0", - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - err := test.config.Verify() - if test.expectedError == "" { - require.NoError(t, err) - } else { - require.Error(t, err) - require.Contains(t, err.Error(), test.expectedError) - } - }) - } -} - -func TestEqual(t *testing.T) { - tests := []struct { - name string - a *FeeConfig - b *FeeConfig - expected bool - }{ - { - name: "equal", - a: &ValidTestFeeConfig, - b: &FeeConfig{ - GasLimit: big.NewInt(8_000_000), - TargetBlockRate: 2, // in seconds - - MinBaseFee: big.NewInt(25_000_000_000), - TargetGas: big.NewInt(15_000_000), - BaseFeeChangeDenominator: big.NewInt(36), - - MinBlockGasCost: big.NewInt(0), - MaxBlockGasCost: big.NewInt(1_000_000), - BlockGasCostStep: big.NewInt(200_000), - }, - expected: true, - }, - { - name: "not equal", - a: &ValidTestFeeConfig, - b: func() *FeeConfig { c := ValidTestFeeConfig; c.GasLimit = big.NewInt(1); return &c }(), - expected: false, - }, - { - name: "not equal nil", - a: &ValidTestFeeConfig, - b: nil, - expected: false, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - require.Equal(t, test.expected, test.a.Equal(test.b)) - }) - } -} diff --git a/commontype/test_fee_config.go b/commontype/test_fee_config.go deleted file mode 100644 index 646f21d1cb..0000000000 --- a/commontype/test_fee_config.go +++ /dev/null @@ -1,19 +0,0 @@ -// (c) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package commontype - -import "math/big" - -var ValidTestFeeConfig = FeeConfig{ - GasLimit: big.NewInt(8_000_000), - TargetBlockRate: 2, // in seconds - - MinBaseFee: big.NewInt(25_000_000_000), - TargetGas: big.NewInt(15_000_000), - BaseFeeChangeDenominator: big.NewInt(36), - - MinBlockGasCost: big.NewInt(0), - MaxBlockGasCost: big.NewInt(1_000_000), - BlockGasCostStep: big.NewInt(200_000), -} diff --git a/compatibility.json b/compatibility.json deleted file mode 100644 index 372773a46b..0000000000 --- a/compatibility.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "rpcChainVMProtocolVersion": { - "v0.6.8": 36, - "v0.6.7": 35, - "v0.6.6": 35, - "v0.6.5": 35, - "v0.6.4": 35, - "v0.6.3": 35, - "v0.6.2": 34, - "v0.6.1": 33, - "v0.6.0": 33, - "v0.5.11": 31, - "v0.5.10": 30, - "v0.5.9": 30, - "v0.5.8": 29, - "v0.5.7": 29, - "v0.5.6": 28, - "v0.5.5": 28, - "v0.5.4": 28, - "v0.5.3": 27, - "v0.5.2": 26, - "v0.5.1": 26, - "v0.5.0": 25, - "v0.4.12": 24, - "v0.4.11": 24, - "v0.4.10": 23, - "v0.4.9": 23, - "v0.4.8": 22, - "v0.4.7": 21, - "v0.4.6": 20, - "v0.4.5": 20, - "v0.4.4": 19, - "v0.4.3": 19, - "v0.4.2": 18, - "v0.4.1": 18, - "v0.4.0": 17, - "v0.3.0": 16, - "v0.2.9": 15, - "v0.2.8": 15, - "v0.2.7": 15, - "v0.2.6": 15, - "v0.2.5": 15, - "v0.2.4": 15, - "v0.2.3": 15, - "v0.2.2": 14, - "v0.2.1": 12, - "v0.2.0": 11, - "v0.1.2": 10, - "v0.1.1": 10, - "v0.1.0": 9 - } -} \ No newline at end of file diff --git a/consensus/consensus.go b/consensus/consensus.go index b96297c4ad..25d3a5d732 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -30,7 +30,6 @@ package consensus import ( "math/big" - "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" @@ -54,13 +53,6 @@ type ChainHeaderReader interface { // GetHeaderByHash retrieves a block header from the database by its hash. GetHeaderByHash(hash common.Hash) *types.Header - - // GetFeeConfigAt retrieves the fee config and last changed block number at block header. - GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error) - - // GetCoinbaseAt retrieves the configured coinbase address at [parent]. - // If fee recipients are allowed, returns true in the second return value and a predefined address in the first value. - GetCoinbaseAt(parent *types.Header) (common.Address, bool, error) } // ChainReader defines a small collection of methods needed to access the local diff --git a/consensus/dummy/README.md b/consensus/dummy/README.md index 4375dff5ab..23ff20072a 100644 --- a/consensus/dummy/README.md +++ b/consensus/dummy/README.md @@ -1,10 +1,10 @@ # Consensus -Disclaimer: the consensus package in subnet-evm is a complete misnomer. +Disclaimer: the consensus package in coreth is a complete misnomer. The consensus package in go-ethereum handles block validation and specifically handles validating the PoW portion of consensus - thus the name. -Since AvalancheGo handles consensus for Subnet-EVM, Subnet-EVM is just the VM, but we keep the consensus package in place to handle part of the block verification process. +Since AvalancheGo handles consensus for Coreth, Coreth is just the VM, but we keep the consensus package in place to handle part of the block verification process. ## Block Verification @@ -12,22 +12,22 @@ The dummy consensus engine is responsible for performing verification on the hea ## Dynamic Fees -Subnet-EVM includes a dynamic fee algorithm based off of (EIP-1559)[https://eips.ethereum.org/EIPS/eip-1559]. This introduces a field to the block type called `BaseFee`. The Base Fee sets a minimum gas price for any transaction to be included in the block. For example, a transaction with a gas price of 49 gwei, will be invalid to include in a block with a base fee of 50 gwei. +As of Apricot Phase 3, the C-Chain includes a dynamic fee algorithm based off of (EIP-1559)[https://eips.ethereum.org/EIPS/eip-1559]. This introduces a field to the block type called `BaseFee`. The Base Fee sets a minimum gas price for any transaction to be included in the block. For example, a transaction with a gas price of 49 gwei, will be invalid to include in a block with a base fee of 50 gwei. -The dynamic fee algorithm aims to adjust the base fee to handle network congestion. Subnet-EVM sets a target utilization on the network, and the dynamic fee algorithm adjusts the base fee accordingly. If the network operates above the target utilization, the dynamic fee algorithm will increase the base fee to make utilizing the network more expensive and bring overall utilization down. If the network operates below the target utilization, the dynamic fee algorithm will decrease the base fee to make it cheaper to use the network. +The dynamic fee algorithm aims to adjust the base fee to handle network congestion. Coreth sets a target utilization on the network, and the dynamic fee algorithm adjusts the base fee accordingly. If the network operates above the target utilization, the dynamic fee algorithm will increase the base fee to make utilizing the network more expensive and bring overall utilization down. If the network operates below the target utilization, the dynamic fee algorithm will decrease the base fee to make it cheaper to use the network. - EIP-1559 is intended for Ethereum where a block is produced roughly every 10s -- The dynamic fee algorithm needs to handle the case that the network quiesces and there are no blocks for a long period of time -- Since Subnet-EVM produces blocks at a different cadence, it adapts EIP-1559 to sum the amount of gas consumed within a 10 second interval instead of using only the amount of gas consumed in the parent block +- C-Chain typically produces blocks every 2 seconds, but the dynamic fee algorithm needs to handle the case that the network quiesces and there are no blocks for a long period of time +- Since C-Chain produces blocks at a different cadence, it adapts EIP-1559 to sum the amount of gas consumed within a 10 second interval instead of using only the amount of gas consumed in the parent block ## Consensus Engine Callbacks -The consensus engine is called while blocks are being both built and processed and Subnet-EVM adds callback functions into the dummy consensus engine to insert its own logic into these stages. +The consensus engine is called while blocks are being both built and processed and Coreth adds callback functions into the dummy consensus engine to insert its own logic into these stages. ### FinalizeAndAssemble -The FinalizeAndAssemble callback is used as the final step in building a block within the miner package. +The FinalizeAndAssemble callback is used as the final step in building a block within the miner package. Coreth adds a callback function within FinalizeAndAssemble in order to process atomic transactions. ### Finalize -Finalize is called as the final step in processing a block [here](../../core/state_processor.go). Since either Finalize or FinalizeAndAssemble are called, but not both, when building or verifying/processing a block they need to perform the exact same processing/verification step to ensure that a block produced by the miner where FinalizeAndAssemble is called will be processed and verified in the same way when Finalize gets called. +Finalize is called as the final step in processing a block [here](../../core/state_processor.go). Finalize adds a callback function in order to process atomic transactions as well. Since either Finalize or FinalizeAndAssemble are called, but not both, when building or verifying/processing a block they need to perform the exact same processing/verification step to ensure that a block produced by the miner where FinalizeAndAssemble is called will be processed and verified in the same way when Finalize gets called. diff --git a/consensus/dummy/consensus.go b/consensus/dummy/consensus.go index e73f94ec4d..8eb5faa394 100644 --- a/consensus/dummy/consensus.go +++ b/consensus/dummy/consensus.go @@ -17,18 +17,19 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/trie" - "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" ) var ( allowedFutureBlockTime = 10 * time.Second // Max time from current time allowed for blocks, before they're considered future blocks - errInvalidBlockTime = errors.New("timestamp less than parent's") - errUnclesUnsupported = errors.New("uncles unsupported") - errBlockGasCostNil = errors.New("block gas cost is nil") - errBlockGasCostTooLarge = errors.New("block gas cost is not uint64") - errBaseFeeNil = errors.New("base fee is nil") + errInvalidBlockTime = errors.New("timestamp less than parent's") + errUnclesUnsupported = errors.New("uncles unsupported") + errBlockGasCostNil = errors.New("block gas cost is nil") + errBlockGasCostTooLarge = errors.New("block gas cost is not uint64") + errBaseFeeNil = errors.New("base fee is nil") + errExtDataGasUsedNil = errors.New("extDataGasUsed is nil") + errExtDataGasUsedTooLarge = errors.New("extDataGasUsed is not uint64") ) type Mode struct { @@ -38,7 +39,16 @@ type Mode struct { } type ( + OnFinalizeAndAssembleCallbackType = func(header *types.Header, state *state.StateDB, txs []*types.Transaction) (extraData []byte, blockFeeContribution *big.Int, extDataGasUsed *big.Int, err error) + OnExtraStateChangeType = func(block *types.Block, statedb *state.StateDB) (blockFeeContribution *big.Int, extDataGasUsed *big.Int, err error) + + ConsensusCallbacks struct { + OnFinalizeAndAssemble OnFinalizeAndAssembleCallbackType + OnExtraStateChange OnExtraStateChangeType + } + DummyEngine struct { + cb ConsensusCallbacks clock *mockable.Clock consensusMode Mode } @@ -57,14 +67,23 @@ func NewFaker() *DummyEngine { } } -func NewFakerWithClock(clock *mockable.Clock) *DummyEngine { +func NewFakerWithClock(cb ConsensusCallbacks, clock *mockable.Clock) *DummyEngine { return &DummyEngine{ + cb: cb, clock: clock, } } -func NewFakerWithMode(mode Mode) *DummyEngine { +func NewFakerWithCallbacks(cb ConsensusCallbacks) *DummyEngine { + return &DummyEngine{ + cb: cb, + clock: &mockable.Clock{}, + } +} + +func NewFakerWithMode(cb ConsensusCallbacks, mode Mode) *DummyEngine { return &DummyEngine{ + cb: cb, clock: &mockable.Clock{}, consensusMode: mode, } @@ -84,30 +103,7 @@ func NewFullFaker() *DummyEngine { } } -// verifyCoinbase checks that the coinbase is valid for the given [header] and [parent]. -func (self *DummyEngine) verifyCoinbase(config *params.ChainConfig, header *types.Header, parent *types.Header, chain consensus.ChainHeaderReader) error { - if self.consensusMode.ModeSkipCoinbase { - return nil - } - // get the coinbase configured at parent - configuredAddressAtParent, isAllowFeeRecipients, err := chain.GetCoinbaseAt(parent) - if err != nil { - return fmt.Errorf("failed to get coinbase at %v: %w", header.Hash(), err) - } - - if isAllowFeeRecipients { - // if fee recipients are allowed we don't need to check the coinbase - return nil - } - // we fetch the configured coinbase at the parent's state - // to check against the coinbase in [header]. - if configuredAddressAtParent != header.Coinbase { - return fmt.Errorf("%w: %v does not match required coinbase address %v", vmerrs.ErrInvalidCoinbase, header.Coinbase, configuredAddressAtParent) - } - return nil -} - -func (self *DummyEngine) verifyHeaderGasFields(config *params.ChainConfig, header *types.Header, parent *types.Header, chain consensus.ChainHeaderReader) error { +func (self *DummyEngine) verifyHeaderGasFields(config *params.ChainConfig, header *types.Header, parent *types.Header) error { // Verify that the gas limit is <= 2^63-1 if header.GasLimit > params.MaxGasLimit { return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, params.MaxGasLimit) @@ -116,19 +112,13 @@ func (self *DummyEngine) verifyHeaderGasFields(config *params.ChainConfig, heade if header.GasUsed > header.GasLimit { return fmt.Errorf("invalid gasUsed: have %d, gasLimit %d", header.GasUsed, header.GasLimit) } - // We verify the current block by checking the parent fee config - // this is because the current block cannot set the fee config for itself - // Fee config might depend on the state when precompile is activated - // but we don't know the final state while forming the block. - // See worker package for more details. - feeConfig, _, err := chain.GetFeeConfigAt(parent) - if err != nil { - return err - } - if config.IsSubnetEVM(header.Time) { - expectedGasLimit := feeConfig.GasLimit.Uint64() - if header.GasLimit != expectedGasLimit { - return fmt.Errorf("expected gas limit to be %d, but found %d", expectedGasLimit, header.GasLimit) + if config.IsCortina(header.Time) { + if header.GasLimit != params.CortinaGasLimit { + return fmt.Errorf("expected gas limit to be %d in Cortina, but found %d", params.CortinaGasLimit, header.GasLimit) + } + } else if config.IsApricotPhase1(header.Time) { + if header.GasLimit != params.ApricotPhase1GasLimit { + return fmt.Errorf("expected gas limit to be %d in ApricotPhase1, but found %d", params.ApricotPhase1GasLimit, header.GasLimit) } } else { // Verify that the gas limit remains within allowed bounds @@ -141,41 +131,55 @@ func (self *DummyEngine) verifyHeaderGasFields(config *params.ChainConfig, heade if uint64(diff) >= limit || header.GasLimit < params.MinGasLimit { return fmt.Errorf("invalid gas limit: have %d, want %d += %d", header.GasLimit, parent.GasLimit, limit) } - // Verify BaseFee is not present before Subnet EVM + } + + if !config.IsApricotPhase3(header.Time) { + // Verify BaseFee is not present before AP3 if header.BaseFee != nil { return fmt.Errorf("invalid baseFee before fork: have %d, want ", header.BaseFee) } + } else { + // Verify baseFee and rollupWindow encoding as part of header verification + // starting in AP3 + expectedRollupWindowBytes, expectedBaseFee, err := CalcBaseFee(config, parent, header.Time) + if err != nil { + return fmt.Errorf("failed to calculate base fee: %w", err) + } + if len(header.Extra) < len(expectedRollupWindowBytes) || !bytes.Equal(expectedRollupWindowBytes, header.Extra[:len(expectedRollupWindowBytes)]) { + return fmt.Errorf("expected rollup window bytes: %x, found %x", expectedRollupWindowBytes, header.Extra) + } + if header.BaseFee == nil { + return errors.New("expected baseFee to be non-nil") + } + if bfLen := header.BaseFee.BitLen(); bfLen > 256 { + return fmt.Errorf("too large base fee: bitlen %d", bfLen) + } + if header.BaseFee.Cmp(expectedBaseFee) != 0 { + return fmt.Errorf("expected base fee (%d), found (%d)", expectedBaseFee, header.BaseFee) + } + } + + // Verify BlockGasCost, ExtDataGasUsed not present before AP4 + if !config.IsApricotPhase4(header.Time) { if header.BlockGasCost != nil { return fmt.Errorf("invalid blockGasCost before fork: have %d, want ", header.BlockGasCost) } + if header.ExtDataGasUsed != nil { + return fmt.Errorf("invalid extDataGasUsed before fork: have %d, want ", header.ExtDataGasUsed) + } return nil } - // Verify baseFee and rollupWindow encoding as part of header verification - // starting in Subnet EVM - expectedRollupWindowBytes, expectedBaseFee, err := CalcBaseFee(config, feeConfig, parent, header.Time) - if err != nil { - return fmt.Errorf("failed to calculate base fee: %w", err) - } - if len(header.Extra) < len(expectedRollupWindowBytes) || !bytes.Equal(expectedRollupWindowBytes, header.Extra[:len(expectedRollupWindowBytes)]) { - return fmt.Errorf("expected rollup window bytes: %x, found %x", expectedRollupWindowBytes, header.Extra) - } - if header.BaseFee == nil { - return errors.New("expected baseFee to be non-nil") - } - if bfLen := header.BaseFee.BitLen(); bfLen > 256 { - return fmt.Errorf("too large base fee: bitlen %d", bfLen) - } - if header.BaseFee.Cmp(expectedBaseFee) != 0 { - return fmt.Errorf("expected base fee (%d), found (%d)", expectedBaseFee, header.BaseFee) - } - // Enforce BlockGasCost constraints + blockGasCostStep := ApricotPhase4BlockGasCostStep + if config.IsApricotPhase5(header.Time) { + blockGasCostStep = ApricotPhase5BlockGasCostStep + } expectedBlockGasCost := calcBlockGasCost( - feeConfig.TargetBlockRate, - feeConfig.MinBlockGasCost, - feeConfig.MaxBlockGasCost, - feeConfig.BlockGasCostStep, + ApricotPhase4TargetBlockRate, + ApricotPhase4MinBlockGasCost, + ApricotPhase4MaxBlockGasCost, + blockGasCostStep, parent.BlockGasCost, parent.Time, header.Time, ) @@ -188,6 +192,14 @@ func (self *DummyEngine) verifyHeaderGasFields(config *params.ChainConfig, heade if header.BlockGasCost.Cmp(expectedBlockGasCost) != 0 { return fmt.Errorf("invalid block gas cost: have %d, want %d", header.BlockGasCost, expectedBlockGasCost) } + // ExtDataGasUsed correctness is checked during block validation + // (when the validator has access to the block contents) + if header.ExtDataGasUsed == nil { + return errExtDataGasUsedNil + } + if !header.ExtDataGasUsed.IsUint64() { + return errExtDataGasUsedTooLarge + } return nil } @@ -203,7 +215,7 @@ func (self *DummyEngine) verifyHeader(chain consensus.ChainHeaderReader, header if len(header.Extra) < params.DynamicFeeExtraDataSize { return fmt.Errorf("expected extra-data field length >= %d, found %d", params.DynamicFeeExtraDataSize, len(header.Extra)) } - case config.IsSubnetEVM(header.Time): + case config.IsApricotPhase3(header.Time): if len(header.Extra) != params.DynamicFeeExtraDataSize { return fmt.Errorf("expected extra-data field to be: %d, but found %d", params.DynamicFeeExtraDataSize, len(header.Extra)) } @@ -213,11 +225,7 @@ func (self *DummyEngine) verifyHeader(chain consensus.ChainHeaderReader, header } } // Ensure gas-related header fields are correct - if err := self.verifyHeaderGasFields(config, header, parent, chain); err != nil { - return err - } - // Ensure that coinbase is valid - if err := self.verifyCoinbase(config, header, parent, chain); err != nil { + if err := self.verifyHeaderGasFields(config, header, parent); err != nil { return err } @@ -255,6 +263,9 @@ func (self *DummyEngine) verifyHeader(chain consensus.ChainHeaderReader, header if err := eip4844.VerifyEIP4844Header(parent, header); err != nil { return err } + if *header.BlobGasUsed > 0 { // VerifyEIP4844Header ensures BlobGasUsed is non-nil + return fmt.Errorf("blobs not enabled on avalanche networks: used %d blob gas, expected 0", *header.BlobGasUsed) + } } return nil } @@ -298,15 +309,16 @@ func (self *DummyEngine) verifyBlockFee( requiredBlockGasCost *big.Int, txs []*types.Transaction, receipts []*types.Receipt, + extraStateChangeContribution *big.Int, ) error { if self.consensusMode.ModeSkipBlockFee { return nil } if baseFee == nil || baseFee.Sign() <= 0 { - return fmt.Errorf("invalid base fee (%d) in SubnetEVM", baseFee) + return fmt.Errorf("invalid base fee (%d) in apricot phase 4", baseFee) } if requiredBlockGasCost == nil || !requiredBlockGasCost.IsUint64() { - return fmt.Errorf("invalid block gas cost (%d) in SubnetEVM", requiredBlockGasCost) + return fmt.Errorf("invalid block gas cost (%d) in apricot phase 4", requiredBlockGasCost) } var ( @@ -314,7 +326,16 @@ func (self *DummyEngine) verifyBlockFee( blockFeeContribution = new(big.Int) totalBlockFee = new(big.Int) ) - // Calculate the total excess over the base fee that was paid towards the block fee + + // Add in the external contribution + if extraStateChangeContribution != nil { + if extraStateChangeContribution.Cmp(common.Big0) < 0 { + return fmt.Errorf("invalid extra state change contribution: %d", extraStateChangeContribution) + } + totalBlockFee.Add(totalBlockFee, extraStateChangeContribution) + } + + // Calculate the total excess (denominated in AVAX) over the base fee that was paid towards the block fee for i, receipt := range receipts { // Each transaction contributes the excess over the baseFee towards the totalBlockFee // This should be equivalent to the sum of the "priority fees" within EIP-1559. @@ -322,7 +343,7 @@ func (self *DummyEngine) verifyBlockFee( if err != nil { return err } - // Multiply the [txFeePremium] by the gasUsed in the transaction since this gives the total coin that was paid + // Multiply the [txFeePremium] by the gasUsed in the transaction since this gives the total AVAX that was paid // above the amount required if the transaction had simply paid the minimum base fee for the block. // // Ex. LegacyTx paying a gas price of 100 gwei for 1M gas in a block with a base fee of 10 gwei. @@ -353,21 +374,39 @@ func (self *DummyEngine) verifyBlockFee( } func (self *DummyEngine) Finalize(chain consensus.ChainHeaderReader, block *types.Block, parent *types.Header, state *state.StateDB, receipts []*types.Receipt) error { - if chain.Config().IsSubnetEVM(block.Time()) { - // we use the parent to determine the fee config - // since the current block has not been finalized yet. - feeConfig, _, err := chain.GetFeeConfigAt(parent) + // Perform extra state change while finalizing the block + var ( + contribution, extDataGasUsed *big.Int + err error + ) + if self.cb.OnExtraStateChange != nil { + contribution, extDataGasUsed, err = self.cb.OnExtraStateChange(block, state) if err != nil { return err } - + } + if chain.Config().IsApricotPhase4(block.Time()) { + // Validate extDataGasUsed and BlockGasCost match expectations + // + // NOTE: This is a duplicate check of what is already performed in + // blockValidator but is done here for symmetry with FinalizeAndAssemble. + if extDataGasUsed == nil { + extDataGasUsed = new(big.Int).Set(common.Big0) + } + if blockExtDataGasUsed := block.ExtDataGasUsed(); blockExtDataGasUsed == nil || !blockExtDataGasUsed.IsUint64() || blockExtDataGasUsed.Cmp(extDataGasUsed) != 0 { + return fmt.Errorf("invalid extDataGasUsed: have %d, want %d", blockExtDataGasUsed, extDataGasUsed) + } + blockGasCostStep := ApricotPhase4BlockGasCostStep + if chain.Config().IsApricotPhase5(block.Time()) { + blockGasCostStep = ApricotPhase5BlockGasCostStep + } // Calculate the expected blockGasCost for this block. // Note: this is a deterministic transtion that defines an exact block fee for this block. blockGasCost := calcBlockGasCost( - feeConfig.TargetBlockRate, - feeConfig.MinBlockGasCost, - feeConfig.MaxBlockGasCost, - feeConfig.BlockGasCostStep, + ApricotPhase4TargetBlockRate, + ApricotPhase4MinBlockGasCost, + ApricotPhase4MaxBlockGasCost, + blockGasCostStep, parent.BlockGasCost, parent.Time, block.Time(), ) @@ -381,6 +420,7 @@ func (self *DummyEngine) Finalize(chain consensus.ChainHeaderReader, block *type block.BlockGasCost(), block.Transactions(), receipts, + contribution, ); err != nil { return err } @@ -392,19 +432,32 @@ func (self *DummyEngine) Finalize(chain consensus.ChainHeaderReader, block *type func (self *DummyEngine) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, parent *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt, ) (*types.Block, error) { - if chain.Config().IsSubnetEVM(header.Time) { - // we use the parent to determine the fee config - // since the current block has not been finalized yet. - feeConfig, _, err := chain.GetFeeConfigAt(parent) + var ( + contribution, extDataGasUsed *big.Int + extraData []byte + err error + ) + if self.cb.OnFinalizeAndAssemble != nil { + extraData, contribution, extDataGasUsed, err = self.cb.OnFinalizeAndAssemble(header, state, txs) if err != nil { return nil, err } + } + if chain.Config().IsApricotPhase4(header.Time) { + header.ExtDataGasUsed = extDataGasUsed + if header.ExtDataGasUsed == nil { + header.ExtDataGasUsed = new(big.Int).Set(common.Big0) + } + blockGasCostStep := ApricotPhase4BlockGasCostStep + if chain.Config().IsApricotPhase5(header.Time) { + blockGasCostStep = ApricotPhase5BlockGasCostStep + } // Calculate the required block gas cost for this block. header.BlockGasCost = calcBlockGasCost( - feeConfig.TargetBlockRate, - feeConfig.MinBlockGasCost, - feeConfig.MaxBlockGasCost, - feeConfig.BlockGasCostStep, + ApricotPhase4TargetBlockRate, + ApricotPhase4MinBlockGasCost, + ApricotPhase4MaxBlockGasCost, + blockGasCostStep, parent.BlockGasCost, parent.Time, header.Time, ) @@ -414,6 +467,7 @@ func (self *DummyEngine) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header.BlockGasCost, txs, receipts, + contribution, ); err != nil { return nil, err } @@ -422,8 +476,9 @@ func (self *DummyEngine) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) // Header seems complete, assemble into a block and return - return types.NewBlock( + return types.NewBlockWithExtData( header, txs, uncles, receipts, trie.NewStackTrie(nil), + extraData, chain.Config().IsApricotPhase1(header.Time), ), nil } diff --git a/consensus/dummy/consensus_test.go b/consensus/dummy/consensus_test.go index 12fc21a499..4cfba4d801 100644 --- a/consensus/dummy/consensus_test.go +++ b/consensus/dummy/consensus_test.go @@ -9,12 +9,9 @@ import ( "testing" "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/params" "github.com/ethereum/go-ethereum/common" ) -var testBlockGasCostStep = big.NewInt(50_000) - func TestVerifyBlockFee(t *testing.T) { tests := map[string]struct { baseFee *big.Int @@ -22,6 +19,7 @@ func TestVerifyBlockFee(t *testing.T) { parentTime, currentTime uint64 txs []*types.Transaction receipts []*types.Receipt + extraStateContribution *big.Int shouldErr bool }{ "tx only base fee": { @@ -35,7 +33,8 @@ func TestVerifyBlockFee(t *testing.T) { receipts: []*types.Receipt{ {GasUsed: 1000}, }, - shouldErr: true, + extraStateContribution: nil, + shouldErr: true, }, "tx covers exactly block fee": { baseFee: big.NewInt(100), @@ -48,7 +47,8 @@ func TestVerifyBlockFee(t *testing.T) { receipts: []*types.Receipt{ {GasUsed: 100_000}, }, - shouldErr: false, + extraStateContribution: nil, + shouldErr: false, }, "txs share block fee": { baseFee: big.NewInt(100), @@ -63,7 +63,8 @@ func TestVerifyBlockFee(t *testing.T) { {GasUsed: 100_000}, {GasUsed: 100_000}, }, - shouldErr: false, + extraStateContribution: nil, + shouldErr: false, }, "txs split block fee": { baseFee: big.NewInt(100), @@ -78,7 +79,62 @@ func TestVerifyBlockFee(t *testing.T) { {GasUsed: 100_000}, {GasUsed: 100_000}, }, - shouldErr: false, + extraStateContribution: nil, + shouldErr: false, + }, + "split block fee with extra state contribution": { + baseFee: big.NewInt(100), + parentBlockGasCost: big.NewInt(0), + parentTime: 10, + currentTime: 10, + txs: []*types.Transaction{ + types.NewTransaction(0, common.HexToAddress("7ef5a6135f1fd6a02593eedc869c6d41d934aef8"), big.NewInt(0), 100_000, big.NewInt(150), nil), + }, + receipts: []*types.Receipt{ + {GasUsed: 100_000}, + }, + extraStateContribution: big.NewInt(5_000_000), + shouldErr: false, + }, + "extra state contribution insufficient": { + baseFee: big.NewInt(100), + parentBlockGasCost: big.NewInt(0), + parentTime: 10, + currentTime: 10, + txs: nil, + receipts: nil, + extraStateContribution: big.NewInt(9_999_999), + shouldErr: true, + }, + "negative extra state contribution": { + baseFee: big.NewInt(100), + parentBlockGasCost: big.NewInt(0), + parentTime: 10, + currentTime: 10, + txs: nil, + receipts: nil, + extraStateContribution: big.NewInt(-1), + shouldErr: true, + }, + "extra state contribution covers block fee": { + baseFee: big.NewInt(100), + parentBlockGasCost: big.NewInt(0), + parentTime: 10, + currentTime: 10, + txs: nil, + receipts: nil, + extraStateContribution: big.NewInt(10_000_000), + shouldErr: false, + }, + "extra state contribution covers more than block fee": { + baseFee: big.NewInt(100), + parentBlockGasCost: big.NewInt(0), + parentTime: 10, + currentTime: 10, + txs: nil, + receipts: nil, + extraStateContribution: big.NewInt(10_000_001), + shouldErr: false, }, "tx only base fee after full time window": { baseFee: big.NewInt(100), @@ -91,7 +147,8 @@ func TestVerifyBlockFee(t *testing.T) { receipts: []*types.Receipt{ {GasUsed: 1000}, }, - shouldErr: false, + extraStateContribution: nil, + shouldErr: false, }, "tx only base fee after large time window": { baseFee: big.NewInt(100), @@ -104,31 +161,33 @@ func TestVerifyBlockFee(t *testing.T) { receipts: []*types.Receipt{ {GasUsed: 1000}, }, - shouldErr: false, + extraStateContribution: nil, + shouldErr: false, }, "parent time > current time": { - baseFee: big.NewInt(100), - parentBlockGasCost: big.NewInt(0), - parentTime: 11, - currentTime: 10, - txs: nil, - receipts: nil, - shouldErr: true, + baseFee: big.NewInt(100), + parentBlockGasCost: big.NewInt(0), + parentTime: 11, + currentTime: 10, + txs: nil, + receipts: nil, + extraStateContribution: big.NewInt(10_000_000), + shouldErr: false, }, } for name, test := range tests { t.Run(name, func(t *testing.T) { blockGasCost := calcBlockGasCost( - params.DefaultFeeConfig.TargetBlockRate, - params.DefaultFeeConfig.MinBlockGasCost, - params.DefaultFeeConfig.MaxBlockGasCost, - testBlockGasCostStep, + ApricotPhase4TargetBlockRate, + ApricotPhase4MinBlockGasCost, + ApricotPhase4MaxBlockGasCost, + ApricotPhase4BlockGasCostStep, test.parentBlockGasCost, test.parentTime, test.currentTime, ) engine := NewFaker() - if err := engine.verifyBlockFee(test.baseFee, blockGasCost, test.txs, test.receipts); err != nil { + if err := engine.verifyBlockFee(test.baseFee, blockGasCost, test.txs, test.receipts, test.extraStateContribution); err != nil { if !test.shouldErr { t.Fatalf("Unexpected error: %s", err) } diff --git a/consensus/dummy/dynamic_fees.go b/consensus/dummy/dynamic_fees.go index ba4ad441eb..1041dbd925 100644 --- a/consensus/dummy/dynamic_fees.go +++ b/consensus/dummy/dynamic_fees.go @@ -9,28 +9,53 @@ import ( "math/big" "github.com/ava-labs/avalanchego/utils/wrappers" - "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" ) +var ( + ApricotPhase3MinBaseFee = big.NewInt(params.ApricotPhase3MinBaseFee) + ApricotPhase3MaxBaseFee = big.NewInt(params.ApricotPhase3MaxBaseFee) + ApricotPhase4MinBaseFee = big.NewInt(params.ApricotPhase4MinBaseFee) + ApricotPhase4MaxBaseFee = big.NewInt(params.ApricotPhase4MaxBaseFee) + ApricotPhase3InitialBaseFee = big.NewInt(params.ApricotPhase3InitialBaseFee) + EtnaMinBaseFee = big.NewInt(params.EtnaMinBaseFee) + + ApricotPhase4BaseFeeChangeDenominator = new(big.Int).SetUint64(params.ApricotPhase4BaseFeeChangeDenominator) + ApricotPhase5BaseFeeChangeDenominator = new(big.Int).SetUint64(params.ApricotPhase5BaseFeeChangeDenominator) + + ApricotPhase3BlockGasFee uint64 = 1_000_000 + ApricotPhase4MinBlockGasCost = new(big.Int).Set(common.Big0) + ApricotPhase4MaxBlockGasCost = big.NewInt(1_000_000) + ApricotPhase4BlockGasCostStep = big.NewInt(50_000) + ApricotPhase4TargetBlockRate uint64 = 2 // in seconds + ApricotPhase5BlockGasCostStep = big.NewInt(200_000) + rollupWindow uint64 = 10 +) + // CalcBaseFee takes the previous header and the timestamp of its child block // and calculates the expected base fee as well as the encoding of the past // pricing information for the child block. -func CalcBaseFee(config *params.ChainConfig, feeConfig commontype.FeeConfig, parent *types.Header, timestamp uint64) ([]byte, *big.Int, error) { +// CalcBaseFee should only be called if [timestamp] >= [config.ApricotPhase3Timestamp] +func CalcBaseFee(config *params.ChainConfig, parent *types.Header, timestamp uint64) ([]byte, *big.Int, error) { // If the current block is the first EIP-1559 block, or it is the genesis block // return the initial slice and initial base fee. - isSubnetEVM := config.IsSubnetEVM(parent.Time) - extraDataSize := params.DynamicFeeExtraDataSize - - if !isSubnetEVM || parent.Number.Cmp(common.Big0) == 0 { - initialSlice := make([]byte, extraDataSize) - return initialSlice, feeConfig.MinBaseFee, nil + var ( + isApricotPhase3 = config.IsApricotPhase3(parent.Time) + isApricotPhase4 = config.IsApricotPhase4(parent.Time) + isApricotPhase5 = config.IsApricotPhase5(parent.Time) + isEtna = config.IsEtna(parent.Time) + ) + if !isApricotPhase3 || parent.Number.Cmp(common.Big0) == 0 { + initialSlice := make([]byte, params.DynamicFeeExtraDataSize) + initialBaseFee := big.NewInt(params.ApricotPhase3InitialBaseFee) + return initialSlice, initialBaseFee, nil } - if len(parent.Extra) < extraDataSize { - return nil, nil, fmt.Errorf("expected length of parent extra data to be >= %d, but found %d", extraDataSize, len(parent.Extra)) + + if uint64(len(parent.Extra)) < params.DynamicFeeExtraDataSize { + return nil, nil, fmt.Errorf("expected length of parent extra data to be %d, but found %d", params.DynamicFeeExtraDataSize, len(parent.Extra)) } dynamicFeeWindow := parent.Extra[:params.DynamicFeeExtraDataSize] @@ -46,25 +71,75 @@ func CalcBaseFee(config *params.ChainConfig, feeConfig commontype.FeeConfig, par return nil, nil, err } - // start off with parent's base fee - baseFee := new(big.Int).Set(parent.BaseFee) - baseFeeChangeDenominator := feeConfig.BaseFeeChangeDenominator - - parentGasTargetBig := feeConfig.TargetGas - parentGasTarget := parentGasTargetBig.Uint64() + // If AP5, use a less responsive [BaseFeeChangeDenominator] and a higher gas + // block limit + var ( + baseFee = new(big.Int).Set(parent.BaseFee) + baseFeeChangeDenominator = ApricotPhase4BaseFeeChangeDenominator + parentGasTarget = params.ApricotPhase3TargetGas + ) + if isApricotPhase5 { + baseFeeChangeDenominator = ApricotPhase5BaseFeeChangeDenominator + parentGasTarget = params.ApricotPhase5TargetGas + } + parentGasTargetBig := new(big.Int).SetUint64(parentGasTarget) // Add in the gas used by the parent block in the correct place // If the parent consumed gas within the rollup window, add the consumed // gas in. - expectedRollUp := params.RollupWindow - if roll < expectedRollUp { - slot := expectedRollUp - 1 - roll + if roll < rollupWindow { + var blockGasCost, parentExtraStateGasUsed uint64 + switch { + case isApricotPhase5: + // [blockGasCost] has been removed in AP5, so it is left as 0. + + // At the start of a new network, the parent + // may not have a populated [ExtDataGasUsed]. + if parent.ExtDataGasUsed != nil { + parentExtraStateGasUsed = parent.ExtDataGasUsed.Uint64() + } + case isApricotPhase4: + // The [blockGasCost] is paid by the effective tips in the block using + // the block's value of [baseFee]. + blockGasCost = calcBlockGasCost( + ApricotPhase4TargetBlockRate, + ApricotPhase4MinBlockGasCost, + ApricotPhase4MaxBlockGasCost, + ApricotPhase4BlockGasCostStep, + parent.BlockGasCost, + parent.Time, timestamp, + ).Uint64() + + // On the boundary of AP3 and AP4 or at the start of a new network, the parent + // may not have a populated [ExtDataGasUsed]. + if parent.ExtDataGasUsed != nil { + parentExtraStateGasUsed = parent.ExtDataGasUsed.Uint64() + } + default: + blockGasCost = ApricotPhase3BlockGasFee + } + + // Compute the new state of the gas rolling window. + addedGas, overflow := math.SafeAdd(parent.GasUsed, parentExtraStateGasUsed) + if overflow { + addedGas = math.MaxUint64 + } + + // Only add the [blockGasCost] to the gas used if it isn't AP5 + if !isApricotPhase5 { + addedGas, overflow = math.SafeAdd(addedGas, blockGasCost) + if overflow { + addedGas = math.MaxUint64 + } + } + + slot := rollupWindow - 1 - roll start := slot * wrappers.LongLen - updateLongWindow(newRollupWindow, start, parent.GasUsed) + updateLongWindow(newRollupWindow, start, addedGas) } // Calculate the amount of gas consumed within the rollup window. - totalGas := sumLongWindow(newRollupWindow, int(expectedRollUp)) + totalGas := sumLongWindow(newRollupWindow, int(rollupWindow)) if totalGas == parentGasTarget { return newRollupWindow, baseFee, nil @@ -95,14 +170,24 @@ func CalcBaseFee(config *params.ChainConfig, feeConfig commontype.FeeConfig, par // for the interval during which no blocks were produced. // We use roll/rollupWindow, so that the transition is applied for every [rollupWindow] seconds // that has elapsed between the parent and this block. - if roll > expectedRollUp { - // Note: roll/params.RollupWindow must be greater than 1 since we've checked that roll > params.RollupWindow - baseFeeDelta = baseFeeDelta.Mul(baseFeeDelta, new(big.Int).SetUint64(roll/expectedRollUp)) + if roll > rollupWindow { + // Note: roll/rollupWindow must be greater than 1 since we've checked that roll > rollupWindow + baseFeeDelta = baseFeeDelta.Mul(baseFeeDelta, new(big.Int).SetUint64(roll/rollupWindow)) } baseFee.Sub(baseFee, baseFeeDelta) } - baseFee = selectBigWithinBounds(feeConfig.MinBaseFee, baseFee, nil) + // Ensure that the base fee does not increase/decrease outside of the bounds + switch { + case isEtna: + baseFee = selectBigWithinBounds(EtnaMinBaseFee, baseFee, nil) + case isApricotPhase5: + baseFee = selectBigWithinBounds(ApricotPhase4MinBaseFee, baseFee, nil) + case isApricotPhase4: + baseFee = selectBigWithinBounds(ApricotPhase4MinBaseFee, baseFee, ApricotPhase4MaxBaseFee) + default: + baseFee = selectBigWithinBounds(ApricotPhase3MinBaseFee, baseFee, ApricotPhase3MaxBaseFee) + } return newRollupWindow, baseFee, nil } @@ -112,11 +197,11 @@ func CalcBaseFee(config *params.ChainConfig, feeConfig commontype.FeeConfig, par // If [timestamp] is less than the timestamp of [parent], then it uses the same timestamp as parent. // Warning: This function should only be used in estimation and should not be used when calculating the canonical // base fee for a subsequent block. -func EstimateNextBaseFee(config *params.ChainConfig, feeConfig commontype.FeeConfig, parent *types.Header, timestamp uint64) ([]byte, *big.Int, error) { +func EstimateNextBaseFee(config *params.ChainConfig, parent *types.Header, timestamp uint64) ([]byte, *big.Int, error) { if timestamp < parent.Time { timestamp = parent.Time } - return CalcBaseFee(config, feeConfig, parent, timestamp) + return CalcBaseFee(config, parent, timestamp) } // selectBigWithinBounds returns [value] if it is within the bounds: @@ -213,7 +298,7 @@ func calcBlockGasCost( parentBlockGasCost *big.Int, parentTime, currentTime uint64, ) *big.Int { - // Handle Subnet EVM boundary by returning the minimum value as the boundary. + // Handle AP3/AP4 boundary by returning the minimum value as the boundary. if parentBlockGasCost == nil { return new(big.Int).Set(minBlockGasCost) } @@ -248,9 +333,9 @@ func calcBlockGasCost( // correctness check performed is that the sum of all tips is >= the // required block fee. // -// This function will return nil for all return values prior to Subnet EVM. +// This function will return nil for all return values prior to Apricot Phase 4. func MinRequiredTip(config *params.ChainConfig, header *types.Header) (*big.Int, error) { - if !config.IsSubnetEVM(header.Time) { + if !config.IsApricotPhase4(header.Time) { return nil, nil } if header.BaseFee == nil { @@ -259,11 +344,18 @@ func MinRequiredTip(config *params.ChainConfig, header *types.Header) (*big.Int, if header.BlockGasCost == nil { return nil, errBlockGasCostNil } + if header.ExtDataGasUsed == nil { + return nil, errExtDataGasUsedNil + } // minTip = requiredBlockFee/blockGasUsage requiredBlockFee := new(big.Int).Mul( header.BlockGasCost, header.BaseFee, ) - return new(big.Int).Div(requiredBlockFee, new(big.Int).SetUint64(header.GasUsed)), nil + blockGasUsage := new(big.Int).Add( + new(big.Int).SetUint64(header.GasUsed), + header.ExtDataGasUsed, + ) + return new(big.Int).Div(requiredBlockFee, blockGasUsage), nil } diff --git a/consensus/dummy/dynamic_fees_test.go b/consensus/dummy/dynamic_fees_test.go index fbccceb91d..2e65ee683f 100644 --- a/consensus/dummy/dynamic_fees_test.go +++ b/consensus/dummy/dynamic_fees_test.go @@ -8,16 +8,14 @@ import ( "math/big" "testing" - "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/log" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -var testMinBaseFee = big.NewInt(75_000_000_000) - func testRollup(t *testing.T, longs []uint64, roll int) { slice := make([]byte, len(longs)*8) numLongs := len(longs) @@ -96,14 +94,16 @@ func TestRollupWindow(t *testing.T) { } type blockDefinition struct { - timestamp uint64 - gasUsed uint64 + timestamp uint64 + gasUsed uint64 + extDataGasUsed *big.Int } type test struct { - baseFee *big.Int - genBlocks func() []blockDefinition - minFee *big.Int + extraData []byte + baseFee *big.Int + genBlocks func() []blockDefinition + minFee, maxFee *big.Int } func TestDynamicFees(t *testing.T) { @@ -112,8 +112,10 @@ func TestDynamicFees(t *testing.T) { var tests []test = []test{ // Test minimal gas usage { - baseFee: nil, - minFee: testMinBaseFee, + extraData: nil, + baseFee: nil, + minFee: big.NewInt(params.ApricotPhase3MinBaseFee), + maxFee: big.NewInt(params.ApricotPhase3MaxBaseFee), genBlocks: func() []blockDefinition { blocks := make([]blockDefinition, 0, len(spacedTimestamps)) for _, timestamp := range spacedTimestamps { @@ -127,23 +129,10 @@ func TestDynamicFees(t *testing.T) { }, // Test overflow handling { - baseFee: nil, - minFee: testMinBaseFee, - genBlocks: func() []blockDefinition { - blocks := make([]blockDefinition, 0, len(spacedTimestamps)) - for _, timestamp := range spacedTimestamps { - blocks = append(blocks, blockDefinition{ - timestamp: timestamp, - gasUsed: math.MaxUint64, - }) - } - return blocks - }, - }, - // Test update increase handling - { - baseFee: big.NewInt(50_000_000_000), - minFee: testMinBaseFee, + extraData: nil, + baseFee: nil, + minFee: big.NewInt(params.ApricotPhase3MinBaseFee), + maxFee: big.NewInt(params.ApricotPhase3MaxBaseFee), genBlocks: func() []blockDefinition { blocks := make([]blockDefinition, 0, len(spacedTimestamps)) for _, timestamp := range spacedTimestamps { @@ -156,8 +145,10 @@ func TestDynamicFees(t *testing.T) { }, }, { - baseFee: nil, - minFee: testMinBaseFee, + extraData: nil, + baseFee: nil, + minFee: big.NewInt(params.ApricotPhase3MinBaseFee), + maxFee: big.NewInt(params.ApricotPhase3MaxBaseFee), genBlocks: func() []blockDefinition { return []blockDefinition{ { @@ -210,26 +201,17 @@ func testDynamicFeesStaysWithinRange(t *testing.T, test test) { GasUsed: initialBlock.gasUsed, Number: big.NewInt(0), BaseFee: test.baseFee, + Extra: test.extraData, } for index, block := range blocks[1:] { - testFeeConfig := commontype.FeeConfig{ - GasLimit: big.NewInt(8_000_000), - TargetBlockRate: 2, // in seconds - - MinBaseFee: test.minFee, - TargetGas: big.NewInt(15_000_000), - BaseFeeChangeDenominator: big.NewInt(36), - - MinBlockGasCost: big.NewInt(0), - MaxBlockGasCost: big.NewInt(1_000_000), - BlockGasCostStep: big.NewInt(200_000), - } - - nextExtraData, nextBaseFee, err := CalcBaseFee(params.TestChainConfig, testFeeConfig, header, block.timestamp) + nextExtraData, nextBaseFee, err := CalcBaseFee(params.TestApricotPhase3Config, header, block.timestamp) if err != nil { t.Fatalf("Failed to calculate base fee at index %d: %s", index, err) } + if nextBaseFee.Cmp(test.maxFee) > 0 { + t.Fatalf("Expected fee to stay less than %d, but found %d", test.maxFee, nextBaseFee) + } if nextBaseFee.Cmp(test.minFee) < 0 { t.Fatalf("Expected fee to stay greater than %d, but found %d", test.minFee, nextBaseFee) } @@ -339,6 +321,122 @@ func TestSelectBigWithinBounds(t *testing.T) { } } +// TestCalcBaseFeeAP4 confirms that the inclusion of ExtDataGasUsage increases +// the base fee. +func TestCalcBaseFeeAP4(t *testing.T) { + events := []struct { + block blockDefinition + extDataFeeGreater bool + }{ + { + block: blockDefinition{ + timestamp: 1, + gasUsed: 1_000_000, + extDataGasUsed: big.NewInt(100_000), + }, + }, + { + block: blockDefinition{ + timestamp: 3, + gasUsed: 1_000_000, + extDataGasUsed: big.NewInt(10_000), + }, + extDataFeeGreater: true, + }, + { + block: blockDefinition{ + timestamp: 5, + gasUsed: 2_000_000, + extDataGasUsed: big.NewInt(50_000), + }, + extDataFeeGreater: true, + }, + { + block: blockDefinition{ + timestamp: 5, + gasUsed: 6_000_000, + extDataGasUsed: big.NewInt(50_000), + }, + extDataFeeGreater: true, + }, + { + block: blockDefinition{ + timestamp: 7, + gasUsed: 6_000_000, + extDataGasUsed: big.NewInt(0), + }, + extDataFeeGreater: true, + }, + { + block: blockDefinition{ + timestamp: 1000, + gasUsed: 6_000_000, + extDataGasUsed: big.NewInt(0), + }, + }, + { + block: blockDefinition{ + timestamp: 1001, + gasUsed: 6_000_000, + extDataGasUsed: big.NewInt(10_000), + }, + }, + { + block: blockDefinition{ + timestamp: 1002, + gasUsed: 6_000_000, + extDataGasUsed: big.NewInt(0), + }, + extDataFeeGreater: true, + }, + } + + header := &types.Header{ + Time: 0, + GasUsed: 1_000_000, + Number: big.NewInt(0), + BaseFee: big.NewInt(225 * params.GWei), + Extra: nil, + } + extDataHeader := &types.Header{ + Time: 0, + GasUsed: 1_000_000, + Number: big.NewInt(0), + BaseFee: big.NewInt(225 * params.GWei), + Extra: nil, + // ExtDataGasUsage is set to be nil to ensure CalcBaseFee can handle the + // AP3/AP4 boundary. + } + + for index, event := range events { + block := event.block + nextExtraData, nextBaseFee, err := CalcBaseFee(params.TestApricotPhase4Config, header, block.timestamp) + assert.NoError(t, err) + log.Info("Update", "baseFee", nextBaseFee) + header = &types.Header{ + Time: block.timestamp, + GasUsed: block.gasUsed, + Number: big.NewInt(int64(index) + 1), + BaseFee: nextBaseFee, + Extra: nextExtraData, + } + + nextExtraData, nextBaseFee, err = CalcBaseFee(params.TestApricotPhase4Config, extDataHeader, block.timestamp) + assert.NoError(t, err) + log.Info("Update", "baseFee (w/extData)", nextBaseFee) + extDataHeader = &types.Header{ + Time: block.timestamp, + GasUsed: block.gasUsed, + Number: big.NewInt(int64(index) + 1), + BaseFee: nextBaseFee, + Extra: nextExtraData, + ExtDataGasUsed: block.extDataGasUsed, + } + + assert.Equal(t, event.extDataFeeGreater, extDataHeader.BaseFee.Cmp(header.BaseFee) == 1, "unexpected cmp for index %d", index) + } +} + func TestCalcBlockGasCost(t *testing.T) { tests := map[string]struct { parentBlockGasCost *big.Int @@ -350,7 +448,7 @@ func TestCalcBlockGasCost(t *testing.T) { parentBlockGasCost: nil, parentTime: 1, currentTime: 1, - expected: params.DefaultFeeConfig.MinBlockGasCost, + expected: ApricotPhase4MinBlockGasCost, }, "Same timestamp from 0": { parentBlockGasCost: big.NewInt(0), @@ -429,10 +527,10 @@ func TestCalcBlockGasCost(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { assert.Zero(t, test.expected.Cmp(calcBlockGasCost( - params.DefaultFeeConfig.TargetBlockRate, - params.DefaultFeeConfig.MinBlockGasCost, - params.DefaultFeeConfig.MaxBlockGasCost, - testBlockGasCostStep, + ApricotPhase4TargetBlockRate, + ApricotPhase4MinBlockGasCost, + ApricotPhase4MaxBlockGasCost, + ApricotPhase4BlockGasCostStep, test.parentBlockGasCost, test.parentTime, test.currentTime, @@ -440,3 +538,29 @@ func TestCalcBlockGasCost(t *testing.T) { }) } } + +func TestDynamicFeesEtna(t *testing.T) { + require := require.New(t) + header := &types.Header{ + Number: big.NewInt(0), + } + + timestamp := uint64(1) + extra, nextBaseFee, err := CalcBaseFee(params.TestEtnaChainConfig, header, timestamp) + require.NoError(err) + // Genesis matches the initial base fee + require.Equal(params.ApricotPhase3InitialBaseFee, nextBaseFee.Int64()) + + timestamp = uint64(10_000) + header = &types.Header{ + Number: big.NewInt(1), + Time: header.Time, + BaseFee: nextBaseFee, + Extra: extra, + } + _, nextBaseFee, err = CalcBaseFee(params.TestEtnaChainConfig, header, timestamp) + require.NoError(err) + // After some time has passed in the Etna phase, the base fee should drop + // lower than the prior base fee minimum. + require.Less(nextBaseFee.Int64(), params.ApricotPhase4MinBaseFee) +} diff --git a/contracts/.gitignore b/contracts/.gitignore deleted file mode 100644 index 46f9335b51..0000000000 --- a/contracts/.gitignore +++ /dev/null @@ -1,150 +0,0 @@ -/dist - -/.idea -*.tsbuildinfo - -.DS_Store - -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -node_modules/ -.env* -!.env*.default -.vscode/* -!.vscode/settings.json.default - -cache/ -artifacts/ - -.yalc -yalc.lock - -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* -.pnpm-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# Snowpack dependency directory (https://snowpack.dev/) -web_modules/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env -.env.test -.env.production - -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - -# Next.js build output -.next -out - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and not Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port - -# Stores VSCode versions used for testing VSCode extensions -.vscode-test - -# yarn v2 -.yarn/cache -.yarn/unplugged -.yarn/build-state.yml -.yarn/install-state.gz -.pnp.* - -local_rpc.json - -# TypeChain files -/typechain -/typechain-types diff --git a/contracts/.npmrc b/contracts/.npmrc deleted file mode 100644 index b6f27f1359..0000000000 --- a/contracts/.npmrc +++ /dev/null @@ -1 +0,0 @@ -engine-strict=true diff --git a/contracts/.prettierrc b/contracts/.prettierrc deleted file mode 100644 index d635b97bd2..0000000000 --- a/contracts/.prettierrc +++ /dev/null @@ -1,15 +0,0 @@ -{ - "overrides": [ - { - "files": "*.sol", - "options": { - "printWidth": 120, - "tabWidth": 2, - "useTabs": false, - "singleQuote": false, - "bracketSpacing": false, - "explicitTypes": "always" - } - } - ] -} diff --git a/contracts/README.md b/contracts/README.md deleted file mode 100644 index 72001db4ea..0000000000 --- a/contracts/README.md +++ /dev/null @@ -1,108 +0,0 @@ -# Subnet EVM Contracts - -CONTRACTS HERE ARE [ALPHA SOFTWARE](https://en.wikipedia.org/wiki/Software_release_life_cycle#Alpha) AND ARE NOT AUDITED. USE AT YOUR OWN RISK! - -## Introduction - -Avalanche is an open-source platform for launching decentralized applications and enterprise blockchain deployments in one interoperable, highly scalable ecosystem. Avalanche gives you complete control on both the network and application layers—helping you build anything you can imagine. - -The Avalanche Network is composed of many subnets and chains. Chains in subnets run with customizable virtual machines. One of these virtual machines is Subnet EVM. The Subnet EVM's API is almost identical to an Ethereum node's API. Subnet EVM brings its own features like minting native tokens via contracts, restrincting contract deployer etc. These features are presented with `Stateful Precompile Contracts`. These contracts are precompiled and deployed when they're activated. - -The goal of this guide is to lay out best practices regarding writing, testing and deployment of smart contracts to Avalanche's Subnet EVM. We'll be building smart contracts with development environment [Hardhat](https://hardhat.org). - -## Prerequisites - -### NodeJS and NPM - -First, install the LTS (long-term support) version of [nodejs](https://nodejs.org/en). This is `18.16.0` at the time of writing. NodeJS bundles `npm`. - -### Solidity and Avalanche - -It is also helpful to have a basic understanding of [Solidity](https://docs.soliditylang.org) and [Avalanche](https://docs.avax.network). - -## Dependencies - -Clone the repo and install the necessary packages via `yarn`. - -```bash -git clone https://github.com/ava-labs/subnet-evm.git -cd contracts -npm install -``` - -## Write Contracts - -`AllowList.sol` is the base contract which provided AllowList precompile capabilities to inheriting contracts. - -`ERC20NativeMinter.sol` is based on [Open Zeppelin](https://openzeppelin.com) [ERC20](https://eips.ethereum.org/EIPS/eip-20) contract powered by native minting capabilities of Subnet EVM. ERC20 is a popular smart contract interface. It uses `INativeMinter` interface to interact with `NativeMinter` precompile. - -`ExampleDeployerList` shows how `ContractDeployerAllowList` precompile can be used in a smart contract. It uses `IAllowList` to interact with `ContractDeployerAllowList` precompile. When the precompile is activated only those allowed can deploy contracts. - -`ExampleFeeManager` shows how a contract can change fee configuration with the `FeeManager` precompile. - -All of these `NativeMinter`, `FeeManager` and `AllowList` contracts should be enabled by a chain config in genesis or as an upgrade. See the example genesis under [Tests](#tests) section. - -For more information about precompiles see [subnet-evm precompiles](https://github.com/ava-labs/subnet-evm#precompiles). - -## Hardhat Config - -Hardhat uses `hardhat.config.js` as the configuration file. You can define tasks, networks, compilers and more in that file. For more information see [here](https://hardhat.org/config/). - -In Subnet-EVM, we provide a pre-configured file [hardhat.config.ts](https://github.com/ava-labs/subnet-evm/blob/master/contracts/hardhat.config.ts). - -The HardHat config file includes a single network configuration: `local`. `local` defaults to using the following values for the RPC URL and the Chain ID: - -```js -var local_rpc_uri = process.env.RPC_URI || "http://127.0.0.1:9650/ext/bc/C/rpc"; -var local_chain_id = process.env.CHAIN_ID || 99999; -``` - -You can use this network configuration by providing the environment variables and specifying the `--network` flag, as Subnet-EVM does in its testing suite: - -```bash -RPC_URI=http://127.0.0.1:9650/ext/bc/28N1Tv5CZziQ3FKCaXmo8xtxoFtuoVA6NvZykAT5MtGjF4JkGs/rpc CHAIN_ID=77777 npx hardhat test --network local -``` - -Alternatively, you can copy and paste the `local` network configuration to create a new network configuration for your own local testing. For example, you can copy and paste the `local` network configuration to create your own network and fill in the required details: - -```json -{ - "networks": { - "mynetwork": { - "url": "http://127.0.0.1:9650/ext/bc/28N1Tv5CZziQ3FKCaXmo8xtxoFtuoVA6NvZykAT5MtGjF4JkGs/rpc", - "chainId": 33333, - "accounts": [ - "0x56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027", - "0x7b4198529994b0dc604278c99d153cfd069d594753d471171a1d102a10438e07", - "0x15614556be13730e9e8d6eacc1603143e7b96987429df8726384c2ec4502ef6e", - "0x31b571bf6894a248831ff937bb49f7754509fe93bbd2517c9c73c4144c0e97dc", - "0x6934bef917e01692b789da754a0eae31a8536eb465e7bff752ea291dad88c675", - "0xe700bdbdbc279b808b1ec45f8c2370e4616d3a02c336e68d85d4668e08f53cff", - "0xbbc2865b76ba28016bc2255c7504d000e046ae01934b04c694592a6276988630", - "0xcdbfd34f687ced8c6968854f8a99ae47712c4f4183b78dcc4a903d1bfe8cbf60", - "0x86f78c5416151fe3546dece84fda4b4b1e36089f2dbc48496faf3a950f16157c", - "0x750839e9dbbd2a0910efe40f50b2f3b2f2f59f5580bb4b83bd8c1201cf9a010a" - ], - "pollingInterval": "1s" - } - } -} -``` - -By creating your own network configuration in the HardHat config, you can run HardHat commands directly on your subnet: - -```bash -npx hardhat accounts --network mynetwork -``` - -## Hardhat Tasks - -You can define custom hardhat tasks in [tasks.ts](https://github.com/ava-labs/avalanche-smart-contract-quickstart/blob/main/tasks.ts). Tasks contain helpers for precompiles `allowList` and `minter`. Precompiles have their own contract already-deployed when they're activated. So these can be called without deploying any intermediate contract. See `npx hardhat --help` for more information about available tasks. - -## Tests - -Tests are written for a local network which runs a Subnet-EVM Blockchain. - -E.g `RPC_URI=http://127.0.0.1:9650/ext/bc/28N1Tv5CZziQ3FKCaXmo8xtxoFtuoVA6NvZykAT5MtGjF4JkGs/rpc CHAIN_ID=77777 npx hardhat test --network local`. - -Subnet-EVM must activate any precompiles used in the test in the genesis. diff --git a/contracts/contracts/AllowList.sol b/contracts/contracts/AllowList.sol deleted file mode 100644 index 75ff747de2..0000000000 --- a/contracts/contracts/AllowList.sol +++ /dev/null @@ -1,82 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import "./interfaces/IAllowList.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; - -// AllowList is a base contract to use AllowList precompile capabilities. -contract AllowList is Ownable { - // Precompiled Allow List Contract Address - IAllowList private allowList; - - uint256 constant STATUS_NONE = 0; - uint256 constant STATUS_ENABLED = 1; - uint256 constant STATUS_ADMIN = 2; - uint256 constant STATUS_MANAGER = 3; - - enum Role { - None, - Enabled, - Admin, - Manager - } - - constructor(address precompileAddr) Ownable() { - allowList = IAllowList(precompileAddr); - } - - modifier onlyEnabled() { - require(isEnabled(msg.sender), "not enabled"); - _; - } - - function isAdmin(address addr) public view returns (bool) { - uint256 result = allowList.readAllowList(addr); - return result == STATUS_ADMIN; - } - - function isManager(address addr) public view returns (bool) { - uint256 result = allowList.readAllowList(addr); - return result == STATUS_MANAGER; - } - - function isEnabled(address addr) public view returns (bool) { - uint256 result = allowList.readAllowList(addr); - // if address is ENABLED or ADMIN or MANAGER it can deploy - // in other words, if it's not NONE it can deploy. - return result != STATUS_NONE; - } - - function setAdmin(address addr) public virtual onlyOwner { - _setAdmin(addr); - } - - function _setAdmin(address addr) private { - allowList.setAdmin(addr); - } - - function setManager(address addr) public virtual onlyOwner { - _setManager(addr); - } - - function _setManager(address addr) private { - allowList.setManager(addr); - } - - function setEnabled(address addr) public virtual onlyOwner { - _setEnabled(addr); - } - - function _setEnabled(address addr) private { - allowList.setEnabled(addr); - } - - function revoke(address addr) public virtual onlyOwner { - _revoke(addr); - } - - function _revoke(address addr) private { - require(msg.sender != addr, "cannot revoke own role"); - allowList.setNone(addr); - } -} diff --git a/contracts/contracts/ERC20NativeMinter.sol b/contracts/contracts/ERC20NativeMinter.sol deleted file mode 100644 index 34fb231fc9..0000000000 --- a/contracts/contracts/ERC20NativeMinter.sol +++ /dev/null @@ -1,59 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "./AllowList.sol"; -import "./interfaces/INativeMinter.sol"; - -// Precompiled Native Minter Contract Address -address constant MINTER_ADDRESS = 0x0200000000000000000000000000000000000001; -// Designated Blackhole Address -address constant BLACKHOLE_ADDRESS = 0x0100000000000000000000000000000000000000; - -contract ERC20NativeMinter is ERC20, AllowList { - string private constant TOKEN_NAME = "ERC20NativeMinterToken"; - string private constant TOKEN_SYMBOL = "XMPL"; - - INativeMinter nativeMinter = INativeMinter(MINTER_ADDRESS); - - event Deposit(address indexed dst, uint256 wad); - event Mintdrawal(address indexed src, uint256 wad); - - constructor(uint256 initSupply) ERC20(TOKEN_NAME, TOKEN_SYMBOL) AllowList(MINTER_ADDRESS) { - // Mints INIT_SUPPLY to owner - _mint(_msgSender(), initSupply); - } - - // Mints [amount] number of ERC20 token to [to] address. - function mint(address to, uint256 amount) external onlyOwner { - _mint(to, amount); - } - - // Burns [amount] number of ERC20 token from [from] address. - function burn(address from, uint256 amount) external onlyOwner { - _burn(from, amount); - } - - // Swaps [amount] number of ERC20 token for native coin. - function mintdraw(uint256 wad) external { - // Burn ERC20 token first. - _burn(_msgSender(), wad); - // Mints [amount] number of native coins (gas coin) to [msg.sender] address. - // Calls NativeMinter precompile through INativeMinter interface. - nativeMinter.mintNativeCoin(_msgSender(), wad); - emit Mintdrawal(_msgSender(), wad); - } - - // Swaps [amount] number of native gas coins for ERC20 tokens. - function deposit() external payable { - // Burn native token by sending to BLACKHOLE_ADDRESS - payable(BLACKHOLE_ADDRESS).transfer(msg.value); - // Mint ERC20 token. - _mint(_msgSender(), msg.value); - emit Deposit(_msgSender(), msg.value); - } - - function decimals() public view virtual override returns (uint8) { - return 18; - } -} diff --git a/contracts/contracts/ExampleDeployerList.sol b/contracts/contracts/ExampleDeployerList.sol deleted file mode 100644 index 3dbc3bbf26..0000000000 --- a/contracts/contracts/ExampleDeployerList.sol +++ /dev/null @@ -1,22 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import "@openzeppelin/contracts/access/Ownable.sol"; -import "./interfaces/IAllowList.sol"; -import "./AllowList.sol"; - -address constant DEPLOYER_LIST = 0x0200000000000000000000000000000000000000; - -// ExampleDeployerList shows how ContractDeployerAllowList precompile can be used in a smart contract -// All methods of [allowList] can be directly called. There are example calls as tasks in hardhat.config.ts file. -contract ExampleDeployerList is AllowList { - // Precompiled Allow List Contract Address - constructor() AllowList(DEPLOYER_LIST) {} - - function deployContract() public { - new Example(); - } -} - -// This is an empty contract that can be used to test contract deployment -contract Example {} diff --git a/contracts/contracts/ExampleFeeManager.sol b/contracts/contracts/ExampleFeeManager.sol deleted file mode 100644 index d1f9a90d51..0000000000 --- a/contracts/contracts/ExampleFeeManager.sol +++ /dev/null @@ -1,104 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; -pragma experimental ABIEncoderV2; - -import "@openzeppelin/contracts/access/Ownable.sol"; -import "./AllowList.sol"; -import "./interfaces/IFeeManager.sol"; - -address constant FEE_MANAGER_ADDRESS = 0x0200000000000000000000000000000000000003; - -uint constant WAGMI_GAS_LIMIT = 20_000_000; -uint constant WAGMI_TARGET_BLOCK_RATE = 2; -uint constant WAGMI_MIN_BASE_FEE = 1_000_000_000; -uint constant WAGMI_TARGET_GAS = 100_000_000; -uint constant WAGMI_BASE_FEE_CHANGE_DENOMINATOR = 48; -uint constant WAGMI_MIN_BLOCK_GAS_COST = 0; -uint constant WAGMI_MAX_BLOCK_GAS_COST = 10_000_000; -uint constant WAGMI_BLOCK_GAS_COST_STEP = 500_000; - -uint constant CCHAIN_GAS_LIMIT = 8_000_000; -uint constant CCHAIN_TARGET_BLOCK_RATE = 2; -uint constant CCHAIN_MIN_BASE_FEE = 25_000_000_000; -uint constant CCHAIN_TARGET_GAS = 15_000_000; -uint constant CCHAIN_BASE_FEE_CHANGE_DENOMINATOR = 36; -uint constant CCHAIN_MIN_BLOCK_GAS_COST = 0; -uint constant CCHAIN_MAX_BLOCK_GAS_COST = 1_000_000; -uint constant CCHAIN_BLOCK_GAS_COST_STEP = 100_000; - -struct FeeConfig { - uint256 gasLimit; - uint256 targetBlockRate; - uint256 minBaseFee; - uint256 targetGas; - uint256 baseFeeChangeDenominator; - uint256 minBlockGasCost; - uint256 maxBlockGasCost; - uint256 blockGasCostStep; -} - -// ExampleFeeManager shows how FeeManager precompile can be used in a smart contract -// All methods of [allowList] can be directly called. There are example calls as tasks in hardhat.config.ts file. -contract ExampleFeeManager is AllowList { - IFeeManager feeManager = IFeeManager(FEE_MANAGER_ADDRESS); - - constructor() AllowList(FEE_MANAGER_ADDRESS) {} - - function enableWAGMIFees() public onlyEnabled { - feeManager.setFeeConfig( - WAGMI_GAS_LIMIT, - WAGMI_TARGET_BLOCK_RATE, - WAGMI_MIN_BASE_FEE, - WAGMI_TARGET_GAS, - WAGMI_BASE_FEE_CHANGE_DENOMINATOR, - WAGMI_MIN_BLOCK_GAS_COST, - WAGMI_MAX_BLOCK_GAS_COST, - WAGMI_BLOCK_GAS_COST_STEP - ); - } - - function enableCChainFees() public onlyEnabled { - feeManager.setFeeConfig( - CCHAIN_GAS_LIMIT, - CCHAIN_TARGET_BLOCK_RATE, - CCHAIN_MIN_BASE_FEE, - CCHAIN_TARGET_GAS, - CCHAIN_BASE_FEE_CHANGE_DENOMINATOR, - CCHAIN_MIN_BLOCK_GAS_COST, - CCHAIN_MAX_BLOCK_GAS_COST, - CCHAIN_BLOCK_GAS_COST_STEP - ); - } - - function enableCustomFees(FeeConfig memory config) public onlyEnabled { - feeManager.setFeeConfig( - config.gasLimit, - config.targetBlockRate, - config.minBaseFee, - config.targetGas, - config.baseFeeChangeDenominator, - config.minBlockGasCost, - config.maxBlockGasCost, - config.blockGasCostStep - ); - } - - function getCurrentFeeConfig() public view returns (FeeConfig memory) { - FeeConfig memory config; - ( - config.gasLimit, - config.targetBlockRate, - config.minBaseFee, - config.targetGas, - config.baseFeeChangeDenominator, - config.minBlockGasCost, - config.maxBlockGasCost, - config.blockGasCostStep - ) = feeManager.getFeeConfig(); - return config; - } - - function getFeeConfigLastChangedAt() public view returns (uint256) { - return feeManager.getFeeConfigLastChangedAt(); - } -} diff --git a/contracts/contracts/ExampleRewardManager.sol b/contracts/contracts/ExampleRewardManager.sol deleted file mode 100644 index 7896c83e37..0000000000 --- a/contracts/contracts/ExampleRewardManager.sol +++ /dev/null @@ -1,34 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import "./interfaces/IRewardManager.sol"; -import "@openzeppelin/contracts/access/Ownable.sol"; - -address constant REWARD_MANAGER_ADDRESS = 0x0200000000000000000000000000000000000004; - -// ExampleRewardManager is a sample wrapper contract for RewardManager precompile. -contract ExampleRewardManager is Ownable { - IRewardManager rewardManager = IRewardManager(REWARD_MANAGER_ADDRESS); - - constructor() Ownable() {} - - function currentRewardAddress() public view returns (address) { - return rewardManager.currentRewardAddress(); - } - - function setRewardAddress(address addr) public onlyOwner { - rewardManager.setRewardAddress(addr); - } - - function allowFeeRecipients() public onlyOwner { - rewardManager.allowFeeRecipients(); - } - - function disableRewards() public onlyOwner { - rewardManager.disableRewards(); - } - - function areFeeRecipientsAllowed() public view returns (bool) { - return rewardManager.areFeeRecipientsAllowed(); - } -} diff --git a/contracts/contracts/ExampleTxAllowList.sol b/contracts/contracts/ExampleTxAllowList.sol deleted file mode 100644 index 0bc9e23da6..0000000000 --- a/contracts/contracts/ExampleTxAllowList.sol +++ /dev/null @@ -1,20 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import "./AllowList.sol"; -import "./interfaces/IAllowList.sol"; - -// Precompiled Allow List Contract Address -address constant TX_ALLOW_LIST = 0x0200000000000000000000000000000000000002; - -// ExampleTxAllowList shows how TxAllowList precompile can be used in a smart contract -// All methods of [allowList] can be directly called. There are example calls as tasks in hardhat.config.ts file. -contract ExampleTxAllowList is AllowList { - constructor() AllowList(TX_ALLOW_LIST) {} - - function deployContract() public { - new Example(); - } -} - -contract Example {} diff --git a/contracts/contracts/ExampleWarp.sol b/contracts/contracts/ExampleWarp.sol index 9c2d8f560a..b6247058ef 100644 --- a/contracts/contracts/ExampleWarp.sol +++ b/contracts/contracts/ExampleWarp.sol @@ -1,5 +1,5 @@ //SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; +pragma solidity ^0.8.0; pragma experimental ABIEncoderV2; import "./interfaces/IWarpMessenger.sol"; diff --git a/contracts/contracts/interfaces/IAllowList.sol b/contracts/contracts/interfaces/IAllowList.sol deleted file mode 100644 index 8b525b12e1..0000000000 --- a/contracts/contracts/interfaces/IAllowList.sol +++ /dev/null @@ -1,21 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -interface IAllowList { - event RoleSet(uint256 indexed role, address indexed account, address indexed sender, uint256 oldRole); - - // Set [addr] to have the admin role over the precompile contract. - function setAdmin(address addr) external; - - // Set [addr] to be enabled on the precompile contract. - function setEnabled(address addr) external; - - // Set [addr] to have the manager role over the precompile contract. - function setManager(address addr) external; - - // Set [addr] to have no role for the precompile contract. - function setNone(address addr) external; - - // Read the status of [addr]. - function readAllowList(address addr) external view returns (uint256 role); -} diff --git a/contracts/contracts/interfaces/IFeeManager.sol b/contracts/contracts/interfaces/IFeeManager.sol deleted file mode 100644 index af35d3b32b..0000000000 --- a/contracts/contracts/interfaces/IFeeManager.sol +++ /dev/null @@ -1,47 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; -import "./IAllowList.sol"; - -interface IFeeManager is IAllowList { - struct FeeConfig { - uint256 gasLimit; - uint256 targetBlockRate; - uint256 minBaseFee; - uint256 targetGas; - uint256 baseFeeChangeDenominator; - uint256 minBlockGasCost; - uint256 maxBlockGasCost; - uint256 blockGasCostStep; - } - event FeeConfigChanged(address indexed sender, FeeConfig oldFeeConfig, FeeConfig newFeeConfig); - - // Set fee config fields to contract storage - function setFeeConfig( - uint256 gasLimit, - uint256 targetBlockRate, - uint256 minBaseFee, - uint256 targetGas, - uint256 baseFeeChangeDenominator, - uint256 minBlockGasCost, - uint256 maxBlockGasCost, - uint256 blockGasCostStep - ) external; - - // Get fee config from the contract storage - function getFeeConfig() - external - view - returns ( - uint256 gasLimit, - uint256 targetBlockRate, - uint256 minBaseFee, - uint256 targetGas, - uint256 baseFeeChangeDenominator, - uint256 minBlockGasCost, - uint256 maxBlockGasCost, - uint256 blockGasCostStep - ); - - // Get the last block number changed the fee config from the contract storage - function getFeeConfigLastChangedAt() external view returns (uint256 blockNumber); -} diff --git a/contracts/contracts/interfaces/INativeMinter.sol b/contracts/contracts/interfaces/INativeMinter.sol deleted file mode 100644 index 822f8af86f..0000000000 --- a/contracts/contracts/interfaces/INativeMinter.sol +++ /dev/null @@ -1,9 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; -import "./IAllowList.sol"; - -interface INativeMinter is IAllowList { - event NativeCoinMinted(address indexed sender, address indexed recipient, uint256 amount); - // Mint [amount] number of native coins and send to [addr] - function mintNativeCoin(address addr, uint256 amount) external; -} diff --git a/contracts/contracts/interfaces/IRewardManager.sol b/contracts/contracts/interfaces/IRewardManager.sol deleted file mode 100644 index f6876e532b..0000000000 --- a/contracts/contracts/interfaces/IRewardManager.sol +++ /dev/null @@ -1,33 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; -import "./IAllowList.sol"; - -interface IRewardManager is IAllowList { - // RewardAddressChanged is the event logged whenever reward address is modified - event RewardAddressChanged( - address indexed sender, - address indexed oldRewardAddress, - address indexed newRewardAddress - ); - - // FeeRecipientsAllowed is the event logged whenever fee recipient is modified - event FeeRecipientsAllowed(address indexed sender); - - // RewardsDisabled is the event logged whenever rewards are disabled - event RewardsDisabled(address indexed sender); - - // setRewardAddress sets the reward address to the given address - function setRewardAddress(address addr) external; - - // allowFeeRecipients allows block builders to claim fees - function allowFeeRecipients() external; - - // disableRewards disables block rewards and starts burning fees - function disableRewards() external; - - // currentRewardAddress returns the current reward address - function currentRewardAddress() external view returns (address rewardAddress); - - // areFeeRecipientsAllowed returns true if fee recipients are allowed - function areFeeRecipientsAllowed() external view returns (bool isAllowed); -} diff --git a/contracts/contracts/interfaces/IWarpMessenger.sol b/contracts/contracts/interfaces/IWarpMessenger.sol index 1f2663ae35..0a77d36640 100644 --- a/contracts/contracts/interfaces/IWarpMessenger.sol +++ b/contracts/contracts/interfaces/IWarpMessenger.sol @@ -3,7 +3,7 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; +pragma solidity ^0.8.0; struct WarpMessage { bytes32 sourceChainID; diff --git a/contracts/contracts/test/AllowListTest.sol b/contracts/contracts/test/AllowListTest.sol deleted file mode 100644 index 73a752bba1..0000000000 --- a/contracts/contracts/test/AllowListTest.sol +++ /dev/null @@ -1,11 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import "../AllowList.sol"; -import "ds-test/src/test.sol"; - -contract AllowListTest is DSTest { - function assertRole(uint result, AllowList.Role role) internal { - assertEq(result, uint(role)); - } -} diff --git a/contracts/contracts/test/ERC20NativeMinterTest.sol b/contracts/contracts/test/ERC20NativeMinterTest.sol deleted file mode 100644 index d8d41b1af0..0000000000 --- a/contracts/contracts/test/ERC20NativeMinterTest.sol +++ /dev/null @@ -1,144 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import "../ERC20NativeMinter.sol"; -import "../interfaces/INativeMinter.sol"; -import "./AllowListTest.sol"; - -// TODO: -// this contract adds another (unwanted) layer of indirection -// but it's the easiest way to match the previous HardHat testing functionality. -// Once we completely migrate to DS-test, we can simplify this set of tests. -contract Minter { - ERC20NativeMinter token; - - constructor(address tokenAddress) { - token = ERC20NativeMinter(tokenAddress); - } - - function mintdraw(uint amount) external { - token.mintdraw(amount); - } - - function deposit(uint value) external { - token.deposit{value: value}(); - } -} - -contract ERC20NativeMinterTest is AllowListTest { - INativeMinter nativeMinter = INativeMinter(MINTER_ADDRESS); - - function setUp() public { - // noop - } - - function step_mintdrawFailure() public { - ERC20NativeMinter token = new ERC20NativeMinter(1000); - address tokenAddress = address(token); - - assertRole(nativeMinter.readAllowList(tokenAddress), AllowList.Role.None); - - try token.mintdraw(100) { - assertTrue(false, "mintdraw should fail"); - } catch {} // TODO should match on an error to make sure that this is failing in the way that's expected - } - - function step_addMinter() public { - ERC20NativeMinter token = new ERC20NativeMinter(1000); - address tokenAddress = address(token); - - assertRole(nativeMinter.readAllowList(tokenAddress), AllowList.Role.None); - - nativeMinter.setEnabled(tokenAddress); - - assertRole(nativeMinter.readAllowList(tokenAddress), AllowList.Role.Enabled); - } - - function step_adminMintdraw() public { - ERC20NativeMinter token = new ERC20NativeMinter(1000); - address tokenAddress = address(token); - - address testAddress = address(this); - - nativeMinter.setEnabled(tokenAddress); - - uint initialTokenBalance = token.balanceOf(testAddress); - uint initialNativeBalance = testAddress.balance; - - uint amount = 100; - - token.mintdraw(amount); - - assertEq(token.balanceOf(testAddress), initialTokenBalance - amount); - assertEq(testAddress.balance, initialNativeBalance + amount); - } - - function step_minterMintdrawFailure() public { - ERC20NativeMinter token = new ERC20NativeMinter(1000); - address tokenAddress = address(token); - - Minter minter = new Minter(tokenAddress); - address minterAddress = address(minter); - - nativeMinter.setEnabled(tokenAddress); - - uint initialTokenBalance = token.balanceOf(minterAddress); - uint initialNativeBalance = minterAddress.balance; - - assertEq(initialTokenBalance, 0); - - try minter.mintdraw(100) { - assertTrue(false, "mintdraw should fail"); - } catch {} // TODO should match on an error to make sure that this is failing in the way that's expected - - assertEq(token.balanceOf(minterAddress), initialTokenBalance); - assertEq(minterAddress.balance, initialNativeBalance); - } - - function step_minterDeposit() public { - ERC20NativeMinter token = new ERC20NativeMinter(1000); - address tokenAddress = address(token); - - Minter minter = new Minter(tokenAddress); - address minterAddress = address(minter); - - nativeMinter.setEnabled(tokenAddress); - - uint amount = 100; - - nativeMinter.mintNativeCoin(minterAddress, amount); - - uint initialTokenBalance = token.balanceOf(minterAddress); - uint initialNativeBalance = minterAddress.balance; - - minter.deposit(amount); - - assertEq(token.balanceOf(minterAddress), initialTokenBalance + amount); - assertEq(minterAddress.balance, initialNativeBalance - amount); - } - - function step_mintdraw() public { - ERC20NativeMinter token = new ERC20NativeMinter(1000); - address tokenAddress = address(token); - - Minter minter = new Minter(tokenAddress); - address minterAddress = address(minter); - - nativeMinter.setEnabled(tokenAddress); - - uint amount = 100; - - uint initialNativeBalance = minterAddress.balance; - assertEq(initialNativeBalance, 0); - - token.mint(minterAddress, amount); - - uint initialTokenBalance = token.balanceOf(minterAddress); - assertEq(initialTokenBalance, amount); - - minter.mintdraw(amount); - - assertEq(token.balanceOf(minterAddress), 0); - assertEq(minterAddress.balance, amount); - } -} diff --git a/contracts/contracts/test/ExampleDeployerListTest.sol b/contracts/contracts/test/ExampleDeployerListTest.sol deleted file mode 100644 index 0bcd3ed9e7..0000000000 --- a/contracts/contracts/test/ExampleDeployerListTest.sol +++ /dev/null @@ -1,122 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import "../ExampleDeployerList.sol"; -import "../interfaces/IAllowList.sol"; -import "../AllowList.sol"; -import "./AllowListTest.sol"; - -// ExampleDeployerListTest defines transactions that are used to test -// the DeployerAllowList precompile by instantiating and calling the -// ExampleDeployerList and making assertions. -// The transactions are put together as steps of a complete test in contract_deployer_allow_list.ts. -// TODO: a bunch of these tests have repeated code that should be combined -contract ExampleDeployerListTest is AllowListTest { - address constant OTHER_ADDRESS = 0x0Fa8EA536Be85F32724D57A37758761B86416123; - - IAllowList allowList = IAllowList(DEPLOYER_LIST); - ExampleDeployerList private example; - - function setUp() public { - example = new ExampleDeployerList(); - allowList.setNone(OTHER_ADDRESS); - } - - function step_verifySenderIsAdmin() public { - assertRole(allowList.readAllowList(msg.sender), AllowList.Role.Admin); - } - - function step_newAddressHasNoRole() public { - address exampleAddress = address(example); - - assertRole(allowList.readAllowList(exampleAddress), AllowList.Role.None); - } - - function step_noRoleIsNotAdmin() public { - address exampleAddress = address(example); - - assertRole(allowList.readAllowList(exampleAddress), AllowList.Role.None); - assertTrue(!example.isAdmin(exampleAddress)); - } - - function step_ownerIsAdmin() public { - address exampleAddress = address(example); - - assertRole(allowList.readAllowList(exampleAddress), AllowList.Role.None); - assertTrue(example.isAdmin(address(this))); - } - - function step_noRoleCannotDeploy() public { - assertRole(allowList.readAllowList(tx.origin), AllowList.Role.None); - - try example.deployContract() { - assertTrue(false, "deployContract should fail"); - } catch {} // TODO should match on an error to make sure that this is failing in the way that's expected - } - - function step_adminAddContractAsAdmin() public { - address exampleAddress = address(example); - - assertRole(allowList.readAllowList(exampleAddress), AllowList.Role.None); - - allowList.setAdmin(exampleAddress); - - assertRole(allowList.readAllowList(exampleAddress), AllowList.Role.Admin); - - assertTrue(example.isAdmin(exampleAddress)); - } - - function step_addDeployerThroughContract() public { - ExampleDeployerList other = new ExampleDeployerList(); - address exampleAddress = address(example); - address otherAddress = address(other); - - assertRole(allowList.readAllowList(exampleAddress), AllowList.Role.None); - - allowList.setAdmin(exampleAddress); - - assertRole(allowList.readAllowList(exampleAddress), AllowList.Role.Admin); - - example.setEnabled(otherAddress); - - assertTrue(example.isEnabled(otherAddress)); - } - - function step_deployerCanDeploy() public { - ExampleDeployerList deployer = new ExampleDeployerList(); - address exampleAddress = address(example); - address deployerAddress = address(deployer); - - assertRole(allowList.readAllowList(exampleAddress), AllowList.Role.None); - - allowList.setAdmin(exampleAddress); - - assertRole(allowList.readAllowList(exampleAddress), AllowList.Role.Admin); - - example.setEnabled(deployerAddress); - - assertTrue(example.isEnabled(deployerAddress)); - - deployer.deployContract(); - } - - function step_adminCanRevokeDeployer() public { - ExampleDeployerList deployer = new ExampleDeployerList(); - address exampleAddress = address(example); - address deployerAddress = address(deployer); - - assertRole(allowList.readAllowList(exampleAddress), AllowList.Role.None); - - allowList.setAdmin(exampleAddress); - - assertRole(allowList.readAllowList(exampleAddress), AllowList.Role.Admin); - - example.setEnabled(deployerAddress); - - assertTrue(example.isEnabled(deployerAddress)); - - example.revoke(deployerAddress); - - assertRole(allowList.readAllowList(deployerAddress), AllowList.Role.None); - } -} diff --git a/contracts/contracts/test/ExampleFeeManagerTest.sol b/contracts/contracts/test/ExampleFeeManagerTest.sol deleted file mode 100644 index 8d0e795e97..0000000000 --- a/contracts/contracts/test/ExampleFeeManagerTest.sol +++ /dev/null @@ -1,127 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; -pragma experimental ABIEncoderV2; - -import "../ExampleFeeManager.sol"; -import "./AllowListTest.sol"; -import "../AllowList.sol"; -import "../interfaces/IFeeManager.sol"; - -contract ExampleFeeManagerTest is AllowListTest { - IFeeManager manager = IFeeManager(FEE_MANAGER_ADDRESS); - - uint256 testNumber; - - function setUp() public { - // noop - } - - function step_addContractDeployerAsOwner() public { - ExampleFeeManager example = new ExampleFeeManager(); - assertEq(address(this), example.owner()); - } - - function step_enableWAGMIFeesFailure() public { - ExampleFeeManager example = new ExampleFeeManager(); - - assertRole(manager.readAllowList(address(this)), AllowList.Role.Admin); - assertRole(manager.readAllowList(address(example)), AllowList.Role.None); - - try example.enableWAGMIFees() { - assertTrue(false, "enableWAGMIFees should fail"); - } catch {} // TODO should match on an error to make sure that this is failing in the way that's expected - } - - function step_addContractToManagerList() public { - ExampleFeeManager example = new ExampleFeeManager(); - - address exampleAddress = address(example); - address thisAddress = address(this); - - assertRole(manager.readAllowList(thisAddress), AllowList.Role.Admin); - assertRole(manager.readAllowList(exampleAddress), AllowList.Role.None); - - manager.setEnabled(exampleAddress); - - assertRole(manager.readAllowList(exampleAddress), AllowList.Role.Enabled); - } - - function step_changeFees() public { - ExampleFeeManager example = new ExampleFeeManager(); - address exampleAddress = address(example); - - manager.setEnabled(exampleAddress); - - FeeConfig memory config = example.getCurrentFeeConfig(); - - FeeConfig memory newFeeConfig = FeeConfig({ - gasLimit: CCHAIN_GAS_LIMIT, - targetBlockRate: CCHAIN_TARGET_BLOCK_RATE, - minBaseFee: CCHAIN_MIN_BASE_FEE, - targetGas: CCHAIN_TARGET_GAS, - baseFeeChangeDenominator: CCHAIN_BASE_FEE_CHANGE_DENOMINATOR, - minBlockGasCost: CCHAIN_MIN_BLOCK_GAS_COST, - maxBlockGasCost: CCHAIN_MAX_BLOCK_GAS_COST, - blockGasCostStep: CCHAIN_BLOCK_GAS_COST_STEP - }); - - assertNotEq(config.gasLimit, newFeeConfig.gasLimit); - // target block rate is the same for wagmi and cchain - // assertNotEq(config.targetBlockRate, newFeeConfig.targetBlockRate); - assertNotEq(config.minBaseFee, newFeeConfig.minBaseFee); - assertNotEq(config.targetGas, newFeeConfig.targetGas); - assertNotEq(config.baseFeeChangeDenominator, newFeeConfig.baseFeeChangeDenominator); - // min block gas cost is the same for wagmi and cchain - // assertNotEq(config.minBlockGasCost, newFeeConfig.minBlockGasCost); - assertNotEq(config.maxBlockGasCost, newFeeConfig.maxBlockGasCost); - assertNotEq(config.blockGasCostStep, newFeeConfig.blockGasCostStep); - - example.enableCChainFees(); - - FeeConfig memory changedFeeConfig = example.getCurrentFeeConfig(); - - assertEq(changedFeeConfig.gasLimit, newFeeConfig.gasLimit); - // target block rate is the same for wagmi and cchain - // assertEq(changedFeeConfig.targetBlockRate, newFeeConfig.targetBlockRate); - assertEq(changedFeeConfig.minBaseFee, newFeeConfig.minBaseFee); - assertEq(changedFeeConfig.targetGas, newFeeConfig.targetGas); - assertEq(changedFeeConfig.baseFeeChangeDenominator, newFeeConfig.baseFeeChangeDenominator); - // min block gas cost is the same for wagmi and cchain - // assertEq(changedFeeConfig.minBlockGasCost, newFeeConfig.minBlockGasCost); - assertEq(changedFeeConfig.maxBlockGasCost, newFeeConfig.maxBlockGasCost); - assertEq(changedFeeConfig.blockGasCostStep, newFeeConfig.blockGasCostStep); - - assertEq(example.getFeeConfigLastChangedAt(), block.number); - - // reset fees to what they were before - example.enableCustomFees(config); - } - - function step_minFeeTransaction() public { - // used as a noop for testing min-fees associated with a transaction - } - - function step_raiseMinFeeByOne() public { - ExampleFeeManager example = new ExampleFeeManager(); - address exampleAddress = address(example); - - manager.setEnabled(exampleAddress); - - FeeConfig memory config = example.getCurrentFeeConfig(); - config.minBaseFee = config.minBaseFee + 1; - - example.enableCustomFees(config); - } - - function step_lowerMinFeeByOne() public { - ExampleFeeManager example = new ExampleFeeManager(); - address exampleAddress = address(example); - - manager.setEnabled(exampleAddress); - - FeeConfig memory config = example.getCurrentFeeConfig(); - config.minBaseFee = config.minBaseFee - 1; - - example.enableCustomFees(config); - } -} diff --git a/contracts/contracts/test/ExampleRewardManagerTest.sol b/contracts/contracts/test/ExampleRewardManagerTest.sol deleted file mode 100644 index b476e1a07c..0000000000 --- a/contracts/contracts/test/ExampleRewardManagerTest.sol +++ /dev/null @@ -1,108 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import "../ExampleRewardManager.sol"; -import "../interfaces/IRewardManager.sol"; -import "./AllowListTest.sol"; - -address constant BLACKHOLE_ADDRESS = 0x0100000000000000000000000000000000000000; - -contract ExampleRewardManagerTest is AllowListTest { - IRewardManager rewardManager = IRewardManager(REWARD_MANAGER_ADDRESS); - - ExampleRewardManager exampleReceiveFees; - uint exampleBalance; - - uint blackholeBalance; - - function setUp() public { - blackholeBalance = BLACKHOLE_ADDRESS.balance; - } - - function step_captureBlackholeBalance() public { - blackholeBalance = BLACKHOLE_ADDRESS.balance; - } - - function step_checkSendFeesToBlackhole() public { - assertGt(BLACKHOLE_ADDRESS.balance, blackholeBalance); - } - - function step_doesNotSetRewardAddressBeforeEnabled() public { - ExampleRewardManager example = new ExampleRewardManager(); - address exampleAddress = address(example); - - assertRole(rewardManager.readAllowList(exampleAddress), AllowList.Role.None); - - try example.setRewardAddress(exampleAddress) { - assertTrue(false, "setRewardAddress should fail"); - } catch {} // TODO should match on an error to make sure that this is failing in the way that's expected - } - - function step_setEnabled() public { - ExampleRewardManager example = new ExampleRewardManager(); - address exampleAddress = address(example); - - assertRole(rewardManager.readAllowList(exampleAddress), AllowList.Role.None); - rewardManager.setEnabled(exampleAddress); - assertRole(rewardManager.readAllowList(exampleAddress), AllowList.Role.Enabled); - } - - function step_setRewardAddress() public { - ExampleRewardManager example = new ExampleRewardManager(); - address exampleAddress = address(example); - - rewardManager.setEnabled(exampleAddress); - example.setRewardAddress(exampleAddress); - - assertEq(example.currentRewardAddress(), exampleAddress); - } - - function step_setupReceiveFees() public { - ExampleRewardManager example = new ExampleRewardManager(); - address exampleAddress = address(example); - - rewardManager.setEnabled(exampleAddress); - example.setRewardAddress(exampleAddress); - - exampleReceiveFees = example; - exampleBalance = exampleAddress.balance; - } - - function step_receiveFees() public { - // used as a noop to test if the correct address receives fees - } - - function step_checkReceiveFees() public { - assertGt(address(exampleReceiveFees).balance, exampleBalance); - } - - function step_areFeeRecipientsAllowed() public { - ExampleRewardManager example = new ExampleRewardManager(); - assertTrue(!example.areFeeRecipientsAllowed()); - } - - function step_allowFeeRecipients() public { - ExampleRewardManager example = new ExampleRewardManager(); - address exampleAddress = address(example); - - rewardManager.setEnabled(exampleAddress); - - example.allowFeeRecipients(); - assertTrue(example.areFeeRecipientsAllowed()); - } - - function step_disableRewardAddress() public { - ExampleRewardManager example = new ExampleRewardManager(); - address exampleAddress = address(example); - - rewardManager.setEnabled(exampleAddress); - - example.setRewardAddress(exampleAddress); - - assertEq(example.currentRewardAddress(), exampleAddress); - - example.disableRewards(); - - assertEq(example.currentRewardAddress(), BLACKHOLE_ADDRESS); - } -} diff --git a/contracts/contracts/test/ExampleTxAllowListTest.sol b/contracts/contracts/test/ExampleTxAllowListTest.sol deleted file mode 100644 index debfa9de2a..0000000000 --- a/contracts/contracts/test/ExampleTxAllowListTest.sol +++ /dev/null @@ -1,303 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity ^0.8.24; - -import "../ExampleTxAllowList.sol"; -import "../AllowList.sol"; -import "../interfaces/IAllowList.sol"; -import "./AllowListTest.sol"; - -contract ExampleTxAllowListTest is AllowListTest { - address constant OTHER_ADDRESS = 0x0Fa8EA536Be85F32724D57A37758761B86416123; - - IAllowList allowList = IAllowList(TX_ALLOW_LIST); - - function setUp() public { - allowList.setNone(OTHER_ADDRESS); - } - - function step_contractOwnerIsAdmin() public { - ExampleTxAllowList example = new ExampleTxAllowList(); - assertTrue(example.isAdmin(address(this))); - } - - function step_precompileHasDeployerAsAdmin() public { - assertRole(allowList.readAllowList(msg.sender), AllowList.Role.Admin); - } - - function step_newAddressHasNoRole() public { - ExampleTxAllowList example = new ExampleTxAllowList(); - assertRole(allowList.readAllowList(address(example)), AllowList.Role.None); - } - - function step_noRoleIsNotAdmin() public { - ExampleTxAllowList example = new ExampleTxAllowList(); - ExampleTxAllowList other = new ExampleTxAllowList(); - assertTrue(!example.isAdmin(address(other))); - } - - function step_exampleAllowListReturnsTestIsAdmin() public { - ExampleTxAllowList example = new ExampleTxAllowList(); - assertTrue(example.isAdmin(address(this))); - } - - function step_fromOther() public { - // used as a noop to test transaction-success or failure, depending on wether the signer has been added to the tx-allow-list - } - - function step_enableOther() public { - assertRole(allowList.readAllowList(OTHER_ADDRESS), AllowList.Role.None); - allowList.setEnabled(OTHER_ADDRESS); - } - - function step_noRoleCannotEnableItself() public { - ExampleTxAllowList example = new ExampleTxAllowList(); - - assertRole(allowList.readAllowList(address(example)), AllowList.Role.None); - - try example.setEnabled(address(example)) { - assertTrue(false, "setEnabled should fail"); - } catch {} // TODO should match on an error to make sure that this is failing in the way that's expected - } - - function step_addContractAsAdmin() public { - ExampleTxAllowList example = new ExampleTxAllowList(); - address exampleAddress = address(example); - - assertRole(allowList.readAllowList(exampleAddress), AllowList.Role.None); - - allowList.setAdmin(exampleAddress); - - assertRole(allowList.readAllowList(exampleAddress), AllowList.Role.Admin); - - assertTrue(example.isAdmin(exampleAddress)); - } - - function step_enableThroughContract() public { - ExampleTxAllowList example = new ExampleTxAllowList(); - ExampleTxAllowList other = new ExampleTxAllowList(); - address exampleAddress = address(example); - address otherAddress = address(other); - - assertTrue(!example.isEnabled(exampleAddress)); - assertTrue(!example.isEnabled(otherAddress)); - - allowList.setAdmin(exampleAddress); - - assertTrue(example.isEnabled(exampleAddress)); - assertTrue(!example.isEnabled(otherAddress)); - - example.setEnabled(otherAddress); - - assertTrue(example.isEnabled(exampleAddress)); - assertTrue(example.isEnabled(otherAddress)); - } - - function step_canDeploy() public { - ExampleTxAllowList example = new ExampleTxAllowList(); - address exampleAddress = address(example); - - allowList.setEnabled(exampleAddress); - - example.deployContract(); - } - - function step_onlyAdminCanEnable() public { - ExampleTxAllowList example = new ExampleTxAllowList(); - ExampleTxAllowList other = new ExampleTxAllowList(); - address exampleAddress = address(example); - address otherAddress = address(other); - - assertTrue(!example.isEnabled(exampleAddress)); - assertTrue(!example.isEnabled(otherAddress)); - - allowList.setEnabled(exampleAddress); - - assertTrue(example.isEnabled(exampleAddress)); - assertTrue(!example.isEnabled(otherAddress)); - - try example.setEnabled(otherAddress) { - assertTrue(false, "setEnabled should fail"); - } catch {} // TODO should match on an error to make sure that this is failing in the way that's expected - - // state should not have changed when setEnabled fails - assertTrue(!example.isEnabled(otherAddress)); - } - - function step_onlyAdminCanRevoke() public { - ExampleTxAllowList example = new ExampleTxAllowList(); - ExampleTxAllowList other = new ExampleTxAllowList(); - address exampleAddress = address(example); - address otherAddress = address(other); - - assertTrue(!example.isEnabled(exampleAddress)); - assertTrue(!example.isEnabled(otherAddress)); - - allowList.setEnabled(exampleAddress); - allowList.setAdmin(otherAddress); - - assertTrue(example.isEnabled(exampleAddress) && !example.isAdmin(exampleAddress)); - assertTrue(example.isAdmin(otherAddress)); - - try example.revoke(otherAddress) { - assertTrue(false, "revoke should fail"); - } catch {} // TODO should match on an error to make sure that this is failing in the way that's expected - - // state should not have changed when revoke fails - assertTrue(example.isAdmin(otherAddress)); - } - - function step_adminCanRevoke() public { - ExampleTxAllowList example = new ExampleTxAllowList(); - ExampleTxAllowList other = new ExampleTxAllowList(); - address exampleAddress = address(example); - address otherAddress = address(other); - - assertTrue(!example.isEnabled(exampleAddress)); - assertTrue(!example.isEnabled(otherAddress)); - - allowList.setAdmin(exampleAddress); - allowList.setAdmin(otherAddress); - - assertTrue(example.isAdmin(exampleAddress)); - assertTrue(other.isAdmin(otherAddress)); - - example.revoke(otherAddress); - assertTrue(!other.isEnabled(otherAddress)); - } - - function step_managerCanAllow() public { - ExampleTxAllowList example = new ExampleTxAllowList(); - ExampleTxAllowList manager = new ExampleTxAllowList(); - address exampleAddress = address(example); - address managerAddress = address(manager); - - assertTrue(!example.isEnabled(exampleAddress)); - assertTrue(!example.isManager(managerAddress)); - - allowList.setManager(managerAddress); - - assertTrue(manager.isManager(managerAddress)); - - manager.setEnabled(exampleAddress); - assertTrue(example.isEnabled(exampleAddress)); - } - - function step_managerCanRevoke() public { - ExampleTxAllowList example = new ExampleTxAllowList(); - ExampleTxAllowList manager = new ExampleTxAllowList(); - address exampleAddress = address(example); - address managerAddress = address(manager); - - assertTrue(!example.isAdmin(exampleAddress)); - assertTrue(!example.isManager(managerAddress)); - - allowList.setEnabled(exampleAddress); - allowList.setManager(managerAddress); - - assertTrue(example.isEnabled(exampleAddress)); - assertTrue(manager.isManager(managerAddress)); - - manager.revoke(exampleAddress); - assertTrue(!example.isEnabled(exampleAddress)); - } - - function step_managerCannotRevokeAdmin() public { - ExampleTxAllowList example = new ExampleTxAllowList(); - ExampleTxAllowList manager = new ExampleTxAllowList(); - address exampleAddress = address(example); - address managerAddress = address(manager); - - assertTrue(!example.isAdmin(exampleAddress)); - assertTrue(!example.isManager(managerAddress)); - - allowList.setAdmin(exampleAddress); - allowList.setManager(managerAddress); - - assertTrue(example.isAdmin(exampleAddress)); - assertTrue(manager.isManager(managerAddress)); - - try manager.revoke(managerAddress) { - assertTrue(false, "revoke should fail"); - } catch {} // TODO should match on an error to make sure that this is failing in the way that's expected - - try manager.revoke(exampleAddress) { - assertTrue(false, "revoke should fail"); - } catch {} // TODO should match on an error to make sure that this is failing in the way that's expected - - // state should not have changed when revoke fails - assertTrue(example.isAdmin(exampleAddress)); - assertTrue(manager.isManager(managerAddress)); - } - - function step_managerCannotGrantAdmin() public { - ExampleTxAllowList example = new ExampleTxAllowList(); - ExampleTxAllowList manager = new ExampleTxAllowList(); - address exampleAddress = address(example); - address managerAddress = address(manager); - - assertTrue(!example.isAdmin(exampleAddress)); - assertTrue(!example.isManager(managerAddress)); - - allowList.setManager(managerAddress); - - assertTrue(manager.isManager(managerAddress)); - - try manager.setAdmin(exampleAddress) { - assertTrue(false, "setAdmin should fail"); - } catch {} // TODO should match on an error to make sure that this is failing in the way that's expected - - // state should not have changed when setAdmin fails - assertTrue(!example.isAdmin(exampleAddress)); - assertTrue(manager.isManager(managerAddress)); - } - - function step_managerCannotGrantManager() public { - ExampleTxAllowList example = new ExampleTxAllowList(); - ExampleTxAllowList manager = new ExampleTxAllowList(); - address exampleAddress = address(example); - address managerAddress = address(manager); - - allowList.setManager(managerAddress); - - assertTrue(!example.isManager(exampleAddress)); - assertTrue(manager.isManager(managerAddress)); - - try manager.setManager(exampleAddress) { - assertTrue(false, "setManager should fail"); - } catch {} // TODO should match on an error to make sure that this is failing in the way that's expected - - // state should not have changed when setManager fails - assertTrue(!example.isManager(exampleAddress)); - assertTrue(manager.isManager(managerAddress)); - } - - function step_managerCannotRevokeManager() public { - ExampleTxAllowList example = new ExampleTxAllowList(); - ExampleTxAllowList manager = new ExampleTxAllowList(); - address exampleAddress = address(example); - address managerAddress = address(manager); - - allowList.setManager(exampleAddress); - allowList.setManager(managerAddress); - - assertTrue(example.isManager(exampleAddress)); - assertTrue(manager.isManager(managerAddress)); - - try manager.revoke(exampleAddress) { - assertTrue(false, "revoke should fail"); - } catch {} // TODO should match on an error to make sure that this is failing in the way that's expected - - // state should not have changed when revoke fails - assertTrue(example.isManager(exampleAddress)); - assertTrue(manager.isManager(managerAddress)); - } - - function step_managerCanDeploy() public { - ExampleTxAllowList manager = new ExampleTxAllowList(); - address managerAddress = address(manager); - - allowList.setManager(managerAddress); - - manager.deployContract(); - } -} diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts deleted file mode 100644 index 1a53402062..0000000000 --- a/contracts/hardhat.config.ts +++ /dev/null @@ -1,44 +0,0 @@ -import "@nomicfoundation/hardhat-toolbox"; -import "./tasks" - -// HardHat users must populate these environment variables in order to connect to their subnet-evm instance -// Since the blockchainID is not known in advance, there's no good default to use and we use the C-Chain here. -var local_rpc_uri = process.env.RPC_URI || "http://127.0.0.1:9650/ext/bc/C/rpc" -var local_chain_id = parseInt(process.env.CHAIN_ID, 10) || 99999 - -export default { - solidity: { - compilers: [ - { - version: "0.8.24", - settings: { - evmVersion: "shanghai", - }, - }, - ] - }, - networks: { - local: { - //"http://{ip}:{port}/ext/bc/{chainID}/rpc - // expected to be populated by the environment variables above - url: local_rpc_uri, - chainId: local_chain_id, - accounts: [ - "0x56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027", - "0x7b4198529994b0dc604278c99d153cfd069d594753d471171a1d102a10438e07", - "0x15614556be13730e9e8d6eacc1603143e7b96987429df8726384c2ec4502ef6e", - "0x31b571bf6894a248831ff937bb49f7754509fe93bbd2517c9c73c4144c0e97dc", - "0x6934bef917e01692b789da754a0eae31a8536eb465e7bff752ea291dad88c675", - "0xe700bdbdbc279b808b1ec45f8c2370e4616d3a02c336e68d85d4668e08f53cff", - "0xbbc2865b76ba28016bc2255c7504d000e046ae01934b04c694592a6276988630", - "0xcdbfd34f687ced8c6968854f8a99ae47712c4f4183b78dcc4a903d1bfe8cbf60", - "0x86f78c5416151fe3546dece84fda4b4b1e36089f2dbc48496faf3a950f16157c", - "0x750839e9dbbd2a0910efe40f50b2f3b2f2f59f5580bb4b83bd8c1201cf9a010a" - ], - pollingInterval: "1s" - }, - }, - mocha: { - timeout: 30000 - } -} diff --git a/contracts/index.ts b/contracts/index.ts deleted file mode 100644 index fb69b71c8a..0000000000 --- a/contracts/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { test } from './test/utils'; diff --git a/contracts/package-lock.json b/contracts/package-lock.json deleted file mode 100644 index b8e16b42a0..0000000000 --- a/contracts/package-lock.json +++ /dev/null @@ -1,7419 +0,0 @@ -{ - "name": "@avalabs/subnet-evm-contracts", - "version": "1.2.2", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@avalabs/subnet-evm-contracts", - "version": "1.2.2", - "license": "BSD-3-Clause", - "dependencies": { - "@avalabs/avalanchejs": "^4.0.5", - "@openzeppelin/contracts": "^4.9.6" - }, - "devDependencies": { - "@nomicfoundation/hardhat-chai-matchers": "^2.0.6", - "@nomicfoundation/hardhat-toolbox": "^5.0.0", - "@types/chai": "^4.3.16", - "@types/mocha": "^9.1.1", - "@types/node": "^20.12.12", - "chai": "^4.4.1", - "ds-test": "https://github.com/dapphub/ds-test.git", - "hardhat": "^2.22.4", - "prettier": "^3.2.4", - "prettier-plugin-solidity": "^1.3.1", - "ts-node": "^10.9.2", - "typescript": "^5.4.5" - }, - "engines": { - "node": ">=20.13.0", - "npm": ">7.0.0" - } - }, - "node_modules/@adraffy/ens-normalize": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", - "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", - "dev": true, - "peer": true - }, - "node_modules/@avalabs/avalanchejs": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@avalabs/avalanchejs/-/avalanchejs-4.0.5.tgz", - "integrity": "sha512-WgJYhGXx1kENxRaY5FwSz5Qbw8aTSQXQCgqrhN1mJKMx5tQbHx3ovtion5/dJPGGyE3SaHerj+mBPIXz/HQoEQ==", - "dependencies": { - "@noble/curves": "1.3.0", - "@noble/hashes": "1.3.3", - "@noble/secp256k1": "2.0.0", - "@scure/base": "1.1.5", - "micro-eth-signer": "0.7.2" - }, - "engines": { - "node": "^20" - } - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@ethereumjs/rlp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-5.0.0.tgz", - "integrity": "sha512-WuS1l7GJmB0n0HsXLozCoEFc9IwYgf3l0gCkKVYgR67puVF1O4OpEaN0hWmm1c+iHUHFCKt1hJrvy5toLg+6ag==", - "bin": { - "rlp": "bin/rlp" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@ethereumjs/util": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/util/-/util-8.1.0.tgz", - "integrity": "sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA==", - "dev": true, - "peer": true, - "dependencies": { - "@ethereumjs/rlp": "^4.0.1", - "ethereum-cryptography": "^2.0.0", - "micro-ftch": "^0.3.1" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@ethereumjs/util/node_modules/@ethereumjs/rlp": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-4.0.1.tgz", - "integrity": "sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw==", - "dev": true, - "peer": true, - "bin": { - "rlp": "bin/rlp" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@ethereumjs/util/node_modules/ethereum-cryptography": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz", - "integrity": "sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA==", - "dev": true, - "peer": true, - "dependencies": { - "@noble/curves": "1.3.0", - "@noble/hashes": "1.3.3", - "@scure/bip32": "1.3.3", - "@scure/bip39": "1.2.2" - } - }, - "node_modules/@ethersproject/abi": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", - "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/hash": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "node_modules/@ethersproject/abstract-provider": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", - "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/networks": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "@ethersproject/web": "^5.7.0" - } - }, - "node_modules/@ethersproject/abstract-signer": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz", - "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/abstract-provider": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0" - } - }, - "node_modules/@ethersproject/address": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz", - "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/rlp": "^5.7.0" - } - }, - "node_modules/@ethersproject/base64": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz", - "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0" - } - }, - "node_modules/@ethersproject/basex": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.7.0.tgz", - "integrity": "sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "peer": true, - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/properties": "^5.7.0" - } - }, - "node_modules/@ethersproject/bignumber": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", - "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "bn.js": "^5.2.1" - } - }, - "node_modules/@ethersproject/bytes": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", - "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/logger": "^5.7.0" - } - }, - "node_modules/@ethersproject/constants": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz", - "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bignumber": "^5.7.0" - } - }, - "node_modules/@ethersproject/contracts": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.7.0.tgz", - "integrity": "sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "peer": true, - "dependencies": { - "@ethersproject/abi": "^5.7.0", - "@ethersproject/abstract-provider": "^5.7.0", - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/transactions": "^5.7.0" - } - }, - "node_modules/@ethersproject/hash": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", - "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/base64": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "node_modules/@ethersproject/hdnode": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.7.0.tgz", - "integrity": "sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "peer": true, - "dependencies": { - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/basex": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/pbkdf2": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/sha2": "^5.7.0", - "@ethersproject/signing-key": "^5.7.0", - "@ethersproject/strings": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "@ethersproject/wordlists": "^5.7.0" - } - }, - "node_modules/@ethersproject/json-wallets": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz", - "integrity": "sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "peer": true, - "dependencies": { - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/hdnode": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/pbkdf2": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/random": "^5.7.0", - "@ethersproject/strings": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "aes-js": "3.0.0", - "scrypt-js": "3.0.1" - } - }, - "node_modules/@ethersproject/json-wallets/node_modules/aes-js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", - "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==", - "dev": true, - "peer": true - }, - "node_modules/@ethersproject/keccak256": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", - "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "js-sha3": "0.8.0" - } - }, - "node_modules/@ethersproject/logger": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", - "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ] - }, - "node_modules/@ethersproject/networks": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz", - "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/logger": "^5.7.0" - } - }, - "node_modules/@ethersproject/pbkdf2": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz", - "integrity": "sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "peer": true, - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/sha2": "^5.7.0" - } - }, - "node_modules/@ethersproject/properties": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", - "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/logger": "^5.7.0" - } - }, - "node_modules/@ethersproject/providers": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.7.2.tgz", - "integrity": "sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "peer": true, - "dependencies": { - "@ethersproject/abstract-provider": "^5.7.0", - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/base64": "^5.7.0", - "@ethersproject/basex": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/hash": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/networks": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/random": "^5.7.0", - "@ethersproject/rlp": "^5.7.0", - "@ethersproject/sha2": "^5.7.0", - "@ethersproject/strings": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "@ethersproject/web": "^5.7.0", - "bech32": "1.1.4", - "ws": "7.4.6" - } - }, - "node_modules/@ethersproject/providers/node_modules/ws": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/@ethersproject/random": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.7.0.tgz", - "integrity": "sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "peer": true, - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0" - } - }, - "node_modules/@ethersproject/rlp": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz", - "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0" - } - }, - "node_modules/@ethersproject/sha2": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.7.0.tgz", - "integrity": "sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "peer": true, - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "hash.js": "1.1.7" - } - }, - "node_modules/@ethersproject/signing-key": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", - "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "bn.js": "^5.2.1", - "elliptic": "6.5.4", - "hash.js": "1.1.7" - } - }, - "node_modules/@ethersproject/solidity": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.7.0.tgz", - "integrity": "sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "peer": true, - "dependencies": { - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/sha2": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "node_modules/@ethersproject/strings": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", - "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/logger": "^5.7.0" - } - }, - "node_modules/@ethersproject/transactions": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz", - "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/rlp": "^5.7.0", - "@ethersproject/signing-key": "^5.7.0" - } - }, - "node_modules/@ethersproject/units": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.7.0.tgz", - "integrity": "sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "peer": true, - "dependencies": { - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/logger": "^5.7.0" - } - }, - "node_modules/@ethersproject/wallet": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.7.0.tgz", - "integrity": "sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "peer": true, - "dependencies": { - "@ethersproject/abstract-provider": "^5.7.0", - "@ethersproject/abstract-signer": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/bignumber": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/hash": "^5.7.0", - "@ethersproject/hdnode": "^5.7.0", - "@ethersproject/json-wallets": "^5.7.0", - "@ethersproject/keccak256": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/random": "^5.7.0", - "@ethersproject/signing-key": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "@ethersproject/wordlists": "^5.7.0" - } - }, - "node_modules/@ethersproject/web": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", - "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "dependencies": { - "@ethersproject/base64": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "node_modules/@ethersproject/wordlists": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.7.0.tgz", - "integrity": "sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "peer": true, - "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/hash": "^5.7.0", - "@ethersproject/logger": "^5.7.0", - "@ethersproject/properties": "^5.7.0", - "@ethersproject/strings": "^5.7.0" - } - }, - "node_modules/@fastify/busboy": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", - "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", - "dev": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@metamask/eth-sig-util": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@metamask/eth-sig-util/-/eth-sig-util-4.0.1.tgz", - "integrity": "sha512-tghyZKLHZjcdlDqCA3gNZmLeR0XvOE9U1qoQO9ohyAZT6Pya+H9vkBPcsyXytmYLNgVoin7CKCmweo/R43V+tQ==", - "dev": true, - "dependencies": { - "ethereumjs-abi": "^0.6.8", - "ethereumjs-util": "^6.2.1", - "ethjs-util": "^0.1.6", - "tweetnacl": "^1.0.3", - "tweetnacl-util": "^0.15.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@metamask/eth-sig-util/node_modules/@types/bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@metamask/eth-sig-util/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/@metamask/eth-sig-util/node_modules/ethereumjs-util": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", - "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", - "dev": true, - "dependencies": { - "@types/bn.js": "^4.11.3", - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "0.1.6", - "rlp": "^2.2.3" - } - }, - "node_modules/@noble/curves": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.3.0.tgz", - "integrity": "sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA==", - "dependencies": { - "@noble/hashes": "1.3.3" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/hashes": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", - "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/secp256k1": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-2.0.0.tgz", - "integrity": "sha512-rUGBd95e2a45rlmFTqQJYEFA4/gdIARFfuTuTqLglz0PZ6AKyzyXsEZZq7UZn8hZsvaBgpCzKKBJizT2cJERXw==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ] - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "peer": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "peer": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nomicfoundation/edr": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr/-/edr-0.3.8.tgz", - "integrity": "sha512-u2UJ5QpznSHVkZRh6ePWoeVb6kmPrrqh08gCnZ9FHlJV9CITqlrTQHJkacd+INH31jx88pTAJnxePE4XAiH5qg==", - "dev": true, - "dependencies": { - "@nomicfoundation/edr-darwin-arm64": "0.3.8", - "@nomicfoundation/edr-darwin-x64": "0.3.8", - "@nomicfoundation/edr-linux-arm64-gnu": "0.3.8", - "@nomicfoundation/edr-linux-arm64-musl": "0.3.8", - "@nomicfoundation/edr-linux-x64-gnu": "0.3.8", - "@nomicfoundation/edr-linux-x64-musl": "0.3.8", - "@nomicfoundation/edr-win32-x64-msvc": "0.3.8" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@nomicfoundation/edr-darwin-arm64": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.3.8.tgz", - "integrity": "sha512-eB0leCexS8sQEmfyD72cdvLj9djkBzQGP4wSQw6SNf2I4Sw4Cnzb3d45caG2FqFFjbvfqL0t+badUUIceqQuMw==", - "dev": true, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@nomicfoundation/edr-darwin-x64": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.3.8.tgz", - "integrity": "sha512-JksVCS1N5ClwVF14EvO25HCQ+Laljh/KRfHERMVAC9ZwPbTuAd/9BtKvToCBi29uCHWqsXMI4lxCApYQv2nznw==", - "dev": true, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@nomicfoundation/edr-linux-arm64-gnu": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.3.8.tgz", - "integrity": "sha512-raCE+fOeNXhVBLUo87cgsHSGvYYRB6arih4eG6B9KGACWK5Veebtm9xtKeiD8YCsdUlUfat6F7ibpeNm91fpsA==", - "dev": true, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@nomicfoundation/edr-linux-arm64-musl": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.3.8.tgz", - "integrity": "sha512-PwiDp4wBZWMCIy29eKkv8moTKRrpiSDlrc+GQMSZLhOAm8T33JKKXPwD/2EbplbhCygJDGXZdtEKl9x9PaH66A==", - "dev": true, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@nomicfoundation/edr-linux-x64-gnu": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.3.8.tgz", - "integrity": "sha512-6AcvA/XKoipGap5jJmQ9Y6yT7Uf39D9lu2hBcDCXnXbMcXaDGw4mn1/L4R63D+9VGZyu1PqlcJixCUZlGGIWlg==", - "dev": true, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@nomicfoundation/edr-linux-x64-musl": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.3.8.tgz", - "integrity": "sha512-cxb0sEmZjlwhYWO28sPsV64VDx31ekskhC1IsDXU1p9ntjHSJRmW4KEIqJ2O3QwJap/kLKfMS6TckvY10gjc6w==", - "dev": true, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@nomicfoundation/edr-win32-x64-msvc": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.3.8.tgz", - "integrity": "sha512-yVuVPqRRNLZk7TbBMkKw7lzCvI8XO8fNTPTYxymGadjr9rEGRuNTU1yBXjfJ59I1jJU/X2TSkRk1OFX0P5tpZQ==", - "dev": true, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@nomicfoundation/ethereumjs-common": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-4.0.4.tgz", - "integrity": "sha512-9Rgb658lcWsjiicr5GzNCjI1llow/7r0k50dLL95OJ+6iZJcVbi15r3Y0xh2cIO+zgX0WIHcbzIu6FeQf9KPrg==", - "dev": true, - "dependencies": { - "@nomicfoundation/ethereumjs-util": "9.0.4" - } - }, - "node_modules/@nomicfoundation/ethereumjs-rlp": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-5.0.4.tgz", - "integrity": "sha512-8H1S3s8F6QueOc/X92SdrA4RDenpiAEqMg5vJH99kcQaCy/a3Q6fgseo75mgWlbanGJXSlAPtnCeG9jvfTYXlw==", - "dev": true, - "bin": { - "rlp": "bin/rlp.cjs" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@nomicfoundation/ethereumjs-tx": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-5.0.4.tgz", - "integrity": "sha512-Xjv8wAKJGMrP1f0n2PeyfFCCojHd7iS3s/Ab7qzF1S64kxZ8Z22LCMynArYsVqiFx6rzYy548HNVEyI+AYN/kw==", - "dev": true, - "dependencies": { - "@nomicfoundation/ethereumjs-common": "4.0.4", - "@nomicfoundation/ethereumjs-rlp": "5.0.4", - "@nomicfoundation/ethereumjs-util": "9.0.4", - "ethereum-cryptography": "0.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "c-kzg": "^2.1.2" - }, - "peerDependenciesMeta": { - "c-kzg": { - "optional": true - } - } - }, - "node_modules/@nomicfoundation/ethereumjs-util": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-9.0.4.tgz", - "integrity": "sha512-sLOzjnSrlx9Bb9EFNtHzK/FJFsfg2re6bsGqinFinH1gCqVfz9YYlXiMWwDM4C/L4ywuHFCYwfKTVr/QHQcU0Q==", - "dev": true, - "dependencies": { - "@nomicfoundation/ethereumjs-rlp": "5.0.4", - "ethereum-cryptography": "0.1.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "c-kzg": "^2.1.2" - }, - "peerDependenciesMeta": { - "c-kzg": { - "optional": true - } - } - }, - "node_modules/@nomicfoundation/hardhat-chai-matchers": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-chai-matchers/-/hardhat-chai-matchers-2.0.6.tgz", - "integrity": "sha512-Te1Uyo9oJcTCF0Jy9dztaLpshmlpjLf2yPtWXlXuLjMt3RRSmJLm/+rKVTW6gfadAEs12U/it6D0ZRnnRGiICQ==", - "dev": true, - "dependencies": { - "@types/chai-as-promised": "^7.1.3", - "chai-as-promised": "^7.1.1", - "deep-eql": "^4.0.1", - "ordinal": "^1.0.3" - }, - "peerDependencies": { - "@nomicfoundation/hardhat-ethers": "^3.0.0", - "chai": "^4.2.0", - "ethers": "^6.1.0", - "hardhat": "^2.9.4" - } - }, - "node_modules/@nomicfoundation/hardhat-ethers": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ethers/-/hardhat-ethers-3.0.6.tgz", - "integrity": "sha512-/xzkFQAaHQhmIAYOQmvHBPwL+NkwLzT9gRZBsgWUYeV+E6pzXsBQsHfRYbAZ3XEYare+T7S+5Tg/1KDJgepSkA==", - "dev": true, - "peer": true, - "dependencies": { - "debug": "^4.1.1", - "lodash.isequal": "^4.5.0" - }, - "peerDependencies": { - "ethers": "^6.1.0", - "hardhat": "^2.0.0" - } - }, - "node_modules/@nomicfoundation/hardhat-ignition": { - "version": "0.15.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ignition/-/hardhat-ignition-0.15.4.tgz", - "integrity": "sha512-x1lhLN9ZRSJ9eiNY9AoinMdeQeU4LDQSQOIw90W9DiZIG/g9YUzcTEIY58QTi2TZOF8YFiF6vJqLSePCpi8R1Q==", - "dev": true, - "peer": true, - "dependencies": { - "@nomicfoundation/ignition-core": "^0.15.4", - "@nomicfoundation/ignition-ui": "^0.15.4", - "chalk": "^4.0.0", - "debug": "^4.3.2", - "fs-extra": "^10.0.0", - "prompts": "^2.4.2" - }, - "peerDependencies": { - "@nomicfoundation/hardhat-verify": "^2.0.1", - "hardhat": "^2.18.0" - } - }, - "node_modules/@nomicfoundation/hardhat-ignition-ethers": { - "version": "0.15.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ignition-ethers/-/hardhat-ignition-ethers-0.15.4.tgz", - "integrity": "sha512-vY30V4b788GSziW/nOd0L/4IPw6mwpluahLs4+gPUUKWaHHGMA8OIeHaYpRRljM1i0M/Kg1yIozrDM/aeRebkg==", - "dev": true, - "peer": true, - "peerDependencies": { - "@nomicfoundation/hardhat-ethers": "^3.0.4", - "@nomicfoundation/hardhat-ignition": "^0.15.4", - "@nomicfoundation/ignition-core": "^0.15.4", - "ethers": "^6.7.0", - "hardhat": "^2.18.0" - } - }, - "node_modules/@nomicfoundation/hardhat-network-helpers": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.0.10.tgz", - "integrity": "sha512-R35/BMBlx7tWN5V6d/8/19QCwEmIdbnA4ZrsuXgvs8i2qFx5i7h6mH5pBS4Pwi4WigLH+upl6faYusrNPuzMrQ==", - "dev": true, - "peer": true, - "dependencies": { - "ethereumjs-util": "^7.1.4" - }, - "peerDependencies": { - "hardhat": "^2.9.5" - } - }, - "node_modules/@nomicfoundation/hardhat-toolbox": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-toolbox/-/hardhat-toolbox-5.0.0.tgz", - "integrity": "sha512-FnUtUC5PsakCbwiVNsqlXVIWG5JIb5CEZoSXbJUsEBun22Bivx2jhF1/q9iQbzuaGpJKFQyOhemPB2+XlEE6pQ==", - "dev": true, - "peerDependencies": { - "@nomicfoundation/hardhat-chai-matchers": "^2.0.0", - "@nomicfoundation/hardhat-ethers": "^3.0.0", - "@nomicfoundation/hardhat-ignition-ethers": "^0.15.0", - "@nomicfoundation/hardhat-network-helpers": "^1.0.0", - "@nomicfoundation/hardhat-verify": "^2.0.0", - "@typechain/ethers-v6": "^0.5.0", - "@typechain/hardhat": "^9.0.0", - "@types/chai": "^4.2.0", - "@types/mocha": ">=9.1.0", - "@types/node": ">=18.0.0", - "chai": "^4.2.0", - "ethers": "^6.4.0", - "hardhat": "^2.11.0", - "hardhat-gas-reporter": "^1.0.8", - "solidity-coverage": "^0.8.1", - "ts-node": ">=8.0.0", - "typechain": "^8.3.0", - "typescript": ">=4.5.0" - } - }, - "node_modules/@nomicfoundation/hardhat-verify": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-verify/-/hardhat-verify-2.0.7.tgz", - "integrity": "sha512-jiYHBX+K6bBN0YhwFHQ5SWWc3dQZliM3pdgpH33C7tnsVACsX1ubZn6gZ9hfwlzG0tyjFM72XQhpaXQ56cE6Ew==", - "dev": true, - "peer": true, - "dependencies": { - "@ethersproject/abi": "^5.1.2", - "@ethersproject/address": "^5.0.2", - "cbor": "^8.1.0", - "chalk": "^2.4.2", - "debug": "^4.1.1", - "lodash.clonedeep": "^4.5.0", - "semver": "^6.3.0", - "table": "^6.8.0", - "undici": "^5.14.0" - }, - "peerDependencies": { - "hardhat": "^2.0.4" - } - }, - "node_modules/@nomicfoundation/hardhat-verify/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "peer": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@nomicfoundation/hardhat-verify/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@nomicfoundation/hardhat-verify/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "peer": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@nomicfoundation/hardhat-verify/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "peer": true - }, - "node_modules/@nomicfoundation/hardhat-verify/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@nomicfoundation/hardhat-verify/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@nomicfoundation/hardhat-verify/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "peer": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@nomicfoundation/ignition-core": { - "version": "0.15.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ignition-core/-/ignition-core-0.15.4.tgz", - "integrity": "sha512-i379lH+xOLFdaDv0KiNma550ZXCHc5ZkmKYhM44xyLMKBlvX6skUVFkgUjjN1gvprgOIxc17GVQXlR1R5FhGZA==", - "dev": true, - "peer": true, - "dependencies": { - "@ethersproject/address": "5.6.1", - "@nomicfoundation/solidity-analyzer": "^0.1.1", - "cbor": "^9.0.0", - "debug": "^4.3.2", - "ethers": "^6.7.0", - "fs-extra": "^10.0.0", - "immer": "10.0.2", - "lodash": "4.17.21", - "ndjson": "2.0.0" - } - }, - "node_modules/@nomicfoundation/ignition-core/node_modules/@ethersproject/address": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.6.1.tgz", - "integrity": "sha512-uOgF0kS5MJv9ZvCz7x6T2EXJSzotiybApn4XlOgoTX0xdtyVIJ7pF+6cGPxiEq/dpBiTfMiw7Yc81JcwhSYA0Q==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "peer": true, - "dependencies": { - "@ethersproject/bignumber": "^5.6.2", - "@ethersproject/bytes": "^5.6.1", - "@ethersproject/keccak256": "^5.6.1", - "@ethersproject/logger": "^5.6.0", - "@ethersproject/rlp": "^5.6.1" - } - }, - "node_modules/@nomicfoundation/ignition-core/node_modules/cbor": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/cbor/-/cbor-9.0.2.tgz", - "integrity": "sha512-JPypkxsB10s9QOWwa6zwPzqE1Md3vqpPc+cai4sAecuCsRyAtAl/pMyhPlMbT/xtPnm2dznJZYRLui57qiRhaQ==", - "dev": true, - "peer": true, - "dependencies": { - "nofilter": "^3.1.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@nomicfoundation/ignition-ui": { - "version": "0.15.4", - "resolved": "https://registry.npmjs.org/@nomicfoundation/ignition-ui/-/ignition-ui-0.15.4.tgz", - "integrity": "sha512-cHbmuxmhso5n2zdIaaIW4p8NNzrFj0mrnv8ufhAZfM3s3IFrRoGc1zo8hI/n1CiOTPuqUbdZcB79d+2tCKtCNw==", - "dev": true, - "peer": true - }, - "node_modules/@nomicfoundation/solidity-analyzer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.1.1.tgz", - "integrity": "sha512-1LMtXj1puAxyFusBgUIy5pZk3073cNXYnXUpuNKFghHbIit/xZgbk0AokpUADbNm3gyD6bFWl3LRFh3dhVdREg==", - "dev": true, - "engines": { - "node": ">= 12" - }, - "optionalDependencies": { - "@nomicfoundation/solidity-analyzer-darwin-arm64": "0.1.1", - "@nomicfoundation/solidity-analyzer-darwin-x64": "0.1.1", - "@nomicfoundation/solidity-analyzer-freebsd-x64": "0.1.1", - "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": "0.1.1", - "@nomicfoundation/solidity-analyzer-linux-arm64-musl": "0.1.1", - "@nomicfoundation/solidity-analyzer-linux-x64-gnu": "0.1.1", - "@nomicfoundation/solidity-analyzer-linux-x64-musl": "0.1.1", - "@nomicfoundation/solidity-analyzer-win32-arm64-msvc": "0.1.1", - "@nomicfoundation/solidity-analyzer-win32-ia32-msvc": "0.1.1", - "@nomicfoundation/solidity-analyzer-win32-x64-msvc": "0.1.1" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-darwin-arm64": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.1.1.tgz", - "integrity": "sha512-KcTodaQw8ivDZyF+D76FokN/HdpgGpfjc/gFCImdLUyqB6eSWVaZPazMbeAjmfhx3R0zm/NYVzxwAokFKgrc0w==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-darwin-x64": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.1.1.tgz", - "integrity": "sha512-XhQG4BaJE6cIbjAVtzGOGbK3sn1BO9W29uhk9J8y8fZF1DYz0Doj8QDMfpMu+A6TjPDs61lbsmeYodIDnfveSA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-freebsd-x64": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-freebsd-x64/-/solidity-analyzer-freebsd-x64-0.1.1.tgz", - "integrity": "sha512-GHF1VKRdHW3G8CndkwdaeLkVBi5A9u2jwtlS7SLhBc8b5U/GcoL39Q+1CSO3hYqePNP+eV5YI7Zgm0ea6kMHoA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-gnu": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.1.1.tgz", - "integrity": "sha512-g4Cv2fO37ZsUENQ2vwPnZc2zRenHyAxHcyBjKcjaSmmkKrFr64yvzeNO8S3GBFCo90rfochLs99wFVGT/0owpg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-musl": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.1.1.tgz", - "integrity": "sha512-WJ3CE5Oek25OGE3WwzK7oaopY8xMw9Lhb0mlYuJl/maZVo+WtP36XoQTb7bW/i8aAdHW5Z+BqrHMux23pvxG3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-gnu": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.1.1.tgz", - "integrity": "sha512-5WN7leSr5fkUBBjE4f3wKENUy9HQStu7HmWqbtknfXkkil+eNWiBV275IOlpXku7v3uLsXTOKpnnGHJYI2qsdA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-musl": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.1.1.tgz", - "integrity": "sha512-KdYMkJOq0SYPQMmErv/63CwGwMm5XHenEna9X9aB8mQmhDBrYrlAOSsIPgFCUSL0hjxE3xHP65/EPXR/InD2+w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-win32-arm64-msvc": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-arm64-msvc/-/solidity-analyzer-win32-arm64-msvc-0.1.1.tgz", - "integrity": "sha512-VFZASBfl4qiBYwW5xeY20exWhmv6ww9sWu/krWSesv3q5hA0o1JuzmPHR4LPN6SUZj5vcqci0O6JOL8BPw+APg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-win32-ia32-msvc": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-ia32-msvc/-/solidity-analyzer-win32-ia32-msvc-0.1.1.tgz", - "integrity": "sha512-JnFkYuyCSA70j6Si6cS1A9Gh1aHTEb8kOTBApp/c7NRTFGNMH8eaInKlyuuiIbvYFhlXW4LicqyYuWNNq9hkpQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nomicfoundation/solidity-analyzer-win32-x64-msvc": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.1.1.tgz", - "integrity": "sha512-HrVJr6+WjIXGnw3Q9u6KQcbZCtk0caVWhCdFADySvRyUxJ8PnzlaP+MhwNE8oyT8OZ6ejHBRrrgjSqDCFXGirw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@openzeppelin/contracts": { - "version": "4.9.6", - "resolved": "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-4.9.6.tgz", - "integrity": "sha512-xSmezSupL+y9VkHZJGDoCBpmnB2ogM13ccaYDWqJTfS3dbuHkgjuwDFUmaFauBCboQMGB/S5UqUl2y54X99BmA==" - }, - "node_modules/@scure/base": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.5.tgz", - "integrity": "sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip32": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.3.tgz", - "integrity": "sha512-LJaN3HwRbfQK0X1xFSi0Q9amqOgzQnnDngIt+ZlsBC3Bm7/nE7K0kwshZHyaru79yIVRv/e1mQAjZyuZG6jOFQ==", - "dev": true, - "peer": true, - "dependencies": { - "@noble/curves": "~1.3.0", - "@noble/hashes": "~1.3.2", - "@scure/base": "~1.1.4" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip39": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.2.tgz", - "integrity": "sha512-HYf9TUXG80beW+hGAt3TRM8wU6pQoYur9iNypTROm42dorCGmLnFe3eWjz3gOq6G62H2WRh0FCzAR1PI+29zIA==", - "dev": true, - "peer": true, - "dependencies": { - "@noble/hashes": "~1.3.2", - "@scure/base": "~1.1.4" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@sentry/core": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.30.0.tgz", - "integrity": "sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg==", - "dev": true, - "dependencies": { - "@sentry/hub": "5.30.0", - "@sentry/minimal": "5.30.0", - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sentry/core/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/@sentry/hub": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.30.0.tgz", - "integrity": "sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ==", - "dev": true, - "dependencies": { - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sentry/hub/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/@sentry/minimal": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.30.0.tgz", - "integrity": "sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw==", - "dev": true, - "dependencies": { - "@sentry/hub": "5.30.0", - "@sentry/types": "5.30.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sentry/minimal/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/@sentry/node": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-5.30.0.tgz", - "integrity": "sha512-Br5oyVBF0fZo6ZS9bxbJZG4ApAjRqAnqFFurMVJJdunNb80brh7a5Qva2kjhm+U6r9NJAB5OmDyPkA1Qnt+QVg==", - "dev": true, - "dependencies": { - "@sentry/core": "5.30.0", - "@sentry/hub": "5.30.0", - "@sentry/tracing": "5.30.0", - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "cookie": "^0.4.1", - "https-proxy-agent": "^5.0.0", - "lru_map": "^0.3.3", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sentry/node/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/@sentry/tracing": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-5.30.0.tgz", - "integrity": "sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw==", - "dev": true, - "dependencies": { - "@sentry/hub": "5.30.0", - "@sentry/minimal": "5.30.0", - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sentry/tracing/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/@sentry/types": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.30.0.tgz", - "integrity": "sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sentry/utils": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.30.0.tgz", - "integrity": "sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==", - "dev": true, - "dependencies": { - "@sentry/types": "5.30.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@sentry/utils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/@solidity-parser/parser": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.14.5.tgz", - "integrity": "sha512-6dKnHZn7fg/iQATVEzqyUOyEidbn05q7YA2mQ9hC0MMXhhV3/JrsxmFSYZAcr7j1yUP700LLhTruvJ3MiQmjJg==", - "dev": true, - "peer": true, - "dependencies": { - "antlr4ts": "^0.5.0-alpha.4" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true - }, - "node_modules/@typechain/ethers-v6": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@typechain/ethers-v6/-/ethers-v6-0.5.1.tgz", - "integrity": "sha512-F+GklO8jBWlsaVV+9oHaPh5NJdd6rAKN4tklGfInX1Q7h0xPgVLP39Jl3eCulPB5qexI71ZFHwbljx4ZXNfouA==", - "dev": true, - "peer": true, - "dependencies": { - "lodash": "^4.17.15", - "ts-essentials": "^7.0.1" - }, - "peerDependencies": { - "ethers": "6.x", - "typechain": "^8.3.2", - "typescript": ">=4.7.0" - } - }, - "node_modules/@typechain/hardhat": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@typechain/hardhat/-/hardhat-9.1.0.tgz", - "integrity": "sha512-mtaUlzLlkqTlfPwB3FORdejqBskSnh+Jl8AIJGjXNAQfRQ4ofHADPl1+oU7Z3pAJzmZbUXII8MhOLQltcHgKnA==", - "dev": true, - "peer": true, - "dependencies": { - "fs-extra": "^9.1.0" - }, - "peerDependencies": { - "@typechain/ethers-v6": "^0.5.1", - "ethers": "^6.1.0", - "hardhat": "^2.9.9", - "typechain": "^8.3.2" - } - }, - "node_modules/@typechain/hardhat/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "peer": true, - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@types/bn.js": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.5.tgz", - "integrity": "sha512-V46N0zwKRF5Q00AZ6hWtN0T8gGmDUaUzLWQvHFo5yThtVwK/VCenFY3wXVbOvNfajEpsTfQM4IN9k/d6gUVX3A==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/chai": { - "version": "4.3.16", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.16.tgz", - "integrity": "sha512-PatH4iOdyh3MyWtmHVFXLWCCIhUbopaltqddG9BzB+gMIzee2MJrvd+jouii9Z3wzQJruGWAm7WOMjgfG8hQlQ==", - "dev": true - }, - "node_modules/@types/chai-as-promised": { - "version": "7.1.8", - "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.8.tgz", - "integrity": "sha512-ThlRVIJhr69FLlh6IctTXFkmhtP3NpMZ2QGq69StYLyKZFp/HOp1VdKZj7RvfNWYYcJ1xlbLGLLWj1UvP5u/Gw==", - "dev": true, - "dependencies": { - "@types/chai": "*" - } - }, - "node_modules/@types/concat-stream": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.1.tgz", - "integrity": "sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==", - "dev": true, - "peer": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/form-data": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz", - "integrity": "sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==", - "dev": true, - "peer": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", - "dev": true, - "peer": true, - "dependencies": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "node_modules/@types/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw==", - "dev": true - }, - "node_modules/@types/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", - "dev": true, - "peer": true - }, - "node_modules/@types/mocha": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", - "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", - "dev": true - }, - "node_modules/@types/node": { - "version": "20.12.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", - "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-uRwJqmiXmh9++aSu1VNEn3iIxWOhd8AHXNSdlaLfdAAdSTY9jYVeGWnzejM3dvrkbqE3/hyQkQQ29IFATEGlew==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/prettier": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", - "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", - "dev": true, - "peer": true - }, - "node_modules/@types/qs": { - "version": "6.9.15", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", - "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", - "dev": true, - "peer": true - }, - "node_modules/@types/secp256k1": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.6.tgz", - "integrity": "sha512-hHxJU6PAEUn0TP4S/ZOzuTUvJWuZ6eIKeNKb5RBpODvSl6hp1Wrw4s7ATY50rklRCScUDpHzVA/DQdSjJ3UoYQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/abbrev": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==", - "dev": true, - "peer": true - }, - "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/adm-zip": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", - "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", - "dev": true, - "engines": { - "node": ">=0.3.0" - } - }, - "node_modules/aes-js": { - "version": "4.0.0-beta.5", - "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", - "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", - "dev": true, - "peer": true - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.14.0.tgz", - "integrity": "sha512-oYs1UUtO97ZO2lJ4bwnWeQW8/zvOIQLGKcvPTsWmvc2SYgBb+upuNS5NxoLaMU4h8Ju3Nbj6Cq8mD2LQoqVKFA==", - "dev": true, - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.4.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=0.4.2" - } - }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "dev": true, - "dependencies": { - "string-width": "^4.1.0" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/antlr4ts": { - "version": "0.5.0-alpha.4", - "resolved": "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz", - "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==", - "dev": true, - "peer": true - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-back": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", - "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true, - "peer": true - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", - "dev": true, - "peer": true - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "peer": true - }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", - "dev": true, - "peer": true, - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/base-x": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", - "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/bech32": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", - "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", - "dev": true, - "peer": true - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/blakejs": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", - "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==", - "dev": true - }, - "node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "dev": true - }, - "node_modules/boxen": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", - "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", - "dev": true, - "dependencies": { - "ansi-align": "^3.0.0", - "camelcase": "^6.2.0", - "chalk": "^4.1.0", - "cli-boxes": "^2.2.1", - "string-width": "^4.2.2", - "type-fest": "^0.20.2", - "widest-line": "^3.1.0", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/boxen/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", - "dev": true - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "node_modules/browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "dependencies": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/bs58": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", - "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", - "dev": true, - "dependencies": { - "base-x": "^3.0.2" - } - }, - "node_modules/bs58check": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", - "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", - "dev": true, - "dependencies": { - "bs58": "^4.0.0", - "create-hash": "^1.1.0", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", - "dev": true - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, - "peer": true, - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true, - "peer": true - }, - "node_modules/cbor": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/cbor/-/cbor-8.1.0.tgz", - "integrity": "sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==", - "dev": true, - "peer": true, - "dependencies": { - "nofilter": "^3.1.0" - }, - "engines": { - "node": ">=12.19" - } - }, - "node_modules/chai": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", - "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", - "dev": true, - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.0.8" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chai-as-promised": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.2.tgz", - "integrity": "sha512-aBDHZxRzYnUYuIAIPBH2s511DjlKPzXNlXSGFC8CwmroWQLfrW0LtE1nK3MAwwNhJPa9raEjNCmRoFpG0Hurdw==", - "dev": true, - "dependencies": { - "check-error": "^1.0.2" - }, - "peerDependencies": { - "chai": ">= 2.1.2 < 6" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", - "dev": true, - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "node_modules/cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-boxes": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-table3": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz", - "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", - "dev": true, - "peer": true, - "dependencies": { - "object-assign": "^4.1.0", - "string-width": "^2.1.1" - }, - "engines": { - "node": ">=6" - }, - "optionalDependencies": { - "colors": "^1.1.2" - } - }, - "node_modules/cli-table3/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/cli-table3/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/cli-table3/node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "peer": true, - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cli-table3/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "peer": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/command-exists": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", - "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", - "dev": true - }, - "node_modules/command-line-args": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", - "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", - "dev": true, - "peer": true, - "dependencies": { - "array-back": "^3.1.0", - "find-replace": "^3.0.0", - "lodash.camelcase": "^4.3.0", - "typical": "^4.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/command-line-usage": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.3.tgz", - "integrity": "sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw==", - "dev": true, - "peer": true, - "dependencies": { - "array-back": "^4.0.2", - "chalk": "^2.4.2", - "table-layout": "^1.0.2", - "typical": "^5.2.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/command-line-usage/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "peer": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/command-line-usage/node_modules/array-back": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", - "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/command-line-usage/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/command-line-usage/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "peer": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/command-line-usage/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "peer": true - }, - "node_modules/command-line-usage/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/command-line-usage/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/command-line-usage/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "peer": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/command-line-usage/node_modules/typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/commander": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", - "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "engines": [ - "node >= 0.8" - ], - "peer": true, - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/concat-stream/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "peer": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/concat-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "peer": true - }, - "node_modules/concat-stream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "peer": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, - "peer": true - }, - "node_modules/create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "node_modules/create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "node_modules/crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", - "dev": true, - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/death": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/death/-/death-1.1.0.tgz", - "integrity": "sha512-vsV6S4KVHvTGxbEcij7hkWRv0It+sGGWVOM67dQde/o5Xjnr+KmLjxWJii2uEObIrt1CcM9w0Yaovx+iOlIL+w==", - "dev": true, - "peer": true - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", - "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "peer": true - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "peer": true, - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/difflib": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/difflib/-/difflib-0.2.4.tgz", - "integrity": "sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w==", - "dev": true, - "peer": true, - "dependencies": { - "heap": ">= 0.2.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "peer": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ds-test": { - "version": "1.0.0", - "resolved": "git+ssh://git@github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0", - "dev": true - }, - "node_modules/elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", - "dev": true, - "dependencies": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/elliptic/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/enquirer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", - "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, - "peer": true, - "dependencies": { - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/escodegen": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha512-yhi5S+mNTOuRvyW4gWlg5W1byMaQGWWSYHXsuFZ7GBo7tpyOwi2EdzMP/QWxh9hwkD2m+wDVHJsxhRIj+v/b/A==", - "dev": true, - "peer": true, - "dependencies": { - "esprima": "^2.7.1", - "estraverse": "^1.9.1", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=0.12.0" - }, - "optionalDependencies": { - "source-map": "~0.2.0" - } - }, - "node_modules/esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", - "dev": true, - "peer": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eth-gas-reporter": { - "version": "0.2.27", - "resolved": "https://registry.npmjs.org/eth-gas-reporter/-/eth-gas-reporter-0.2.27.tgz", - "integrity": "sha512-femhvoAM7wL0GcI8ozTdxfuBtBFJ9qsyIAsmKVjlWAHUbdnnXHt+lKzz/kmldM5lA9jLuNHGwuIxorNpLbR1Zw==", - "dev": true, - "peer": true, - "dependencies": { - "@solidity-parser/parser": "^0.14.0", - "axios": "^1.5.1", - "cli-table3": "^0.5.0", - "colors": "1.4.0", - "ethereum-cryptography": "^1.0.3", - "ethers": "^5.7.2", - "fs-readdir-recursive": "^1.1.0", - "lodash": "^4.17.14", - "markdown-table": "^1.1.3", - "mocha": "^10.2.0", - "req-cwd": "^2.0.0", - "sha1": "^1.1.1", - "sync-request": "^6.0.0" - }, - "peerDependencies": { - "@codechecks/client": "^0.1.0" - }, - "peerDependenciesMeta": { - "@codechecks/client": { - "optional": true - } - } - }, - "node_modules/eth-gas-reporter/node_modules/@noble/hashes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz", - "integrity": "sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "peer": true - }, - "node_modules/eth-gas-reporter/node_modules/@noble/secp256k1": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz", - "integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "peer": true - }, - "node_modules/eth-gas-reporter/node_modules/@scure/bip32": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.5.tgz", - "integrity": "sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "peer": true, - "dependencies": { - "@noble/hashes": "~1.2.0", - "@noble/secp256k1": "~1.7.0", - "@scure/base": "~1.1.0" - } - }, - "node_modules/eth-gas-reporter/node_modules/@scure/bip39": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.1.tgz", - "integrity": "sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "peer": true, - "dependencies": { - "@noble/hashes": "~1.2.0", - "@scure/base": "~1.1.0" - } - }, - "node_modules/eth-gas-reporter/node_modules/ethereum-cryptography": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz", - "integrity": "sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==", - "dev": true, - "peer": true, - "dependencies": { - "@noble/hashes": "1.2.0", - "@noble/secp256k1": "1.7.1", - "@scure/bip32": "1.1.5", - "@scure/bip39": "1.1.1" - } - }, - "node_modules/eth-gas-reporter/node_modules/ethers": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", - "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "peer": true, - "dependencies": { - "@ethersproject/abi": "5.7.0", - "@ethersproject/abstract-provider": "5.7.0", - "@ethersproject/abstract-signer": "5.7.0", - "@ethersproject/address": "5.7.0", - "@ethersproject/base64": "5.7.0", - "@ethersproject/basex": "5.7.0", - "@ethersproject/bignumber": "5.7.0", - "@ethersproject/bytes": "5.7.0", - "@ethersproject/constants": "5.7.0", - "@ethersproject/contracts": "5.7.0", - "@ethersproject/hash": "5.7.0", - "@ethersproject/hdnode": "5.7.0", - "@ethersproject/json-wallets": "5.7.0", - "@ethersproject/keccak256": "5.7.0", - "@ethersproject/logger": "5.7.0", - "@ethersproject/networks": "5.7.1", - "@ethersproject/pbkdf2": "5.7.0", - "@ethersproject/properties": "5.7.0", - "@ethersproject/providers": "5.7.2", - "@ethersproject/random": "5.7.0", - "@ethersproject/rlp": "5.7.0", - "@ethersproject/sha2": "5.7.0", - "@ethersproject/signing-key": "5.7.0", - "@ethersproject/solidity": "5.7.0", - "@ethersproject/strings": "5.7.0", - "@ethersproject/transactions": "5.7.0", - "@ethersproject/units": "5.7.0", - "@ethersproject/wallet": "5.7.0", - "@ethersproject/web": "5.7.1", - "@ethersproject/wordlists": "5.7.0" - } - }, - "node_modules/ethereum-bloom-filters": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ethereum-bloom-filters/-/ethereum-bloom-filters-1.1.0.tgz", - "integrity": "sha512-J1gDRkLpuGNvWYzWslBQR9cDV4nd4kfvVTE/Wy4Kkm4yb3EYRSlyi0eB/inTsSTTVyA0+HyzHgbr95Fn/Z1fSw==", - "dev": true, - "peer": true, - "dependencies": { - "@noble/hashes": "^1.4.0" - } - }, - "node_modules/ethereum-bloom-filters/node_modules/@noble/hashes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", - "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/ethereum-cryptography": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", - "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", - "dev": true, - "dependencies": { - "@types/pbkdf2": "^3.0.0", - "@types/secp256k1": "^4.0.1", - "blakejs": "^1.1.0", - "browserify-aes": "^1.2.0", - "bs58check": "^2.1.2", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "hash.js": "^1.1.7", - "keccak": "^3.0.0", - "pbkdf2": "^3.0.17", - "randombytes": "^2.1.0", - "safe-buffer": "^5.1.2", - "scrypt-js": "^3.0.0", - "secp256k1": "^4.0.1", - "setimmediate": "^1.0.5" - } - }, - "node_modules/ethereumjs-abi": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.8.tgz", - "integrity": "sha512-Tx0r/iXI6r+lRsdvkFDlut0N08jWMnKRZ6Gkq+Nmw75lZe4e6o3EkSnkaBP5NF6+m5PTGAr9JP43N3LyeoglsA==", - "dev": true, - "dependencies": { - "bn.js": "^4.11.8", - "ethereumjs-util": "^6.0.0" - } - }, - "node_modules/ethereumjs-abi/node_modules/@types/bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/ethereumjs-abi/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", - "dev": true - }, - "node_modules/ethereumjs-abi/node_modules/ethereumjs-util": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-6.2.1.tgz", - "integrity": "sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==", - "dev": true, - "dependencies": { - "@types/bn.js": "^4.11.3", - "bn.js": "^4.11.0", - "create-hash": "^1.1.2", - "elliptic": "^6.5.2", - "ethereum-cryptography": "^0.1.3", - "ethjs-util": "0.1.6", - "rlp": "^2.2.3" - } - }, - "node_modules/ethereumjs-util": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", - "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", - "dev": true, - "peer": true, - "dependencies": { - "@types/bn.js": "^5.1.0", - "bn.js": "^5.1.2", - "create-hash": "^1.1.2", - "ethereum-cryptography": "^0.1.3", - "rlp": "^2.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/ethers": { - "version": "6.12.1", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.12.1.tgz", - "integrity": "sha512-j6wcVoZf06nqEcBbDWkKg8Fp895SS96dSnTCjiXT+8vt2o02raTn4Lo9ERUuIVU5bAjoPYeA+7ytQFexFmLuVw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/ethers-io/" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "peer": true, - "dependencies": { - "@adraffy/ens-normalize": "1.10.1", - "@noble/curves": "1.2.0", - "@noble/hashes": "1.3.2", - "@types/node": "18.15.13", - "aes-js": "4.0.0-beta.5", - "tslib": "2.4.0", - "ws": "8.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/ethers/node_modules/@noble/curves": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", - "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", - "dev": true, - "peer": true, - "dependencies": { - "@noble/hashes": "1.3.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/ethers/node_modules/@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/ethers/node_modules/@types/node": { - "version": "18.15.13", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.13.tgz", - "integrity": "sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q==", - "dev": true, - "peer": true - }, - "node_modules/ethjs-unit": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/ethjs-unit/-/ethjs-unit-0.1.6.tgz", - "integrity": "sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw==", - "dev": true, - "peer": true, - "dependencies": { - "bn.js": "4.11.6", - "number-to-bn": "1.7.0" - }, - "engines": { - "node": ">=6.5.0", - "npm": ">=3" - } - }, - "node_modules/ethjs-unit/node_modules/bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==", - "dev": true, - "peer": true - }, - "node_modules/ethjs-util": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/ethjs-util/-/ethjs-util-0.1.6.tgz", - "integrity": "sha512-CUnVOQq7gSpDHZVVrQW8ExxUETWrnrvXYvYz55wOU8Uj4VCgw56XC2B/fVqQN+f7gmrnRHSLVnFAwsCuNwji8w==", - "dev": true, - "dependencies": { - "is-hex-prefixed": "1.0.0", - "strip-hex-prefix": "1.0.0" - }, - "engines": { - "node": ">=6.5.0", - "npm": ">=3" - } - }, - "node_modules/evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "dependencies": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "peer": true - }, - "node_modules/fast-glob": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dev": true, - "peer": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "peer": true - }, - "node_modules/fastq": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dev": true, - "peer": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-replace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", - "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", - "dev": true, - "peer": true, - "dependencies": { - "array-back": "^3.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", - "dev": true, - "dependencies": { - "locate-path": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "peer": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fp-ts": { - "version": "1.19.3", - "resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-1.19.3.tgz", - "integrity": "sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg==", - "dev": true - }, - "node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "peer": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/fs-readdir-recursive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", - "dev": true, - "peer": true - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "peer": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, - "peer": true, - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-port": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", - "integrity": "sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/ghost-testrpc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/ghost-testrpc/-/ghost-testrpc-0.0.2.tgz", - "integrity": "sha512-i08dAEgJ2g8z5buJIrCTduwPIhih3DP+hOCTyyryikfV8T0bNvHnGXO67i0DD1H4GBDETTclPy9njZbfluQYrQ==", - "dev": true, - "peer": true, - "dependencies": { - "chalk": "^2.4.2", - "node-emoji": "^1.10.0" - }, - "bin": { - "testrpc-sc": "index.js" - } - }, - "node_modules/ghost-testrpc/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "peer": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ghost-testrpc/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ghost-testrpc/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "peer": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/ghost-testrpc/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "peer": true - }, - "node_modules/ghost-testrpc/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/ghost-testrpc/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/ghost-testrpc/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "peer": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/global-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", - "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", - "dev": true, - "peer": true, - "dependencies": { - "global-prefix": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/global-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", - "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", - "dev": true, - "peer": true, - "dependencies": { - "ini": "^1.3.5", - "kind-of": "^6.0.2", - "which": "^1.3.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/globby": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz", - "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", - "dev": true, - "peer": true, - "dependencies": { - "@types/glob": "^7.1.1", - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.0.3", - "glob": "^7.1.3", - "ignore": "^5.1.1", - "merge2": "^1.2.3", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, - "peer": true, - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", - "dev": true, - "peer": true, - "dependencies": { - "minimist": "^1.2.5", - "neo-async": "^2.6.2", - "source-map": "^0.6.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "handlebars": "bin/handlebars" - }, - "engines": { - "node": ">=0.4.7" - }, - "optionalDependencies": { - "uglify-js": "^3.1.4" - } - }, - "node_modules/handlebars/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hardhat": { - "version": "2.22.4", - "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-2.22.4.tgz", - "integrity": "sha512-09qcXJFBHQUaraJkYNr7XlmwjOj27xBB0SL2rYS024hTj9tPMbp26AFjlf5quBMO9SR4AJFg+4qWahcYcvXBuQ==", - "dev": true, - "dependencies": { - "@ethersproject/abi": "^5.1.2", - "@metamask/eth-sig-util": "^4.0.0", - "@nomicfoundation/edr": "^0.3.7", - "@nomicfoundation/ethereumjs-common": "4.0.4", - "@nomicfoundation/ethereumjs-tx": "5.0.4", - "@nomicfoundation/ethereumjs-util": "9.0.4", - "@nomicfoundation/solidity-analyzer": "^0.1.0", - "@sentry/node": "^5.18.1", - "@types/bn.js": "^5.1.0", - "@types/lru-cache": "^5.1.0", - "adm-zip": "^0.4.16", - "aggregate-error": "^3.0.0", - "ansi-escapes": "^4.3.0", - "boxen": "^5.1.2", - "chalk": "^2.4.2", - "chokidar": "^3.4.0", - "ci-info": "^2.0.0", - "debug": "^4.1.1", - "enquirer": "^2.3.0", - "env-paths": "^2.2.0", - "ethereum-cryptography": "^1.0.3", - "ethereumjs-abi": "^0.6.8", - "find-up": "^2.1.0", - "fp-ts": "1.19.3", - "fs-extra": "^7.0.1", - "glob": "7.2.0", - "immutable": "^4.0.0-rc.12", - "io-ts": "1.10.4", - "keccak": "^3.0.2", - "lodash": "^4.17.11", - "mnemonist": "^0.38.0", - "mocha": "^10.0.0", - "p-map": "^4.0.0", - "raw-body": "^2.4.1", - "resolve": "1.17.0", - "semver": "^6.3.0", - "solc": "0.7.3", - "source-map-support": "^0.5.13", - "stacktrace-parser": "^0.1.10", - "tsort": "0.0.1", - "undici": "^5.14.0", - "uuid": "^8.3.2", - "ws": "^7.4.6" - }, - "bin": { - "hardhat": "internal/cli/bootstrap.js" - }, - "peerDependencies": { - "ts-node": "*", - "typescript": "*" - }, - "peerDependenciesMeta": { - "ts-node": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/hardhat-gas-reporter": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/hardhat-gas-reporter/-/hardhat-gas-reporter-1.0.10.tgz", - "integrity": "sha512-02N4+So/fZrzJ88ci54GqwVA3Zrf0C9duuTyGt0CFRIh/CdNwbnTgkXkRfojOMLBQ+6t+lBIkgbsOtqMvNwikA==", - "dev": true, - "peer": true, - "dependencies": { - "array-uniq": "1.0.3", - "eth-gas-reporter": "^0.2.25", - "sha1": "^1.1.1" - }, - "peerDependencies": { - "hardhat": "^2.0.2" - } - }, - "node_modules/hardhat/node_modules/@noble/hashes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.2.0.tgz", - "integrity": "sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ] - }, - "node_modules/hardhat/node_modules/@noble/secp256k1": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz", - "integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ] - }, - "node_modules/hardhat/node_modules/@scure/bip32": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.1.5.tgz", - "integrity": "sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "@noble/hashes": "~1.2.0", - "@noble/secp256k1": "~1.7.0", - "@scure/base": "~1.1.0" - } - }, - "node_modules/hardhat/node_modules/@scure/bip39": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.1.tgz", - "integrity": "sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "@noble/hashes": "~1.2.0", - "@scure/base": "~1.1.0" - } - }, - "node_modules/hardhat/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/hardhat/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/hardhat/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/hardhat/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "node_modules/hardhat/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/hardhat/node_modules/ethereum-cryptography": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz", - "integrity": "sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==", - "dev": true, - "dependencies": { - "@noble/hashes": "1.2.0", - "@noble/secp256k1": "1.7.1", - "@scure/bip32": "1.1.5", - "@scure/bip39": "1.1.1" - } - }, - "node_modules/hardhat/node_modules/fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/hardhat/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/hardhat/node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/hardhat/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/hardhat/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/hardhat/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "dev": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "peer": true, - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "peer": true, - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/heap": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", - "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==", - "dev": true, - "peer": true - }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", - "dev": true, - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/http-basic": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz", - "integrity": "sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==", - "dev": true, - "peer": true, - "dependencies": { - "caseless": "^0.12.0", - "concat-stream": "^1.6.2", - "http-response-object": "^3.0.1", - "parse-cache-control": "^1.0.1" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-response-object": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", - "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", - "dev": true, - "peer": true, - "dependencies": { - "@types/node": "^10.0.3" - } - }, - "node_modules/http-response-object/node_modules/@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", - "dev": true, - "peer": true - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/immer": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/immer/-/immer-10.0.2.tgz", - "integrity": "sha512-Rx3CqeqQ19sxUtYV9CU911Vhy8/721wRFnJv3REVGWUmoAcIwzifTsdmJte/MV+0/XpM35LZdQMBGkRIoLPwQA==", - "dev": true, - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, - "node_modules/immutable": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz", - "integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==", - "dev": true - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "peer": true - }, - "node_modules/interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/io-ts": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-1.10.4.tgz", - "integrity": "sha512-b23PteSnYXSONJ6JQXRAlvJhuw8KOtkqa87W4wDtvMrud/DTJd5X+NpOOI+O/zZwVq6v0VLAaJ+1EDViKEuN9g==", - "dev": true, - "dependencies": { - "fp-ts": "^1.0.0" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-hex-prefixed": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", - "integrity": "sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA==", - "dev": true, - "engines": { - "node": ">=6.5.0", - "npm": ">=3" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "peer": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "peer": true - }, - "node_modules/js-sha3": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", - "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "peer": true - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true, - "peer": true - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "peer": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonschema": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz", - "integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==", - "dev": true, - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/keccak": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz", - "integrity": "sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/klaw": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", - "integrity": "sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.9" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "peer": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", - "dev": true, - "dependencies": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "dev": true, - "peer": true - }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", - "dev": true, - "peer": true - }, - "node_modules/lodash.isequal": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "dev": true, - "peer": true - }, - "node_modules/lodash.truncate": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", - "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true, - "peer": true - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.1" - } - }, - "node_modules/lru_map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", - "integrity": "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==", - "dev": true - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "node_modules/markdown-table": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.3.tgz", - "integrity": "sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==", - "dev": true, - "peer": true - }, - "node_modules/md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/memorystream": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", - "dev": true, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micro-eth-signer": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/micro-eth-signer/-/micro-eth-signer-0.7.2.tgz", - "integrity": "sha512-uFH23nqPNdg2KZ9ZdvLG4GO3bTAOWRhwGTsecY4Et2IdQOJ26x6inu8lJ9oyslnYL/0o1vnETCGhMimMvO0SqQ==", - "dependencies": { - "@ethereumjs/rlp": "5.0.0", - "@noble/curves": "~1.3.0", - "@noble/hashes": "~1.3.3", - "@scure/base": "~1.1.5", - "micro-packed": "~0.5.1" - } - }, - "node_modules/micro-ftch": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/micro-ftch/-/micro-ftch-0.3.1.tgz", - "integrity": "sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==", - "dev": true, - "peer": true - }, - "node_modules/micro-packed": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/micro-packed/-/micro-packed-0.5.3.tgz", - "integrity": "sha512-zWRoH+qUb/ZMp9gVZhexvRGCENDM5HEQF4sflqpdilUHWK2/zKR7/MT8GBctnTwbhNJwy1iuk5q6+TYP7/twYA==", - "dependencies": { - "@scure/base": "~1.1.5" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", - "dev": true, - "peer": true, - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "peer": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", - "dev": true - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "peer": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "peer": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mnemonist": { - "version": "0.38.5", - "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.5.tgz", - "integrity": "sha512-bZTFT5rrPKtPJxj8KSV0WkPyNxl72vQepqqVUAW2ARUpUSF2qXMB6jZj7hW5/k7C1rtpzqbD/IIbJwLXUjCHeg==", - "dev": true, - "dependencies": { - "obliterator": "^2.0.0" - } - }, - "node_modules/mocha": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.4.0.tgz", - "integrity": "sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==", - "dev": true, - "dependencies": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "8.1.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/mocha/node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/mocha/node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/mocha/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/mocha/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mocha/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/ndjson": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ndjson/-/ndjson-2.0.0.tgz", - "integrity": "sha512-nGl7LRGrzugTtaFcJMhLbpzJM6XdivmbkdlaGcrk/LXg2KL/YBC6z1g70xh0/al+oFuVFP8N8kiWRucmeEH/qQ==", - "dev": true, - "peer": true, - "dependencies": { - "json-stringify-safe": "^5.0.1", - "minimist": "^1.2.5", - "readable-stream": "^3.6.0", - "split2": "^3.0.0", - "through2": "^4.0.0" - }, - "bin": { - "ndjson": "cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "peer": true - }, - "node_modules/node-addon-api": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", - "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", - "dev": true - }, - "node_modules/node-emoji": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", - "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", - "dev": true, - "peer": true, - "dependencies": { - "lodash": "^4.17.21" - } - }, - "node_modules/node-gyp-build": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz", - "integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==", - "dev": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/nofilter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", - "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", - "dev": true, - "peer": true, - "engines": { - "node": ">=12.19" - } - }, - "node_modules/nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", - "dev": true, - "peer": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/number-to-bn": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/number-to-bn/-/number-to-bn-1.7.0.tgz", - "integrity": "sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig==", - "dev": true, - "peer": true, - "dependencies": { - "bn.js": "4.11.6", - "strip-hex-prefix": "1.0.0" - }, - "engines": { - "node": ">=6.5.0", - "npm": ">=3" - } - }, - "node_modules/number-to-bn/node_modules/bn.js": { - "version": "4.11.6", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", - "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==", - "dev": true, - "peer": true - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", - "dev": true, - "peer": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/obliterator": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.4.tgz", - "integrity": "sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==", - "dev": true - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "peer": true, - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ordinal": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/ordinal/-/ordinal-1.0.3.tgz", - "integrity": "sha512-cMddMgb2QElm8G7vdaa02jhUNbTSrhsgAGUz1OokD83uJTwSUn+nKoNoKVVaRa08yF6sgfO7Maou1+bgLd9rdQ==", - "dev": true - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "dependencies": { - "p-try": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", - "dev": true, - "dependencies": { - "p-limit": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/parse-cache-control": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", - "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==", - "dev": true, - "peer": true - }, - "node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/pbkdf2": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", - "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", - "dev": true, - "dependencies": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", - "dev": true, - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/prettier-plugin-solidity": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.3.1.tgz", - "integrity": "sha512-MN4OP5I2gHAzHZG1wcuJl0FsLS3c4Cc5494bbg+6oQWBPuEamjwDvmGfFMZ6NFzsh3Efd9UUxeT7ImgjNH4ozA==", - "dev": true, - "dependencies": { - "@solidity-parser/parser": "^0.17.0", - "semver": "^7.5.4", - "solidity-comments-extractor": "^0.0.8" - }, - "engines": { - "node": ">=16" - }, - "peerDependencies": { - "prettier": ">=2.3.0" - } - }, - "node_modules/prettier-plugin-solidity/node_modules/@solidity-parser/parser": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.17.0.tgz", - "integrity": "sha512-Nko8R0/kUo391jsEHHxrGM07QFdnPGvlmox4rmH0kNiNAashItAilhy4Mv4pK5gQmW5f4sXAF58fwJbmlkGcVw==", - "dev": true - }, - "node_modules/prettier-plugin-solidity/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, - "peer": true - }, - "node_modules/promise": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", - "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", - "dev": true, - "peer": true, - "dependencies": { - "asap": "~2.0.6" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "peer": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true, - "peer": true - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.12.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", - "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", - "dev": true, - "peer": true, - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "peer": true - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", - "dev": true, - "peer": true, - "dependencies": { - "resolve": "^1.1.6" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/recursive-readdir": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", - "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", - "dev": true, - "peer": true, - "dependencies": { - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/reduce-flatten": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", - "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/req-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/req-cwd/-/req-cwd-2.0.0.tgz", - "integrity": "sha512-ueoIoLo1OfB6b05COxAA9UpeoscNpYyM+BqYlA7H6LVF4hKGPXQQSSaD2YmvDVJMkk4UDpAHIeU1zG53IqjvlQ==", - "dev": true, - "peer": true, - "dependencies": { - "req-from": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/req-from": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/req-from/-/req-from-2.0.0.tgz", - "integrity": "sha512-LzTfEVDVQHBRfjOUMgNBA+V6DWsSnoeKzf42J7l0xa/B4jyPOuuF5MlNSmomLNGemWTnV2TIdjSSLnEn95fOQA==", - "dev": true, - "peer": true, - "dependencies": { - "resolve-from": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, - "dependencies": { - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "peer": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "node_modules/rlp": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.7.tgz", - "integrity": "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==", - "dev": true, - "dependencies": { - "bn.js": "^5.2.0" - }, - "bin": { - "rlp": "bin/rlp" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "peer": true, - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/sc-istanbul": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/sc-istanbul/-/sc-istanbul-0.4.6.tgz", - "integrity": "sha512-qJFF/8tW/zJsbyfh/iT/ZM5QNHE3CXxtLJbZsL+CzdJLBsPD7SedJZoUA4d8iAcN2IoMp/Dx80shOOd2x96X/g==", - "dev": true, - "peer": true, - "dependencies": { - "abbrev": "1.0.x", - "async": "1.x", - "escodegen": "1.8.x", - "esprima": "2.7.x", - "glob": "^5.0.15", - "handlebars": "^4.0.1", - "js-yaml": "3.x", - "mkdirp": "0.5.x", - "nopt": "3.x", - "once": "1.x", - "resolve": "1.1.x", - "supports-color": "^3.1.0", - "which": "^1.1.1", - "wordwrap": "^1.0.0" - }, - "bin": { - "istanbul": "lib/cli.js" - } - }, - "node_modules/sc-istanbul/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "peer": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/sc-istanbul/node_modules/glob": { - "version": "5.0.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", - "integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "peer": true, - "dependencies": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/sc-istanbul/node_modules/has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sc-istanbul/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "peer": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/sc-istanbul/node_modules/js-yaml/node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "peer": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/sc-istanbul/node_modules/resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==", - "dev": true, - "peer": true - }, - "node_modules/sc-istanbul/node_modules/supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==", - "dev": true, - "peer": true, - "dependencies": { - "has-flag": "^1.0.0" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/scrypt-js": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", - "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", - "dev": true - }, - "node_modules/secp256k1": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", - "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "elliptic": "^6.5.4", - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "peer": true, - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true - }, - "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" - } - }, - "node_modules/sha1": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/sha1/-/sha1-1.1.1.tgz", - "integrity": "sha512-dZBS6OrMjtgVkopB1Gmo4RQCDKiZsqcpAQpkV/aaj+FCrCg8r4I4qMkDPQjBgLIxlmu9k4nUbWq6ohXahOneYA==", - "dev": true, - "peer": true, - "dependencies": { - "charenc": ">= 0.0.1", - "crypt": ">= 0.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/shelljs": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", - "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", - "dev": true, - "peer": true, - "dependencies": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - }, - "bin": { - "shjs": "bin/shjs" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "dev": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "peer": true - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/solc": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/solc/-/solc-0.7.3.tgz", - "integrity": "sha512-GAsWNAjGzIDg7VxzP6mPjdurby3IkGCjQcM8GFYZT6RyaoUZKmMU6Y7YwG+tFGhv7dwZ8rmR4iwFDrrD99JwqA==", - "dev": true, - "dependencies": { - "command-exists": "^1.2.8", - "commander": "3.0.2", - "follow-redirects": "^1.12.1", - "fs-extra": "^0.30.0", - "js-sha3": "0.8.0", - "memorystream": "^0.3.1", - "require-from-string": "^2.0.0", - "semver": "^5.5.0", - "tmp": "0.0.33" - }, - "bin": { - "solcjs": "solcjs" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/solc/node_modules/fs-extra": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", - "integrity": "sha512-UvSPKyhMn6LEd/WpUaV9C9t3zATuqoqfWc3QdPhPLb58prN9tqYPlPWi8Krxi44loBoUzlobqZ3+8tGpxxSzwA==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0", - "path-is-absolute": "^1.0.0", - "rimraf": "^2.2.8" - } - }, - "node_modules/solc/node_modules/jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", - "dev": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/solc/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/solidity-comments-extractor": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/solidity-comments-extractor/-/solidity-comments-extractor-0.0.8.tgz", - "integrity": "sha512-htM7Vn6LhHreR+EglVMd2s+sZhcXAirB1Zlyrv5zBuTxieCvjfnRpd7iZk75m/u6NOlEyQ94C6TWbBn2cY7w8g==", - "dev": true - }, - "node_modules/solidity-coverage": { - "version": "0.8.12", - "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.12.tgz", - "integrity": "sha512-8cOB1PtjnjFRqOgwFiD8DaUsYJtVJ6+YdXQtSZDrLGf8cdhhh8xzTtGzVTGeBf15kTv0v7lYPJlV/az7zLEPJw==", - "dev": true, - "peer": true, - "dependencies": { - "@ethersproject/abi": "^5.0.9", - "@solidity-parser/parser": "^0.18.0", - "chalk": "^2.4.2", - "death": "^1.1.0", - "difflib": "^0.2.4", - "fs-extra": "^8.1.0", - "ghost-testrpc": "^0.0.2", - "global-modules": "^2.0.0", - "globby": "^10.0.1", - "jsonschema": "^1.2.4", - "lodash": "^4.17.21", - "mocha": "^10.2.0", - "node-emoji": "^1.10.0", - "pify": "^4.0.1", - "recursive-readdir": "^2.2.2", - "sc-istanbul": "^0.4.5", - "semver": "^7.3.4", - "shelljs": "^0.8.3", - "web3-utils": "^1.3.6" - }, - "bin": { - "solidity-coverage": "plugins/bin.js" - }, - "peerDependencies": { - "hardhat": "^2.11.0" - } - }, - "node_modules/solidity-coverage/node_modules/@solidity-parser/parser": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.18.0.tgz", - "integrity": "sha512-yfORGUIPgLck41qyN7nbwJRAx17/jAIXCTanHOJZhB6PJ1iAk/84b/xlsVKFSyNyLXIj0dhppoE0+CRws7wlzA==", - "dev": true, - "peer": true - }, - "node_modules/solidity-coverage/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "peer": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/solidity-coverage/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/solidity-coverage/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "peer": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/solidity-coverage/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "peer": true - }, - "node_modules/solidity-coverage/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/solidity-coverage/node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", - "dev": true, - "peer": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/solidity-coverage/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/solidity-coverage/node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "peer": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/solidity-coverage/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", - "dev": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/solidity-coverage/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "peer": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/solidity-coverage/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "amdefine": ">=0.0.4" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/split2": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", - "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", - "dev": true, - "peer": true, - "dependencies": { - "readable-stream": "^3.0.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "peer": true - }, - "node_modules/stacktrace-parser": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", - "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", - "dev": true, - "dependencies": { - "type-fest": "^0.7.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/stacktrace-parser/node_modules/type-fest": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", - "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-format": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/string-format/-/string-format-2.0.0.tgz", - "integrity": "sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA==", - "dev": true, - "peer": true - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-hex-prefix": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", - "integrity": "sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A==", - "dev": true, - "dependencies": { - "is-hex-prefixed": "1.0.0" - }, - "engines": { - "node": ">=6.5.0", - "npm": ">=3" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/sync-request": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-6.1.0.tgz", - "integrity": "sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==", - "dev": true, - "peer": true, - "dependencies": { - "http-response-object": "^3.0.1", - "sync-rpc": "^1.2.1", - "then-request": "^6.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/sync-rpc": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/sync-rpc/-/sync-rpc-1.3.6.tgz", - "integrity": "sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==", - "dev": true, - "peer": true, - "dependencies": { - "get-port": "^3.1.0" - } - }, - "node_modules/table": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", - "integrity": "sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==", - "dev": true, - "peer": true, - "dependencies": { - "ajv": "^8.0.1", - "lodash.truncate": "^4.4.2", - "slice-ansi": "^4.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/table-layout": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.2.tgz", - "integrity": "sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==", - "dev": true, - "peer": true, - "dependencies": { - "array-back": "^4.0.1", - "deep-extend": "~0.6.0", - "typical": "^5.2.0", - "wordwrapjs": "^4.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/table-layout/node_modules/array-back": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", - "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/table-layout/node_modules/typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/then-request": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/then-request/-/then-request-6.0.2.tgz", - "integrity": "sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==", - "dev": true, - "peer": true, - "dependencies": { - "@types/concat-stream": "^1.6.0", - "@types/form-data": "0.0.33", - "@types/node": "^8.0.0", - "@types/qs": "^6.2.31", - "caseless": "~0.12.0", - "concat-stream": "^1.6.0", - "form-data": "^2.2.0", - "http-basic": "^8.1.1", - "http-response-object": "^3.0.1", - "promise": "^8.0.0", - "qs": "^6.4.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/then-request/node_modules/@types/node": { - "version": "8.10.66", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz", - "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==", - "dev": true, - "peer": true - }, - "node_modules/then-request/node_modules/form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", - "dev": true, - "peer": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/through2": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", - "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", - "dev": true, - "peer": true, - "dependencies": { - "readable-stream": "3" - } - }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/ts-command-line-args": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/ts-command-line-args/-/ts-command-line-args-2.5.1.tgz", - "integrity": "sha512-H69ZwTw3rFHb5WYpQya40YAX2/w7Ut75uUECbgBIsLmM+BNuYnxsltfyyLMxy6sEeKxgijLTnQtLd0nKd6+IYw==", - "dev": true, - "peer": true, - "dependencies": { - "chalk": "^4.1.0", - "command-line-args": "^5.1.1", - "command-line-usage": "^6.1.0", - "string-format": "^2.0.0" - }, - "bin": { - "write-markdown": "dist/write-markdown.js" - } - }, - "node_modules/ts-essentials": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-7.0.3.tgz", - "integrity": "sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ==", - "dev": true, - "peer": true, - "peerDependencies": { - "typescript": ">=3.7.0" - } - }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/ts-node/node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "dev": true, - "peer": true - }, - "node_modules/tsort": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/tsort/-/tsort-0.0.1.tgz", - "integrity": "sha512-Tyrf5mxF8Ofs1tNoxA13lFeZ2Zrbd6cKbuH3V+MQ5sb6DtBj5FjrXVsRWT8YvNAQTqNoz66dz1WsbigI22aEnw==", - "dev": true - }, - "node_modules/tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "dev": true - }, - "node_modules/tweetnacl-util": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz", - "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==", - "dev": true - }, - "node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dev": true, - "peer": true, - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typechain": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/typechain/-/typechain-8.3.2.tgz", - "integrity": "sha512-x/sQYr5w9K7yv3es7jo4KTX05CLxOf7TRWwoHlrjRh8H82G64g+k7VuWPJlgMo6qrjfCulOdfBjiaDtmhFYD/Q==", - "dev": true, - "peer": true, - "dependencies": { - "@types/prettier": "^2.1.1", - "debug": "^4.3.1", - "fs-extra": "^7.0.0", - "glob": "7.1.7", - "js-sha3": "^0.8.0", - "lodash": "^4.17.15", - "mkdirp": "^1.0.4", - "prettier": "^2.3.1", - "ts-command-line-args": "^2.2.0", - "ts-essentials": "^7.0.1" - }, - "bin": { - "typechain": "dist/cli/cli.js" - }, - "peerDependencies": { - "typescript": ">=4.3.0" - } - }, - "node_modules/typechain/node_modules/fs-extra": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", - "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", - "dev": true, - "peer": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "node_modules/typechain/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "peer": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/typechain/node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "peer": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/typechain/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "peer": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/typechain/node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, - "peer": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/typechain/node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", - "dev": true, - "peer": true - }, - "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typical": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", - "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", - "dev": true, - "optional": true, - "peer": true, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/undici": { - "version": "5.28.4", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", - "integrity": "sha512-72RFADWFqKmUb2hmmvNODKL3p9hcB6Gt2DOQMis1SEBaV6a4MH8soBvzg+95CYhCKPFedut2JY9bMfrDl9D23g==", - "dev": true, - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, - "engines": { - "node": ">=14.0" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "peer": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", - "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", - "dev": true, - "peer": true - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, - "node_modules/web3-utils": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.10.4.tgz", - "integrity": "sha512-tsu8FiKJLk2PzhDl9fXbGUWTkkVXYhtTA+SmEFkKft+9BgwLxfCRpU96sWv7ICC8zixBNd3JURVoiR3dUXgP8A==", - "dev": true, - "peer": true, - "dependencies": { - "@ethereumjs/util": "^8.1.0", - "bn.js": "^5.2.1", - "ethereum-bloom-filters": "^1.0.6", - "ethereum-cryptography": "^2.1.2", - "ethjs-unit": "0.1.6", - "number-to-bn": "1.7.0", - "randombytes": "^2.1.0", - "utf8": "3.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/web3-utils/node_modules/ethereum-cryptography": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.1.3.tgz", - "integrity": "sha512-BlwbIL7/P45W8FGW2r7LGuvoEZ+7PWsniMvQ4p5s2xCyw9tmaDlpfsN9HjAucbF+t/qpVHwZUisgfK24TCW8aA==", - "dev": true, - "peer": true, - "dependencies": { - "@noble/curves": "1.3.0", - "@noble/hashes": "1.3.3", - "@scure/bip32": "1.3.3", - "@scure/bip39": "1.2.2" - } - }, - "node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "peer": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/widest-line": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", - "dev": true, - "dependencies": { - "string-width": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true, - "peer": true - }, - "node_modules/wordwrapjs": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.1.tgz", - "integrity": "sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA==", - "dev": true, - "peer": true, - "dependencies": { - "reduce-flatten": "^2.0.0", - "typical": "^5.2.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/wordwrapjs/node_modules/typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", - "dev": true - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/ws": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", - "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/contracts/package.json b/contracts/package.json deleted file mode 100644 index 39aa250a3b..0000000000 --- a/contracts/package.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "name": "@avalabs/subnet-evm-contracts", - "devDependencies": { - "@nomicfoundation/hardhat-chai-matchers": "^2.0.6", - "@nomicfoundation/hardhat-toolbox": "^5.0.0", - "@types/chai": "^4.3.16", - "@types/mocha": "^9.1.1", - "@types/node": "^20.12.12", - "chai": "^4.4.1", - "ds-test": "https://github.com/dapphub/ds-test.git", - "hardhat": "^2.22.4", - "prettier": "^3.2.4", - "prettier-plugin-solidity": "^1.3.1", - "ts-node": "^10.9.2", - "typescript": "^5.4.5" - }, - "version": "1.2.2", - "description": "", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "module": "dist/index.js", - "repository": { - "type": "git", - "url": "https://github.com/ava-labs/subnet-evm.git", - "directory": "contracts" - }, - "license": "BSD-3-Clause", - "scripts": { - "build": "rm -rf dist/ && npx hardhat compile && npx tsc -b ", - "compile": "npx hardhat compile", - "console": "npx hardhat console", - "test": "npx hardhat test", - "lint": "prettier --list-different 'contracts/**/*.sol'", - "prepublish": "npm run build", - "release:prepare": "rm -rf ./node_modules && npm install && npm run build" - }, - "dependencies": { - "@avalabs/avalanchejs": "^4.0.5", - "@openzeppelin/contracts": "^4.9.6" - }, - "engines": { - "npm": ">7.0.0", - "node": ">=20.13.0" - } -} \ No newline at end of file diff --git a/contracts/scripts/deployERC20NativeMinter.ts b/contracts/scripts/deployERC20NativeMinter.ts deleted file mode 100644 index 9ca2461726..0000000000 --- a/contracts/scripts/deployERC20NativeMinter.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ethers } from "hardhat" -import { ERC20NativeMinter } from "typechain-types" - -const main = async (): Promise => { - const token: ERC20NativeMinter = await ethers.deployContract("ERC20NativeMinter") - await token.waitForDeployment() - console.log(`Token deployed to: ${token.target}`) -} - -main() - .then(() => process.exit(0)) - .catch(error => { - console.error(error) - process.exit(1) - }) diff --git a/contracts/scripts/deployExampleDeployerList.ts b/contracts/scripts/deployExampleDeployerList.ts deleted file mode 100644 index cab5236fea..0000000000 --- a/contracts/scripts/deployExampleDeployerList.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ethers } from "hardhat" -import { ExampleDeployerList } from "typechain-types" - -const main = async (): Promise => { - const contract: ExampleDeployerList = await ethers.deployContract("ExampleDeployerList") - await contract.waitForDeployment() - console.log(`Contract deployed to: ${contract.target}`) -} - -main() - .then(() => process.exit(0)) - .catch(error => { - console.error(error) - process.exit(1) - }) diff --git a/contracts/scripts/deployExampleRewardManager.ts b/contracts/scripts/deployExampleRewardManager.ts deleted file mode 100644 index 0a58c75460..0000000000 --- a/contracts/scripts/deployExampleRewardManager.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ethers } from "hardhat" -import { ExampleRewardManager } from "typechain-types" - -const main = async (): Promise => { - const contract: ExampleRewardManager = await ethers.deployContract("ExampleRewardManager") - - await contract.waitForDeployment() - console.log(`Contract deployed to: ${contract.target}`) -} - -main() - .then(() => process.exit(0)) - .catch(error => { - console.error(error) - process.exit(1) - }) diff --git a/contracts/scripts/deployExampleTxAllowList.ts b/contracts/scripts/deployExampleTxAllowList.ts deleted file mode 100644 index 8350a06941..0000000000 --- a/contracts/scripts/deployExampleTxAllowList.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ethers } from "hardhat" -import { ExampleTxAllowList } from "typechain-types" - -const main = async (): Promise => { - const contract: ExampleTxAllowList = await ethers.deployContract("ExampleTxAllowList") - - await contract.waitForDeployment() - console.log(`Contract deployed to: ${contract.target}`) -} - -main() - .then(() => process.exit(0)) - .catch(error => { - console.error(error) - process.exit(1) - }) diff --git a/contracts/tasks.ts b/contracts/tasks.ts deleted file mode 100644 index 1596c80ad1..0000000000 --- a/contracts/tasks.ts +++ /dev/null @@ -1,270 +0,0 @@ -import { task } from "hardhat/config" - -const BLACKHOLE_ADDRESS = "0x0100000000000000000000000000000000000000" -const CONTRACT_ALLOW_LIST_ADDRESS = "0x0200000000000000000000000000000000000000" -const MINT_ADDRESS = "0x0200000000000000000000000000000000000001" -const TX_ALLOW_LIST_ADDRESS = "0x0200000000000000000000000000000000000002" -const FEE_MANAGER_ADDRESS = "0x0200000000000000000000000000000000000003" -const REWARD_MANAGER_ADDDRESS = "0x0200000000000000000000000000000000000004" - - -const ROLES = { - 0: "None", - 1: "Enabled", - 2: "Admin", -} - -const getRole = async (allowList, address) => { - const role = await allowList.readAllowList(address) - console.log(`${address} has role: ${ROLES[role.toNumber()]}`) -} - -task("accounts", "Prints the list of accounts", async (args, hre): Promise => { - const accounts = await hre.ethers.getSigners() - accounts.forEach((account): void => { - console.log(account.address) - }) -}) - -task("balances", "Prints the list of account balances", async (args, hre): Promise => { - const accounts = await hre.ethers.getSigners() - for (const account of accounts) { - const balance = await hre.ethers.provider.getBalance( - account.address - ) - console.log(`${account.address} has balance ${balance.toString()}`) - } -}) - - -task("balance", "get the balance") - .addParam("address", "the address you want to know balance of") - .setAction(async (args, hre) => { - const balance = await hre.ethers.provider.getBalance(args.address) - const balanceInCoin = hre.ethers.formatEther(balance) - console.log(`balance: ${balanceInCoin} Coin`) - }) - -// npx hardhat allowList:readRole --network local --address [address] -task("deployerAllowList:readRole", "Gets the network deployer allow list") - .addParam("address", "the address you want to know the allowlist role for") - .setAction(async (args, hre) => { - const allowList = await hre.ethers.getContractAt("IAllowList", CONTRACT_ALLOW_LIST_ADDRESS) - await getRole(allowList, args.address) - }) - -// npx hardhat allowList:addDeployer --network local --address [address] -task("deployerAllowList:addDeployer", "Adds the deployer on the allow list") - .addParam("address", "the address you want to add as a deployer") - .setAction(async (args, hre) => { - const allowList = await hre.ethers.getContractAt("IAllowList", CONTRACT_ALLOW_LIST_ADDRESS) - // ADD CODE BELOW - await allowList.setEnabled(args.address) - await getRole(allowList, args.address) - }) - -// npx hardhat allowList:addAdmin --network local --address [address] -task("deployerAllowList:addAdmin", "Adds an admin on the allowlist") - .addParam("address", "the address you want to add as a admin") - .setAction(async (args, hre) => { - const allowList = await hre.ethers.getContractAt("IAllowList", CONTRACT_ALLOW_LIST_ADDRESS) - await allowList.setAdmin(args.address) - await getRole(allowList, args.address) - }) - -// npx hardhat allowList:revoke --network local --address [address] -task("deployerAllowList:revoke", "Removes the address from the list") - .addParam("address", "the address you want to revoke all permission") - .setAction(async (args, hre) => { - const allowList = await hre.ethers.getContractAt("IAllowList", CONTRACT_ALLOW_LIST_ADDRESS) - await allowList.setNone(args.address) - await getRole(allowList, args.address) - }) - -// npx hardhat allowList:readRole --network local --address [address] -task("txAllowList:readRole", "Gets the network transaction allow list") - .addParam("address", "the address you want to know the allowlist role for") - .setAction(async (args, hre) => { - const allowList = await hre.ethers.getContractAt("IAllowList", TX_ALLOW_LIST_ADDRESS) - await getRole(allowList, args.address) - }) - -// npx hardhat allowList:addDeployer --network local --address [address] -task("txAllowList:addDeployer", "Adds an address to the transaction allow list") - .addParam("address", "the address you want to add as a deployer") - .setAction(async (args, hre) => { - const allowList = await hre.ethers.getContractAt("IAllowList", TX_ALLOW_LIST_ADDRESS) - // ADD CODE BELOW - await allowList.setEnabled(args.address) - await getRole(allowList, args.address) - }) - -// npx hardhat allowList:addAdmin --network local --address [address] -task("txAllowList:addAdmin", "Adds an admin on the transaction allow list") - .addParam("address", "the address you want to add as a admin") - .setAction(async (args, hre) => { - const allowList = await hre.ethers.getContractAt("IAllowList", TX_ALLOW_LIST_ADDRESS) - await allowList.setAdmin(args.address) - await getRole(allowList, args.address) - }) - -// npx hardhat allowList:revoke --network local --address [address] -task("txAllowList:revoke", "Removes the address from the transaction allow list") - .addParam("address", "the address you want to revoke all permission") - .setAction(async (args, hre) => { - const allowList = await hre.ethers.getContractAt("IAllowList", TX_ALLOW_LIST_ADDRESS) - await allowList.setNone(args.address) - await getRole(allowList, args.address) - }) - -// npx hardhat minter:readRole --network local --address [address] -task("minter:readRole", "Gets the network deployer minter list") - .addParam("address", "the address you want to know the minter role for") - .setAction(async (args, hre) => { - const allowList = await hre.ethers.getContractAt("INativeMinter", MINT_ADDRESS) - await getRole(allowList, args.address) - }) - - -// npx hardhat minter:addMinter --network local --address [address] -task("minter:addMinter", "Adds the address on the minter list") - .addParam("address", "the address you want to add as a minter") - .setAction(async (args, hre) => { - const allowList = await hre.ethers.getContractAt("INativeMinter", MINT_ADDRESS) - await allowList.setEnabled(args.address) - await getRole(allowList, args.address) - }) - -// npx hardhat minter:addAdmin --network local --address [address] -task("minter:addAdmin", "Adds an admin on the minter list") - .addParam("address", "the address you want to add as a admin") - .setAction(async (args, hre) => { - const allowList = await hre.ethers.getContractAt("INativeMinter", MINT_ADDRESS) - await allowList.setAdmin(args.address) - await getRole(allowList, args.address) - }) - -// npx hardhat minter:revoke --network local --address [address] -task("minter:revoke", "Removes the address from the list") - .addParam("address", "the address you want to revoke all permission") - .setAction(async (args, hre) => { - const allowList = await hre.ethers.getContractAt("INativeMinter", MINT_ADDRESS) - await allowList.setNone(args.address) - await getRole(allowList, args.address) - }) - -// npx hardhat minter:mint --network local --address [address] -task("minter:mint", "Mints native tokens") - .addParam("address", "the address you want to mint for") - .addParam("amount", "the amount you want to mint") - .setAction(async (args, hre) => { - const minter = await hre.ethers.getContractAt("INativeMinter", MINT_ADDRESS) - await minter.mintNativeCoin(args.address, args.amount) - }) - -// npx hardhat minter:burn --network local --address [address] -task("minter:burn", "Burns native tokens") - .addParam("amount", "the amount you want to burn (in AVAX unit)") - .setAction(async (args, hre) => { - const [owner] = await hre.ethers.getSigners() - const transactionHash = await owner.sendTransaction({ - to: BLACKHOLE_ADDRESS, - value: hre.ethers.parseEther(args.amount), - }) - console.log(transactionHash) - }) - -// npx hardhat feeManager:set --network local --address [address] -task("feeManager:set", "Sets the provided fee config") - .addParam("gaslimit", "", undefined, undefined, false) - .addParam("targetblockrate", "", undefined, undefined, false) - .addParam("minbasefee", "", undefined, undefined, false) - .addParam("targetgas", "", undefined, undefined, false) - .addParam("basefeechangedenominator", "", undefined, undefined, false) - .addParam("minblockgascost", "", undefined, undefined, false) - .addParam("maxblockgascost", "", undefined, undefined, false) - .addParam("blockgascoststep", "", undefined, undefined, false) - - .setAction(async (args, hre) => { - const feeManager = await hre.ethers.getContractAt("IFeeManager", FEE_MANAGER_ADDRESS) - await feeManager.setFeeConfig( - args.gaslimit, - args.targetblockrate, - args.minbasefee, - args.targetgas, - args.basefeechangedenominator, - args.minblockgascost, - args.maxblockgascost, - args.blockgascoststep) - }) - -task("feeManager:get", "Gets the fee config") - .setAction(async (_, hre) => { - const feeManager = await hre.ethers.getContractAt("IFeeManager", FEE_MANAGER_ADDRESS) - const result = await feeManager.getFeeConfig() - console.log(`Fee Manager Precompile Config is set to: - gasLimit: ${result[0]} - targetBlockRate: ${result[1]} - minBaseFee: ${result[2]} - targetGas: ${result[3]} - baseFeeChangeDenominator: ${result[4]} - minBlockGasCost: ${result[5]} - maxBlockGasCost: ${result[6]} - blockGasCostStep: ${result[7]}`) - }) - - -// npx hardhat feeManager:readRole --network local --address [address] -task("feeManager:readRole", "Gets the network deployer minter list") - .addParam("address", "the address you want to know the minter role for") - .setAction(async (args, hre) => { - const allowList = await hre.ethers.getContractAt("IFeeManager", FEE_MANAGER_ADDRESS) - await getRole(allowList, args.address) - }) - - -// npx hardhat rewardManager:currentRewardAddress --network local -task("rewardManager:currentRewardAddress", "Gets the current configured rewarding address") - .setAction(async (_, hre) => { - const rewardManager = await hre.ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDDRESS) - const areFeeRecipientsAllowed = await rewardManager.areFeeRecipientsAllowed() - const result = await rewardManager.currentRewardAddress() - if (areFeeRecipientsAllowed) { - console.log("Custom Fee Recipients are allowed. (%s)", result) - } else { - console.log(`Current reward address is ${result}`) - } - }) - -// npx hardhat rewardManager:areFeeRecipientsAllowed --network local -task("rewardManager:areFeeRecipientsAllowed", "Gets whether the fee recipients are allowed to receive rewards") - .setAction(async (_, hre) => { - const rewardManager = await hre.ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDDRESS) - const result = await rewardManager.areFeeRecipientsAllowed() - console.log(result) - }) - -// npx hardhat rewardManager:setRewardAddress --network local --address [address] -task("rewardManager:setRewardAddress", "Sets a new reward address") - .addParam("address", "the address that will receive rewards") - .setAction(async (args, hre) => { - const rewardManager = await hre.ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDDRESS) - const result = await rewardManager.setRewardAddress(args.address) - console.log(result) - }) - -// npx hardhat rewardManager:allowFeeRecipients --network local -task("rewardManager:allowFeeRecipients", "Allows custom fee recipients to receive rewards") - .setAction(async (_, hre) => { - const rewardManager = await hre.ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDDRESS) - const result = await rewardManager.allowFeeRecipients() - console.log(result) - }) - -// npx hardhat rewardManager:disableRewards --network local -task("rewardManager:disableRewards", "Disables all rewards, and starts burning fees.") - .setAction(async (_, hre) => { - const rewardManager = await hre.ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDDRESS) - const result = await rewardManager.disableRewards() - console.log(result) - }) diff --git a/contracts/test/README.md b/contracts/test/README.md deleted file mode 100644 index 2983ba29d3..0000000000 --- a/contracts/test/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# Testing Precompiles - -If you can, put all of your test logic into DS-test tests. Prefix test functions with `step_`. There's also a `setUp` function that gets called before the test contract is deployed. The current best-practice is to re-deploy one test contract per `test` function called in `*.ts` test definitions. The `setUp` method should be called once, then `step_` functions passed in as the 2nd argument to `test("", )` will be called in order. `test.only` and `test.skip` behave the same way as `it.only` and `it.skip`. There's also a `test.debug` that combines `test.only` with some extra event logging (you can use `emit log_string` to help debug Solidity test code). - -The `test` function is a wrapper around Mocha's `it` function. It provides a normalized framework for running the -majority of your test assertions inside of a smart-contract, using `DS-Test`. -The API can be used as follows (all of the examples are equivalent): - -```ts -test("", ""); - -test("", [""]); - -test("", { - method: "", - overrides: {}, - shouldFail: false, - debug: false, -}); - -test("", [ - { - method: "", - overrides: {}, - shouldFail: false, - debug: false, - }, -]); - -test( - "", - [{ method: "", shouldFail: false, debug: false }], - {} -); -``` - -Many contract functions can be called as a part of the same test: - -```ts -test("", ["", "", ""]) -``` - -Individual test functions can describe their own overrides with the `overrides` property. -If an object is passed in as the third argument to `test`, it will be used as the default overrides for all test -functions. -The following are equivalent: - -```ts -test("", [ - { method: "", overrides: { from: "0x123" } }, -]); - -test("", [{ method: "" }], { - from: "0x123", -}); -``` - -In the above cases, the `from` override must be a signer. -The `shouldFail` property can be used to indicate that the test function should fail. This should be used sparingly -as it is not possible to match on the failure reason. -Furthermore, the `debug` property can be used to print any thrown errors when attempting to -send a transaction or while waiting for the transaction to be confirmed (the transaction is the smart contract call). -`debug` will also cause any parseable event logs to be printed that start with the `log_` prefix. -`DSTest` contracts have several options for emitting `log_` events. diff --git a/contracts/test/contract_deployer_allow_list.ts b/contracts/test/contract_deployer_allow_list.ts deleted file mode 100644 index ff724b1537..0000000000 --- a/contracts/test/contract_deployer_allow_list.ts +++ /dev/null @@ -1,101 +0,0 @@ -// (c) 2019-2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -import { ethers } from "hardhat" - -import { Roles, test } from "./utils" -import { expect } from "chai"; -import { Contract, Signer } from "ethers" -import { IAllowList } from "typechain-types"; - -const ADMIN_ADDRESS: string = "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" -const OTHER_SIGNER = "0x0Fa8EA536Be85F32724D57A37758761B86416123" -const DEPLOYER_ALLOWLIST_ADDRESS = "0x0200000000000000000000000000000000000000" - -describe("ExampleDeployerList", function () { - beforeEach('Setup DS-Test contract', async function () { - const signer = await ethers.getSigner(ADMIN_ADDRESS) - const allowListPromise = ethers.getContractAt("IAllowList", DEPLOYER_ALLOWLIST_ADDRESS, signer) - - return ethers.getContractFactory("ExampleDeployerListTest", { signer }) - .then(factory => factory.deploy()) - .then(contract => { - this.testContract = contract - return Promise.all([ - contract.waitForDeployment().then(() => contract), - allowListPromise.then(allowList => allowList.setAdmin(contract.target)).then(tx => tx.wait()), - ]) - }) - .then(([contract]) => contract.setUp()) - .then(tx => tx.wait()) - }) - - test("precompile should see owner address has admin role", "step_verifySenderIsAdmin") - - test("precompile should see test address has no role", "step_newAddressHasNoRole") - - test("contract should report test address has no admin role", "step_noRoleIsNotAdmin") - - test("contract should report owner address has admin role", "step_ownerIsAdmin") - - test("should not let test address deploy", { - method: "step_noRoleCannotDeploy", - overrides: { from: OTHER_SIGNER }, - shouldFail: false, - }) - - test("should allow admin to add contract as admin", "step_adminAddContractAsAdmin") - - test("should allow admin to add deployer address as deployer through contract", "step_addDeployerThroughContract") - - test("should let deployer address to deploy", "step_deployerCanDeploy") - - test("should let admin revoke deployer", "step_adminCanRevokeDeployer") -}) - -describe("IAllowList", function () { - let owner: Signer - let ownerAddress: string - let contract: IAllowList - before(async function () { - owner = await ethers.getSigner(ADMIN_ADDRESS); - ownerAddress = await owner.getAddress() - contract = await ethers.getContractAt("IAllowList", DEPLOYER_ALLOWLIST_ADDRESS, owner) - }); - - it("should emit event after set admin", async function () { - let testAddress = "0x0111000000000000000000000000000000000001" - let tx = await contract.setAdmin(testAddress) - let receipt = await tx.wait() - await expect(receipt) - .to.emit(contract, 'RoleSet') - .withArgs(Roles.Admin, testAddress, ownerAddress, Roles.None) - }) - - it("should emit event after set manager", async function () { - let testAddress = "0x0222000000000000000000000000000000000002" - let tx = await contract.setManager(testAddress) - let receipt = await tx.wait() - await expect(receipt) - .to.emit(contract, 'RoleSet') - .withArgs(Roles.Manager, testAddress, ownerAddress, Roles.None) - }) - - it("should emit event after set enabled", async function () { - let testAddress = "0x0333000000000000000000000000000000000003" - let tx = await contract.setEnabled(testAddress) - let receipt = await tx.wait() - await expect(receipt) - .to.emit(contract, 'RoleSet') - .withArgs(Roles.Enabled, testAddress, ownerAddress, Roles.None) - }) - - it("should emit event after set none", async function () { - let testAddress = "0x0333000000000000000000000000000000000003" - let tx = await contract.setNone(testAddress) - let receipt = await tx.wait() - await expect(receipt) - .to.emit(contract, 'RoleSet') - .withArgs(Roles.None, testAddress, ownerAddress, Roles.Enabled) - }) -}) diff --git a/contracts/test/contract_native_minter.ts b/contracts/test/contract_native_minter.ts deleted file mode 100644 index e2a67cf5a5..0000000000 --- a/contracts/test/contract_native_minter.ts +++ /dev/null @@ -1,63 +0,0 @@ -// (c) 2019-2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -import { ethers } from "hardhat" -import { test } from "./utils" -import { expect } from "chai"; -import { Contract, Signer } from "ethers" -import { INativeMinter } from "typechain-types"; - -const ADMIN_ADDRESS: string = "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" -const MINT_PRECOMPILE_ADDRESS = "0x0200000000000000000000000000000000000001" - -describe("ERC20NativeMinter", function () { - beforeEach('Setup DS-Test contract', async function () { - const signer = await ethers.getSigner(ADMIN_ADDRESS) - const nativeMinterPromise = ethers.getContractAt("INativeMinter", MINT_PRECOMPILE_ADDRESS, signer) - - return ethers.getContractFactory("ERC20NativeMinterTest", { signer }) - .then(factory => factory.deploy()) - .then(contract => { - this.testContract = contract - return contract.waitForDeployment().then(() => contract) - }) - .then(contract => contract.setUp()) - .then(tx => Promise.all([nativeMinterPromise, tx.wait()])) - .then(([nativeMinter]) => nativeMinter.setAdmin(this.testContract.target)) - .then(tx => tx.wait()) - }) - - test("contract should not be able to mintdraw", "step_mintdrawFailure") - - test("should be added to minter list", "step_addMinter") - - test("admin should mintdraw", "step_adminMintdraw") - - test("minter should not mintdraw", "step_minterMintdrawFailure") - - test("should deposit for minter", "step_minterDeposit") - - test("minter should mintdraw", "step_mintdraw") -}) - - -describe("INativeMinter", function () { - let owner: Signer - let ownerAddress: string - let contract: INativeMinter - before(async function () { - owner = await ethers.getSigner(ADMIN_ADDRESS); - ownerAddress = await owner.getAddress() - contract = await ethers.getContractAt("INativeMinter", MINT_PRECOMPILE_ADDRESS, owner) - }); - - it("should emit NativeCoinMinted event", async function () { - let testAddress = "0x0444400000000000000000000000000000000004" - let amount = 1000 - let tx = await contract.mintNativeCoin(testAddress, amount) - let receipt = await tx.wait() - await expect(receipt) - .to.emit(contract, 'NativeCoinMinted') - .withArgs(ownerAddress, testAddress, amount) - }) -}) diff --git a/contracts/test/fee_manager.ts b/contracts/test/fee_manager.ts deleted file mode 100644 index a42e771dd3..0000000000 --- a/contracts/test/fee_manager.ts +++ /dev/null @@ -1,125 +0,0 @@ -// (c) 2019-2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -import { expect } from "chai" -import { ethers } from "hardhat" -import { test } from "./utils" -import { Contract, Signer } from "ethers" -import { IFeeManager } from "typechain-types" - -const ADMIN_ADDRESS: string = "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" -const FEE_MANAGER = "0x0200000000000000000000000000000000000003" - -const GENESIS_CONFIG = require('../../tests/precompile/genesis/fee_manager.json') - -describe("ExampleFeeManager", function () { - beforeEach("setup DS-Test contract", async function () { - const signer = await ethers.getSigner(ADMIN_ADDRESS) - const feeManagerPromise = ethers.getContractAt("IFeeManager", FEE_MANAGER, signer) - - return ethers.getContractFactory("ExampleFeeManagerTest", { signer }) - .then(factory => factory.deploy()) - .then(contract => { - this.testContract = contract - return contract.waitForDeployment().then(() => contract) - }) - .then(contract => contract.setUp()) - .then(tx => Promise.all([feeManagerPromise, tx.wait()])) - .then(([feeManager]) => feeManager.setAdmin(this.testContract.target)) - .then(tx => tx.wait()) - }) - - test("should add contract deployer as owner", "step_addContractDeployerAsOwner") - - test("contract should not be able to change fee without enabled", "step_enableWAGMIFeesFailure") - - test("contract should be added to manager list", "step_addContractToManagerList") - - test("admin should be able to enable change fees", "step_changeFees") - - test("should confirm min-fee transaction", "step_minFeeTransaction", { - maxFeePerGas: GENESIS_CONFIG.config.feeConfig.minBaseFee, - maxPriorityFeePerGas: 0, - }) - - test("should reject a transaction below the minimum", [ - "step_raiseMinFeeByOne", - { - method: "step_minFeeTransaction", - shouldFail: true, - overrides: { - maxFeePerGas: GENESIS_CONFIG.config.feeConfig.minBaseFee, - maxPriorityFeePerGas: 0, - }, - }, - "step_lowerMinFeeByOne", - ]) -}) - -const C_FEES = { - gasLimit: 8_000_000, // gasLimit - targetBlockRate: 2, // targetBlockRate - minBaseFee: 25_000_000_000, // minBaseFee - targetGas: 15_000_000, // targetGas - baseFeeChangeDenominator: 36, // baseFeeChangeDenominator - minBlockGasCost: 0, // minBlockGasCost - maxBlockGasCost: 1_000_000, // maxBlockGasCost - blockGasCostStep: 100_000 // blockGasCostStep -} - -const WAGMI_FEES = { - gasLimit: 20_000_000, // gasLimit - targetBlockRate: 2, // targetBlockRate - minBaseFee: 1_000_000_000, // minBaseFee - targetGas: 100_000_000, // targetGas - baseFeeChangeDenominator: 48, // baseFeeChangeDenominator - minBlockGasCost: 0, // minBlockGasCost - maxBlockGasCost: 10_000_000, // maxBlockGasCost - blockGasCostStep: 100_000 // blockGasCostStep -} - -describe("IFeeManager", function () { - let owner: Signer - let ownerAddress: string - let contract: IFeeManager - before(async function () { - owner = await ethers.getSigner(ADMIN_ADDRESS); - ownerAddress = await owner.getAddress() - contract = await ethers.getContractAt("IFeeManager", FEE_MANAGER, owner) - // reset to C fees - let tx = await contract.setFeeConfig( - C_FEES.gasLimit, - C_FEES.targetBlockRate, - C_FEES.minBaseFee, - C_FEES.targetGas, - C_FEES.baseFeeChangeDenominator, - C_FEES.minBlockGasCost, - C_FEES.maxBlockGasCost, - C_FEES.blockGasCostStep) - await tx.wait() - }); - - it("should emit fee config changed event", async function () { - let tx = await (contract.setFeeConfig( - WAGMI_FEES.gasLimit, - WAGMI_FEES.targetBlockRate, - WAGMI_FEES.minBaseFee, - WAGMI_FEES.targetGas, - WAGMI_FEES.baseFeeChangeDenominator, - WAGMI_FEES.minBlockGasCost, - WAGMI_FEES.maxBlockGasCost, - WAGMI_FEES.blockGasCostStep) - ) - let receipt = await tx.wait() - await expect(receipt) - .to.emit(contract, 'FeeConfigChanged') - .withArgs( - ownerAddress, - // old config - [C_FEES.gasLimit, C_FEES.targetBlockRate, C_FEES.minBaseFee, C_FEES.targetGas, C_FEES.baseFeeChangeDenominator, C_FEES.minBlockGasCost, C_FEES.maxBlockGasCost, C_FEES.blockGasCostStep], - // new config - [WAGMI_FEES.gasLimit, WAGMI_FEES.targetBlockRate, WAGMI_FEES.minBaseFee, WAGMI_FEES.targetGas, WAGMI_FEES.baseFeeChangeDenominator, WAGMI_FEES.minBlockGasCost, WAGMI_FEES.maxBlockGasCost, WAGMI_FEES.blockGasCostStep] - ); - }) -}) - diff --git a/contracts/test/reward_manager.ts b/contracts/test/reward_manager.ts deleted file mode 100644 index 24c6119161..0000000000 --- a/contracts/test/reward_manager.ts +++ /dev/null @@ -1,85 +0,0 @@ -// (c) 2019-2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -import { ethers } from "hardhat" -import { test } from "./utils" -import { expect } from "chai"; -import { Contract, Signer } from "ethers" -import { IRewardManager } from "typechain-types"; - -// make sure this is always an admin for reward manager precompile -const ADMIN_ADDRESS = "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" -const REWARD_MANAGER_ADDRESS = "0x0200000000000000000000000000000000000004" -const BLACKHOLE_ADDRESS = "0x0100000000000000000000000000000000000000" - -describe("ExampleRewardManager", function () { - beforeEach('Setup DS-Test contract', async function () { - const signer = await ethers.getSigner(ADMIN_ADDRESS) - const rewardManagerPromise = ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDRESS, signer) - - return ethers.getContractFactory("ExampleRewardManagerTest", signer) - .then(factory => factory.deploy()) - .then(contract => { - this.testContract = contract - return contract.waitForDeployment().then(() => contract) - }) - .then(contract => contract.setUp()) - .then(tx => Promise.all([rewardManagerPromise, tx.wait()])) - .then(([rewardManager]) => rewardManager.setAdmin(this.testContract.target)) - .then(tx => tx.wait()) - }) - - test("should send fees to blackhole", ["step_captureBlackholeBalance", "step_checkSendFeesToBlackhole"]) - - test("should not appoint reward address before enabled", "step_doesNotSetRewardAddressBeforeEnabled") - - test("contract should be added to enabled list", "step_setEnabled") - - test("should be appointed as reward address", "step_setRewardAddress") - - // we need to change the fee receiver, send a transaction for the new receiver to receive fees, then check the balance change. - // the new fee receiver won't receive fees in the same block where it was set. - test("should be able to receive fees", ["step_setupReceiveFees", "step_receiveFees", "step_checkReceiveFees"]) - - test("should return false for allowFeeRecipients check", "step_areFeeRecipientsAllowed") - - test("should enable allowFeeRecipients", "step_allowFeeRecipients") - - test("should disable reward address", "step_disableRewardAddress") -}); - -describe("IRewardManager", function () { - let owner: Signer - let ownerAddress: string - let contract: IRewardManager - before(async function () { - owner = await ethers.getSigner(ADMIN_ADDRESS); - ownerAddress = await owner.getAddress() - contract = await ethers.getContractAt("IRewardManager", REWARD_MANAGER_ADDRESS, owner) - }); - - it("should emit reward address changed event ", async function () { - let testAddress = "0x0444400000000000000000000000000000000004" - let tx = await contract.setRewardAddress(testAddress) - let receipt = await tx.wait() - await expect(receipt) - .to.be.emit(contract, 'RewardAddressChanged') - .withArgs(ownerAddress, BLACKHOLE_ADDRESS, testAddress) - }) - - it("should emit fee recipients allowed event ", async function () { - let tx = await contract.allowFeeRecipients() - let receipt = await tx.wait() - await expect(receipt) - .to.be.emit(contract, 'FeeRecipientsAllowed') - .withArgs(ownerAddress) - }) - - it("should emit rewards disabled event ", async function () { - let tx = await contract.disableRewards() - let receipt = await tx.wait() - await expect(receipt) - .to.be.emit(contract, 'RewardsDisabled') - .withArgs(ownerAddress) - }) -}) diff --git a/contracts/test/tx_allow_list.ts b/contracts/test/tx_allow_list.ts deleted file mode 100644 index 5f9cfb5c30..0000000000 --- a/contracts/test/tx_allow_list.ts +++ /dev/null @@ -1,135 +0,0 @@ -// (c) 2019-2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -import { ethers } from "hardhat" -import { Roles, test } from "./utils" -import { expect } from "chai"; -import { Contract, Signer } from "ethers" -import { IAllowList } from "typechain-types"; - -// make sure this is always an admin for minter precompile -const ADMIN_ADDRESS = "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" -const OTHER_SIGNER = "0x0Fa8EA536Be85F32724D57A37758761B86416123" -const TX_ALLOW_LIST_ADDRESS = "0x0200000000000000000000000000000000000002" - -describe("ExampleTxAllowList", function () { - beforeEach('Setup DS-Test contract', async function () { - const signer = await ethers.getSigner(ADMIN_ADDRESS) - const allowListPromise = ethers.getContractAt("IAllowList", TX_ALLOW_LIST_ADDRESS, signer) - - return ethers.getContractFactory("ExampleTxAllowListTest", { signer }) - .then(factory => factory.deploy()) - .then(contract => { - this.testContract = contract - return Promise.all([ - contract.waitForDeployment().then(() => contract), - allowListPromise.then(allowList => allowList.setAdmin(contract.target)).then(tx => tx.wait()), - ]) - }) - .then(([contract]) => contract.setUp()) - .then(tx => tx.wait()) - }) - - test("should add contract deployer as admin", "step_contractOwnerIsAdmin") - - test("precompile should see admin address has admin role", "step_precompileHasDeployerAsAdmin") - - test("precompile should see test address has no role", "step_newAddressHasNoRole") - - test("contract should report test address has on admin role", "step_noRoleIsNotAdmin") - - test("contract should report admin address has admin role", "step_exampleAllowListReturnsTestIsAdmin") - - test("should not let test address submit txs", [ - { - method: "step_fromOther", - overrides: { from: OTHER_SIGNER }, - shouldFail: true, - }, - { - method: "step_enableOther", - overrides: { from: ADMIN_ADDRESS }, - shouldFail: false, - }, - { - method: "step_fromOther", - overrides: { from: OTHER_SIGNER }, - shouldFail: false, - }, - ]); - - test("should not allow noRole to enable itself", "step_noRoleCannotEnableItself") - - test("should allow admin to add contract as admin", "step_addContractAsAdmin") - - test("should allow admin to add allowed address as allowed through contract", "step_enableThroughContract") - - test("should let allowed address deploy", "step_canDeploy") - - test("should not let allowed add another allowed", "step_onlyAdminCanEnable") - - test("should not let allowed to revoke admin", "step_onlyAdminCanRevoke") - - test("should let admin to revoke allowed", "step_adminCanRevoke") - - test("should let manager to add allowed", "step_managerCanAllow") - - test("should let manager to revoke allowed", "step_managerCanRevoke") - - test("should not let manager to revoke admin", "step_managerCannotRevokeAdmin") - - test("should not let manager to add admin", "step_managerCannotGrantAdmin") - - test("should not let manager to add manager", "step_managerCannotGrantManager") - - test("should not let manager to revoke manager", "step_managerCannotRevokeManager") - - test("should let manager to deploy", "step_managerCanDeploy") -}) - -describe("IAllowList", function () { - let owner: Signer - let ownerAddress: string - let contract: IAllowList - before(async function () { - owner = await ethers.getSigner(ADMIN_ADDRESS); - ownerAddress = await owner.getAddress() - contract = await ethers.getContractAt("IAllowList", TX_ALLOW_LIST_ADDRESS, owner) - }); - - it("should emit event after set admin", async function () { - let testAddress = "0x0111000000000000000000000000000000000001" - let tx = await contract.setAdmin(testAddress) - let receipt = await tx.wait() - await expect(receipt) - .to.emit(contract, 'RoleSet') - .withArgs(Roles.Admin, testAddress, ownerAddress, Roles.None) - }) - - it("should emit event after set manager", async function () { - let testAddress = "0x0222000000000000000000000000000000000002" - let tx = await contract.setManager(testAddress) - let receipt = await tx.wait() - await expect(receipt) - .to.emit(contract, 'RoleSet') - .withArgs(Roles.Manager, testAddress, ownerAddress, Roles.None) - }) - - it("should emit event after set enabled", async function () { - let testAddress = "0x0333000000000000000000000000000000000003" - let tx = await contract.setEnabled(testAddress) - let receipt = await tx.wait() - await expect(receipt) - .to.emit(contract, 'RoleSet') - .withArgs(Roles.Enabled, testAddress, ownerAddress, Roles.None) - }) - - it("should emit event after set none", async function () { - let testAddress = "0x0333000000000000000000000000000000000003" - let tx = await contract.setNone(testAddress) - let receipt = await tx.wait() - await expect(receipt) - .to.emit(contract, 'RoleSet') - .withArgs(Roles.None, testAddress, ownerAddress, Roles.Enabled) - }) -}) diff --git a/contracts/test/utils.ts b/contracts/test/utils.ts deleted file mode 100644 index aa85b66733..0000000000 --- a/contracts/test/utils.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { ethers } from "hardhat" -import { Overrides } from "ethers" -import assert from "assert" - -/* - * - * The `test` function is a wrapper around Mocha's `it` function. It provides a normalized framework for running the - * majority of your test assertions inside of a smart-contract, using `DS-Test`. - * The API can be used as follows (all of the examples are equivalent): - * ```ts - * test("", "") - * test("", [""]) - * test("", { method: "", overrides: {}, shouldFail: false, debug: false }) - * test("", [{ method: "", overrides: {}, shouldFail: false, debug: false }]) - * test("", [{ method: "", shouldFail: false, debug: false }], {}) - * ``` - * Many contract functions can be called as a part of the same test: - * ```ts - * test("", ["", "", ""]) - * ``` - * Individual test functions can describe their own overrides with the `overrides` property. - * If an object is passed in as the third argument to `test`, it will be used as the default overrides for all test - * functions. - * The following are equivalent: - * ```ts - * test("", [{ method: "", overrides: { from: "0x123" } }]) - * test("", [{ method: "" }], { from: "0x123" }) - * ``` - * In the above cases, the `from` override must be a signer. - * The `shouldFail` property can be used to indicate that the test function should fail. This should be used sparingly - * as it is not possible to match on the failure reason. - * Furthermore, the `debug` property can be used to print any thrown errors when attempting to - * send a transaction or while waiting for the transaction to be confirmed (the transaction is the smart contract call). - * `debug` will also cause any parseable event logs to be printed that start with the `log_` prefix. - * `DSTest` contracts have several options for emitting `log_` events. - * - */ - -// Below are the types that help define all the different ways to call `test` -type FnNameOrObject = string | string[] | MethodObject | MethodObject[] - -// Limit `from` property to be a `string` instead of `string | Promise` -type CallOverrides = Overrides & { from?: string } - -type MethodObject = { method: string, debug?: boolean, overrides?: CallOverrides, shouldFail?: boolean } - -// This type is after all default values have been applied -type MethodWithDebugAndOverrides = MethodObject & { debug: boolean, overrides: CallOverrides, shouldFail: boolean } - -// `test` is used very similarly to `it` from Mocha -export const test = (name, fnNameOrObject, overrides = {}) => it(name, buildTestFn(fnNameOrObject, overrides)) -// `test.only` is used very similarly to `it.only` from Mocha, it will isolate all tests marked with `test.only` -test.only = (name, fnNameOrObject, overrides = {}) => it.only(name, buildTestFn(fnNameOrObject, overrides)) -// `test.debug` is used to apply `debug: true` to all DSTest contract method calls in the test -test.debug = (name, fnNameOrObject, overrides = {}) => it.only(name, buildTestFn(fnNameOrObject, overrides, true)) -// `test.skip` is used very similarly to `it.skip` from Mocha, it will skip all tests marked with `test.skip` -test.skip = (name, fnNameOrObject, overrides = {}) => it.skip(name, buildTestFn(fnNameOrObject, overrides)) - -// `buildTestFn` is a higher-order function. It returns a function that can be used as the test function for `it` -const buildTestFn = (fnNameOrObject: FnNameOrObject, overrides = {}, debug = false) => { - // normalize the input to an array of objects - const fnObjects: MethodWithDebugAndOverrides[] = (Array.isArray(fnNameOrObject) ? fnNameOrObject : [fnNameOrObject]).map(fnNameOrObject => { - fnNameOrObject = typeof fnNameOrObject === 'string' ? { method: fnNameOrObject } : fnNameOrObject - // assign all default values and overrides - fnNameOrObject.overrides = Object.assign({}, overrides, fnNameOrObject.overrides ?? {}) - fnNameOrObject.debug = fnNameOrObject.debug ?? debug - fnNameOrObject.shouldFail = fnNameOrObject.shouldFail ?? false - - return fnNameOrObject as MethodWithDebugAndOverrides - }) - - // only `step_` prefixed functions can be called on the `DSTest` contracts to clearly separate tests and helpers - assert(fnObjects.every(({ method }) => method.startsWith('step_')), "Solidity test functions must be prefixed with 'step_'") - - // return the test function that will be used by `it` - // this function must be defined with the `function` keyword so that `this` is bound to the Mocha context - return async function () { - // `Array.prototype.reduce` is used here to ensure that the test functions are called in order. - // Each test function waits for its predecessor to complete before starting - return fnObjects.reduce((p: Promise, fn) => p.then(async () => { - const contract = fn.overrides.from - ? this.testContract.connect(await ethers.getSigner(fn.overrides.from)) - : this.testContract - const tx = await contract[fn.method](fn.overrides).catch(err => { - if (fn.shouldFail) { - if (fn.debug){ - console.error(`smart contract call failed with error:\n${err}\n`) - } - - return { failed: true } - } - - console.error("smart contract call failed with error:", err) - throw err - }) - - // no more assertions necessary if the method-call should fail and did fail - if (tx.failed && fn.shouldFail) return - - const txReceipt = await tx.wait().catch(err => { - if (fn.debug) console.error(`tx failed with error:\n${err}\n`) - return err.receipt - }) - - // `txReceipt.status` will be `0` if the transaction failed. - // `contract.failed` will return `true` if any of the `DSTest` assertions failed. - const failed = txReceipt.status === 0 ? true : await contract.failed.staticCall() - if (fn.debug || failed) { - console.log('') - - if (!txReceipt.events) console.warn('WARNING: No parseable events found in tx-receipt\n') - - // If `DSTest` assertions failed, the contract will emit logs describing the assertion failure(s). - txReceipt - .events - ?.filter(event => fn.debug || event.event?.startsWith('log')) - .map(event => event.args?.forEach(arg => console.log(arg))) - - console.log('') - } - - assert(!failed, `${fn.method} failed`) - }), Promise.resolve()) - } -} - -export const Roles = { - None: 0, - Enabled: 1, - Admin: 2, - Manager: 3, -} diff --git a/contracts/test/warp.ts b/contracts/test/warp.ts deleted file mode 100644 index df2c29e5a1..0000000000 --- a/contracts/test/warp.ts +++ /dev/null @@ -1,40 +0,0 @@ -// (c) 2019-2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -import { expect } from "chai"; -import { ethers } from "hardhat"; -import { Contract, Signer } from "ethers"; -import { IWarpMessenger } from "typechain-types"; - -const WARP_ADDRESS = "0x0200000000000000000000000000000000000005"; -let senderAddress = process.env["SENDER_ADDRESS"]; -// Expected to be a hex string -let payload = process.env["PAYLOAD"]; -let expectedUnsignedMessage = process.env["EXPECTED_UNSIGNED_MESSAGE"]; -let sourceID = process.env["SOURCE_CHAIN_ID"]; - -describe("IWarpMessenger", function () { - let owner: Signer; - let contract: IWarpMessenger; - before(async function () { - owner = await ethers.getSigner(senderAddress); - contract = await ethers.getContractAt("IWarpMessenger", WARP_ADDRESS, owner) - }); - - it("contract should be to send warp message", async function () { - console.log(`Sending warp message with payload ${payload}, expected unsigned message ${expectedUnsignedMessage}`); - - // Get ID of payload by taking sha256 of unsigned message - let messageID = ethers.sha256(expectedUnsignedMessage); - let tx = await contract.sendWarpMessage(payload) - let receipt = await tx.wait() - await expect(receipt) - .to.emit(contract, 'SendWarpMessage') - .withArgs(senderAddress, messageID, expectedUnsignedMessage); - }) - - it("should be able to fetch correct blockchain ID", async function () { - let blockchainID = await contract.getBlockchainID(); - expect(blockchainID).to.be.equal(sourceID); - }) -}) diff --git a/contracts/tsconfig.json b/contracts/tsconfig.json deleted file mode 100644 index c94172e3bd..0000000000 --- a/contracts/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "compilerOptions": { - "outDir": "./dist/", - "baseUrl": ".", - "module": "commonjs", - "target": "es2020", - "typeRoots": ["./typings/**", "./node_modules/@types"], - "esModuleInterop": true, - "forceConsistentCasingInFileNames": true, - "skipLibCheck": true, - "resolveJsonModule": true, - }, - "exclude": ["node_modules", "artifacts", "cache"], - "typeAcquisition": { - "enable": true, - "exclude": ["bn.js"] - }, -} diff --git a/core/README.md b/core/README.md index f8b71693f5..5f623ff79e 100644 --- a/core/README.md +++ b/core/README.md @@ -10,7 +10,7 @@ When the consensus engine verifies blocks as they are ready to be issued into co InsertBlockManual verifies the block, inserts it into the state manager to track the merkle trie for the block, and adds it to the canonical chain if it extends the currently preferred chain. -Subnet-EVM adds functions for Accept and Reject, which take care of marking a block as finalized and performing garbage collection where possible. +Coreth adds functions for Accept and Reject, which take care of marking a block as finalized and performing garbage collection where possible. The consensus engine can also call `SetPreference` on a VM to tell the VM that a specific block is preferred by the consensus engine to be accepted. This triggers a call to `reorg` the blockchain and set the newly preferred block as the preferred chain. diff --git a/core/TrieStressTest.abi b/core/TrieStressTest.abi deleted file mode 100644 index 2914db4584..0000000000 --- a/core/TrieStressTest.abi +++ /dev/null @@ -1 +0,0 @@ -[{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getData","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"writeValues","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/core/TrieStressTest.bin b/core/TrieStressTest.bin deleted file mode 100644 index b8c143c38f..0000000000 --- a/core/TrieStressTest.bin +++ /dev/null @@ -1 +0,0 @@ -608060405234801561000f575f80fd5b506103938061001d5f395ff3fe608060405234801561000f575f80fd5b506004361061003f575f3560e01c80630178fe3f14610043578063514a19d614610073578063be1c766b1461008f575b5f80fd5b61005d600480360381019061005891906101c0565b6100ad565b60405161006a9190610203565b60405180910390f35b61008d600480360381019061008891906101c0565b610117565b005b61009761017e565b6040516100a4919061022b565b60405180910390f35b5f808054905082106100f4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016100eb9061029e565b60405180910390fd5b5f8281548110610107576101066102bc565b5b905f5260205f2001549050919050565b5f60603373ffffffffffffffffffffffffffffffffffffffff16901b5f1b90505f5b82811015610179575f82908060018154018082558091505060019003905f5260205f20015f9091909190915055808061017190610316565b915050610139565b505050565b5f8080549050905090565b5f80fd5b5f819050919050565b61019f8161018d565b81146101a9575f80fd5b50565b5f813590506101ba81610196565b92915050565b5f602082840312156101d5576101d4610189565b5b5f6101e2848285016101ac565b91505092915050565b5f819050919050565b6101fd816101eb565b82525050565b5f6020820190506102165f8301846101f4565b92915050565b6102258161018d565b82525050565b5f60208201905061023e5f83018461021c565b92915050565b5f82825260208201905092915050565b7f496e646578206f7574206f6620626f756e6400000000000000000000000000005f82015250565b5f610288601283610244565b915061029382610254565b602082019050919050565b5f6020820190508181035f8301526102b58161027c565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6103208261018d565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610352576103516102e9565b5b60018201905091905056fea26469706673582212206a77a7e1e9aaf41a68d7c07cec79019e175a15c152db425e1c143d7cfd7da65e64736f6c63430008150033 \ No newline at end of file diff --git a/core/TrieStressTest.sol b/core/TrieStressTest.sol deleted file mode 100644 index 84dca42f97..0000000000 --- a/core/TrieStressTest.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -contract TrieStressTest { - bytes32[] private data; - - function writeValues(uint value) public { - bytes32 dataToPush = bytes32(uint256(uint160(msg.sender)) << 96); - for (uint i = 0; i < value; i++) { - data.push(dataToPush); - } - } - - function getLength() public view returns (uint) { - return data.length; - } - - function getData(uint index) public view returns (bytes32) { - require(index < data.length, "Index out of bound"); - return data[index]; - } -} diff --git a/core/blockchain.go b/core/blockchain.go index 6a855d907c..dd0edbf535 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -39,7 +39,6 @@ import ( "sync/atomic" "time" - "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/consensus" "github.com/ava-labs/subnet-evm/consensus/misc/eip4844" "github.com/ava-labs/subnet-evm/core/rawdb" @@ -84,14 +83,12 @@ var ( blockValidationTimer = metrics.NewRegisteredCounter("chain/block/validations/state", nil) blockWriteTimer = metrics.NewRegisteredCounter("chain/block/writes", nil) - acceptorQueueGauge = metrics.NewRegisteredGauge("chain/acceptor/queue/size", nil) - acceptorWorkTimer = metrics.NewRegisteredCounter("chain/acceptor/work", nil) - acceptorWorkCount = metrics.NewRegisteredCounter("chain/acceptor/work/count", nil) - lastAcceptedBlockBaseFeeGauge = metrics.NewRegisteredGauge("chain/block/fee/basefee", nil) - blockTotalFeesGauge = metrics.NewRegisteredGauge("chain/block/fee/total", nil) - processedBlockGasUsedCounter = metrics.NewRegisteredCounter("chain/block/gas/used/processed", nil) - acceptedBlockGasUsedCounter = metrics.NewRegisteredCounter("chain/block/gas/used/accepted", nil) - badBlockCounter = metrics.NewRegisteredCounter("chain/block/bad/count", nil) + acceptorQueueGauge = metrics.NewRegisteredGauge("chain/acceptor/queue/size", nil) + acceptorWorkTimer = metrics.NewRegisteredCounter("chain/acceptor/work", nil) + acceptorWorkCount = metrics.NewRegisteredCounter("chain/acceptor/work/count", nil) + processedBlockGasUsedCounter = metrics.NewRegisteredCounter("chain/block/gas/used/processed", nil) + acceptedBlockGasUsedCounter = metrics.NewRegisteredCounter("chain/block/gas/used/accepted", nil) + badBlockCounter = metrics.NewRegisteredCounter("chain/block/bad/count", nil) txUnindexTimer = metrics.NewRegisteredCounter("chain/txs/unindex", nil) acceptedTxsCounter = metrics.NewRegisteredCounter("chain/txs/accepted", nil) @@ -109,13 +106,11 @@ var ( ) const ( - bodyCacheLimit = 256 - blockCacheLimit = 256 - receiptsCacheLimit = 32 - txLookupCacheLimit = 1024 - feeConfigCacheLimit = 256 - coinbaseConfigCacheLimit = 256 - badBlockLimit = 10 + bodyCacheLimit = 256 + blockCacheLimit = 256 + receiptsCacheLimit = 32 + txLookupCacheLimit = 1024 + badBlockLimit = 10 // BlockChainVersion ensures that an incompatible database forces a resync from scratch. // @@ -151,20 +146,6 @@ const ( trieCleanCacheStatsNamespace = "hashdb/memcache/clean/fastcache" ) -// cacheableFeeConfig encapsulates fee configuration itself and the block number that it has changed at, -// in order to cache them together. -type cacheableFeeConfig struct { - feeConfig commontype.FeeConfig - lastChangedAt *big.Int -} - -// cacheableCoinbaseConfig encapsulates coinbase address itself and allowFeeRecipient flag, -// in order to cache them together. -type cacheableCoinbaseConfig struct { - coinbaseAddress common.Address - allowFeeRecipients bool -} - // CacheConfig contains the configuration values for the trie database // and state snapshot these are resident in a blockchain. type CacheConfig struct { @@ -183,7 +164,7 @@ type CacheConfig struct { SnapshotVerify bool // Verify generated snapshots Preimages bool // Whether to store preimage of trie key to the disk AcceptedCacheSize int // Depth of accepted headers cache and accepted logs cache at the accepted tip - TransactionHistory uint64 // Number of recent blocks for which to maintain transaction lookup indices + TxLookupLimit uint64 // Number of recent blocks for which to maintain transaction lookup indices SkipTxIndexing bool // Whether to skip transaction indexing StateHistory uint64 // Number of blocks from head whose state histories are reserved. StateScheme string // Scheme used to store ethereum states and merkle tree nodes on top @@ -277,13 +258,11 @@ type BlockChain struct { currentBlock atomic.Pointer[types.Header] // Current head of the block chain - bodyCache *lru.Cache[common.Hash, *types.Body] // Cache for the most recent block bodies - receiptsCache *lru.Cache[common.Hash, []*types.Receipt] // Cache for the most recent receipts per block - blockCache *lru.Cache[common.Hash, *types.Block] // Cache for the most recent entire blocks - txLookupCache *lru.Cache[common.Hash, *rawdb.LegacyTxLookupEntry] // Cache for the most recent transaction lookup data. - badBlocks *lru.Cache[common.Hash, *badBlock] // Cache for bad blocks - feeConfigCache *lru.Cache[common.Hash, *cacheableFeeConfig] // Cache for the most recent feeConfig lookup data. - coinbaseConfigCache *lru.Cache[common.Hash, *cacheableCoinbaseConfig] // Cache for the most recent coinbaseConfig lookup data. + bodyCache *lru.Cache[common.Hash, *types.Body] // Cache for the most recent block bodies + receiptsCache *lru.Cache[common.Hash, []*types.Receipt] // Cache for the most recent receipts per block + blockCache *lru.Cache[common.Hash, *types.Block] // Cache for the most recent entire blocks + txLookupCache *lru.Cache[common.Hash, *rawdb.LegacyTxLookupEntry] // Cache for the most recent transaction lookup data. + badBlocks *lru.Cache[common.Hash, *badBlock] // Cache for bad blocks stopping atomic.Bool // false if chain is running, true when stopped @@ -372,23 +351,21 @@ func NewBlockChain( log.Info("") bc := &BlockChain{ - chainConfig: chainConfig, - cacheConfig: cacheConfig, - db: db, - triedb: triedb, - bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit), - receiptsCache: lru.NewCache[common.Hash, []*types.Receipt](receiptsCacheLimit), - blockCache: lru.NewCache[common.Hash, *types.Block](blockCacheLimit), - txLookupCache: lru.NewCache[common.Hash, *rawdb.LegacyTxLookupEntry](txLookupCacheLimit), - badBlocks: lru.NewCache[common.Hash, *badBlock](badBlockLimit), - feeConfigCache: lru.NewCache[common.Hash, *cacheableFeeConfig](feeConfigCacheLimit), - coinbaseConfigCache: lru.NewCache[common.Hash, *cacheableCoinbaseConfig](coinbaseConfigCacheLimit), - engine: engine, - vmConfig: vmConfig, - senderCacher: NewTxSenderCacher(runtime.NumCPU()), - acceptorQueue: make(chan *types.Block, cacheConfig.AcceptorQueueLimit), - quit: make(chan struct{}), - acceptedLogsCache: NewFIFOCache[common.Hash, [][]*types.Log](cacheConfig.AcceptedCacheSize), + chainConfig: chainConfig, + cacheConfig: cacheConfig, + db: db, + triedb: triedb, + bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit), + receiptsCache: lru.NewCache[common.Hash, []*types.Receipt](receiptsCacheLimit), + blockCache: lru.NewCache[common.Hash, *types.Block](blockCacheLimit), + txLookupCache: lru.NewCache[common.Hash, *rawdb.LegacyTxLookupEntry](txLookupCacheLimit), + badBlocks: lru.NewCache[common.Hash, *badBlock](badBlockLimit), + engine: engine, + vmConfig: vmConfig, + senderCacher: NewTxSenderCacher(runtime.NumCPU()), + acceptorQueue: make(chan *types.Block, cacheConfig.AcceptorQueueLimit), + quit: make(chan struct{}), + acceptedLogsCache: NewFIFOCache[common.Hash, [][]*types.Log](cacheConfig.AcceptedCacheSize), } bc.stateCache = state.NewDatabaseWithNodeDB(bc.db, bc.triedb) bc.validator = NewBlockValidator(chainConfig, bc, engine) @@ -448,7 +425,7 @@ func NewBlockChain( bc.warmAcceptedCaches() // if txlookup limit is 0 (uindexing disabled), we don't need to repair the tx index tail. - if bc.cacheConfig.TransactionHistory != 0 { + if bc.cacheConfig.TxLookupLimit != 0 { latestStateSynced := rawdb.GetLatestSyncPerformed(bc.db) bc.setTxIndexTail(latestStateSynced) } @@ -457,7 +434,7 @@ func NewBlockChain( go bc.startAcceptor() // Start tx indexer/unindexer if required. - if bc.cacheConfig.TransactionHistory != 0 { + if bc.cacheConfig.TxLookupLimit != 0 { bc.wg.Add(1) var ( headCh = make(chan ChainEvent, 1) // Buffered to avoid locking up the event feed @@ -480,7 +457,7 @@ func NewBlockChain( // unindexBlocks unindexes transactions depending on user configuration func (bc *BlockChain) unindexBlocks(tail uint64, head uint64, done chan struct{}) { start := time.Now() - txLookupLimit := bc.cacheConfig.TransactionHistory + txLookupLimit := bc.cacheConfig.TxLookupLimit bc.txIndexTailLock.Lock() defer func() { txUnindexTimer.Inc(time.Since(start).Milliseconds()) @@ -506,7 +483,7 @@ func (bc *BlockChain) unindexBlocks(tail uint64, head uint64, done chan struct{} // Invariant: If TxLookupLimit is 0, it means all tx indices will be preserved. // Meaning that this function should never be called. func (bc *BlockChain) maintainTxIndex(headCh <-chan ChainEvent) { - txLookupLimit := bc.cacheConfig.TransactionHistory + txLookupLimit := bc.cacheConfig.TxLookupLimit // If the user just upgraded to a new version which supports transaction // index pruning, write the new tail and remove anything older. @@ -952,7 +929,7 @@ func (bc *BlockChain) ValidateCanonicalChain() error { // that the transactions have been indexed, if we are checking below the last accepted // block. shouldIndexTxs := !bc.cacheConfig.SkipTxIndexing && - (bc.cacheConfig.TransactionHistory == 0 || bc.lastAccepted.NumberU64() < current.Number.Uint64()+bc.cacheConfig.TransactionHistory) + (bc.cacheConfig.TxLookupLimit == 0 || bc.lastAccepted.NumberU64() < current.Number.Uint64()+bc.cacheConfig.TxLookupLimit) if current.Number.Uint64() <= bc.lastAccepted.NumberU64() && shouldIndexTxs { // Ensure that all of the transactions have been stored correctly in the canonical // chain @@ -1163,47 +1140,9 @@ func (bc *BlockChain) Accept(block *types.Block) error { bc.addAcceptorQueue(block) acceptedBlockGasUsedCounter.Inc(int64(block.GasUsed())) acceptedTxsCounter.Inc(int64(len(block.Transactions()))) - if baseFee := block.BaseFee(); baseFee != nil { - lastAcceptedBlockBaseFeeGauge.Update(baseFee.Int64()) - } - total, err := TotalFees(block, bc.GetReceiptsByHash(block.Hash())) - if err != nil { - log.Error(fmt.Sprintf("TotalFees error: %s", err)) - } else { - blockTotalFeesGauge.Update(total.Int64()) - } return nil } -// TotalFees computes total consumed fees in wei. Block transactions and receipts have to have the same order. -func TotalFees(block *types.Block, receipts []*types.Receipt) (*big.Int, error) { - baseFee := block.BaseFee() - feesWei := new(big.Int) - if len(block.Transactions()) != len(receipts) { - return nil, errors.New("mismatch between total number of transactions and receipts") - } - for i, tx := range block.Transactions() { - var minerFee *big.Int - if baseFee == nil { - // legacy block, no baseFee - minerFee = tx.GasPrice() - } else { - minerFee = new(big.Int).Add(baseFee, tx.EffectiveGasTipValue(baseFee)) - } - feesWei.Add(feesWei, new(big.Int).Mul(new(big.Int).SetUint64(receipts[i].GasUsed), minerFee)) - } - return feesWei, nil -} - -// TotalFees computes total consumed fees in ether. Block transactions and receipts have to have the same order. -func TotalFeesFloat(block *types.Block, receipts []*types.Receipt) (*big.Float, error) { - total, err := TotalFees(block, receipts) - if err != nil { - return nil, err - } - return new(big.Float).Quo(new(big.Float).SetInt(total), new(big.Float).SetInt(big.NewInt(params.Ether))), nil -} - func (bc *BlockChain) Reject(block *types.Block) error { bc.chainmu.Lock() defer bc.chainmu.Unlock() @@ -2221,7 +2160,7 @@ func (bc *BlockChain) ResetToStateSyncedBlock(block *types.Block) error { } // if txlookup limit is 0 (uindexing disabled), we don't need to repair the tx index tail. - if bc.cacheConfig.TransactionHistory != 0 { + if bc.cacheConfig.TxLookupLimit != 0 { bc.setTxIndexTail(block.NumberU64()) } diff --git a/core/blockchain_log_test.go b/core/blockchain_log_test.go index fa16293634..1fbf4c04c4 100644 --- a/core/blockchain_log_test.go +++ b/core/blockchain_log_test.go @@ -43,7 +43,7 @@ func TestAcceptedLogsSubscription(t *testing.T) { gspec = &Genesis{ Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: funds}}, - BaseFee: big.NewInt(params.TestInitialBaseFee), + BaseFee: big.NewInt(params.ApricotPhase3InitialBaseFee), } contractAddress = crypto.CreateAddress(addr1, 0) signer = types.LatestSigner(gspec.Config) @@ -59,13 +59,13 @@ func TestAcceptedLogsSubscription(t *testing.T) { switch i { case 0: // First, we deploy the contract - contractTx := types.NewContractCreation(0, common.Big0, 200000, big.NewInt(params.TestInitialBaseFee), common.FromHex(callableBin)) + contractTx := types.NewContractCreation(0, common.Big0, 200000, big.NewInt(params.ApricotPhase3InitialBaseFee), common.FromHex(callableBin)) contractSignedTx, err := types.SignTx(contractTx, signer, key1) require.NoError(err) b.AddTx(contractSignedTx) case 1: // In the next block, we call the contract function - tx := types.NewTransaction(1, contractAddress, common.Big0, 23000, big.NewInt(params.TestInitialBaseFee), packedFunction) + tx := types.NewTransaction(1, contractAddress, common.Big0, 23000, big.NewInt(params.ApricotPhase3InitialBaseFee), packedFunction) tx, err := types.SignTx(tx, signer, key1) require.NoError(err) b.AddTx(tx) diff --git a/core/blockchain_reader.go b/core/blockchain_reader.go index 554c20e730..51dd3fc309 100644 --- a/core/blockchain_reader.go +++ b/core/blockchain_reader.go @@ -27,19 +27,13 @@ package core import ( - "math/big" - - "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/consensus" - "github.com/ava-labs/subnet-evm/constants" "github.com/ava-labs/subnet-evm/core/rawdb" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/core/state/snapshot" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile/contracts/feemanager" - "github.com/ava-labs/subnet-evm/precompile/contracts/rewardmanager" "github.com/ava-labs/subnet-evm/trie" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/event" @@ -338,74 +332,6 @@ func (bc *BlockChain) SubscribeAcceptedTransactionEvent(ch chan<- NewTxsEvent) e return bc.scope.Track(bc.txAcceptedFeed.Subscribe(ch)) } -// GetFeeConfigAt returns the fee configuration and the last changed block number at [parent]. -// If FeeManager is activated at [parent], returns the fee config in the precompile contract state. -// Otherwise returns the fee config in the chain config. -// Assumes that a valid configuration is stored when the precompile is activated. -func (bc *BlockChain) GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error) { - config := bc.Config() - if !config.IsPrecompileEnabled(feemanager.ContractAddress, parent.Time) { - return config.FeeConfig, common.Big0, nil - } - - // try to return it from the cache - if cached, hit := bc.feeConfigCache.Get(parent.Root); hit { - return cached.feeConfig, cached.lastChangedAt, nil - } - - stateDB, err := bc.StateAt(parent.Root) - if err != nil { - return commontype.EmptyFeeConfig, nil, err - } - - storedFeeConfig := feemanager.GetStoredFeeConfig(stateDB) - // this should not return an invalid fee config since it's assumed that - // StoreFeeConfig returns an error when an invalid fee config is attempted to be stored. - // However an external stateDB call can modify the contract state. - // This check is added to add a defense in-depth. - if err := storedFeeConfig.Verify(); err != nil { - return commontype.EmptyFeeConfig, nil, err - } - lastChangedAt := feemanager.GetFeeConfigLastChangedAt(stateDB) - cacheable := &cacheableFeeConfig{feeConfig: storedFeeConfig, lastChangedAt: lastChangedAt} - // add it to the cache - bc.feeConfigCache.Add(parent.Root, cacheable) - return storedFeeConfig, lastChangedAt, nil -} - -// GetCoinbaseAt returns the configured coinbase address at [parent]. -// If RewardManager is activated at [parent], returns the reward manager config in the precompile contract state. -// If fee recipients are allowed, returns true in the second return value. -func (bc *BlockChain) GetCoinbaseAt(parent *types.Header) (common.Address, bool, error) { - config := bc.Config() - if !config.IsSubnetEVM(parent.Time) { - return constants.BlackholeAddr, false, nil - } - - if !config.IsPrecompileEnabled(rewardmanager.ContractAddress, parent.Time) { - if bc.chainConfig.AllowFeeRecipients { - return common.Address{}, true, nil - } else { - return constants.BlackholeAddr, false, nil - } - } - - // try to return it from the cache - if cached, hit := bc.coinbaseConfigCache.Get(parent.Root); hit { - return cached.coinbaseAddress, cached.allowFeeRecipients, nil - } - - stateDB, err := bc.StateAt(parent.Root) - if err != nil { - return common.Address{}, false, err - } - rewardAddress, feeRecipients := rewardmanager.GetStoredRewardAddress(stateDB) - - cacheable := &cacheableCoinbaseConfig{coinbaseAddress: rewardAddress, allowFeeRecipients: feeRecipients} - bc.coinbaseConfigCache.Add(parent.Root, cacheable) - return rewardAddress, feeRecipients, nil -} - // GetLogs fetches all logs from a given block. func (bc *BlockChain) GetLogs(hash common.Hash, number uint64) [][]*types.Log { logs, ok := bc.acceptedLogsCache.Get(hash) // this cache is thread-safe diff --git a/core/blockchain_repair_test.go b/core/blockchain_repair_test.go index f124b0efaa..c7b77a27b9 100644 --- a/core/blockchain_repair_test.go +++ b/core/blockchain_repair_test.go @@ -527,15 +527,13 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s defer db.Close() // Might double close, should be fine // Initialize a fresh chain - chainConfig := *params.TestChainConfig - chainConfig.FeeConfig.MinBaseFee = big.NewInt(1) var ( require = require.New(t) key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") addr1 = crypto.PubkeyToAddress(key1.PublicKey) gspec = &Genesis{ - BaseFee: chainConfig.FeeConfig.MinBaseFee, - Config: &chainConfig, + BaseFee: big.NewInt(params.ApricotPhase3InitialBaseFee), + Config: params.TestChainConfig, Alloc: GenesisAlloc{addr1: {Balance: big.NewInt(params.Ether)}}, } signer = types.LatestSigner(gspec.Config) @@ -567,7 +565,7 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s gspec.MustCommit(genDb, trie.NewDatabase(genDb, nil)) sideblocks, _, err = GenerateChain(gspec.Config, gspec.ToBlock(), engine, genDb, tt.sidechainBlocks, 10, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{0x01}) - tx, err := types.SignTx(types.NewTransaction(b.TxNonce(addr1), common.Address{0x01}, big.NewInt(10000), params.TxGas, common.Big1, nil), signer, key1) + tx, err := types.SignTx(types.NewTransaction(b.TxNonce(addr1), common.Address{0x01}, big.NewInt(10000), params.TxGas, dummy.ApricotPhase3InitialBaseFee, nil), signer, key1) require.NoError(err) b.AddTx(tx) }) @@ -581,7 +579,7 @@ func testRepairWithScheme(t *testing.T, tt *rewindTest, snapshots bool, scheme s canonblocks, _, err := GenerateChain(gspec.Config, gspec.ToBlock(), engine, genDb, tt.canonicalBlocks, 10, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{0x02}) b.SetDifficulty(big.NewInt(1000000)) - tx, err := types.SignTx(types.NewTransaction(b.TxNonce(addr1), common.Address{0x02}, big.NewInt(10000), params.TxGas, common.Big1, nil), signer, key1) + tx, err := types.SignTx(types.NewTransaction(b.TxNonce(addr1), common.Address{0x02}, big.NewInt(10000), params.TxGas, dummy.ApricotPhase3InitialBaseFee, nil), signer, key1) require.NoError(err) b.AddTx(tx) }) diff --git a/core/blockchain_snapshot_test.go b/core/blockchain_snapshot_test.go index 9b5cea4aa1..c757f964e6 100644 --- a/core/blockchain_snapshot_test.go +++ b/core/blockchain_snapshot_test.go @@ -84,7 +84,7 @@ func (basic *snapshotTestBasic) prepare(t *testing.T) (*BlockChain, []*types.Blo // Initialize a fresh chain var ( gspec = &Genesis{ - BaseFee: big.NewInt(params.TestInitialBaseFee), + BaseFee: big.NewInt(params.ApricotPhase3InitialBaseFee), Config: params.TestChainConfig, } engine = dummy.NewFullFaker() diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 8fad072f9e..a3102da308 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -9,7 +9,6 @@ import ( "os" "testing" - "github.com/ava-labs/avalanchego/upgrade" "github.com/ava-labs/subnet-evm/consensus/dummy" "github.com/ava-labs/subnet-evm/core/rawdb" "github.com/ava-labs/subnet-evm/core/state" @@ -62,7 +61,7 @@ func createBlockChain( db, cacheConfig, gspec, - dummy.NewCoinbaseFaker(), + dummy.NewFakerWithCallbacks(TestCallbacks), vm.Config{}, lastAcceptedHash, false, @@ -270,9 +269,6 @@ func TestBlockChainOfflinePruningUngracefulShutdown(t *testing.T) { } // get the target root to prune to before stopping the blockchain targetRoot := blockchain.LastAcceptedBlock().Root() - if targetRoot == types.EmptyRootHash { - return blockchain, nil - } blockchain.Stop() tempDir := t.TempDir() @@ -313,7 +309,7 @@ func testRepopulateMissingTriesParallel(t *testing.T, parallelism int) { // Ensure that key1 has some funds in the genesis block. genesisBalance := big.NewInt(1000000) gspec := &Genesis{ - Config: ¶ms.ChainConfig{HomesteadBlock: new(big.Int), FeeConfig: params.DefaultFeeConfig}, + Config: ¶ms.ChainConfig{HomesteadBlock: new(big.Int)}, Alloc: GenesisAlloc{addr1: {Balance: genesisBalance}}, } @@ -426,7 +422,7 @@ func TestUngracefulAsyncShutdown(t *testing.T) { // Ensure that key1 has some funds in the genesis block. genesisBalance := big.NewInt(1000000) gspec := &Genesis{ - Config: ¶ms.ChainConfig{HomesteadBlock: new(big.Int), FeeConfig: params.DefaultFeeConfig}, + Config: ¶ms.ChainConfig{HomesteadBlock: new(big.Int)}, Alloc: GenesisAlloc{addr1: {Balance: genesisBalance}}, } @@ -553,14 +549,14 @@ func TestTransactionIndices(t *testing.T) { } signer = types.LatestSigner(gspec.Config) ) - genDb, blocks, _, err := GenerateChainWithGenesis(gspec, dummy.NewFaker(), 128, 10, func(i int, block *BlockGen) { + genDb, blocks, _, err := GenerateChainWithGenesis(gspec, dummy.NewFakerWithCallbacks(TestCallbacks), 128, 10, func(i int, block *BlockGen) { tx, err := types.SignTx(types.NewTransaction(block.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1) require.NoError(err) block.AddTx(tx) }) require.NoError(err) - blocks2, _, err := GenerateChain(gspec.Config, blocks[len(blocks)-1], dummy.NewFaker(), genDb, 10, 10, func(i int, block *BlockGen) { + blocks2, _, err := GenerateChain(gspec.Config, blocks[len(blocks)-1], dummy.NewFakerWithCallbacks(TestCallbacks), genDb, 10, 10, func(i int, block *BlockGen) { tx, err := types.SignTx(types.NewTransaction(block.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1) require.NoError(err) block.AddTx(tx) @@ -610,7 +606,7 @@ func TestTransactionIndices(t *testing.T) { } for i, l := range limits { t.Run(fmt.Sprintf("test-%d, limit: %d", i+1, l), func(t *testing.T) { - conf.TransactionHistory = l + conf.TxLookupLimit = l chain, err := createBlockChain(chainDB, conf, gspec, lastAcceptedBlock.Hash()) require.NoError(err) @@ -664,14 +660,14 @@ func TestTransactionSkipIndexing(t *testing.T) { } signer = types.LatestSigner(gspec.Config) ) - genDb, blocks, _, err := GenerateChainWithGenesis(gspec, dummy.NewCoinbaseFaker(), 5, 10, func(i int, block *BlockGen) { + genDb, blocks, _, err := GenerateChainWithGenesis(gspec, dummy.NewFakerWithCallbacks(TestCallbacks), 5, 10, func(i int, block *BlockGen) { tx, err := types.SignTx(types.NewTransaction(block.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1) require.NoError(err) block.AddTx(tx) }) require.NoError(err) - blocks2, _, err := GenerateChain(gspec.Config, blocks[len(blocks)-1], dummy.NewCoinbaseFaker(), genDb, 5, 10, func(i int, block *BlockGen) { + blocks2, _, err := GenerateChain(gspec.Config, blocks[len(blocks)-1], dummy.NewFakerWithCallbacks(TestCallbacks), genDb, 5, 10, func(i int, block *BlockGen) { tx, err := types.SignTx(types.NewTransaction(block.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1) require.NoError(err) block.AddTx(tx) @@ -702,19 +698,19 @@ func TestTransactionSkipIndexing(t *testing.T) { chain.Stop() // test2: specify lookuplimit with tx index skipping enabled. Blocks should not be indexed but tail should be updated. - conf.TransactionHistory = 2 + conf.TxLookupLimit = 2 chainDB = rawdb.NewMemoryDatabase() chain, err = createAndInsertChain(chainDB, conf, gspec, blocks, common.Hash{}, func(b *types.Block) { bNumber := b.NumberU64() - tail := bNumber - conf.TransactionHistory + 1 + tail := bNumber - conf.TxLookupLimit + 1 checkTxIndicesHelper(t, &tail, bNumber+1, bNumber+1, bNumber, chainDB, false) // check all indices has been skipped }) require.NoError(err) chain.Stop() // test3: tx index skipping and unindexer disabled. Blocks should be indexed and tail should be updated. - conf.TransactionHistory = 0 + conf.TxLookupLimit = 0 conf.SkipTxIndexing = false chainDB = rawdb.NewMemoryDatabase() chain, err = createAndInsertChain(chainDB, conf, gspec, blocks, common.Hash{}, @@ -727,12 +723,12 @@ func TestTransactionSkipIndexing(t *testing.T) { // now change tx index skipping to true and check that the indices are skipped for the last block // and old indices are removed up to the tail, but [tail, current) indices are still there. - conf.TransactionHistory = 2 + conf.TxLookupLimit = 2 conf.SkipTxIndexing = true chain, err = createAndInsertChain(chainDB, conf, gspec, blocks2[0:1], chain.CurrentHeader().Hash(), func(b *types.Block) { bNumber := b.NumberU64() - tail := bNumber - conf.TransactionHistory + 1 + tail := bNumber - conf.TxLookupLimit + 1 checkTxIndicesHelper(t, &tail, tail, bNumber-1, bNumber, chainDB, false) }) require.NoError(err) @@ -786,7 +782,7 @@ func testCanonicalHashMarker(t *testing.T, scheme string) { gspec = &Genesis{ Config: params.TestChainConfig, Alloc: GenesisAlloc{}, - BaseFee: big.NewInt(params.TestInitialBaseFee), + BaseFee: big.NewInt(params.ApricotPhase3InitialBaseFee), } engine = dummy.NewCoinbaseFaker() ) @@ -866,7 +862,7 @@ func TestTxLookupBlockChain(t *testing.T) { SnapshotLimit: 256, SnapshotNoBuild: true, // Ensure the test errors if snapshot initialization fails AcceptorQueueLimit: 64, // ensure channel doesn't block - TransactionHistory: 5, + TxLookupLimit: 5, } createTxLookupBlockChain := func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) { return createBlockChain(db, cacheConf, gspec, lastAcceptedHash) @@ -889,7 +885,7 @@ func TestTxLookupSkipIndexingBlockChain(t *testing.T) { SnapshotLimit: 256, SnapshotNoBuild: true, // Ensure the test errors if snapshot initialization fails AcceptorQueueLimit: 64, // ensure channel doesn't block - TransactionHistory: 5, + TxLookupLimit: 5, SkipTxIndexing: true, } createTxLookupBlockChain := func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error) { @@ -905,7 +901,7 @@ func TestTxLookupSkipIndexingBlockChain(t *testing.T) { func TestCreateThenDeletePreByzantium(t *testing.T) { // We want to use pre-byzantium rules where we have intermediate state roots // between transactions. - config := *params.TestPreSubnetEVMChainConfig + config := *params.TestLaunchConfig config.ByzantiumBlock = nil config.ConstantinopleBlock = nil config.PetersburgBlock = nil @@ -1216,8 +1212,7 @@ func TestEIP3651(t *testing.T) { addr2 = crypto.PubkeyToAddress(key2.PublicKey) funds = new(big.Int).Mul(common.Big1, big.NewInt(params.Ether)) gspec = &Genesis{ - Config: params.TestChainConfig, - Timestamp: uint64(upgrade.InitiallyActiveTime.Unix()), + Config: params.TestChainConfig, Alloc: GenesisAlloc{ addr1: {Balance: funds}, addr2: {Balance: funds}, diff --git a/core/chain_makers.go b/core/chain_makers.go index e34330d44f..4c7d8226dd 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -30,11 +30,9 @@ import ( "fmt" "math/big" - "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/consensus" "github.com/ava-labs/subnet-evm/consensus/dummy" "github.com/ava-labs/subnet-evm/consensus/misc/eip4844" - "github.com/ava-labs/subnet-evm/constants" "github.com/ava-labs/subnet-evm/core/rawdb" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/core/types" @@ -372,28 +370,31 @@ func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int, func (cm *chainMaker) makeHeader(parent *types.Block, gap uint64, state *state.StateDB, engine consensus.Engine) *types.Header { time := parent.Time() + gap // block time is fixed at [gap] seconds + + var gasLimit uint64 + if cm.config.IsCortina(time) { + gasLimit = params.CortinaGasLimit + } else if cm.config.IsApricotPhase1(time) { + gasLimit = params.ApricotPhase1GasLimit + } else { + gasLimit = CalcGasLimit(parent.GasUsed(), parent.GasLimit(), parent.GasLimit(), parent.GasLimit()) + } + header := &types.Header{ Root: state.IntermediateRoot(cm.config.IsEIP158(parent.Number())), ParentHash: parent.Hash(), Coinbase: parent.Coinbase(), Difficulty: engine.CalcDifficulty(cm, time, parent.Header()), - GasLimit: parent.GasLimit(), + GasLimit: gasLimit, Number: new(big.Int).Add(parent.Number(), common.Big1), Time: time, } - if cm.config.IsSubnetEVM(time) { - feeConfig, _, err := cm.GetFeeConfigAt(parent.Header()) + if cm.config.IsApricotPhase3(time) { + var err error + header.Extra, header.BaseFee, err = dummy.CalcBaseFee(cm.config, parent.Header(), time) if err != nil { panic(err) } - - header.GasLimit = feeConfig.GasLimit.Uint64() - header.Extra, header.BaseFee, err = dummy.CalcBaseFee(cm.config, feeConfig, parent.Header(), time) - if err != nil { - panic(err) - } - } else { - header.GasLimit = CalcGasLimit(parent.GasUsed(), parent.GasLimit(), parent.GasLimit(), parent.GasLimit()) } if cm.config.IsCancun(header.Number, header.Time) { var ( @@ -491,11 +492,3 @@ func (cm *chainMaker) GetHeader(hash common.Hash, number uint64) *types.Header { func (cm *chainMaker) GetBlock(hash common.Hash, number uint64) *types.Block { return cm.blockByNumber(number) } - -func (cm *chainMaker) GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error) { - return cm.config.FeeConfig, nil, nil -} - -func (cm *chainMaker) GetCoinbaseAt(parent *types.Header) (common.Address, bool, error) { - return constants.BlackholeAddr, cm.config.AllowFeeRecipients, nil -} diff --git a/core/evm.go b/core/evm.go index 326a530af4..c50fd99ea4 100644 --- a/core/evm.go +++ b/core/evm.go @@ -97,17 +97,19 @@ func newEVMBlockContext(header *types.Header, chain ChainContext, author *common blobBaseFee = eip4844.CalcBlobFee(*header.ExcessBlobGas) } return vm.BlockContext{ - CanTransfer: CanTransfer, - Transfer: Transfer, - GetHash: GetHashFn(header, chain), - PredicateResults: predicateResults, - Coinbase: beneficiary, - BlockNumber: new(big.Int).Set(header.Number), - Time: header.Time, - Difficulty: new(big.Int).Set(header.Difficulty), - BaseFee: baseFee, - BlobBaseFee: blobBaseFee, - GasLimit: header.GasLimit, + CanTransfer: CanTransfer, + CanTransferMC: CanTransferMC, + Transfer: Transfer, + TransferMultiCoin: TransferMultiCoin, + GetHash: GetHashFn(header, chain), + PredicateResults: predicateResults, + Coinbase: beneficiary, + BlockNumber: new(big.Int).Set(header.Number), + Time: header.Time, + Difficulty: new(big.Int).Set(header.Difficulty), + BaseFee: baseFee, + BlobBaseFee: blobBaseFee, + GasLimit: header.GasLimit, } } @@ -169,8 +171,18 @@ func CanTransfer(db vm.StateDB, addr common.Address, amount *big.Int) bool { return db.GetBalance(addr).Cmp(amount) >= 0 } +func CanTransferMC(db vm.StateDB, addr common.Address, to common.Address, coinID common.Hash, amount *big.Int) bool { + return db.GetBalanceMultiCoin(addr, coinID).Cmp(amount) >= 0 +} + // Transfer subtracts amount from sender and adds amount to recipient using the given Db func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) { db.SubBalance(sender, amount) db.AddBalance(recipient, amount) } + +// Transfer subtracts amount from sender and adds amount to recipient using the given Db +func TransferMultiCoin(db vm.StateDB, sender, recipient common.Address, coinID common.Hash, amount *big.Int) { + db.SubBalanceMultiCoin(sender, coinID, amount) + db.AddBalanceMultiCoin(recipient, coinID, amount) +} diff --git a/core/gen_genesis.go b/core/gen_genesis.go index d2938b70d0..d85412d8ee 100644 --- a/core/gen_genesis.go +++ b/core/gen_genesis.go @@ -27,9 +27,6 @@ func (g Genesis) MarshalJSON() ([]byte, error) { Mixhash common.Hash `json:"mixHash"` Coinbase common.Address `json:"coinbase"` Alloc map[common.UnprefixedAddress]GenesisAccount `json:"alloc" gencodec:"required"` - AirdropHash common.Hash `json:"airdropHash"` - AirdropAmount *math.HexOrDecimal256 `json:"airdropAmount"` - AirdropData []byte `json:"-"` Number math.HexOrDecimal64 `json:"number"` GasUsed math.HexOrDecimal64 `json:"gasUsed"` ParentHash common.Hash `json:"parentHash"` @@ -52,9 +49,6 @@ func (g Genesis) MarshalJSON() ([]byte, error) { enc.Alloc[common.UnprefixedAddress(k)] = v } } - enc.AirdropHash = g.AirdropHash - enc.AirdropAmount = (*math.HexOrDecimal256)(g.AirdropAmount) - enc.AirdropData = g.AirdropData enc.Number = math.HexOrDecimal64(g.Number) enc.GasUsed = math.HexOrDecimal64(g.GasUsed) enc.ParentHash = g.ParentHash @@ -76,9 +70,6 @@ func (g *Genesis) UnmarshalJSON(input []byte) error { Mixhash *common.Hash `json:"mixHash"` Coinbase *common.Address `json:"coinbase"` Alloc map[common.UnprefixedAddress]GenesisAccount `json:"alloc" gencodec:"required"` - AirdropHash *common.Hash `json:"airdropHash"` - AirdropAmount *math.HexOrDecimal256 `json:"airdropAmount"` - AirdropData []byte `json:"-"` Number *math.HexOrDecimal64 `json:"number"` GasUsed *math.HexOrDecimal64 `json:"gasUsed"` ParentHash *common.Hash `json:"parentHash"` @@ -123,15 +114,6 @@ func (g *Genesis) UnmarshalJSON(input []byte) error { for k, v := range dec.Alloc { g.Alloc[common.Address(k)] = v } - if dec.AirdropHash != nil { - g.AirdropHash = *dec.AirdropHash - } - if dec.AirdropAmount != nil { - g.AirdropAmount = (*big.Int)(dec.AirdropAmount) - } - if dec.AirdropData != nil { - g.AirdropData = dec.AirdropData - } if dec.Number != nil { g.Number = uint64(*dec.Number) } diff --git a/core/gen_genesis_account.go b/core/gen_genesis_account.go index a9d47e6ba3..a2f503fe57 100644 --- a/core/gen_genesis_account.go +++ b/core/gen_genesis_account.go @@ -20,6 +20,7 @@ func (g GenesisAccount) MarshalJSON() ([]byte, error) { Code hexutil.Bytes `json:"code,omitempty"` Storage map[storageJSON]storageJSON `json:"storage,omitempty"` Balance *math.HexOrDecimal256 `json:"balance" gencodec:"required"` + MCBalance GenesisMultiCoinBalance `json:"mcbalance,omitempty"` Nonce math.HexOrDecimal64 `json:"nonce,omitempty"` PrivateKey hexutil.Bytes `json:"secretKey,omitempty"` } @@ -32,6 +33,7 @@ func (g GenesisAccount) MarshalJSON() ([]byte, error) { } } enc.Balance = (*math.HexOrDecimal256)(g.Balance) + enc.MCBalance = g.MCBalance enc.Nonce = math.HexOrDecimal64(g.Nonce) enc.PrivateKey = g.PrivateKey return json.Marshal(&enc) @@ -43,6 +45,7 @@ func (g *GenesisAccount) UnmarshalJSON(input []byte) error { Code *hexutil.Bytes `json:"code,omitempty"` Storage map[storageJSON]storageJSON `json:"storage,omitempty"` Balance *math.HexOrDecimal256 `json:"balance" gencodec:"required"` + MCBalance *GenesisMultiCoinBalance `json:"mcbalance,omitempty"` Nonce *math.HexOrDecimal64 `json:"nonce,omitempty"` PrivateKey *hexutil.Bytes `json:"secretKey,omitempty"` } @@ -63,6 +66,9 @@ func (g *GenesisAccount) UnmarshalJSON(input []byte) error { return errors.New("missing required field 'balance' for GenesisAccount") } g.Balance = (*big.Int)(dec.Balance) + if dec.MCBalance != nil { + g.MCBalance = *dec.MCBalance + } if dec.Nonce != nil { g.Nonce = uint64(*dec.Nonce) } diff --git a/core/genesis.go b/core/genesis.go index 1b786345a2..dbedd27961 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -33,7 +33,6 @@ import ( "errors" "fmt" "math/big" - "time" "github.com/ava-labs/subnet-evm/core/rawdb" "github.com/ava-labs/subnet-evm/core/state" @@ -44,7 +43,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" ) @@ -54,26 +52,18 @@ import ( var errGenesisNoConfig = errors.New("genesis has no chain configuration") -type Airdrop struct { - // Address strings are hex-formatted common.Address - Address common.Address `json:"address"` -} - // Genesis specifies the header fields, state of a genesis block. It also defines hard // fork switch-over blocks through the chain configuration. type Genesis struct { - Config *params.ChainConfig `json:"config"` - Nonce uint64 `json:"nonce"` - Timestamp uint64 `json:"timestamp"` - ExtraData []byte `json:"extraData"` - GasLimit uint64 `json:"gasLimit" gencodec:"required"` - Difficulty *big.Int `json:"difficulty" gencodec:"required"` - Mixhash common.Hash `json:"mixHash"` - Coinbase common.Address `json:"coinbase"` - Alloc GenesisAlloc `json:"alloc" gencodec:"required"` - AirdropHash common.Hash `json:"airdropHash"` - AirdropAmount *big.Int `json:"airdropAmount"` - AirdropData []byte `json:"-"` // provided in a separate file, not serialized in this struct. + Config *params.ChainConfig `json:"config"` + Nonce uint64 `json:"nonce"` + Timestamp uint64 `json:"timestamp"` + ExtraData []byte `json:"extraData"` + GasLimit uint64 `json:"gasLimit" gencodec:"required"` + Difficulty *big.Int `json:"difficulty" gencodec:"required"` + Mixhash common.Hash `json:"mixHash"` + Coinbase common.Address `json:"coinbase"` + Alloc GenesisAlloc `json:"alloc" gencodec:"required"` // These fields are used for consensus tests. Please don't use them // in actual genesis blocks. @@ -100,11 +90,14 @@ func (ga *GenesisAlloc) UnmarshalJSON(data []byte) error { return nil } +type GenesisMultiCoinBalance map[common.Hash]*big.Int + // GenesisAccount is an account in the state of the genesis block. type GenesisAccount struct { Code []byte `json:"code,omitempty"` Storage map[common.Hash]common.Hash `json:"storage,omitempty"` Balance *big.Int `json:"balance" gencodec:"required"` + MCBalance GenesisMultiCoinBalance `json:"mcbalance,omitempty"` Nonce uint64 `json:"nonce,omitempty"` PrivateKey []byte `json:"secretKey,omitempty"` // for tests } @@ -118,9 +111,8 @@ type genesisSpecMarshaling struct { GasUsed math.HexOrDecimal64 Number math.HexOrDecimal64 Difficulty *math.HexOrDecimal256 - Alloc map[common.UnprefixedAddress]GenesisAccount BaseFee *math.HexOrDecimal256 - AirdropAmount *math.HexOrDecimal256 + Alloc map[common.UnprefixedAddress]GenesisAccount ExcessBlobGas *math.HexOrDecimal64 BlobGasUsed *math.HexOrDecimal64 } @@ -222,13 +214,12 @@ func SetupGenesisBlock( return newcfg, common.Hash{}, err } storedcfg := rawdb.ReadChainConfig(db, stored) - // If there is no previously stored chain config, write the chain config to disk. if storedcfg == nil { - // Note: this can happen since we did not previously write the genesis block and chain config in the same batch. log.Warn("Found genesis block without chain config") rawdb.WriteChainConfig(db, stored, newcfg) return newcfg, stored, nil } + storedData, _ := json.Marshal(storedcfg) // Check config compatibility and write the config. Compatibility errors // are returned to the caller unless we're already at block zero. // we use last accepted block for cfg compatibility check. Note this allows @@ -248,15 +239,13 @@ func SetupGenesisBlock( } else { compatErr := storedcfg.CheckCompatible(newcfg, height, timestamp) if compatErr != nil && ((height != 0 && compatErr.RewindToBlock != 0) || (timestamp != 0 && compatErr.RewindToTime != 0)) { - storedData, _ := storedcfg.ToWithUpgradesJSON().MarshalJSON() - newData, _ := newcfg.ToWithUpgradesJSON().MarshalJSON() - log.Error("found mismatch between config on database vs. new config", "storedConfig", string(storedData), "newConfig", string(newData), "err", compatErr) return newcfg, stored, compatErr } } - // Required to write the chain config to disk to ensure both the chain config and upgrade bytes are persisted to disk. - // Note: this intentionally removes an extra check from upstream. - rawdb.WriteChainConfig(db, stored, newcfg) + // Don't overwrite if the old is identical to the new + if newData, _ := json.Marshal(newcfg); !bytes.Equal(storedData, newData) { + rawdb.WriteChainConfig(db, stored, newcfg) + } return newcfg, stored, nil } @@ -288,25 +277,6 @@ func (g *Genesis) toBlock(db ethdb.Database, triedb *trie.Database) *types.Block if err != nil { panic(err) } - if g.AirdropHash != (common.Hash{}) { - t := time.Now() - h := common.BytesToHash(crypto.Keccak256(g.AirdropData)) - if g.AirdropHash != h { - panic(fmt.Sprintf("expected standard allocation %s but got %s", g.AirdropHash, h)) - } - airdrop := []*Airdrop{} - if err := json.Unmarshal(g.AirdropData, &airdrop); err != nil { - panic(err) - } - for _, alloc := range airdrop { - statedb.SetBalance(alloc.Address, g.AirdropAmount) - } - log.Debug( - "applied airdrop allocation", - "hash", h, "addrs", len(airdrop), "balance", g.AirdropAmount, - "t", time.Since(t), - ) - } head := &types.Header{ Number: new(big.Int).SetUint64(g.Number), @@ -328,15 +298,18 @@ func (g *Genesis) toBlock(db ethdb.Database, triedb *trie.Database) *types.Block panic(fmt.Sprintf("unable to configure precompiles in genesis block: %v", err)) } - // Do custom allocation after airdrop in case an address shows up in standard - // allocation for addr, account := range g.Alloc { - statedb.SetBalance(addr, account.Balance) + statedb.AddBalance(addr, account.Balance) statedb.SetCode(addr, account.Code) statedb.SetNonce(addr, account.Nonce) for key, value := range account.Storage { statedb.SetState(addr, key, value) } + if account.MCBalance != nil { + for coinID, value := range account.MCBalance { + statedb.AddBalanceMultiCoin(addr, coinID, value) + } + } } root := statedb.IntermediateRoot(false) head.Root = root @@ -349,11 +322,11 @@ func (g *Genesis) toBlock(db ethdb.Database, triedb *trie.Database) *types.Block } if conf := g.Config; conf != nil { num := new(big.Int).SetUint64(g.Number) - if conf.IsSubnetEVM(g.Timestamp) { + if conf.IsApricotPhase3(g.Timestamp) { if g.BaseFee != nil { head.BaseFee = g.BaseFee } else { - head.BaseFee = new(big.Int).Set(g.Config.FeeConfig.MinBaseFee) + head.BaseFee = new(big.Int).SetInt64(params.ApricotPhase3InitialBaseFee) } } if conf.IsCancun(num, g.Timestamp) { @@ -397,16 +370,12 @@ func (g *Genesis) Commit(db ethdb.Database, triedb *trie.Database) (*types.Block if err := config.CheckConfigForkOrder(); err != nil { return nil, err } - batch := db.NewBatch() - rawdb.WriteBlock(batch, block) - rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), nil) - rawdb.WriteCanonicalHash(batch, block.Hash(), block.NumberU64()) - rawdb.WriteHeadBlockHash(batch, block.Hash()) - rawdb.WriteHeadHeaderHash(batch, block.Hash()) - rawdb.WriteChainConfig(batch, block.Hash(), config) - if err := batch.Write(); err != nil { - return nil, fmt.Errorf("failed to write genesis block: %w", err) - } + rawdb.WriteBlock(db, block) + rawdb.WriteReceipts(db, block.Hash(), block.NumberU64(), nil) + rawdb.WriteCanonicalHash(db, block.Hash(), block.NumberU64()) + rawdb.WriteHeadBlockHash(db, block.Hash()) + rawdb.WriteHeadHeaderHash(db, block.Hash()) + rawdb.WriteChainConfig(db, block.Hash(), config) return block, nil } @@ -420,29 +389,12 @@ func (g *Genesis) MustCommit(db ethdb.Database, triedb *trie.Database) *types.Bl return block } -func (g *Genesis) Verify() error { - // Make sure genesis gas limit is consistent - gasLimitConfig := g.Config.FeeConfig.GasLimit.Uint64() - if gasLimitConfig != g.GasLimit { - return fmt.Errorf( - "gas limit in fee config (%d) does not match gas limit in header (%d)", - gasLimitConfig, - g.GasLimit, - ) - } - // Verify config - if err := g.Config.Verify(); err != nil { - return err - } - return nil -} - // GenesisBlockForTesting creates and writes a block in which addr has the given wei balance. func GenesisBlockForTesting(db ethdb.Database, addr common.Address, balance *big.Int) *types.Block { g := Genesis{ Config: params.TestChainConfig, Alloc: GenesisAlloc{addr: {Balance: balance}}, - BaseFee: big.NewInt(params.TestMaxBaseFee), + BaseFee: big.NewInt(params.ApricotPhase3InitialBaseFee), } return g.MustCommit(db, trie.NewDatabase(db, trie.HashDefaults)) } diff --git a/core/genesis_test.go b/core/genesis_test.go index 4b4023e27b..3ca6952aba 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -35,19 +35,16 @@ import ( "github.com/ava-labs/subnet-evm/consensus/dummy" "github.com/ava-labs/subnet-evm/core/rawdb" - "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/contracts/deployerallowlist" + "github.com/ava-labs/subnet-evm/precompile/contracts/warp" "github.com/ava-labs/subnet-evm/trie" "github.com/ava-labs/subnet-evm/trie/triedb/pathdb" "github.com/ava-labs/subnet-evm/utils" "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -56,7 +53,7 @@ func setupGenesisBlock(db ethdb.Database, triedb *trie.Database, genesis *Genesi } func TestGenesisBlockForTesting(t *testing.T) { - genesisBlockForTestingHash := common.HexToHash("0x114ce61b50051f70768f982f7b59e82dd73b7bbd768e310c9d9f508d492e687b") + genesisBlockForTestingHash := common.HexToHash("0xb378f22ccd9ad52c6c42f5d46ef2aad6d6866cfcb778ea97a0b6dfde13387330") block := GenesisBlockForTesting(rawdb.NewMemoryDatabase(), common.Address{1}, big.NewInt(1)) if block.Hash() != genesisBlockForTestingHash { t.Errorf("wrong testing genesis hash, got %v, want %v", block.Hash(), genesisBlockForTestingHash) @@ -69,24 +66,22 @@ func TestSetupGenesis(t *testing.T) { } func testSetupGenesis(t *testing.T, scheme string) { - preSubnetConfig := *params.TestPreSubnetEVMChainConfig - preSubnetConfig.SubnetEVMTimestamp = utils.NewUint64(100) + apricotPhase1Config := *params.TestApricotPhase1Config + apricotPhase1Config.ApricotPhase1BlockTimestamp = utils.NewUint64(100) var ( - customghash = common.HexToHash("0x4a12fe7bf8d40d152d7e9de22337b115186a4662aa3a97217b36146202bbfc66") + customghash = common.HexToHash("0x1099a11e9e454bd3ef31d688cf21936671966407bc330f051d754b5ce401e7ed") customg = Genesis{ - Config: &preSubnetConfig, + Config: &apricotPhase1Config, Alloc: GenesisAlloc{ {1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}}, }, - GasLimit: preSubnetConfig.FeeConfig.GasLimit.Uint64(), } oldcustomg = customg ) - rollbackpreSubnetConfig := preSubnetConfig - rollbackpreSubnetConfig.SubnetEVMTimestamp = utils.NewUint64(90) - oldcustomg.Config = &rollbackpreSubnetConfig - + rollbackApricotPhase1Config := apricotPhase1Config + rollbackApricotPhase1Config.ApricotPhase1BlockTimestamp = utils.NewUint64(90) + oldcustomg.Config = &rollbackApricotPhase1Config tests := []struct { name string fn func(ethdb.Database) (*params.ChainConfig, common.Hash, error) @@ -133,8 +128,8 @@ func testSetupGenesis(t *testing.T, scheme string) { { name: "incompatible config for avalanche fork in DB", fn: func(db ethdb.Database) (*params.ChainConfig, common.Hash, error) { - // Commit the 'old' genesis block with SubnetEVM transition at 90. - // Advance to block #4, past the SubnetEVM transition block of customg. + // Commit the 'old' genesis block with ApricotPhase1 transition at 90. + // Advance to block #4, past the ApricotPhase1 transition block of customg. tdb := trie.NewDatabase(db, newDbConfig(scheme)) genesis, err := oldcustomg.Commit(db, tdb) if err != nil { @@ -162,7 +157,7 @@ func testSetupGenesis(t *testing.T, scheme string) { wantHash: customghash, wantConfig: customg.Config, wantErr: ¶ms.ConfigCompatError{ - What: "SubnetEVM fork block timestamp", + What: "ApricotPhase1 fork block timestamp", StoredTime: u64(90), NewTime: u64(100), RewindToTime: 89, @@ -195,77 +190,19 @@ func testSetupGenesis(t *testing.T, scheme string) { } } -func TestStatefulPrecompilesConfigure(t *testing.T) { - type test struct { - getConfig func() *params.ChainConfig // Return the config that enables the stateful precompile at the genesis for the test - assertState func(t *testing.T, sdb *state.StateDB) // Check that the stateful precompiles were configured correctly - } - - addr := common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC") - - // Test suite to ensure that stateful precompiles are configured correctly in the genesis. - for name, test := range map[string]test{ - "allow list enabled in genesis": { - getConfig: func() *params.ChainConfig { - config := *params.TestChainConfig - config.GenesisPrecompiles = params.Precompiles{ - deployerallowlist.ConfigKey: deployerallowlist.NewConfig(utils.NewUint64(0), []common.Address{addr}, nil, nil), - } - return &config - }, - assertState: func(t *testing.T, sdb *state.StateDB) { - assert.Equal(t, allowlist.AdminRole, deployerallowlist.GetContractDeployerAllowListStatus(sdb, addr), "unexpected allow list status for modified address") - assert.Equal(t, uint64(1), sdb.GetNonce(deployerallowlist.ContractAddress)) - }, - }, - } { - t.Run(name, func(t *testing.T) { - config := test.getConfig() - - genesis := &Genesis{ - Config: config, - Alloc: GenesisAlloc{ - {1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}}, - }, - GasLimit: config.FeeConfig.GasLimit.Uint64(), - } - - db := rawdb.NewMemoryDatabase() - - genesisBlock := genesis.ToBlock() - genesisRoot := genesisBlock.Root() - - _, _, err := setupGenesisBlock(db, trie.NewDatabase(db, trie.HashDefaults), genesis, genesisBlock.Hash()) - if err != nil { - t.Fatal(err) - } - - statedb, err := state.New(genesisRoot, state.NewDatabase(db), nil) - if err != nil { - t.Fatal(err) - } - - if test.assertState != nil { - test.assertState(t, statedb) - } - }) - } -} - // regression test for precompile activation after header block -func TestPrecompileActivationAfterHeaderBlock(t *testing.T) { +func TestNetworkUpgradeBetweenHeadAndAcceptedBlock(t *testing.T) { db := rawdb.NewMemoryDatabase() customg := Genesis{ - Config: params.TestChainConfig, + Config: params.TestApricotPhase1Config, Alloc: GenesisAlloc{ {1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}}, }, - GasLimit: params.TestChainConfig.FeeConfig.GasLimit.Uint64(), } bc, _ := NewBlockChain(db, DefaultCacheConfig, &customg, dummy.NewFullFaker(), vm.Config{}, common.Hash{}, false) defer bc.Stop() - // Advance header to block #4, past the ContractDeployerAllowListConfig. + // Advance header to block #4, past the ApricotPhase2 timestamp. _, blocks, _, _ := GenerateChainWithGenesis(&customg, dummy.NewFullFaker(), 4, 25, nil) require := require.New(t) @@ -282,25 +219,23 @@ func TestPrecompileActivationAfterHeaderBlock(t *testing.T) { // header must be bigger than last accepted require.Greater(block.Time, bc.lastAccepted.Time()) - activatedGenesisConfig := *customg.Config - contractDeployerConfig := deployerallowlist.NewConfig(utils.NewUint64(51), nil, nil, nil) - activatedGenesisConfig.UpgradeConfig.PrecompileUpgrades = []params.PrecompileUpgrade{ - { - Config: contractDeployerConfig, - }, - } - customg.Config = &activatedGenesisConfig + activatedGenesis := customg + apricotPhase2Timestamp := utils.NewUint64(51) + updatedApricotPhase2Config := *params.TestApricotPhase1Config + updatedApricotPhase2Config.ApricotPhase2BlockTimestamp = apricotPhase2Timestamp + + activatedGenesis.Config = &updatedApricotPhase2Config // assert block is after the activation block - require.Greater(block.Time, *contractDeployerConfig.Timestamp()) + require.Greater(block.Time, *apricotPhase2Timestamp) // assert last accepted block is before the activation block - require.Less(bc.lastAccepted.Time(), *contractDeployerConfig.Timestamp()) + require.Less(bc.lastAccepted.Time(), *apricotPhase2Timestamp) // This should not return any error since the last accepted block is before the activation block. - config, _, err := setupGenesisBlock(db, trie.NewDatabase(db, nil), &customg, bc.lastAccepted.Hash()) + config, _, err := setupGenesisBlock(db, trie.NewDatabase(db, nil), &activatedGenesis, bc.lastAccepted.Hash()) require.NoError(err) - if !reflect.DeepEqual(config, customg.Config) { - t.Errorf("returned %v\nwant %v", config, customg.Config) + if !reflect.DeepEqual(config, activatedGenesis.Config) { + t.Errorf("returned %v\nwant %v", config, activatedGenesis.Config) } } @@ -312,7 +247,6 @@ func TestGenesisWriteUpgradesRegression(t *testing.T) { Alloc: GenesisAlloc{ {1}: {Balance: big.NewInt(1), Storage: map[common.Hash]common.Hash{{1}: {1}}}, }, - GasLimit: config.FeeConfig.GasLimit.Uint64(), } db := rawdb.NewMemoryDatabase() @@ -324,7 +258,7 @@ func TestGenesisWriteUpgradesRegression(t *testing.T) { genesis.Config.UpgradeConfig.PrecompileUpgrades = []params.PrecompileUpgrade{ { - Config: deployerallowlist.NewConfig(utils.NewUint64(51), nil, nil, nil), + Config: warp.NewConfig(utils.NewUint64(51), 0), }, } _, _, err = SetupGenesisBlock(db, trieDB, genesis, genesisBlock.Hash(), false) @@ -371,7 +305,7 @@ func TestVerkleGenesisCommit(t *testing.T) { } genesis := &Genesis{ - BaseFee: big.NewInt(params.TestInitialBaseFee), + BaseFee: big.NewInt(params.ApricotPhase3InitialBaseFee), Config: verkleConfig, Timestamp: verkleTime, Difficulty: big.NewInt(0), @@ -380,7 +314,7 @@ func TestVerkleGenesisCommit(t *testing.T) { }, } - expected := common.Hex2Bytes("14398d42be3394ff8d50681816a4b7bf8d8283306f577faba2d5bc57498de23b") + expected := common.Hex2Bytes("22678ccc2daa04e91013ce47799973bd6c1824f37989d7cea4cbdcd79b39137f") got := genesis.ToBlock().Root().Bytes() if !bytes.Equal(got, expected) { t.Fatalf("invalid genesis state root, expected %x, got %x", expected, got) diff --git a/core/headerchain_test.go b/core/headerchain_test.go index a5e65e81a8..4bde30e2a8 100644 --- a/core/headerchain_test.go +++ b/core/headerchain_test.go @@ -75,7 +75,7 @@ func TestHeaderInsertion(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() gspec = &Genesis{ - BaseFee: big.NewInt(params.TestInitialBaseFee), + BaseFee: big.NewInt(params.ApricotPhase3InitialBaseFee), Config: params.TestChainConfig, } ) diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index eca8abfbc7..eb9aca0a0a 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -522,7 +522,7 @@ func ReadBlock(db ethdb.Reader, hash common.Hash, number uint64) *types.Block { if body == nil { return nil } - return types.NewBlockWithHeader(header).WithBody(body.Transactions, body.Uncles) + return types.NewBlockWithHeader(header).WithBody(body.Transactions, body.Uncles).WithExtData(body.Version, body.ExtData) } // WriteBlock serializes a block into the database, header and body separately. diff --git a/core/rawdb/accessors_metadata.go b/core/rawdb/accessors_metadata.go index eafc43d83e..f61da5fbf9 100644 --- a/core/rawdb/accessors_metadata.go +++ b/core/rawdb/accessors_metadata.go @@ -74,17 +74,6 @@ func ReadChainConfig(db ethdb.KeyValueReader, hash common.Hash) *params.ChainCon log.Error("Invalid chain config JSON", "hash", hash, "err", err) return nil } - - // Read the upgrade config for this chain config - data, _ = db.Get(upgradeConfigKey(hash)) - if len(data) == 0 { - return &config // return early if no upgrade config is found - } - if err := json.Unmarshal(data, &config.UpgradeConfig); err != nil { - log.Error("Invalid upgrade config JSON", "err", err) - return nil - } - return &config } @@ -100,15 +89,6 @@ func WriteChainConfig(db ethdb.KeyValueWriter, hash common.Hash, cfg *params.Cha if err := db.Put(configKey(hash), data); err != nil { log.Crit("Failed to store chain config", "err", err) } - - // Write the upgrade config for this chain config - data, err = json.Marshal(cfg.UpgradeConfig) - if err != nil { - log.Crit("Failed to JSON encode upgrade config", "err", err) - } - if err := db.Put(upgradeConfigKey(hash), data); err != nil { - log.Crit("Failed to store upgrade config", "err", err) - } } // crashList is a list of unclean-shutdown-markers, for rlp-encoding to the diff --git a/core/rawdb/accessors_state.go b/core/rawdb/accessors_state.go index 31f89b0d13..03cbf44b6c 100644 --- a/core/rawdb/accessors_state.go +++ b/core/rawdb/accessors_state.go @@ -53,7 +53,7 @@ func WritePreimages(db ethdb.KeyValueWriter, preimages map[common.Hash][]byte) { // ReadCode retrieves the contract code of the provided code hash. func ReadCode(db ethdb.KeyValueReader, hash common.Hash) []byte { - // Try with the prefixed code scheme first and only. The legacy scheme was never used in subnet-evm. + // Try with the prefixed code scheme first and only. The legacy scheme was never used in coreth. data, _ := db.Get(codeKey(hash)) return data } @@ -61,7 +61,7 @@ func ReadCode(db ethdb.KeyValueReader, hash common.Hash) []byte { // HasCode checks if the contract code corresponding to the // provided code hash is present in the db. func HasCode(db ethdb.KeyValueReader, hash common.Hash) bool { - // Try with the prefixed code scheme first and only. The legacy scheme was never used in subnet-evm. + // Try with the prefixed code scheme first and only. The legacy scheme was never used in coreth. ok, _ := db.Has(codeKey(hash)) return ok } diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 85fa6ac114..0fbec54093 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -356,8 +356,6 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { preimages.Add(size) case bytes.HasPrefix(key, configPrefix) && len(key) == (len(configPrefix)+common.HashLength): metadata.Add(size) - case bytes.HasPrefix(key, upgradeConfigPrefix) && len(key) == (len(upgradeConfigPrefix)+common.HashLength): - metadata.Add(size) case bytes.HasPrefix(key, bloomBitsPrefix) && len(key) == (len(bloomBitsPrefix)+10+common.HashLength): bloomBits.Add(size) case bytes.HasPrefix(key, BloomBitsIndexPrefix): diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 78d1cb4a72..2f90673211 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -101,9 +101,8 @@ var ( trieNodeStoragePrefix = []byte("O") // trieNodeStoragePrefix + accountHash + hexPath -> trie node stateIDPrefix = []byte("L") // stateIDPrefix + state root -> state id - PreimagePrefix = []byte("secure-key-") // PreimagePrefix + hash -> preimage - configPrefix = []byte("ethereum-config-") // config prefix for the db - upgradeConfigPrefix = []byte("upgrade-config-") // upgrade bytes passed to the chain are stored with this prefix + PreimagePrefix = []byte("secure-key-") // PreimagePrefix + hash -> preimage + configPrefix = []byte("ethereum-config-") // config prefix for the db // BloomBitsIndexPrefix is the data table of a chain indexer to track its progress BloomBitsIndexPrefix = []byte("iB") @@ -230,11 +229,6 @@ func configKey(hash common.Hash) []byte { return append(configPrefix, hash.Bytes()...) } -// upgradeConfigKey = upgradeConfigPrefix + hash -func upgradeConfigKey(hash common.Hash) []byte { - return append(upgradeConfigPrefix, hash.Bytes()...) -} - // stateIDKey = stateIDPrefix + root (32 bytes) func stateIDKey(root common.Hash) []byte { return append(stateIDPrefix, root.Bytes()...) diff --git a/core/rlp_test.go b/core/rlp_test.go index f2b30c5908..f0379d38cc 100644 --- a/core/rlp_test.go +++ b/core/rlp_test.go @@ -100,16 +100,24 @@ func testRlpIterator(t *testing.T, txs, uncles, datasize int) { } // Check that txs exist if !it.Next() { - t.Fatal("expected two elems, got zero") + t.Fatal("expected four elems, got zero") } txdata := it.Value() // Check that uncles exist if !it.Next() { - t.Fatal("expected two elems, got one") + t.Fatal("expected four elems, got one") + } + // Check that version exist + if !it.Next() { + t.Fatal("expected four elems, got two") + } + // Check that extdata exist + if !it.Next() { + t.Fatal("expected four elems, got three") } // No more after that if it.Next() { - t.Fatal("expected only two elems, got more") + t.Fatal("expected only four elems, got more") } txIt, err := rlp.NewListIterator(txdata) if err != nil { diff --git a/core/state/dump.go b/core/state/dump.go index ee02e4b40c..a480329860 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -64,6 +64,7 @@ type DumpAccount struct { Root hexutil.Bytes `json:"root"` CodeHash hexutil.Bytes `json:"codeHash"` Code hexutil.Bytes `json:"code,omitempty"` + IsMultiCoin bool `json:"isMultiCoin"` Storage map[common.Hash]string `json:"storage,omitempty"` Address *common.Address `json:"address,omitempty"` // Address only present in iterative (line-by-line) mode AddressHash hexutil.Bytes `json:"key,omitempty"` // If we don't have address, we can output the key @@ -106,6 +107,7 @@ func (d iterativeDump) OnAccount(addr *common.Address, account DumpAccount) { Nonce: account.Nonce, Root: account.Root, CodeHash: account.CodeHash, + IsMultiCoin: account.IsMultiCoin, Code: account.Code, Storage: account.Storage, AddressHash: account.AddressHash, @@ -154,6 +156,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey [] Nonce: data.Nonce, Root: data.Root[:], CodeHash: data.CodeHash, + IsMultiCoin: data.IsMultiCoin, AddressHash: it.Key, } address *common.Address diff --git a/core/state/journal.go b/core/state/journal.go index 4ba90fba5f..5a1ee1aef6 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -121,6 +121,9 @@ type ( account *common.Address prev *big.Int } + multiCoinEnable struct { + account *common.Address + } nonceChange struct { account *common.Address prev uint64 @@ -223,6 +226,14 @@ func (ch balanceChange) dirtied() *common.Address { return ch.account } +func (ch multiCoinEnable) revert(s *StateDB) { + s.getStateObject(*ch.account).data.IsMultiCoin = false +} + +func (ch multiCoinEnable) dirtied() *common.Address { + return ch.account +} + func (ch nonceChange) revert(s *StateDB) { s.getStateObject(*ch.account).setNonce(ch.prev) } diff --git a/core/state/snapshot/generate_test.go b/core/state/snapshot/generate_test.go index 956cc589a3..e0ea693503 100644 --- a/core/state/snapshot/generate_test.go +++ b/core/state/snapshot/generate_test.go @@ -79,7 +79,7 @@ func testGeneration(t *testing.T, scheme string) { root, snap := helper.CommitAndGenerate() // two of which also has the same 3-slot storage trie attached. - if have, want := root, common.HexToHash("0xe3712f1a226f3782caca78ca770ccc19ee000552813a9f59d479f8611db9b1fd"); have != want { + if have, want := root, common.HexToHash("0xa819054cfef894169a5b56ccc4e5e06f14829d4a57498e8b9fb13ff21491828d"); have != want { t.Fatalf("have %#x want %#x", have, want) } select { @@ -337,7 +337,7 @@ func testGenerateExistentStateWithWrongStorage(t *testing.T, scheme string) { } root, snap := helper.CommitAndGenerate() - t.Logf("Root: %#x\n", root) // Root = 0x8746cce9fd9c658b2cfd639878ed6584b7a2b3e73bb40f607fcfa156002429a0 + t.Logf("Root: %#x\n", root) // Root = 0xdd912272f1befd3e1ca84007817f532b6e8a08f7b273c88b0ea0d6701ad3ad03 select { case <-snap.genPending: @@ -399,7 +399,7 @@ func testGenerateExistentStateWithWrongAccounts(t *testing.T, scheme string) { } root, snap := helper.CommitAndGenerate() - t.Logf("Root: %#x\n", root) // Root = 0x825891472281463511e7ebcc7f109e4f9200c20fa384754e11fd605cd98464e8 + t.Logf("Root: %#x\n", root) // Root = 0xe614035f519c878aea2b5e658a3dd61916ff090354222cfad624403e89d96440 select { case <-snap.genPending: @@ -429,15 +429,15 @@ func testGenerateCorruptAccountTrie(t *testing.T, scheme string) { // without any storage slots to keep the test smaller. helper := newHelper(scheme) - helper.addTrieAccount("acc-1", &types.StateAccount{Balance: big.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0xc7a30f39aff471c95d8a837497ad0e49b65be475cc0953540f80cfcdbdcd9074 - helper.addTrieAccount("acc-2", &types.StateAccount{Balance: big.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7 - helper.addTrieAccount("acc-3", &types.StateAccount{Balance: big.NewInt(3), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x19ead688e907b0fab07176120dceec244a72aff2f0aa51e8b827584e378772f4 + helper.addTrieAccount("acc-1", &types.StateAccount{Balance: big.NewInt(1), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x7dd654835190324640832972b7c4c6eaa0c50541e36766d054ed57721f1dc7eb + helper.addTrieAccount("acc-2", &types.StateAccount{Balance: big.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0xf73118e0254ce091588d66038744a0afae5f65a194de67cff310c683ae43329e + helper.addTrieAccount("acc-3", &types.StateAccount{Balance: big.NewInt(3), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x515d3de35e143cd976ad476398d910aa7bf8a02e8fd7eb9e3baacddbbcbfcb41 - root := helper.Commit() // Root: 0xa04693ea110a31037fb5ee814308a6f1d76bdab0b11676bdf4541d2de55ba978 + root := helper.Commit() // Root: 0xfa04f652e8bd3938971bf7d71c3c688574af334ca8bc20e64b01ba610ae93cad // Delete an account trie node and ensure the generator chokes targetPath := []byte{0xc} - targetHash := common.HexToHash("0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7") + targetHash := common.HexToHash("0xf73118e0254ce091588d66038744a0afae5f65a194de67cff310c683ae43329e") rawdb.DeleteTrieNode(helper.diskdb, common.Hash{}, targetPath, targetHash, scheme) @@ -477,7 +477,7 @@ func testGenerateMissingStorageTrie(t *testing.T, scheme string) { helper.addTrieAccount("acc-1", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e helper.addTrieAccount("acc-2", &types.StateAccount{Balance: big.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7 stRoot = helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) - helper.addTrieAccount("acc-3", &types.StateAccount{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2 + helper.addTrieAccount("acc-3", &types.StateAccount{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x70da4ebd7602dd313c936b39000ed9ab7f849986a90ea934f0c3ec4cc9840441 root := helper.Commit() @@ -517,7 +517,7 @@ func testGenerateCorruptStorageTrie(t *testing.T, scheme string) { helper.addTrieAccount("acc-1", &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e helper.addTrieAccount("acc-2", &types.StateAccount{Balance: big.NewInt(2), Root: types.EmptyRootHash, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x65145f923027566669a1ae5ccac66f945b55ff6eaeb17d2ea8e048b7d381f2d7 stRoot = helper.makeStorageTrie(hashData([]byte("acc-3")), []string{"key-1", "key-2", "key-3"}, []string{"val-1", "val-2", "val-3"}, true) - helper.addTrieAccount("acc-3", &types.StateAccount{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x50815097425d000edfc8b3a4a13e175fc2bdcfee8bdfbf2d1ff61041d3c235b2 + helper.addTrieAccount("acc-3", &types.StateAccount{Balance: big.NewInt(3), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) // 0x70da4ebd7602dd313c936b39000ed9ab7f849986a90ea934f0c3ec4cc9840441 root := helper.Commit() @@ -635,7 +635,7 @@ func testGenerateWithManyExtraAccounts(t *testing.T, scheme string) { ) acc := &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()} val, _ := rlp.EncodeToBytes(acc) - helper.accTrie.MustUpdate([]byte("acc-1"), val) // 0x9250573b9c18c664139f3b6a7a8081b7d8f8916a8fcc5d94feec6c29f5fd4e9e + helper.accTrie.MustUpdate([]byte("acc-1"), val) // 0x547b07c3a71669c00eda14077d85c7fd14575b92d459572540b25b9a11914dcb // Identical in the snap key := hashData([]byte("acc-1")) @@ -774,7 +774,7 @@ func testGenerateFromEmptySnap(t *testing.T, scheme string) { &types.StateAccount{Balance: big.NewInt(1), Root: stRoot, CodeHash: types.EmptyCodeHash.Bytes()}) } root, snap := helper.CommitAndGenerate() - t.Logf("Root: %#x\n", root) // Root: 0x6f7af6d2e1a1bf2b84a3beb3f8b64388465fbc1e274ca5d5d3fc787ca78f59e4 + t.Logf("Root: %#x\n", root) // Root: 0x2609234ce43f5e471202c87e017ffb4dfecdb3163cfcbaa55de04baa59cad42d select { case <-snap.genPending: @@ -824,7 +824,7 @@ func testGenerateWithIncompleteStorage(t *testing.T, scheme string) { helper.addSnapStorage(accKey, moddedKeys, moddedVals) } root, snap := helper.CommitAndGenerate() - t.Logf("Root: %#x\n", root) // Root: 0xca73f6f05ba4ca3024ef340ef3dfca8fdabc1b677ff13f5a9571fd49c16e67ff + t.Logf("Root: %#x\n", root) // Root: 0x90cb912ad55795de8c08a41bfadb79109d4067fb2004e7bc17c942a3e551904d select { case <-snap.genPending: diff --git a/core/state/state_object.go b/core/state/state_object.go index 9d06e32a27..71194cd63b 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -103,7 +103,7 @@ type stateObject struct { // empty returns whether the account is considered empty. func (s *stateObject) empty() bool { - return s.data.Nonce == 0 && s.data.Balance.Sign() == 0 && bytes.Equal(s.data.CodeHash, types.EmptyCodeHash.Bytes()) + return s.data.Nonce == 0 && s.data.Balance.Sign() == 0 && bytes.Equal(s.data.CodeHash, types.EmptyCodeHash.Bytes()) && !s.data.IsMultiCoin } // newObject creates a state object. @@ -444,10 +444,42 @@ func (s *stateObject) SetBalance(amount *big.Int) { s.setBalance(amount) } +// AddBalanceMultiCoin adds amount of coinID to s's balance. +// It is used to add multicoin funds to the destination account of a transfer. +func (s *stateObject) AddBalanceMultiCoin(coinID common.Hash, amount *big.Int, db Database) { + if amount.Sign() == 0 { + if s.empty() { + s.touch() + } + + return + } + s.SetBalanceMultiCoin(coinID, new(big.Int).Add(s.BalanceMultiCoin(coinID, db), amount), db) +} + +// SubBalanceMultiCoin removes amount of coinID from s's balance. +// It is used to remove multicoin funds from the origin account of a transfer. +func (s *stateObject) SubBalanceMultiCoin(coinID common.Hash, amount *big.Int, db Database) { + if amount.Sign() == 0 { + return + } + s.SetBalanceMultiCoin(coinID, new(big.Int).Sub(s.BalanceMultiCoin(coinID, db), amount), db) +} + +func (s *stateObject) SetBalanceMultiCoin(coinID common.Hash, amount *big.Int, db Database) { + s.EnableMultiCoin() + NormalizeCoinID(&coinID) + s.SetState(coinID, common.BigToHash(amount)) +} + func (s *stateObject) setBalance(amount *big.Int) { s.data.Balance = amount } +func (s *stateObject) enableMultiCoin() { + s.data.IsMultiCoin = true +} + func (s *stateObject) deepCopy(db *StateDB) *stateObject { obj := &stateObject{ db: db, @@ -547,6 +579,38 @@ func (s *stateObject) Balance() *big.Int { return s.data.Balance } +// NormalizeCoinID ORs the 0th bit of the first byte in +// [coinID], which ensures this bit will be 1 and all other +// bits are left the same. +// This partitions multicoin storage from normal state storage. +func NormalizeCoinID(coinID *common.Hash) { + coinID[0] |= 0x01 +} + +// NormalizeStateKey ANDs the 0th bit of the first byte in +// [key], which ensures this bit will be 0 and all other bits +// are left the same. +// This partitions normal state storage from multicoin storage. +func NormalizeStateKey(key *common.Hash) { + key[0] &= 0xfe +} + +func (s *stateObject) BalanceMultiCoin(coinID common.Hash, db Database) *big.Int { + NormalizeCoinID(&coinID) + return s.GetState(coinID).Big() +} + +func (s *stateObject) EnableMultiCoin() bool { + if s.data.IsMultiCoin { + return false + } + s.db.journal.append(multiCoinEnable{ + account: &s.address, + }) + s.enableMultiCoin() + return true +} + func (s *stateObject) Nonce() uint64 { return s.data.Nonce } diff --git a/core/state/state_test.go b/core/state/state_test.go index 547a599611..1b8c747e89 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -77,11 +77,11 @@ func TestIterativeDump(t *testing.T) { s.state.IterativeDump(nil, json.NewEncoder(b)) // check that DumpToCollector contains the state objects that are in trie got := b.String() - want := `{"root":"0xd5710ea8166b7b04bc2bfb129d7db12931cee82f75ca8e2d075b4884322bf3de"} -{"balance":"22","nonce":0,"root":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","codeHash":"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470","address":"0x0000000000000000000000000000000000000001","key":"0x1468288056310c82aa4c01a7e12a10f8111a0560e72b700555479031b86c357d"} -{"balance":"1337","nonce":0,"root":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","codeHash":"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470","address":"0x0000000000000000000000000000000000000000","key":"0x5380c7b7ae81a58eb98d9c78de4a1fd7fd9535fc953ed2be602daaa41767312a"} -{"balance":"0","nonce":0,"root":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","codeHash":"0x87874902497a5bb968da31a2998d8f22e949d1ef6214bcdedd8bae24cca4b9e3","code":"0x03030303030303","address":"0x0000000000000000000000000000000000000102","key":"0xa17eacbc25cda025e81db9c5c62868822c73ce097cee2a63e33a2e41268358a1"} -{"balance":"44","nonce":0,"root":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","codeHash":"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470","address":"0x0000000000000000000000000000000000000002","key":"0xd52688a8f926c816ca1e079067caba944f158e764817b83fc43594370ca9cf62"} + want := `{"root":"0x0ffca661efa3b7504ac015083994c94fd7d0d24db60354c717c936afcced762a"} +{"balance":"22","nonce":0,"root":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","codeHash":"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470","isMultiCoin":false,"address":"0x0000000000000000000000000000000000000001","key":"0x1468288056310c82aa4c01a7e12a10f8111a0560e72b700555479031b86c357d"} +{"balance":"1337","nonce":0,"root":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","codeHash":"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470","isMultiCoin":false,"address":"0x0000000000000000000000000000000000000000","key":"0x5380c7b7ae81a58eb98d9c78de4a1fd7fd9535fc953ed2be602daaa41767312a"} +{"balance":"0","nonce":0,"root":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","codeHash":"0x87874902497a5bb968da31a2998d8f22e949d1ef6214bcdedd8bae24cca4b9e3","code":"0x03030303030303","isMultiCoin":false,"address":"0x0000000000000000000000000000000000000102","key":"0xa17eacbc25cda025e81db9c5c62868822c73ce097cee2a63e33a2e41268358a1"} +{"balance":"44","nonce":0,"root":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","codeHash":"0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470","isMultiCoin":false,"address":"0x0000000000000000000000000000000000000002","key":"0xd52688a8f926c816ca1e079067caba944f158e764817b83fc43594370ca9cf62"} ` if got != want { t.Errorf("DumpToCollector mismatch:\ngot: %s\nwant: %s\n", got, want) diff --git a/core/state/statedb.go b/core/state/statedb.go index 67ea4dd458..0491424bd0 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -341,6 +341,15 @@ func (s *StateDB) GetBalance(addr common.Address) *big.Int { return new(big.Int).Set(common.Big0) } +// Retrieve the balance from the given address or 0 if object not found +func (s *StateDB) GetBalanceMultiCoin(addr common.Address, coinID common.Hash) *big.Int { + stateObject := s.getStateObject(addr) + if stateObject != nil { + return stateObject.BalanceMultiCoin(coinID, s.db) + } + return new(big.Int).Set(common.Big0) +} + // GetNonce retrieves the nonce from the given address or 0 if object not found func (s *StateDB) GetNonce(addr common.Address) uint64 { stateObject := s.getStateObject(addr) @@ -394,6 +403,7 @@ func (s *StateDB) GetCodeHash(addr common.Address) common.Hash { func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { stateObject := s.getStateObject(addr) if stateObject != nil { + NormalizeStateKey(&hash) return stateObject.GetState(hash) } return common.Hash{} @@ -408,6 +418,16 @@ func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) commo return common.Hash{} } +// GetCommittedStateAP1 retrieves a value from the given account's committed storage trie. +func (s *StateDB) GetCommittedStateAP1(addr common.Address, hash common.Hash) common.Hash { + stateObject := s.getStateObject(addr) + if stateObject != nil { + NormalizeStateKey(&hash) + return stateObject.GetCommittedState(hash) + } + return common.Hash{} +} + // Database retrieves the low level database supporting the lower level trie ops. func (s *StateDB) Database() Database { return s.db @@ -448,6 +468,29 @@ func (s *StateDB) SetBalance(addr common.Address, amount *big.Int) { } } +// AddBalance adds amount to the account associated with addr. +func (s *StateDB) AddBalanceMultiCoin(addr common.Address, coinID common.Hash, amount *big.Int) { + stateObject := s.GetOrNewStateObject(addr) + if stateObject != nil { + stateObject.AddBalanceMultiCoin(coinID, amount, s.db) + } +} + +// SubBalance subtracts amount from the account associated with addr. +func (s *StateDB) SubBalanceMultiCoin(addr common.Address, coinID common.Hash, amount *big.Int) { + stateObject := s.GetOrNewStateObject(addr) + if stateObject != nil { + stateObject.SubBalanceMultiCoin(coinID, amount, s.db) + } +} + +func (s *StateDB) SetBalanceMultiCoin(addr common.Address, coinID common.Hash, amount *big.Int) { + stateObject := s.GetOrNewStateObject(addr) + if stateObject != nil { + stateObject.SetBalanceMultiCoin(coinID, amount, s.db) + } +} + func (s *StateDB) SetNonce(addr common.Address, nonce uint64) { stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { @@ -465,6 +508,7 @@ func (s *StateDB) SetCode(addr common.Address, code []byte) { func (s *StateDB) SetState(addr common.Address, key, value common.Hash) { stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { + NormalizeStateKey(&key) stateObject.SetState(key, value) } } @@ -628,10 +672,11 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { return nil } data = &types.StateAccount{ - Nonce: acc.Nonce, - Balance: acc.Balance, - CodeHash: acc.CodeHash, - Root: common.BytesToHash(acc.Root), + Nonce: acc.Nonce, + Balance: acc.Balance, + CodeHash: acc.CodeHash, + IsMultiCoin: acc.IsMultiCoin, + Root: common.BytesToHash(acc.Root), } if len(data.CodeHash) == 0 { data.CodeHash = types.EmptyCodeHash.Bytes() @@ -1373,18 +1418,18 @@ func (s *StateDB) commit(block uint64, deleteEmptyObjects bool, snaps *snapshot. // Prepare handles the preparatory steps for executing a state transition with. // This method must be invoked before state transition. // -// Berlin fork: +// Berlin fork (aka ApricotPhase2): // - Add sender to access list (2929) // - Add destination to access list (2929) // - Add precompiles to access list (2929) // - Add the contents of the optional tx access list (2930) // // Potential EIPs: -// - Reset access list (Berlin) +// - Reset access list (Berlin/ApricotPhase2) // - Add coinbase to access list (EIP-3651/Durango) // - Reset transient storage (EIP-1153) func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) { - if rules.IsSubnetEVM { + if rules.IsApricotPhase2 { // Clear out any leftover from previous executions al := newAccessList() s.accessList = al diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 7c603cbe4f..78e1ae7049 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -1037,6 +1037,159 @@ func TestStateDBAccessList(t *testing.T) { } } +func TestMultiCoinOperations(t *testing.T) { + s := newStateEnv() + addr := common.Address{1} + assetID := common.Hash{2} + + s.state.GetOrNewStateObject(addr) + root, _ := s.state.Commit(0, false, false) + s.state, _ = NewWithSnapshot(root, s.state.db, s.state.snap) + + s.state.AddBalance(addr, new(big.Int)) + + balance := s.state.GetBalanceMultiCoin(addr, assetID) + if balance.Cmp(big.NewInt(0)) != 0 { + t.Fatal("expected zero multicoin balance") + } + + s.state.SetBalanceMultiCoin(addr, assetID, big.NewInt(10)) + s.state.SubBalanceMultiCoin(addr, assetID, big.NewInt(5)) + s.state.AddBalanceMultiCoin(addr, assetID, big.NewInt(3)) + + balance = s.state.GetBalanceMultiCoin(addr, assetID) + if balance.Cmp(big.NewInt(8)) != 0 { + t.Fatal("expected multicoin balance to be 8") + } +} + +func TestMultiCoinSnapshot(t *testing.T) { + db := rawdb.NewMemoryDatabase() + sdb := NewDatabase(db) + + // Create empty snapshot.Tree and StateDB + root := common.HexToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421") + // Use the root as both the stateRoot and blockHash for this test. + snapTree := snapshot.NewTestTree(db, root, root) + + addr := common.Address{1} + assetID1 := common.Hash{1} + assetID2 := common.Hash{2} + + var stateDB *StateDB + assertBalances := func(regular, multicoin1, multicoin2 int64) { + balance := stateDB.GetBalance(addr) + if balance.Cmp(big.NewInt(regular)) != 0 { + t.Fatal("incorrect non-multicoin balance") + } + balance = stateDB.GetBalanceMultiCoin(addr, assetID1) + if balance.Cmp(big.NewInt(multicoin1)) != 0 { + t.Fatal("incorrect multicoin1 balance") + } + balance = stateDB.GetBalanceMultiCoin(addr, assetID2) + if balance.Cmp(big.NewInt(multicoin2)) != 0 { + t.Fatal("incorrect multicoin2 balance") + } + } + + // Create new state + stateDB, _ = New(root, sdb, snapTree) + assertBalances(0, 0, 0) + + stateDB.AddBalance(addr, big.NewInt(10)) + assertBalances(10, 0, 0) + + // Commit and get the new root + root, _ = stateDB.Commit(0, false, false) + assertBalances(10, 0, 0) + + // Create a new state from the latest root, add a multicoin balance, and + // commit it to the tree. + stateDB, _ = New(root, sdb, snapTree) + stateDB.AddBalanceMultiCoin(addr, assetID1, big.NewInt(10)) + root, _ = stateDB.Commit(0, false, false) + assertBalances(10, 10, 0) + + // Add more layers than the cap and ensure the balances and layers are correct + for i := 0; i < 256; i++ { + stateDB, _ = New(root, sdb, snapTree) + stateDB.AddBalanceMultiCoin(addr, assetID1, big.NewInt(1)) + stateDB.AddBalanceMultiCoin(addr, assetID2, big.NewInt(2)) + root, _ = stateDB.Commit(0, false, false) + } + assertBalances(10, 266, 512) + + // Do one more add, including the regular balance which is now in the + // collapsed snapshot + stateDB, _ = New(root, sdb, snapTree) + stateDB.AddBalance(addr, big.NewInt(1)) + stateDB.AddBalanceMultiCoin(addr, assetID1, big.NewInt(1)) + root, _ = stateDB.Commit(0, false, false) + stateDB, _ = New(root, sdb, snapTree) + assertBalances(11, 267, 512) +} + +func TestGenerateMultiCoinAccounts(t *testing.T) { + var ( + diskdb = rawdb.NewMemoryDatabase() + database = NewDatabase(diskdb) + + addr = common.BytesToAddress([]byte("addr1")) + addrHash = crypto.Keccak256Hash(addr[:]) + + assetID = common.BytesToHash([]byte("coin1")) + assetBalance = big.NewInt(10) + ) + + stateDB, err := New(common.Hash{}, database, nil) + if err != nil { + t.Fatal(err) + } + stateDB.SetBalanceMultiCoin(addr, assetID, assetBalance) + root, err := stateDB.Commit(0, false, false) + if err != nil { + t.Fatal(err) + } + + triedb := database.TrieDB() + if err := triedb.Commit(root, true); err != nil { + t.Fatal(err) + } + // Build snapshot from scratch + snapConfig := snapshot.Config{ + CacheSize: 16, + AsyncBuild: false, + NoBuild: false, + SkipVerify: true, + } + snaps, err := snapshot.New(snapConfig, diskdb, triedb, common.Hash{}, root) + if err != nil { + t.Error("Unexpected error while rebuilding snapshot:", err) + } + + // Get latest snapshot and make sure it has the correct account and storage + snap := snaps.Snapshot(root) + snapAccount, err := snap.Account(addrHash) + if err != nil { + t.Fatal(err) + } + if !snapAccount.IsMultiCoin { + t.Fatalf("Expected SnapAccount to return IsMultiCoin: true, found: %v", snapAccount.IsMultiCoin) + } + + NormalizeCoinID(&assetID) + assetHash := crypto.Keccak256Hash(assetID.Bytes()) + storageBytes, err := snap.Storage(addrHash, assetHash) + if err != nil { + t.Fatal(err) + } + + actualAssetBalance := new(big.Int).SetBytes(storageBytes) + if actualAssetBalance.Cmp(assetBalance) != 0 { + t.Fatalf("Expected asset balance: %v, found %v", assetBalance, actualAssetBalance) + } +} + // Tests that account and storage tries are flushed in the correct order and that // no data loss occurs. func TestFlushOrderDataLoss(t *testing.T) { diff --git a/core/state/test_statedb.go b/core/state/test_statedb.go index c18bdd2c65..9dd51e9f48 100644 --- a/core/state/test_statedb.go +++ b/core/state/test_statedb.go @@ -1,6 +1,5 @@ // (c) 2023, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. - package state import ( diff --git a/core/state_processor.go b/core/state_processor.go index fb839cc187..fb4ff0cc43 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -38,7 +38,6 @@ import ( "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ava-labs/subnet-evm/precompile/modules" - "github.com/ava-labs/subnet-evm/stateupgrade" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" @@ -252,20 +251,6 @@ func ApplyPrecompileActivations(c *params.ChainConfig, parentTimestamp *uint64, return nil } -// applyStateUpgrades checks if any of the state upgrades specified by the chain config are activated by the block -// transition from [parentTimestamp] to the timestamp set in [header]. If this is the case, it calls [Configure] -// to apply the necessary state transitions for the upgrade. -func applyStateUpgrades(c *params.ChainConfig, parentTimestamp *uint64, blockContext contract.ConfigurationBlockContext, statedb *state.StateDB) error { - // Apply state upgrades - for _, upgrade := range c.GetActivatingStateUpgrades(parentTimestamp, blockContext.Timestamp(), c.StateUpgrades) { - log.Info("Applying state upgrade", "blockNumber", blockContext.Number(), "upgrade", upgrade) - if err := stateupgrade.Configure(&upgrade, c, statedb, blockContext); err != nil { - return fmt.Errorf("could not configure state upgrade: %w", err) - } - } - return nil -} - // ApplyUpgrades checks if any of the precompile or state upgrades specified by the chain config are activated by the block // transition from [parentTimestamp] to the timestamp set in [header]. If this is the case, it calls [Configure] // to apply the necessary state transitions for the upgrade. @@ -273,8 +258,5 @@ func applyStateUpgrades(c *params.ChainConfig, parentTimestamp *uint64, blockCon // - in block processing to update the state when processing a block. // - in the miner to apply the state upgrades when producing a block. func ApplyUpgrades(c *params.ChainConfig, parentTimestamp *uint64, blockContext contract.ConfigurationBlockContext, statedb *state.StateDB) error { - if err := ApplyPrecompileActivations(c, parentTimestamp, blockContext, statedb); err != nil { - return err - } - return applyStateUpgrades(c, parentTimestamp, blockContext, statedb) + return ApplyPrecompileActivations(c, parentTimestamp, blockContext, statedb) } diff --git a/core/state_processor_test.go b/core/state_processor_test.go index ad35b811bd..d110de8651 100644 --- a/core/state_processor_test.go +++ b/core/state_processor_test.go @@ -31,7 +31,6 @@ import ( "math/big" "testing" - "github.com/ava-labs/avalanchego/upgrade" "github.com/ava-labs/subnet-evm/consensus" "github.com/ava-labs/subnet-evm/consensus/dummy" "github.com/ava-labs/subnet-evm/consensus/misc/eip4844" @@ -39,7 +38,6 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist" "github.com/ava-labs/subnet-evm/trie" "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" @@ -58,7 +56,6 @@ func TestStateProcessorErrors(t *testing.T) { cpcfg := *params.TestChainConfig config := &cpcfg config.CancunTime = u64(0) - config.FeeConfig.MinBaseFee = big.NewInt(params.TestMaxBaseFee) var ( signer = types.LatestSigner(config) @@ -111,17 +108,17 @@ func TestStateProcessorErrors(t *testing.T) { var ( db = rawdb.NewMemoryDatabase() gspec = &Genesis{ - Config: config, - Timestamp: uint64(upgrade.InitiallyActiveTime.Unix()), + Config: config, Alloc: GenesisAlloc{ common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): GenesisAccount{ Balance: big.NewInt(4000000000000000000), // 4 ether Nonce: 0, }, }, - GasLimit: params.TestChainConfig.FeeConfig.GasLimit.Uint64(), + GasLimit: params.CortinaGasLimit, } - blockchain, _ = NewBlockChain(db, DefaultCacheConfig, gspec, dummy.NewCoinbaseFaker(), vm.Config{}, common.Hash{}, false) + // FullFaker used to skip header verification that enforces no blobs. + blockchain, _ = NewBlockChain(db, DefaultCacheConfig, gspec, dummy.NewFullFaker(), vm.Config{}, common.Hash{}, false) tooBigInitCode = [params.MaxInitCodeSize + 1]byte{} ) @@ -223,13 +220,13 @@ func TestStateProcessorErrors(t *testing.T) { }, { // ErrMaxInitCodeSizeExceeded txs: []*types.Transaction{ - mkDynamicCreationTx(0, 500000, common.Big0, big.NewInt(params.TestInitialBaseFee), tooBigInitCode[:]), + mkDynamicCreationTx(0, 500000, common.Big0, big.NewInt(params.ApricotPhase3InitialBaseFee), tooBigInitCode[:]), }, want: "could not apply tx 0 [0x18a05f40f29ff16d5287f6f88b21c9f3c7fbc268f707251144996294552c4cd6]: max initcode size exceeded: code size 49153 limit 49152", }, { // ErrIntrinsicGas: Not enough gas to cover init code txs: []*types.Transaction{ - mkDynamicCreationTx(0, 54299, common.Big0, big.NewInt(params.TestInitialBaseFee), make([]byte, 320)), + mkDynamicCreationTx(0, 54299, common.Big0, big.NewInt(params.ApricotPhase3InitialBaseFee), make([]byte, 320)), }, want: "could not apply tx 0 [0x849278f616d51ab56bba399551317213ce7a10e4d9cbc3d14bb663e50cb7ab99]: intrinsic gas too low: have 54299, want 54300", }, @@ -240,7 +237,8 @@ func TestStateProcessorErrors(t *testing.T) { want: "could not apply tx 0 [0x6c11015985ce82db691d7b2d017acda296db88b811c3c60dc71449c76256c716]: max fee per gas less than block base fee: address 0x71562b71999873DB5b286dF957af199Ec94617F7, maxFeePerGas: 1, baseFee: 225000000000", }, } { - block := GenerateBadBlock(gspec.ToBlock(), dummy.NewCoinbaseFaker(), tt.txs, gspec.Config) + // FullFaker used to skip header verification that enforces no blobs. + block := GenerateBadBlock(gspec.ToBlock(), dummy.NewFullFaker(), tt.txs, gspec.Config) _, err := blockchain.InsertChain(types.Blocks{block}) if err == nil { t.Fatal("block imported without errors") @@ -258,7 +256,6 @@ func TestStateProcessorErrors(t *testing.T) { gspec = &Genesis{ Config: ¶ms.ChainConfig{ ChainID: big.NewInt(1), - FeeConfig: params.DefaultFeeConfig, HomesteadBlock: big.NewInt(0), EIP150Block: big.NewInt(0), EIP155Block: big.NewInt(0), @@ -268,6 +265,10 @@ func TestStateProcessorErrors(t *testing.T) { PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(0), MuirGlacierBlock: big.NewInt(0), + NetworkUpgrades: params.NetworkUpgrades{ + ApricotPhase1BlockTimestamp: utils.NewUint64(0), + ApricotPhase2BlockTimestamp: utils.NewUint64(0), + }, }, Alloc: GenesisAlloc{ common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7"): GenesisAccount{ @@ -275,7 +276,7 @@ func TestStateProcessorErrors(t *testing.T) { Nonce: 0, }, }, - GasLimit: params.TestChainConfig.FeeConfig.GasLimit.Uint64(), + GasLimit: params.ApricotPhase1GasLimit, } blockchain, _ = NewBlockChain(db, DefaultCacheConfig, gspec, dummy.NewCoinbaseFaker(), vm.Config{}, common.Hash{}, false) ) @@ -315,7 +316,7 @@ func TestStateProcessorErrors(t *testing.T) { Code: common.FromHex("0xB0B0FACE"), }, }, - GasLimit: params.TestChainConfig.FeeConfig.GasLimit.Uint64(), + GasLimit: params.CortinaGasLimit, } blockchain, _ = NewBlockChain(db, DefaultCacheConfig, gspec, dummy.NewCoinbaseFaker(), vm.Config{}, common.Hash{}, false) ) @@ -343,85 +344,6 @@ func TestStateProcessorErrors(t *testing.T) { } } -// TestBadTxAllowListBlock tests the output generated when the -// blockchain imports a bad block with a transaction from a -// non-whitelisted TX Allow List address. -func TestBadTxAllowListBlock(t *testing.T) { - var ( - db = rawdb.NewMemoryDatabase() - testAddr = common.HexToAddress("0x71562b71999873DB5b286dF957af199Ec94617F7") - - config = ¶ms.ChainConfig{ - ChainID: big.NewInt(1), - FeeConfig: params.DefaultFeeConfig, - HomesteadBlock: big.NewInt(0), - EIP150Block: big.NewInt(0), - EIP155Block: big.NewInt(0), - EIP158Block: big.NewInt(0), - ByzantiumBlock: big.NewInt(0), - ConstantinopleBlock: big.NewInt(0), - PetersburgBlock: big.NewInt(0), - IstanbulBlock: big.NewInt(0), - MuirGlacierBlock: big.NewInt(0), - NetworkUpgrades: params.NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(0), - }, - GenesisPrecompiles: params.Precompiles{ - txallowlist.ConfigKey: txallowlist.NewConfig(utils.NewUint64(0), nil, nil, nil), - }, - } - signer = types.LatestSigner(config) - testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - - gspec = &Genesis{ - Config: config, - Alloc: GenesisAlloc{ - testAddr: GenesisAccount{ - Balance: big.NewInt(1000000000000000000), // 1 ether - Nonce: 0, - }, - }, - GasLimit: config.FeeConfig.GasLimit.Uint64(), - } - blockchain, _ = NewBlockChain(db, DefaultCacheConfig, gspec, dummy.NewCoinbaseFaker(), vm.Config{}, common.Hash{}, false) - ) - defer blockchain.Stop() - - mkDynamicTx := func(nonce uint64, to common.Address, gasLimit uint64, gasTipCap, gasFeeCap *big.Int) *types.Transaction { - tx, _ := types.SignTx(types.NewTx(&types.DynamicFeeTx{ - Nonce: nonce, - GasTipCap: gasTipCap, - GasFeeCap: gasFeeCap, - Gas: gasLimit, - To: &to, - Value: big.NewInt(0), - }), signer, testKey) - return tx - } - - defer blockchain.Stop() - for i, tt := range []struct { - txs []*types.Transaction - want string - }{ - { // Nonwhitelisted address - txs: []*types.Transaction{ - mkDynamicTx(0, common.Address{}, params.TxGas, big.NewInt(0), big.NewInt(225000000000)), - }, - want: "could not apply tx 0 [0xc5725e8baac950b2925dd4fea446ccddead1cc0affdae18b31a7d910629d9225]: cannot issue transaction from non-allow listed address: 0x71562b71999873DB5b286dF957af199Ec94617F7", - }, - } { - block := GenerateBadBlock(gspec.ToBlock(), dummy.NewCoinbaseFaker(), tt.txs, gspec.Config) - _, err := blockchain.InsertChain(types.Blocks{block}) - if err == nil { - t.Fatal("block imported without errors") - } - if have, want := err.Error(), tt.want; have != want { - t.Errorf("test %d:\nhave \"%v\"\nwant \"%v\"\n", i, have, want) - } - } -} - // GenerateBadBlock constructs a "block" which contains the transactions. The transactions are not expected to be // valid, and no proper post-state can be made. But from the perspective of the blockchain, the block is sufficiently // valid to be considered for import: @@ -442,10 +364,12 @@ func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Tr Time: parent.Time() + 10, UncleHash: types.EmptyUncleHash, } - - if config.IsSubnetEVM(header.Time) { - header.Extra, header.BaseFee, _ = dummy.CalcBaseFee(config, config.FeeConfig, parent.Header(), header.Time) + if config.IsApricotPhase3(header.Time) { + header.Extra, header.BaseFee, _ = dummy.CalcBaseFee(config, parent.Header(), header.Time) + } + if config.IsApricotPhase4(header.Time) { header.BlockGasCost = big.NewInt(0) + header.ExtDataGasUsed = big.NewInt(0) } var receipts []*types.Receipt // The post-state result doesn't need to be correct (this is a bad block), but we do need something there diff --git a/core/state_transition.go b/core/state_transition.go index a71bacc192..babd01c7a3 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -35,7 +35,6 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist" "github.com/ava-labs/subnet-evm/utils" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" @@ -350,17 +349,9 @@ func (st *StateTransition) preCheck() error { if vm.IsProhibited(msg.From) { return fmt.Errorf("%w: address %v", vmerrs.ErrAddrProhibited, msg.From) } - - // Check that the sender is on the tx allow list if enabled - if st.evm.ChainConfig().IsPrecompileEnabled(txallowlist.ContractAddress, st.evm.Context.Time) { - txAllowListRole := txallowlist.GetTxAllowListStatus(st.state, msg.From) - if !txAllowListRole.IsEnabled() { - return fmt.Errorf("%w: %s", vmerrs.ErrSenderAddressNotAllowListed, msg.From) - } - } } // Make sure that transaction gasFeeCap is greater than the baseFee (post london) - if st.evm.ChainConfig().IsSubnetEVM(st.evm.Context.Time) { + if st.evm.ChainConfig().IsApricotPhase3(st.evm.Context.Time) { // Skip the checks if gas fields are zero and baseFee was explicitly disabled (eth_call) skipCheck := st.evm.Config.NoBaseFee && msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0 if !skipCheck { @@ -431,12 +422,11 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // 1. the nonce of the message caller is correct // 2. caller has enough balance to cover transaction fee(gaslimit * gasprice) // 3. the amount of gas required is available in the block - // 4. the message caller is on the tx allow list (if enabled) - // 5. the purchased gas is enough to cover intrinsic usage - // 6. there is no overflow when calculating intrinsic gas - // 7. caller has enough balance to cover asset transfer for **topmost** call + // 4. the purchased gas is enough to cover intrinsic usage + // 5. there is no overflow when calculating intrinsic gas + // 6. caller has enough balance to cover asset transfer for **topmost** call - // Check clauses 1-4, buy gas if everything is correct + // Check clauses 1-3, buy gas if everything is correct if err := st.preCheck(); err != nil { return nil, err } @@ -476,7 +466,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { } // Execute the preparatory steps for state transition which includes: - // - prepare accessList(post-berlin) + // - prepare accessList(post-berlin/ApricotPhase2) // - reset transient storage(eip 1153) st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList) @@ -491,7 +481,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { st.state.SetNonce(msg.From, st.state.GetNonce(sender.Address())+1) ret, st.gasRemaining, vmerr = st.evm.Call(sender, st.to(), msg.Data, st.gasRemaining, msg.Value) } - gasRefund := st.refundGas(rules.IsSubnetEVM) + gasRefund := st.refundGas(rules.IsApricotPhase1) st.state.AddBalance(st.evm.Context.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), msg.GasPrice)) return &ExecutionResult{ @@ -502,10 +492,10 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { }, nil } -func (st *StateTransition) refundGas(subnetEVM bool) uint64 { +func (st *StateTransition) refundGas(apricotPhase1 bool) uint64 { var refund uint64 // Inspired by: https://gist.github.com/holiman/460f952716a74eeb9ab358bb1836d821#gistcomment-3642048 - if !subnetEVM { + if !apricotPhase1 { // Apply refund counter, capped to half of the used gas. refund = st.gasUsed() / 2 if refund > st.state.GetRefund() { diff --git a/core/test_blockchain.go b/core/test_blockchain.go index 0a109c6551..dca6e617bc 100644 --- a/core/test_blockchain.go +++ b/core/test_blockchain.go @@ -10,16 +10,11 @@ import ( "testing" "time" - "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/consensus/dummy" "github.com/ava-labs/subnet-evm/core/rawdb" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/contracts/deployerallowlist" - "github.com/ava-labs/subnet-evm/precompile/contracts/feemanager" - "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" @@ -27,6 +22,17 @@ import ( "github.com/stretchr/testify/require" ) +var TestCallbacks = dummy.ConsensusCallbacks{ + OnExtraStateChange: func(block *types.Block, sdb *state.StateDB) (*big.Int, *big.Int, error) { + sdb.SetBalanceMultiCoin(common.HexToAddress("0xdeadbeef"), common.HexToHash("0xdeadbeef"), big.NewInt(block.Number().Int64())) + return nil, nil, nil + }, + OnFinalizeAndAssemble: func(header *types.Header, sdb *state.StateDB, txs []*types.Transaction) ([]byte, *big.Int, *big.Int, error) { + sdb.SetBalanceMultiCoin(common.HexToAddress("0xdeadbeef"), common.HexToHash("0xdeadbeef"), big.NewInt(header.Number.Int64())) + return nil, nil, nil, nil + }, +} + type ChainTest struct { Name string testFunc func( @@ -60,10 +66,6 @@ var tests = []ChainTest{ "EmptyBlocks", TestEmptyBlocks, }, - { - "ReorgReInsert", - TestReorgReInsert, - }, { "AcceptBlockIdenticalStateRoot", TestAcceptBlockIdenticalStateRoot, @@ -84,10 +86,6 @@ var tests = []ChainTest{ "InsertChainValidBlockFee", TestInsertChainValidBlockFee, }, - { - "TestStatefulPrecompiles", - TestStatefulPrecompiles, - }, } func copyMemDB(db ethdb.Database) (ethdb.Database, error) { @@ -430,6 +428,8 @@ func TestAcceptNonCanonicalBlock(t *testing.T, create func(db ethdb.Database, gs key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) + // We use two separate databases since GenerateChain commits the state roots to its underlying + // database. chainDB = rawdb.NewMemoryDatabase() ) @@ -830,6 +830,7 @@ func TestBuildOnVariousStages(t *testing.T, create func(db ethdb.Database, gspec func TestEmptyBlocks(t *testing.T, create func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { chainDB := rawdb.NewMemoryDatabase() + // Ensure that key1 has some funds in the genesis block. gspec := &Genesis{ Config: ¶ms.ChainConfig{HomesteadBlock: new(big.Int)}, Alloc: GenesisAlloc{}, @@ -871,6 +872,9 @@ func TestReorgReInsert(t *testing.T, create func(db ethdb.Database, gspec *Genes key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) + // We use two separate databases since GenerateChain commits the state roots to its underlying + // database. + genDB = rawdb.NewMemoryDatabase() chainDB = rawdb.NewMemoryDatabase() ) @@ -880,6 +884,7 @@ func TestReorgReInsert(t *testing.T, create func(db ethdb.Database, gspec *Genes Config: ¶ms.ChainConfig{HomesteadBlock: new(big.Int)}, Alloc: GenesisAlloc{addr1: {Balance: genesisBalance}}, } + genesis := gspec.ToBlock() blockchain, err := create(chainDB, gspec, common.Hash{}) if err != nil { @@ -889,7 +894,7 @@ func TestReorgReInsert(t *testing.T, create func(db ethdb.Database, gspec *Genes signer := types.HomesteadSigner{} numBlocks := 3 - _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, numBlocks, 10, func(i int, gen *BlockGen) { + chain, _, err := GenerateChain(gspec.Config, genesis, blockchain.engine, genDB, numBlocks, 10, func(i int, gen *BlockGen) { // Generate a transaction to create a unique block tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1) gen.AddTx(tx) @@ -1339,7 +1344,7 @@ func TestInsertChainInvalidBlockFee(t *testing.T, create func(db ethdb.Database, // This call generates a chain of 3 blocks. signer := types.LatestSigner(params.TestChainConfig) - eng := dummy.NewFakerWithMode(dummy.Mode{ModeSkipBlockFee: true, ModeSkipCoinbase: true}) + eng := dummy.NewFakerWithMode(TestCallbacks, dummy.Mode{ModeSkipBlockFee: true, ModeSkipCoinbase: true}) _, chain, _, err := GenerateChainWithGenesis(gspec, eng, 3, 0, func(i int, gen *BlockGen) { tx := types.NewTx(&types.DynamicFeeTx{ ChainID: params.TestChainConfig.ChainID, @@ -1375,6 +1380,8 @@ func TestInsertChainValidBlockFee(t *testing.T, create func(db ethdb.Database, g key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") addr1 = crypto.PubkeyToAddress(key1.PublicKey) addr2 = crypto.PubkeyToAddress(key2.PublicKey) + // We use two separate databases since GenerateChain commits the state roots to its underlying + // database. chainDB = rawdb.NewMemoryDatabase() ) @@ -1435,8 +1442,7 @@ func TestInsertChainValidBlockFee(t *testing.T, create func(db ethdb.Database, g } balance1 := sdb.GetBalance(addr1) expectedBalance1 := new(big.Int).Sub(genesisBalance, transfer) - baseFee := params.DefaultFeeConfig.MinBaseFee - feeSpend := new(big.Int).Mul(new(big.Int).Add(baseFee, tip), new(big.Int).SetUint64(params.TxGas)) + feeSpend := new(big.Int).Mul(new(big.Int).Add(big.NewInt(225*params.GWei), tip), new(big.Int).SetUint64(params.TxGas)) expectedBalance1.Sub(expectedBalance1, feeSpend) if balance1.Cmp(expectedBalance1) != 0 { return fmt.Errorf("expected addr1 balance: %d, found balance: %d", expectedBalance1, balance1) @@ -1458,196 +1464,6 @@ func TestInsertChainValidBlockFee(t *testing.T, create func(db ethdb.Database, g checkBlockChainState(t, blockchain, gspec, chainDB, create, checkState) } -// TestStatefulPrecompiles provides a testing framework to ensure that processing transactions interacting with the stateful precompiles work as expected. -func TestStatefulPrecompiles(t *testing.T, create func(db ethdb.Database, gspec *Genesis, lastAcceptedHash common.Hash) (*BlockChain, error)) { - var ( - key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") - addr1 = crypto.PubkeyToAddress(key1.PublicKey) - addr2 = crypto.PubkeyToAddress(key2.PublicKey) - chainDB = rawdb.NewMemoryDatabase() - ) - - // Ensure that key1 has sufficient funds in the genesis block for all of the tests. - genesisBalance := new(big.Int).Mul(big.NewInt(1000000), big.NewInt(params.Ether)) - config := *params.TestChainConfig - // Set all of the required config parameters - config.GenesisPrecompiles = params.Precompiles{ - deployerallowlist.ConfigKey: deployerallowlist.NewConfig(utils.NewUint64(0), []common.Address{addr1}, nil, nil), - feemanager.ConfigKey: feemanager.NewConfig(utils.NewUint64(0), []common.Address{addr1}, nil, nil, nil), - } - gspec := &Genesis{ - Config: &config, - Alloc: GenesisAlloc{addr1: {Balance: genesisBalance}}, - } - - blockchain, err := create(chainDB, gspec, common.Hash{}) - if err != nil { - t.Fatal(err) - } - defer blockchain.Stop() - - signer := types.LatestSigner(params.TestChainConfig) - tip := big.NewInt(50000 * params.GWei) - - // Simple framework to add a test that the stateful precompile works as expected - type test struct { - addTx func(gen *BlockGen) - verifyGenesis func(sdb *state.StateDB) - verifyState func(sdb *state.StateDB) error - } - testFeeConfig := commontype.FeeConfig{ - GasLimit: big.NewInt(11_000_000), - TargetBlockRate: 5, // in seconds - - MinBaseFee: big.NewInt(28_000_000_000), - TargetGas: big.NewInt(18_000_000), - BaseFeeChangeDenominator: big.NewInt(3396), - - MinBlockGasCost: big.NewInt(0), - MaxBlockGasCost: big.NewInt(4_000_000), - BlockGasCostStep: big.NewInt(500_000), - } - assert := assert.New(t) - tests := map[string]test{ - "allow list": { - addTx: func(gen *BlockGen) { - feeCap := new(big.Int).Add(gen.BaseFee(), tip) - input, err := allowlist.PackModifyAllowList(addr2, allowlist.AdminRole) - if err != nil { - t.Fatal(err) - } - tx := types.NewTx(&types.DynamicFeeTx{ - ChainID: params.TestChainConfig.ChainID, - Nonce: gen.TxNonce(addr1), - To: &deployerallowlist.ContractAddress, - Gas: 3_000_000, - Value: common.Big0, - GasFeeCap: feeCap, - GasTipCap: tip, - Data: input, - }) - - signedTx, err := types.SignTx(tx, signer, key1) - if err != nil { - t.Fatal(err) - } - gen.AddTx(signedTx) - }, - verifyState: func(sdb *state.StateDB) error { - res := deployerallowlist.GetContractDeployerAllowListStatus(sdb, addr1) - if allowlist.AdminRole != res { - return fmt.Errorf("unexpected allow list status for addr1 %s, expected %s", res, allowlist.AdminRole) - } - res = deployerallowlist.GetContractDeployerAllowListStatus(sdb, addr2) - if allowlist.AdminRole != res { - return fmt.Errorf("unexpected allow list status for addr2 %s, expected %s", res, allowlist.AdminRole) - } - return nil - }, - verifyGenesis: func(sdb *state.StateDB) { - res := deployerallowlist.GetContractDeployerAllowListStatus(sdb, addr1) - if allowlist.AdminRole != res { - t.Fatalf("unexpected allow list status for addr1 %s, expected %s", res, allowlist.AdminRole) - } - res = deployerallowlist.GetContractDeployerAllowListStatus(sdb, addr2) - if allowlist.NoRole != res { - t.Fatalf("unexpected allow list status for addr2 %s, expected %s", res, allowlist.NoRole) - } - }, - }, - "fee manager set config": { - addTx: func(gen *BlockGen) { - feeCap := new(big.Int).Add(gen.BaseFee(), tip) - input, err := feemanager.PackSetFeeConfig(testFeeConfig) - if err != nil { - t.Fatal(err) - } - tx := types.NewTx(&types.DynamicFeeTx{ - ChainID: params.TestChainConfig.ChainID, - Nonce: gen.TxNonce(addr1), - To: &feemanager.ContractAddress, - Gas: 3_000_000, - Value: common.Big0, - GasFeeCap: feeCap, - GasTipCap: tip, - Data: input, - }) - - signedTx, err := types.SignTx(tx, signer, key1) - if err != nil { - t.Fatal(err) - } - gen.AddTx(signedTx) - }, - verifyState: func(sdb *state.StateDB) error { - res := feemanager.GetFeeManagerStatus(sdb, addr1) - assert.Equal(allowlist.AdminRole, res) - - storedConfig := feemanager.GetStoredFeeConfig(sdb) - assert.EqualValues(testFeeConfig, storedConfig) - - feeConfig, _, err := blockchain.GetFeeConfigAt(blockchain.CurrentHeader()) - assert.NoError(err) - assert.EqualValues(testFeeConfig, feeConfig) - return nil - }, - verifyGenesis: func(sdb *state.StateDB) { - res := feemanager.GetFeeManagerStatus(sdb, addr1) - assert.Equal(allowlist.AdminRole, res) - - feeConfig, _, err := blockchain.GetFeeConfigAt(blockchain.Genesis().Header()) - assert.NoError(err) - assert.EqualValues(config.FeeConfig, feeConfig) - }, - }, - } - - // Generate chain of blocks using [genDB] instead of [chainDB] to avoid writing - // to the BlockChain's database while generating blocks. - _, chain, _, err := GenerateChainWithGenesis(gspec, blockchain.engine, 1, 0, func(i int, gen *BlockGen) { - for _, test := range tests { - test.addTx(gen) - } - }) - if err != nil { - t.Fatal(err) - } - - // Insert three blocks into the chain and accept only the first block. - if _, err := blockchain.InsertChain(chain); err != nil { - t.Fatal(err) - } - if err := blockchain.Accept(chain[0]); err != nil { - t.Fatal(err) - } - blockchain.DrainAcceptorQueue() - - genesisState, err := blockchain.StateAt(blockchain.Genesis().Root()) - if err != nil { - t.Fatal(err) - } - for _, test := range tests { - if test.verifyGenesis == nil { - continue - } - test.verifyGenesis(genesisState) - } - - // Run all of the necessary state verification - checkState := func(sdb *state.StateDB) error { - for _, test := range tests { - if err := test.verifyState(sdb); err != nil { - return err - } - } - return nil - } - - // This tests that the precompiles work as expected when they are enabled - checkBlockChainState(t, blockchain, gspec, chainDB, create, checkState) -} - // CheckTxIndices checks that the transaction indices are correctly stored in the database ([tail, head]). func CheckTxIndices(t *testing.T, expectedTail *uint64, head uint64, db ethdb.Database, allowNilBlocks bool) { var tailValue uint64 diff --git a/core/trie_stress_bench_test.go b/core/trie_stress_bench_test.go deleted file mode 100644 index faaea2ca10..0000000000 --- a/core/trie_stress_bench_test.go +++ /dev/null @@ -1,120 +0,0 @@ -// (c) 2020-2021, Ava Labs, Inc. -// -// This file is a derived work, based on the go-ethereum library whose original -// notices appear below. -// -// It is distributed under a license compatible with the licensing terms of the -// original code from which it is derived. -// -// Much love to the original authors for their work. -// ********** -// Copyright 2014 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package core - -import ( - _ "embed" - "math/big" - "testing" - - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/stretchr/testify/require" -) - -var ( - //go:embed TrieStressTest.bin - stressBinStr string - //go:embed TrieStressTest.abi - stressABIStr string -) - -func BenchmarkTrie(t *testing.B) { - benchInsertChain(t, true, stressTestTrieDb(t, 100, 6, 50, 1202102)) -} - -func stressTestTrieDb(t *testing.B, numContracts int, callsPerBlock int, elements int64, gasTxLimit uint64) func(int, *BlockGen) { - require := require.New(t) - config := params.TestChainConfig - signer := types.LatestSigner(config) - testKey, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - - contractAddr := make([]common.Address, numContracts) - contractTxs := make([]*types.Transaction, numContracts) - - gasPrice := big.NewInt(225000000000) - gasCreation := uint64(258000) - deployedContracts := 0 - - for i := 0; i < numContracts; i++ { - nonce := uint64(i) - tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{ - Nonce: nonce, - Value: big.NewInt(0), - Gas: gasCreation, - GasPrice: gasPrice, - Data: common.FromHex(stressBinStr), - }), signer, testKey) - sender, _ := types.Sender(signer, tx) - contractTxs[i] = tx - contractAddr[i] = crypto.CreateAddress(sender, nonce) - } - - stressABI := contract.ParseABI(stressABIStr) - txPayload, _ := stressABI.Pack( - "writeValues", - big.NewInt(elements), - ) - - return func(i int, gen *BlockGen) { - if len(contractTxs) != deployedContracts { - // This is the first stage of the bench, the preparation stage, at - // this state the requested amount of smart contract will be - // deployed to be used in the second stage - // - // This block will be executed until all contracts are deployed, the - // code will make sure that enough instances are deployed per block - block := gen.PrevBlock(i - 1) - gas := block.GasLimit() - for ; deployedContracts < len(contractTxs) && gasCreation < gas; deployedContracts++ { - gen.AddTx(contractTxs[deployedContracts]) - require.Equal(gen.receipts[len(gen.receipts)-1].Status, types.ReceiptStatusSuccessful, "Execution of last transaction failed") - gas -= gasCreation - } - return - } - - // Benchmark itself, this is the second stage - for e := 0; e < callsPerBlock; e++ { - contractId := (i + e) % deployedContracts - tx, err := types.SignTx(types.NewTx(&types.LegacyTx{ - Nonce: gen.TxNonce(benchRootAddr), - To: &contractAddr[contractId], - Value: big.NewInt(0), - Gas: gasTxLimit, - GasPrice: gasPrice, - Data: txPayload, - }), signer, testKey) - require.NoError(err) - gen.AddTx(tx) - require.Equal(gen.receipts[len(gen.receipts)-1].Status, types.ReceiptStatusSuccessful, "Execution of last transaction failed") - } - } -} diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index 8de9a0765b..51ebc2c47d 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -408,14 +408,8 @@ func (p *BlobPool) Init(gasTip *big.Int, head *types.Header, reserve txpool.Addr for addr := range p.index { p.recheck(addr, nil) } - feeConfig, _, err := p.chain.GetFeeConfigAt(p.head) - if err != nil { - p.Close() - return err - } _, baseFee, err := dummy.EstimateNextBaseFee( p.chain.Config(), - feeConfig, p.head, uint64(time.Now().Unix()), ) @@ -808,14 +802,8 @@ func (p *BlobPool) Reset(oldHead, newHead *types.Header) { if p.chain.Config().IsCancun(p.head.Number, p.head.Time) { p.limbo.finalize(p.chain.CurrentFinalBlock()) } - feeConfig, _, err := p.chain.GetFeeConfigAt(p.head) - if err != nil { - log.Error("Failed to get fee config to reset blobpool fees", "err", err) - return - } _, baseFee, err := dummy.EstimateNextBaseFee( p.chain.Config(), - feeConfig, p.head, uint64(time.Now().Unix()), ) diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index 95b3e48c68..5a22eeed81 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -39,7 +39,6 @@ import ( "testing" "time" - "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/consensus/dummy" "github.com/ava-labs/subnet-evm/consensus/misc/eip4844" "github.com/ava-labs/subnet-evm/core" @@ -85,12 +84,18 @@ var testChainConfig *params.ChainConfig func init() { testChainConfig = new(params.ChainConfig) *testChainConfig = *params.TestChainConfig - testChainConfig.FeeConfig.MinBaseFee = new(big.Int).SetUint64(1) testChainConfig.CancunTime = new(uint64) *testChainConfig.CancunTime = uint64(time.Now().Unix()) } +// overrideMinFee sets the minimum base fee to 1 wei for the duration of the test. +func overrideMinFee(t *testing.T) { + orig := dummy.EtnaMinBaseFee + dummy.EtnaMinBaseFee = big.NewInt(1) + t.Cleanup(func() { dummy.EtnaMinBaseFee = orig }) +} + // testBlockChain is a mock of the live chain for testing the pool. type testBlockChain struct { config *params.ChainConfig @@ -129,7 +134,7 @@ func (bc *testBlockChain) CurrentBlock() *types.Header { Extra: make([]byte, params.DynamicFeeExtraDataSize), } _, baseFee, err := dummy.CalcBaseFee( - bc.config, bc.config.FeeConfig, parent, blockTime, + bc.config, parent, blockTime, ) if err != nil { panic(err) @@ -183,10 +188,6 @@ func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) { return bc.statedb, nil } -func (bc *testBlockChain) GetFeeConfigAt(header *types.Header) (commontype.FeeConfig, *big.Int, error) { - return bc.config.FeeConfig, nil, nil -} - // makeAddressReserver is a utility method to sanity check that accounts are // properly reserved by the blobpool (no duplicate reserves or unreserves). func makeAddressReserver() txpool.AddressReserver { @@ -552,7 +553,7 @@ func TestOpenDrops(t *testing.T) { chain := &testBlockChain{ config: testChainConfig, - basefee: uint256.NewInt(uint64(params.TestInitialBaseFee)), + basefee: uint256.NewInt(uint64(params.ApricotPhase3MinBaseFee)), blobfee: uint256.NewInt(params.BlobTxMinBlobGasprice), statedb: statedb, } @@ -667,7 +668,7 @@ func TestOpenIndex(t *testing.T) { chain := &testBlockChain{ config: testChainConfig, - basefee: uint256.NewInt(uint64(params.TestInitialBaseFee)), + basefee: uint256.NewInt(uint64(params.ApricotPhase3MinBaseFee)), blobfee: uint256.NewInt(params.BlobTxMinBlobGasprice), statedb: statedb, } @@ -714,6 +715,7 @@ func TestOpenIndex(t *testing.T) { // Tests that after indexing all the loaded transactions from disk, a price heap // is correctly constructed based on the head basefee and blobfee. func TestOpenHeap(t *testing.T) { + overrideMinFee(t) log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) // Create a temporary folder for the persistent backend @@ -801,6 +803,7 @@ func TestOpenHeap(t *testing.T) { // Tests that after the pool's previous state is loaded back, any transactions // over the new storage cap will get dropped. func TestOpenCap(t *testing.T) { + overrideMinFee(t) log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) // Create a temporary folder for the persistent backend @@ -842,9 +845,9 @@ func TestOpenCap(t *testing.T) { for _, datacap := range []uint64{2 * (txAvgSize + blobSize), 100 * (txAvgSize + blobSize)} { // Create a blob pool out of the pre-seeded data, but cap it to 2 blob transaction statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) - statedb.AddBalance(addr1, big.NewInt(1_000_000_000)) - statedb.AddBalance(addr2, big.NewInt(1_000_000_000)) - statedb.AddBalance(addr3, big.NewInt(1_000_000_000)) + statedb.AddBalance(addr1, big.NewInt(params.Ether)) + statedb.AddBalance(addr2, big.NewInt(params.Ether)) + statedb.AddBalance(addr3, big.NewInt(params.Ether)) statedb.Commit(0, true, false) chain := &testBlockChain{ diff --git a/core/txpool/blobpool/interface.go b/core/txpool/blobpool/interface.go index d5603cf566..230f46e79c 100644 --- a/core/txpool/blobpool/interface.go +++ b/core/txpool/blobpool/interface.go @@ -27,9 +27,6 @@ package blobpool import ( - "math/big" - - "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" @@ -54,6 +51,4 @@ type BlockChain interface { // StateAt returns a state database for a given root hash (generally the head). StateAt(root common.Hash) (*state.StateDB, error) - - GetFeeConfigAt(header *types.Header) (commontype.FeeConfig, *big.Int, error) } diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 669c483e9f..0905622e4c 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -36,7 +36,6 @@ import ( "sync/atomic" "time" - "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/consensus/dummy" "github.com/ava-labs/subnet-evm/core" "github.com/ava-labs/subnet-evm/core/state" @@ -44,7 +43,6 @@ import ( "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/metrics" "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile/contracts/feemanager" "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/prque" @@ -77,7 +75,7 @@ var ( var ( evictionInterval = time.Minute // Time interval to check for evictable transactions statsReportInterval = 8 * time.Second // Time interval to report transaction pool stats - baseFeeUpdateInterval = 10 * time.Second // Time interval at which to schedule a base fee update for the tx pool after SubnetEVM is enabled + baseFeeUpdateInterval = 10 * time.Second // Time interval at which to schedule a base fee update for the tx pool after ApricotPhase3 is enabled ) var ( @@ -134,7 +132,6 @@ type BlockChain interface { StateAt(root common.Hash) (*state.StateDB, error) SenderCacher() *core.TxSenderCacher - GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error) } // Config are the configuration parameters of the transaction pool. @@ -1359,7 +1356,7 @@ func (pool *LegacyPool) runReorg(done chan struct{}, reset *txpoolResetRequest, if reset != nil { pool.demoteUnexecutables() if reset.newHead != nil { - if pool.chainconfig.IsSubnetEVM(reset.newHead.Time) { + if pool.chainconfig.IsApricotPhase3(reset.newHead.Time) { if err := pool.updateBaseFeeAt(reset.newHead); err != nil { log.Error("error at updating base fee in tx pool", "error", err) } @@ -1495,17 +1492,6 @@ func (pool *LegacyPool) reset(oldHead, newHead *types.Header) { pool.currentStateLock.Unlock() pool.pendingNonces = newNoncer(statedb) - // when we reset txPool we should explicitly check if fee struct for min base fee has changed - // so that we can correctly drop txs with < minBaseFee from tx pool. - if pool.chainconfig.IsPrecompileEnabled(feemanager.ContractAddress, newHead.Time) { - feeConfig, _, err := pool.chain.GetFeeConfigAt(newHead) - if err != nil { - log.Error("Failed to get fee config state", "err", err, "root", newHead.Root) - return - } - pool.minimumFee = feeConfig.MinBaseFee - } - // Inject any transactions discarded due to reorgs log.Debug("Reinjecting stale transactions", "count", len(reinject)) pool.chain.SenderCacher().Recover(pool.signer, reinject) @@ -1783,13 +1769,13 @@ func (pool *LegacyPool) demoteUnexecutables() { } func (pool *LegacyPool) startPeriodicFeeUpdate() { - if pool.chainconfig.SubnetEVMTimestamp == nil { + if pool.chainconfig.ApricotPhase3BlockTimestamp == nil { return } // Call updateBaseFee here to ensure that there is not a [baseFeeUpdateInterval] delay - // when starting up in Subnet EVM before the base fee is updated. - if time.Now().After(utils.Uint64ToTime(pool.chainconfig.SubnetEVMTimestamp)) { + // when starting up in ApricotPhase3 before the base fee is updated. + if time.Now().After(utils.Uint64ToTime(pool.chainconfig.ApricotPhase3BlockTimestamp)) { pool.updateBaseFee() } @@ -1802,7 +1788,7 @@ func (pool *LegacyPool) periodicBaseFeeUpdate() { // Sleep until its time to start the periodic base fee update or the tx pool is shutting down select { - case <-time.After(time.Until(utils.Uint64ToTime(pool.chainconfig.SubnetEVMTimestamp))): + case <-time.After(time.Until(utils.Uint64ToTime(pool.chainconfig.ApricotPhase3BlockTimestamp))): case <-pool.generalShutdownChan: return // Return early if shutting down } @@ -1831,11 +1817,7 @@ func (pool *LegacyPool) updateBaseFee() { // assumes lock is already held func (pool *LegacyPool) updateBaseFeeAt(head *types.Header) error { - feeConfig, _, err := pool.chain.GetFeeConfigAt(head) - if err != nil { - return err - } - _, baseFeeEstimate, err := dummy.EstimateNextBaseFee(pool.chainconfig, feeConfig, head, uint64(time.Now().Unix())) + _, baseFeeEstimate, err := dummy.EstimateNextBaseFee(pool.chainconfig, head, uint64(time.Now().Unix())) if err != nil { return err } diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go index b3d01d2b7d..0b565502d9 100644 --- a/core/txpool/legacypool/legacypool_test.go +++ b/core/txpool/legacypool/legacypool_test.go @@ -39,7 +39,6 @@ import ( "testing" "time" - "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/core" "github.com/ava-labs/subnet-evm/core/rawdb" "github.com/ava-labs/subnet-evm/core/state" @@ -60,25 +59,13 @@ var ( // eip1559Config is a chain config with EIP-1559 enabled at block 0. eip1559Config *params.ChainConfig - - testFeeConfig = commontype.FeeConfig{ - GasLimit: big.NewInt(8_000_000), - TargetBlockRate: 2, // in seconds - - MinBaseFee: big.NewInt(25_000_000_000), - TargetGas: big.NewInt(15_000_000), - BaseFeeChangeDenominator: big.NewInt(36), - - MinBlockGasCost: big.NewInt(0), - MaxBlockGasCost: big.NewInt(1_000_000), - BlockGasCostStep: big.NewInt(200_000), - } ) func init() { cpy := *params.TestChainConfig eip1559Config = &cpy - eip1559Config.SubnetEVMTimestamp = utils.NewUint64(0) + eip1559Config.ApricotPhase2BlockTimestamp = utils.NewUint64(0) + eip1559Config.ApricotPhase3BlockTimestamp = utils.NewUint64(0) } type testBlockChain struct { @@ -127,10 +114,6 @@ func (bc *testBlockChain) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) return bc.chainHeadFeed.Subscribe(ch) } -func (bc *testBlockChain) GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error) { - return testFeeConfig, common.Big0, nil -} - func (bc *testBlockChain) SenderCacher() *core.TxSenderCacher { // Zero threads avoids starting goroutines. return core.NewTxSenderCacher(0) diff --git a/core/txpool/txpool.go b/core/txpool/txpool.go index 7539001ac0..5d0bd3b165 100644 --- a/core/txpool/txpool.go +++ b/core/txpool/txpool.go @@ -90,6 +90,7 @@ type TxPool struct { quit chan chan error // Quit channel to tear down the head updater gasTip atomic.Pointer[big.Int] // Remember last value set so it can be retrieved + minFee atomic.Pointer[big.Int] // Remember last value set so it can be retrieved (in tests) reorgFeed event.Feed } @@ -114,7 +115,6 @@ func New(gasTip *big.Int, chain BlockChain, subpools []SubPool) (*TxPool, error) return nil, err } } - pool.gasTip.Store(gasTip) // Subscribe to chain head events to trigger subpool resets var ( @@ -271,9 +271,16 @@ func (p *TxPool) SetGasTip(tip *big.Int) { } } +// MinFee returns the current minimum fee enforced by the transaction pool. +func (p *TxPool) MinFee() *big.Int { + return new(big.Int).Set(p.minFee.Load()) +} + // SetMinFee updates the minimum fee required by the transaction pool for a // new transaction, and drops all transactions below this threshold. func (p *TxPool) SetMinFee(fee *big.Int) { + p.minFee.Store(new(big.Int).Set(fee)) + for _, subpool := range p.subpools { subpool.SetMinFee(fee) } diff --git a/core/txpool/validation.go b/core/txpool/validation.go index 6e80ac9a91..3192633825 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -35,7 +35,6 @@ import ( "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto/kzg4844" @@ -69,10 +68,10 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types return fmt.Errorf("%w: transaction size %v, limit %v", ErrOversizedData, tx.Size(), opts.MaxSize) } // Ensure only transactions that have been enabled are accepted - if !opts.Config.IsSubnetEVM(head.Time) && tx.Type() != types.LegacyTxType { + if !opts.Config.IsApricotPhase2(head.Time) && tx.Type() != types.LegacyTxType { return fmt.Errorf("%w: type %d rejected, pool not yet in Berlin", core.ErrTxTypeNotSupported, tx.Type()) } - if !opts.Config.IsSubnetEVM(head.Time) && tx.Type() == types.DynamicFeeTxType { + if !opts.Config.IsApricotPhase3(head.Time) && tx.Type() == types.DynamicFeeTxType { return fmt.Errorf("%w: type %d rejected, pool not yet in London", core.ErrTxTypeNotSupported, tx.Type()) } if !opts.Config.IsCancun(head.Number, head.Time) && tx.Type() == types.BlobTxType { @@ -271,13 +270,5 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op } } - // If the tx allow list is enabled, return an error if the from address is not allow listed. - if opts.Rules.IsPrecompileEnabled(txallowlist.ContractAddress) { - txAllowListRole := txallowlist.GetTxAllowListStatus(opts.State, from) - if !txAllowListRole.IsEnabled() { - return fmt.Errorf("%w: %s", vmerrs.ErrSenderAddressNotAllowListed, from) - } - } - return nil } diff --git a/core/types/block.go b/core/types/block.go index 81393e52ee..6038046e31 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -86,11 +86,19 @@ type Header struct { Extra []byte `json:"extraData" gencodec:"required"` MixDigest common.Hash `json:"mixHash"` Nonce BlockNonce `json:"nonce"` + ExtDataHash common.Hash `json:"extDataHash" gencodec:"required"` // BaseFee was added by EIP-1559 and is ignored in legacy headers. BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"` - // BlockGasCost was added by SubnetEVM and is ignored in legacy + // ExtDataGasUsed was added by Apricot Phase 4 and is ignored in legacy + // headers. + // + // It is not a uint64 like GasLimit or GasUsed because it is not possible to + // correctly encode this field optionally with uint64. + ExtDataGasUsed *big.Int `json:"extDataGasUsed" rlp:"optional"` + + // BlockGasCost was added by Apricot Phase 4 and is ignored in legacy // headers. BlockGasCost *big.Int `json:"blockGasCost" rlp:"optional"` @@ -106,17 +114,18 @@ type Header struct { // field type overrides for gencodec type headerMarshaling struct { - Difficulty *hexutil.Big - Number *hexutil.Big - GasLimit hexutil.Uint64 - GasUsed hexutil.Uint64 - Time hexutil.Uint64 - Extra hexutil.Bytes - BaseFee *hexutil.Big - BlockGasCost *hexutil.Big - Hash common.Hash `json:"hash"` // adds call to Hash() in MarshalJSON - BlobGasUsed *hexutil.Uint64 - ExcessBlobGas *hexutil.Uint64 + Difficulty *hexutil.Big + Number *hexutil.Big + GasLimit hexutil.Uint64 + GasUsed hexutil.Uint64 + Time hexutil.Uint64 + Extra hexutil.Bytes + BaseFee *hexutil.Big + ExtDataGasUsed *hexutil.Big + BlockGasCost *hexutil.Big + Hash common.Hash `json:"hash"` // adds call to Hash() in MarshalJSON + BlobGasUsed *hexutil.Uint64 + ExcessBlobGas *hexutil.Uint64 } // Hash returns the block hash of the header, which is simply the keccak256 hash of its @@ -153,6 +162,8 @@ func (h *Header) EmptyReceipts() bool { type Body struct { Transactions []*Transaction Uncles []*Header + Version uint32 + ExtData *[]byte `rlp:"nil"` } // Block represents an Ethereum block. @@ -177,6 +188,10 @@ type Block struct { uncles []*Header transactions Transactions + // Coreth specific data structures to support atomic transactions + version uint32 + extdata *[]byte + // caches hash atomic.Value size atomic.Value @@ -184,9 +199,11 @@ type Block struct { // "external" block encoding. used for eth protocol, etc. type extblock struct { - Header *Header - Txs []*Transaction - Uncles []*Header + Header *Header + Txs []*Transaction + Uncles []*Header + Version uint32 + ExtData *[]byte `rlp:"nil"` } // NewBlock creates a new block. The input data is copied, changes to header and to the @@ -241,6 +258,9 @@ func CopyHeader(h *Header) *Header { if h.BaseFee != nil { cpy.BaseFee = new(big.Int).Set(h.BaseFee) } + if h.ExtDataGasUsed != nil { + cpy.ExtDataGasUsed = new(big.Int).Set(h.ExtDataGasUsed) + } if h.BlockGasCost != nil { cpy.BlockGasCost = new(big.Int).Set(h.BlockGasCost) } @@ -270,7 +290,7 @@ func (b *Block) DecodeRLP(s *rlp.Stream) error { if err := s.Decode(&eb); err != nil { return err } - b.header, b.uncles, b.transactions = eb.Header, eb.Uncles, eb.Txs + b.header, b.uncles, b.transactions, b.version, b.extdata = eb.Header, eb.Uncles, eb.Txs, eb.Version, eb.ExtData b.size.Store(rlp.ListSize(size)) return nil } @@ -278,16 +298,18 @@ func (b *Block) DecodeRLP(s *rlp.Stream) error { // EncodeRLP serializes a block as RLP. func (b *Block) EncodeRLP(w io.Writer) error { return rlp.Encode(w, &extblock{ - Header: b.header, - Txs: b.transactions, - Uncles: b.uncles, + Header: b.header, + Txs: b.transactions, + Uncles: b.uncles, + Version: b.version, + ExtData: b.extdata, }) } // Body returns the non-header content of the block. // Note the returned data is not an independent copy. func (b *Block) Body() *Body { - return &Body{b.transactions, b.uncles} + return &Body{b.transactions, b.uncles, b.version, b.extdata} } // Accessors for body data. These do not return a copy because the content diff --git a/core/types/block_test.go b/core/types/block_test.go index c484dd268d..bb50d5d28a 100644 --- a/core/types/block_test.go +++ b/core/types/block_test.go @@ -40,8 +40,10 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) +// This test has been modified from https://github.com/ethereum/go-ethereum/blob/v1.9.21/core/types/block_test.go#L35 to fit +// the modified block format of Coreth func TestBlockEncoding(t *testing.T) { - blockEnc := common.FromHex("f90260f901f9a083cafc574e1f51ba9dc0568fc617a08ea2429fb384059c972f13b19fa1c8dd55a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a05fe50b260da6308036625b850b5d6ced6d0a9f814c0688bc91ffb7b7a3a54b67a0bc37d79753ad738a6dac4921e57392f145d8887476de3f783dfa7edae9283e52b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001832fefd8825208845506eb0780a0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4f861f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1c0") + blockEnc := common.FromHex("f90291f90217a04504ee98a94d16dbd70a35370501a3cb00c2965b012672085fbd328a72962902a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940100000000000000000000000000000000000000a00202e12a30c13562445052414c24dce5f1c530bb164e2a50897f0a6a1f78f158a0ecdf3b2c973d4156782b95816451fe9ed66b099cdca22f1168591ae2087765f4a0056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000103837a12008252088460674e8a80a00000000000000000000000000000000000000000000000000000000000000000880000000000000000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421f872f870808534630b8a00825208941954b772512974978793a809ecd8dce02dc71ba989014d1120d7b160000080830150f4a0beebf298ec38f9f4204f924686c4e5dd00f525fc1979ad224661ed2839ed55fda0267c480d1236c1684bdbad564a422e0a05007d7e8ca1acefe34e790b1d3a450ec08080") var block Block if err := rlp.DecodeBytes(blockEnc, &block); err != nil { t.Fatal("decode error: ", err) @@ -52,28 +54,30 @@ func TestBlockEncoding(t *testing.T) { t.Errorf("%s mismatch: got %v, want %v", f, got, want) } } - check("ParentHash", block.ParentHash(), common.HexToHash("0x83cafc574e1f51ba9dc0568fc617a08ea2429fb384059c972f13b19fa1c8dd55")) + check("ParentHash", block.ParentHash(), common.HexToHash("4504ee98a94d16dbd70a35370501a3cb00c2965b012672085fbd328a72962902")) check("UncleHash", block.UncleHash(), common.HexToHash("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347")) - check("Coinbase", block.Coinbase(), common.HexToAddress("0x8888f1F195AFa192CfeE860698584c030f4c9dB1")) - check("Root", block.Root(), common.HexToHash("0xef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017")) - check("TxHash", block.TxHash(), common.HexToHash("0x5fe50b260da6308036625b850b5d6ced6d0a9f814c0688bc91ffb7b7a3a54b67")) - check("ReceiptHash", block.ReceiptHash(), common.HexToHash("0xbc37d79753ad738a6dac4921e57392f145d8887476de3f783dfa7edae9283e52")) + check("Coinbase", block.Coinbase(), common.HexToAddress("0100000000000000000000000000000000000000")) + check("Root", block.Root(), common.HexToHash("0202e12a30c13562445052414c24dce5f1c530bb164e2a50897f0a6a1f78f158")) + check("TxHash", block.TxHash(), common.HexToHash("ecdf3b2c973d4156782b95816451fe9ed66b099cdca22f1168591ae2087765f4")) + check("ReceiptHash", block.ReceiptHash(), common.HexToHash("056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2")) check("Bloom", block.Bloom(), BytesToBloom(common.FromHex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"))) - check("Difficulty", block.Difficulty(), big.NewInt(131072)) - check("BlockNumber", block.NumberU64(), uint64(1)) - check("GasLimit", block.GasLimit(), uint64(3141592)) + check("Difficulty", block.Difficulty(), big.NewInt(1)) + check("BlockNumber", block.NumberU64(), uint64(3)) + check("GasLimit", block.GasLimit(), uint64(8000000)) check("GasUsed", block.GasUsed(), uint64(21000)) - check("Time", block.Time(), uint64(1426516743)) + check("Time", block.Time(), uint64(1617383050)) check("Extra", block.Extra(), common.FromHex("")) - check("MixDigest", block.MixDigest(), common.HexToHash("0xbd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff498")) - check("Nonce", block.Nonce(), uint64(11617697748499542468)) + check("MixDigest", block.MixDigest(), common.HexToHash("0000000000000000000000000000000000000000000000000000000000000000")) + check("Nonce", block.Nonce(), uint64(0)) + check("ExtDataHash", block.header.ExtDataHash, common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")) check("BaseFee", block.BaseFee(), (*big.Int)(nil)) + check("ExtDataGasUsed", block.ExtDataGasUsed(), (*big.Int)(nil)) check("BlockGasCost", block.BlockGasCost(), (*big.Int)(nil)) check("Size", block.Size(), uint64(len(blockEnc))) - check("BlockHash", block.Hash(), common.HexToHash("0x0a5843ac1cb04865017cb35a57b50b07084e5fcee39b5acadade33149f4fff9e")) + check("BlockHash", block.Hash(), common.HexToHash("0608e5d5e13c337f226b621a0b08b3d50470f1961329826fd59f5a241d1df49e")) - txHash := common.HexToHash("0x77b19baa4de67e45a7b26e4a220bccdbb6731885aa9927064e239ca232023215") + txHash := common.HexToHash("f5a60149da2ea4e97061a9f47c66036ee843fa76cd1f9ce5a71eb55ff90b2e0e") check("len(Transactions)", len(block.Transactions()), 1) check("Transactions[0].Hash", block.Transactions()[0].Hash(), txHash) ourBlockEnc, err := rlp.EncodeToBytes(&block) @@ -86,7 +90,7 @@ func TestBlockEncoding(t *testing.T) { } func TestEIP1559BlockEncoding(t *testing.T) { - blockEnc := common.FromHex("f9030bf901fea083cafc574e1f51ba9dc0568fc617a08ea2429fb384059c972f13b19fa1c8dd55a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a05fe50b260da6308036625b850b5d6ced6d0a9f814c0688bc91ffb7b7a3a54b67a0bc37d79753ad738a6dac4921e57392f145d8887476de3f783dfa7edae9283e52b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001832fefd8825208845506eb0780a0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4843b9aca00f90106f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1b8a302f8a0018080843b9aca008301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8c0") + blockEnc := common.FromHex("f9032ef9021fa04504ee98a94d16dbd70a35370501a3cb00c2965b012672085fbd328a72962902a00000000000000000000000000000000000000000000000000000000000000000948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000003832fefd8825208845506eb0780a0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4a00000000000000000000000000000000000000000000000000000000000000000843b9aca00f90106f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1b8a302f8a0018080843b9aca008301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8c08080") var block Block if err := rlp.DecodeBytes(blockEnc, &block); err != nil { t.Fatal("decode error: ", err) @@ -104,11 +108,12 @@ func TestEIP1559BlockEncoding(t *testing.T) { check("Coinbase", block.Coinbase(), common.HexToAddress("8888f1f195afa192cfee860698584c030f4c9db1")) check("MixDigest", block.MixDigest(), common.HexToHash("bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff498")) check("Root", block.Root(), common.HexToHash("ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017")) - check("Hash", block.Hash(), common.HexToHash("c7252048cd273fe0dac09650027d07f0e3da4ee0675ebbb26627cea92729c372")) + check("Hash", block.Hash(), common.HexToHash("2aefaa81ae43541bf2d608e2bb26a157212394abad4d219c06163be0d5d010f8")) check("Nonce", block.Nonce(), uint64(0xa13a5a8c8f2bb1c4)) check("Time", block.Time(), uint64(1426516743)) check("Size", block.Size(), uint64(len(blockEnc))) - check("BaseFee", block.BaseFee(), new(big.Int).SetUint64(1000000000)) + check("BaseFee", block.BaseFee(), new(big.Int).SetUint64(1_000_000_000)) + check("ExtDataGasUsed", block.ExtDataGasUsed(), (*big.Int)(nil)) check("BlockGasCost", block.BlockGasCost(), (*big.Int)(nil)) tx1 := NewTransaction(0, common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), big.NewInt(10), 50000, big.NewInt(10), nil) @@ -152,7 +157,7 @@ func TestEIP1559BlockEncoding(t *testing.T) { } func TestEIP2718BlockEncoding(t *testing.T) { - blockEnc := common.FromHex("f90319f90211a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a0e6e49996c7ec59f7a23d22b83239a60151512c65613bf84a0d7da336399ebc4aa0cafe75574d59780665a97fbfd11365c7545aa8f1abf4e5e12e8243334ef7286bb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000820200832fefd882a410845506eb0796636f6f6c65737420626c6f636b206f6e20636861696ea0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4f90101f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1b89e01f89b01800a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000001a03dbacc8d0259f2508625e97fdfc57cd85fdd16e5821bc2c10bdd1a52649e8335a0476e10695b183a87b0aa292a7f4b78ef0c3fbe62aa2c42c84e1d9c3da159ef14c0") + blockEnc := common.FromHex("f9033cf90232a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a0e6e49996c7ec59f7a23d22b83239a60151512c65613bf84a0d7da336399ebc4aa0cafe75574d59780665a97fbfd11365c7545aa8f1abf4e5e12e8243334ef7286bb901000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000083020000820200832fefd882a410845506eb0796636f6f6c65737420626c6f636b206f6e20636861696ea0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421f90101f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1b89e01f89b01800a8301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000001a03dbacc8d0259f2508625e97fdfc57cd85fdd16e5821bc2c10bdd1a52649e8335a0476e10695b183a87b0aa292a7f4b78ef0c3fbe62aa2c42c84e1d9c3da159ef14c08080") var block Block if err := rlp.DecodeBytes(blockEnc, &block); err != nil { t.Fatal("decode error: ", err) @@ -172,7 +177,9 @@ func TestEIP2718BlockEncoding(t *testing.T) { check("Nonce", block.Nonce(), uint64(0xa13a5a8c8f2bb1c4)) check("Time", block.Time(), uint64(1426516743)) check("Size", block.Size(), uint64(len(blockEnc))) + check("ExtDataHash", block.header.ExtDataHash, common.HexToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")) check("BaseFee", block.BaseFee(), (*big.Int)(nil)) + check("ExtDataGasUsed", block.ExtDataGasUsed(), (*big.Int)(nil)) check("BlockGasCost", block.BlockGasCost(), (*big.Int)(nil)) // Create legacy tx. @@ -205,6 +212,61 @@ func TestEIP2718BlockEncoding(t *testing.T) { check("Transactions[1].Hash", block.Transactions()[1].Hash(), tx2.Hash()) check("Transactions[1].Type()", block.Transactions()[1].Type(), uint8(AccessListTxType)) + if !bytes.Equal(block.ExtData(), []byte{}) { + t.Errorf("Block ExtraData field mismatch, expected empty byte array, but found 0x%x", block.ExtData()) + } + + ourBlockEnc, err := rlp.EncodeToBytes(&block) + if err != nil { + t.Fatal("encode error: ", err) + } + if !bytes.Equal(ourBlockEnc, blockEnc) { + t.Errorf("encoded block mismatch:\ngot: %x\nwant: %x", ourBlockEnc, blockEnc) + } +} + +func TestBlockEncodingWithExtraData(t *testing.T) { + blockEnc := common.FromHex("f903f2f90215a02a0d1d68d26eb213cf1c6c1e6abbaf374f0ee9a5428558df334c36d380c6a080a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940100000000000000000000000000000000000000a0c0caa90fe3722cb2e288f7998d54a855a6d40f67e0e77a695d0d65dad22c6290a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000102837a1200808460674e3380a00000000000000000000000000000000000000000000000000000000000000000880000000000000000a0296ff3bfdebf7c4b1fb71f589d69ed03b1c59b278d1780d54dc86ea7cb87cf17c0c080b901d400000000000000003039c85fc1980a77c5da78fe5486233fc09a769bb812bcb2cc548cf9495d046b3f1bd891ad56056d9c01f18f43f58b5c784ad07a4a49cf3d1f11623804b5cba2c6bf000000028a0f7c3e4d840143671a4c4ecacccb4d60fb97dce97a7aa5d60dfd072a7509cf00000001dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db0000000500002d79883d20000000000100000000e0d5c4edc78f594b79025a56c44933c28e8ba3e51e6e23318727eeaac10eb27d00000001dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db0000000500002d79883d20000000000100000000000000016dc8ea73dd39ab12fa2ecbc3427abaeb87d56fd800005af3107a4000dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db0000000200000009000000010d9f115cd63c3ab78b5b82cfbe4339cd6be87f21cda14cf192b269c7a6cb2d03666aa8f8b23ca0a2ceee4050e75c9b05525a17aa1dd0e9ea391a185ce395943f0000000009000000010d9f115cd63c3ab78b5b82cfbe4339cd6be87f21cda14cf192b269c7a6cb2d03666aa8f8b23ca0a2ceee4050e75c9b05525a17aa1dd0e9ea391a185ce395943f00") + var block Block + if err := rlp.DecodeBytes(blockEnc, &block); err != nil { + t.Fatal("decode error: ", err) + } + + check := func(f string, got, want interface{}) { + if !reflect.DeepEqual(got, want) { + t.Errorf("%s mismatch: got %v, want %v", f, got, want) + } + } + check("ParentHash", block.ParentHash(), common.HexToHash("2a0d1d68d26eb213cf1c6c1e6abbaf374f0ee9a5428558df334c36d380c6a080")) + check("UncleHash", block.UncleHash(), common.HexToHash("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347")) + check("Coinbase", block.Coinbase(), common.HexToAddress("0100000000000000000000000000000000000000")) + check("Root", block.Root(), common.HexToHash("c0caa90fe3722cb2e288f7998d54a855a6d40f67e0e77a695d0d65dad22c6290")) + check("TxHash", block.TxHash(), common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")) + check("ReceiptHash", block.ReceiptHash(), common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")) + check("Bloom", block.Bloom(), BytesToBloom(common.FromHex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"))) + check("Difficulty", block.Difficulty(), big.NewInt(1)) + check("BlockNumber", block.NumberU64(), uint64(2)) + check("GasLimit", block.GasLimit(), uint64(8000000)) + check("GasUsed", block.GasUsed(), uint64(0)) + check("Time", block.Time(), uint64(1617382963)) + check("Extra", block.Extra(), common.FromHex("")) + check("MixDigest", block.MixDigest(), common.HexToHash("0000000000000000000000000000000000000000000000000000000000000000")) + check("Nonce", block.Nonce(), uint64(0)) + check("ExtDataHash", block.header.ExtDataHash, common.HexToHash("296ff3bfdebf7c4b1fb71f589d69ed03b1c59b278d1780d54dc86ea7cb87cf17")) + check("BaseFee", block.BaseFee(), (*big.Int)(nil)) + check("ExtDataGasUsed", block.ExtDataGasUsed(), (*big.Int)(nil)) + check("BlockGasCost", block.BlockGasCost(), (*big.Int)(nil)) + + check("Size", block.Size(), uint64(len(blockEnc))) + check("BlockHash", block.Hash(), common.HexToHash("4504ee98a94d16dbd70a35370501a3cb00c2965b012672085fbd328a72962902")) + + check("len(Transactions)", len(block.Transactions()), 0) + + expectedBlockExtraData := common.FromHex("00000000000000003039c85fc1980a77c5da78fe5486233fc09a769bb812bcb2cc548cf9495d046b3f1bd891ad56056d9c01f18f43f58b5c784ad07a4a49cf3d1f11623804b5cba2c6bf000000028a0f7c3e4d840143671a4c4ecacccb4d60fb97dce97a7aa5d60dfd072a7509cf00000001dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db0000000500002d79883d20000000000100000000e0d5c4edc78f594b79025a56c44933c28e8ba3e51e6e23318727eeaac10eb27d00000001dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db0000000500002d79883d20000000000100000000000000016dc8ea73dd39ab12fa2ecbc3427abaeb87d56fd800005af3107a4000dbcf890f77f49b96857648b72b77f9f82937f28a68704af05da0dc12ba53f2db0000000200000009000000010d9f115cd63c3ab78b5b82cfbe4339cd6be87f21cda14cf192b269c7a6cb2d03666aa8f8b23ca0a2ceee4050e75c9b05525a17aa1dd0e9ea391a185ce395943f0000000009000000010d9f115cd63c3ab78b5b82cfbe4339cd6be87f21cda14cf192b269c7a6cb2d03666aa8f8b23ca0a2ceee4050e75c9b05525a17aa1dd0e9ea391a185ce395943f00") + if !bytes.Equal(block.ExtData(), expectedBlockExtraData) { + t.Errorf("Block ExtraData field mismatch, expected 0x%x, but found 0x%x", block.ExtData(), expectedBlockExtraData) + } + ourBlockEnc, err := rlp.EncodeToBytes(&block) if err != nil { t.Fatal("encode error: ", err) @@ -278,9 +340,8 @@ func makeBenchBlock() *Block { return NewBlock(header, txs, uncles, receipts, blocktest.NewHasher()) } -func TestSubnetEVMBlockEncoding(t *testing.T) { - blockEnc := common.FromHex("f9030ff90202a083cafc574e1f51ba9dc0568fc617a08ea2429fb384059c972f13b19fa1c8dd55a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a05fe50b260da6308036625b850b5d6ced6d0a9f814c0688bc91ffb7b7a3a54b67a0bc37d79753ad738a6dac4921e57392f145d8887476de3f783dfa7edae9283e52b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000001832fefd8825208845506eb0780a0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4843b9aca00830186a0f90106f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1b8a302f8a0018080843b9aca008301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8c0") - +func TestAP4BlockEncoding(t *testing.T) { + blockEnc := common.FromHex("f90335f90226a04504ee98a94d16dbd70a35370501a3cb00c2965b012672085fbd328a72962902a00000000000000000000000000000000000000000000000000000000000000000948888f1f195afa192cfee860698584c030f4c9db1a0ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017a00000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000b90100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008302000003832fefd8825208845506eb0780a0bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff49888a13a5a8c8f2bb1c4a00000000000000000000000000000000000000000000000000000000000000000843b9aca008261a8830f4240f90106f85f800a82c35094095e7baea6a6c7c4c2dfeb977efac326af552d870a801ba09bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094fa08a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b1b8a302f8a0018080843b9aca008301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0fe38ca4e44a30002ac54af7cf922a6ac2ba11b7d22f548e8ecb3f51f41cb31b0a06de6a5cbae13c0c856e33acf021b51819636cfc009d39eafb9f606d546e305a8c08080") var block Block if err := rlp.DecodeBytes(blockEnc, &block); err != nil { t.Fatal("decode error: ", err) @@ -298,12 +359,14 @@ func TestSubnetEVMBlockEncoding(t *testing.T) { check("Coinbase", block.Coinbase(), common.HexToAddress("8888f1f195afa192cfee860698584c030f4c9db1")) check("MixDigest", block.MixDigest(), common.HexToHash("bd4472abb6659ebe3ee06ee4d7b72a00a9f4d001caca51342001075469aff498")) check("Root", block.Root(), common.HexToHash("ef1552a40b7165c3cd773806b9e0c165b75356e0314bf0706f279c729f51e017")) - check("Hash", block.Hash(), common.HexToHash("0x06206d4ff804e93b36a8447a12a47653b07fd18115a05956c5ed8817f0b11eb9")) + // Hash of EIP-1559 block without ExtDataGasUsed and BlockGasCost is `0x2aefaa81ae43541bf2d608e2bb26a157212394abad4d219c06163be0d5d010f8` + check("Hash", block.Hash(), common.HexToHash("0xc41340f5d2af79a12373bc8d6f0f05f9f98b240834608f428da171449e8a1468")) check("Nonce", block.Nonce(), uint64(0xa13a5a8c8f2bb1c4)) check("Time", block.Time(), uint64(1426516743)) check("Size", block.Size(), uint64(len(blockEnc))) check("BaseFee", block.BaseFee(), big.NewInt(1_000_000_000)) - check("BlockGasCost", block.BlockGasCost(), big.NewInt(100_000)) + check("ExtDataGasUsed", block.ExtDataGasUsed(), big.NewInt(25_000)) + check("BlockGasCost", block.BlockGasCost(), big.NewInt(1_000_000)) tx1 := NewTransaction(0, common.HexToAddress("095e7baea6a6c7c4c2dfeb977efac326af552d87"), big.NewInt(10), 50000, big.NewInt(10), nil) tx1, _ = tx1.WithSignature(HomesteadSigner{}, common.Hex2Bytes("9bea4c4daac7c7c52e093e6a4c35dbbcf8856f1af7b059ba20253e70848d094f8a8fae537ce25ed8cb5af9adac3f141af69bd515bd2ba031522df09b97dd72b100")) diff --git a/core/types/gen_account_rlp.go b/core/types/gen_account_rlp.go index 951632fb58..cf0e572336 100644 --- a/core/types/gen_account_rlp.go +++ b/core/types/gen_account_rlp.go @@ -22,6 +22,7 @@ func (obj *StateAccount) EncodeRLP(_w io.Writer) error { } w.WriteBytes(obj.Root[:]) w.WriteBytes(obj.CodeHash) + w.WriteBool(obj.IsMultiCoin) w.ListEnd(_tmp0) return w.Flush() } diff --git a/core/types/gen_header_json.go b/core/types/gen_header_json.go index 0bc26511f0..632a6ed48e 100644 --- a/core/types/gen_header_json.go +++ b/core/types/gen_header_json.go @@ -31,7 +31,9 @@ func (h Header) MarshalJSON() ([]byte, error) { Extra hexutil.Bytes `json:"extraData" gencodec:"required"` MixDigest common.Hash `json:"mixHash"` Nonce BlockNonce `json:"nonce"` + ExtDataHash common.Hash `json:"extDataHash" gencodec:"required"` BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` + ExtDataGasUsed *hexutil.Big `json:"extDataGasUsed" rlp:"optional"` BlockGasCost *hexutil.Big `json:"blockGasCost" rlp:"optional"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"` @@ -54,7 +56,9 @@ func (h Header) MarshalJSON() ([]byte, error) { enc.Extra = h.Extra enc.MixDigest = h.MixDigest enc.Nonce = h.Nonce + enc.ExtDataHash = h.ExtDataHash enc.BaseFee = (*hexutil.Big)(h.BaseFee) + enc.ExtDataGasUsed = (*hexutil.Big)(h.ExtDataGasUsed) enc.BlockGasCost = (*hexutil.Big)(h.BlockGasCost) enc.BlobGasUsed = (*hexutil.Uint64)(h.BlobGasUsed) enc.ExcessBlobGas = (*hexutil.Uint64)(h.ExcessBlobGas) @@ -81,7 +85,9 @@ func (h *Header) UnmarshalJSON(input []byte) error { Extra *hexutil.Bytes `json:"extraData" gencodec:"required"` MixDigest *common.Hash `json:"mixHash"` Nonce *BlockNonce `json:"nonce"` + ExtDataHash *common.Hash `json:"extDataHash" gencodec:"required"` BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` + ExtDataGasUsed *hexutil.Big `json:"extDataGasUsed" rlp:"optional"` BlockGasCost *hexutil.Big `json:"blockGasCost" rlp:"optional"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed" rlp:"optional"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas" rlp:"optional"` @@ -149,9 +155,16 @@ func (h *Header) UnmarshalJSON(input []byte) error { if dec.Nonce != nil { h.Nonce = *dec.Nonce } + if dec.ExtDataHash == nil { + return errors.New("missing required field 'extDataHash' for Header") + } + h.ExtDataHash = *dec.ExtDataHash if dec.BaseFee != nil { h.BaseFee = (*big.Int)(dec.BaseFee) } + if dec.ExtDataGasUsed != nil { + h.ExtDataGasUsed = (*big.Int)(dec.ExtDataGasUsed) + } if dec.BlockGasCost != nil { h.BlockGasCost = (*big.Int)(dec.BlockGasCost) } diff --git a/core/types/gen_header_rlp.go b/core/types/gen_header_rlp.go index 6553c079ca..711a33b8ca 100644 --- a/core/types/gen_header_rlp.go +++ b/core/types/gen_header_rlp.go @@ -37,12 +37,14 @@ func (obj *Header) EncodeRLP(_w io.Writer) error { w.WriteBytes(obj.Extra) w.WriteBytes(obj.MixDigest[:]) w.WriteBytes(obj.Nonce[:]) + w.WriteBytes(obj.ExtDataHash[:]) _tmp1 := obj.BaseFee != nil - _tmp2 := obj.BlockGasCost != nil - _tmp3 := obj.BlobGasUsed != nil - _tmp4 := obj.ExcessBlobGas != nil - _tmp5 := obj.ParentBeaconRoot != nil - if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 { + _tmp2 := obj.ExtDataGasUsed != nil + _tmp3 := obj.BlockGasCost != nil + _tmp4 := obj.BlobGasUsed != nil + _tmp5 := obj.ExcessBlobGas != nil + _tmp6 := obj.ParentBeaconRoot != nil + if _tmp1 || _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 { if obj.BaseFee == nil { w.Write(rlp.EmptyString) } else { @@ -52,7 +54,17 @@ func (obj *Header) EncodeRLP(_w io.Writer) error { w.WriteBigInt(obj.BaseFee) } } - if _tmp2 || _tmp3 || _tmp4 || _tmp5 { + if _tmp2 || _tmp3 || _tmp4 || _tmp5 || _tmp6 { + if obj.ExtDataGasUsed == nil { + w.Write(rlp.EmptyString) + } else { + if obj.ExtDataGasUsed.Sign() == -1 { + return rlp.ErrNegativeBigInt + } + w.WriteBigInt(obj.ExtDataGasUsed) + } + } + if _tmp3 || _tmp4 || _tmp5 || _tmp6 { if obj.BlockGasCost == nil { w.Write(rlp.EmptyString) } else { @@ -62,21 +74,21 @@ func (obj *Header) EncodeRLP(_w io.Writer) error { w.WriteBigInt(obj.BlockGasCost) } } - if _tmp3 || _tmp4 || _tmp5 { + if _tmp4 || _tmp5 || _tmp6 { if obj.BlobGasUsed == nil { w.Write([]byte{0x80}) } else { w.WriteUint64((*obj.BlobGasUsed)) } } - if _tmp4 || _tmp5 { + if _tmp5 || _tmp6 { if obj.ExcessBlobGas == nil { w.Write([]byte{0x80}) } else { w.WriteUint64((*obj.ExcessBlobGas)) } } - if _tmp5 { + if _tmp6 { if obj.ParentBeaconRoot == nil { w.Write([]byte{0x80}) } else { diff --git a/core/types/hashes.go b/core/types/hashes.go index 8ed4dd152b..3d8a8a013b 100644 --- a/core/types/hashes.go +++ b/core/types/hashes.go @@ -50,6 +50,9 @@ var ( // EmptyVerkleHash is the known hash of an empty verkle trie. EmptyVerkleHash = common.Hash{} + + // EmptyExtDataHash is the known hash of empty extdata bytes. + EmptyExtDataHash = rlpHash([]byte(nil)) ) // TrieRootHash returns the hash itself if it's non-empty or the predefined diff --git a/core/types/state_account.go b/core/types/state_account.go index 16dc9c2795..54d22c8ba9 100644 --- a/core/types/state_account.go +++ b/core/types/state_account.go @@ -39,10 +39,11 @@ import ( // StateAccount is the Ethereum consensus representation of accounts. // These objects are stored in the main account trie. type StateAccount struct { - Nonce uint64 - Balance *big.Int - Root common.Hash // merkle root of the storage trie - CodeHash []byte + Nonce uint64 + Balance *big.Int + Root common.Hash // merkle root of the storage trie + CodeHash []byte + IsMultiCoin bool } // NewEmptyStateAccount constructs an empty state account. @@ -61,10 +62,11 @@ func (acct *StateAccount) Copy() *StateAccount { balance = new(big.Int).Set(acct.Balance) } return &StateAccount{ - Nonce: acct.Nonce, - Balance: balance, - Root: acct.Root, - CodeHash: common.CopyBytes(acct.CodeHash), + Nonce: acct.Nonce, + Balance: balance, + Root: acct.Root, + CodeHash: common.CopyBytes(acct.CodeHash), + IsMultiCoin: acct.IsMultiCoin, } } @@ -72,17 +74,19 @@ func (acct *StateAccount) Copy() *StateAccount { // with a byte slice. This format can be used to represent full-consensus format // or slim format which replaces the empty root and code hash as nil byte slice. type SlimAccount struct { - Nonce uint64 - Balance *big.Int - Root []byte // Nil if root equals to types.EmptyRootHash - CodeHash []byte // Nil if hash equals to types.EmptyCodeHash + Nonce uint64 + Balance *big.Int + Root []byte // Nil if root equals to types.EmptyRootHash + CodeHash []byte // Nil if hash equals to types.EmptyCodeHash + IsMultiCoin bool } // SlimAccountRLP encodes the state account in 'slim RLP' format. func SlimAccountRLP(account StateAccount) []byte { slim := SlimAccount{ - Nonce: account.Nonce, - Balance: account.Balance, + Nonce: account.Nonce, + Balance: account.Balance, + IsMultiCoin: account.IsMultiCoin, } if account.Root != EmptyRootHash { slim.Root = account.Root[:] @@ -105,7 +109,7 @@ func FullAccount(data []byte) (*StateAccount, error) { return nil, err } var account StateAccount - account.Nonce, account.Balance = slim.Nonce, slim.Balance + account.Nonce, account.Balance, account.IsMultiCoin = slim.Nonce, slim.Balance, slim.IsMultiCoin // Interpret the storage root and code hash in slim format. if len(slim.Root) == 0 { diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index 8fd50748f1..377136c8a3 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -51,8 +51,10 @@ func MakeSigner(config *params.ChainConfig, blockNumber *big.Int, blockTime uint switch { case config.IsCancun(blockNumber, blockTime): return NewCancunSigner(config.ChainID) - case config.IsSubnetEVM(blockTime): + case config.IsApricotPhase3(blockTime): return NewLondonSigner(config.ChainID) + case config.IsApricotPhase2(blockTime): + return NewEIP2930Signer(config.ChainID) case config.IsEIP155(blockNumber): return NewEIP155Signer(config.ChainID) case config.IsHomestead(blockNumber): @@ -74,9 +76,12 @@ func LatestSigner(config *params.ChainConfig) Signer { if config.CancunTime != nil { return NewCancunSigner(config.ChainID) } - if config.SubnetEVMTimestamp != nil { + if config.ApricotPhase3BlockTimestamp != nil { return NewLondonSigner(config.ChainID) } + if config.ApricotPhase2BlockTimestamp != nil { + return NewEIP2930Signer(config.ChainID) + } if config.EIP155Block != nil { return NewEIP155Signer(config.ChainID) } diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 4eacdd2456..d2e6dd4632 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -91,9 +91,9 @@ var PrecompiledContractsIstanbul = map[common.Address]contract.StatefulPrecompil common.BytesToAddress([]byte{9}): newWrappedPrecompiledContract(&blake2F{}), } -// PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum -// contracts used in the Berlin release. -var PrecompiledContractsBerlin = map[common.Address]contract.StatefulPrecompiledContract{ +// PrecompiledContractsApricotPhase2 contains the default set of pre-compiled Ethereum +// contracts used in the Apricot Phase 2 release. +var PrecompiledContractsApricotPhase2 = map[common.Address]contract.StatefulPrecompiledContract{ common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), @@ -103,6 +103,60 @@ var PrecompiledContractsBerlin = map[common.Address]contract.StatefulPrecompiled common.BytesToAddress([]byte{7}): newWrappedPrecompiledContract(&bn256ScalarMulIstanbul{}), common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingIstanbul{}), common.BytesToAddress([]byte{9}): newWrappedPrecompiledContract(&blake2F{}), + genesisContractAddr: &deprecatedContract{}, + NativeAssetBalanceAddr: &nativeAssetBalance{gasCost: params.AssetBalanceApricot}, + NativeAssetCallAddr: &nativeAssetCall{gasCost: params.AssetCallApricot}, +} + +// PrecompiledContractsApricotPhasePre6 contains the default set of pre-compiled Ethereum +// contracts used in the PrecompiledContractsApricotPhasePre6 release. +var PrecompiledContractsApricotPhasePre6 = map[common.Address]contract.StatefulPrecompiledContract{ + common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), + common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), + common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), + common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}), + common.BytesToAddress([]byte{5}): newWrappedPrecompiledContract(&bigModExp{eip2565: true}), + common.BytesToAddress([]byte{6}): newWrappedPrecompiledContract(&bn256AddIstanbul{}), + common.BytesToAddress([]byte{7}): newWrappedPrecompiledContract(&bn256ScalarMulIstanbul{}), + common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingIstanbul{}), + common.BytesToAddress([]byte{9}): newWrappedPrecompiledContract(&blake2F{}), + genesisContractAddr: &deprecatedContract{}, + NativeAssetBalanceAddr: &deprecatedContract{}, + NativeAssetCallAddr: &deprecatedContract{}, +} + +// PrecompiledContractsApricotPhase6 contains the default set of pre-compiled Ethereum +// contracts used in the Apricot Phase 6 release. +var PrecompiledContractsApricotPhase6 = map[common.Address]contract.StatefulPrecompiledContract{ + common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), + common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), + common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), + common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}), + common.BytesToAddress([]byte{5}): newWrappedPrecompiledContract(&bigModExp{eip2565: true}), + common.BytesToAddress([]byte{6}): newWrappedPrecompiledContract(&bn256AddIstanbul{}), + common.BytesToAddress([]byte{7}): newWrappedPrecompiledContract(&bn256ScalarMulIstanbul{}), + common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingIstanbul{}), + common.BytesToAddress([]byte{9}): newWrappedPrecompiledContract(&blake2F{}), + genesisContractAddr: &deprecatedContract{}, + NativeAssetBalanceAddr: &nativeAssetBalance{gasCost: params.AssetBalanceApricot}, + NativeAssetCallAddr: &nativeAssetCall{gasCost: params.AssetCallApricot}, +} + +// PrecompiledContractsBanff contains the default set of pre-compiled Ethereum +// contracts used in the Banff release. +var PrecompiledContractsBanff = map[common.Address]contract.StatefulPrecompiledContract{ + common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}), + common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}), + common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}), + common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}), + common.BytesToAddress([]byte{5}): newWrappedPrecompiledContract(&bigModExp{eip2565: true}), + common.BytesToAddress([]byte{6}): newWrappedPrecompiledContract(&bn256AddIstanbul{}), + common.BytesToAddress([]byte{7}): newWrappedPrecompiledContract(&bn256ScalarMulIstanbul{}), + common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingIstanbul{}), + common.BytesToAddress([]byte{9}): newWrappedPrecompiledContract(&blake2F{}), + genesisContractAddr: &deprecatedContract{}, + NativeAssetBalanceAddr: &deprecatedContract{}, + NativeAssetCallAddr: &deprecatedContract{}, } // PrecompiledContractsCancun contains the default set of pre-compiled Ethereum @@ -135,13 +189,16 @@ var PrecompiledContractsBLS = map[common.Address]contract.StatefulPrecompiledCon } var ( - PrecompiledAddressesCancun []common.Address - PrecompiledAddressesBerlin []common.Address - PrecompiledAddressesIstanbul []common.Address - PrecompiledAddressesByzantium []common.Address - PrecompiledAddressesHomestead []common.Address - PrecompiledAddressesBLS []common.Address - PrecompileAllNativeAddresses map[common.Address]struct{} + PrecompiledAddressesCancun []common.Address + PrecompiledAddressesBanff []common.Address + PrecompiledAddressesApricotPhase6 []common.Address + PrecompiledAddressesApricotPhasePre6 []common.Address + PrecompiledAddressesApricotPhase2 []common.Address + PrecompiledAddressesIstanbul []common.Address + PrecompiledAddressesByzantium []common.Address + PrecompiledAddressesHomestead []common.Address + PrecompiledAddressesBLS []common.Address + PrecompileAllNativeAddresses map[common.Address]struct{} ) func init() { @@ -154,8 +211,17 @@ func init() { for k := range PrecompiledContractsIstanbul { PrecompiledAddressesIstanbul = append(PrecompiledAddressesIstanbul, k) } - for k := range PrecompiledContractsBerlin { - PrecompiledAddressesBerlin = append(PrecompiledAddressesBerlin, k) + for k := range PrecompiledContractsApricotPhase2 { + PrecompiledAddressesApricotPhase2 = append(PrecompiledAddressesApricotPhase2, k) + } + for k := range PrecompiledContractsApricotPhasePre6 { + PrecompiledAddressesApricotPhasePre6 = append(PrecompiledAddressesApricotPhasePre6, k) + } + for k := range PrecompiledContractsApricotPhase6 { + PrecompiledAddressesApricotPhase6 = append(PrecompiledAddressesApricotPhase6, k) + } + for k := range PrecompiledContractsBanff { + PrecompiledAddressesBanff = append(PrecompiledAddressesBanff, k) } for k := range PrecompiledContractsCancun { PrecompiledAddressesCancun = append(PrecompiledAddressesCancun, k) @@ -169,7 +235,10 @@ func init() { PrecompileAllNativeAddresses = make(map[common.Address]struct{}) addrsList := append(PrecompiledAddressesHomestead, PrecompiledAddressesByzantium...) addrsList = append(addrsList, PrecompiledAddressesIstanbul...) - addrsList = append(addrsList, PrecompiledAddressesBerlin...) + addrsList = append(addrsList, PrecompiledAddressesApricotPhase2...) + addrsList = append(addrsList, PrecompiledAddressesApricotPhasePre6...) + addrsList = append(addrsList, PrecompiledAddressesApricotPhase6...) + addrsList = append(addrsList, PrecompiledAddressesBanff...) addrsList = append(addrsList, PrecompiledAddressesCancun...) addrsList = append(addrsList, PrecompiledAddressesBLS...) for _, k := range addrsList { @@ -191,8 +260,10 @@ func ActivePrecompiles(rules params.Rules) []common.Address { switch { case rules.IsCancun: return PrecompiledAddressesCancun - case rules.IsSubnetEVM: - return PrecompiledAddressesBerlin + case rules.IsBanff: + return PrecompiledAddressesBanff + case rules.IsApricotPhase2: + return PrecompiledAddressesApricotPhase2 case rules.IsIstanbul: return PrecompiledAddressesIstanbul case rules.IsByzantium: diff --git a/core/vm/eips.go b/core/vm/eips.go index 655bbb9fd3..f9bbc8f7f1 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -163,6 +163,20 @@ func enable2929(jt *JumpTable) { jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP2929 } +// enableAP1 disables gas refunds for SSTORE and SELFDESTRUCT. It is very +// similar to EIP-3298: Removal of Refunds [DRAFT] +// (https://eips.ethereum.org/EIPS/eip-3298). +func enableAP1(jt *JumpTable) { + jt[SSTORE].dynamicGas = gasSStoreAP1 + jt[SELFDESTRUCT].dynamicGas = gasSelfdestructAP1 + jt[CALLEX].dynamicGas = gasCallExpertAP1 +} + +func enableAP2(jt *JumpTable) { + jt[BALANCEMC] = &operation{execute: opUndefined, maxStack: maxStack(0, 0)} + jt[CALLEX] = &operation{execute: opUndefined, maxStack: maxStack(0, 0)} +} + // enable3198 applies EIP-3198 (BASEFEE Opcode) // - Adds an opcode that returns the current block's base fee. func enable3198(jt *JumpTable) { diff --git a/core/vm/evm.go b/core/vm/evm.go index 7a95719c9e..3e7e7013fa 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -27,16 +27,15 @@ package vm import ( - "fmt" "math/big" "sync/atomic" + "time" "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/subnet-evm/constants" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ava-labs/subnet-evm/precompile/contracts/deployerallowlist" "github.com/ava-labs/subnet-evm/precompile/modules" "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ava-labs/subnet-evm/predicate" @@ -63,9 +62,11 @@ func IsProhibited(addr common.Address) bool { type ( // CanTransferFunc is the signature of a transfer guard function - CanTransferFunc func(StateDB, common.Address, *big.Int) bool + CanTransferFunc func(StateDB, common.Address, *big.Int) bool + CanTransferMCFunc func(StateDB, common.Address, common.Address, common.Hash, *big.Int) bool // TransferFunc is the signature of a transfer function - TransferFunc func(StateDB, common.Address, common.Address, *big.Int) + TransferFunc func(StateDB, common.Address, common.Address, *big.Int) + TransferMCFunc func(StateDB, common.Address, common.Address, common.Hash, *big.Int) // GetHashFunc returns the n'th block hash in the blockchain // and is used by the BLOCKHASH EVM op code. GetHashFunc func(uint64) common.Hash @@ -76,8 +77,14 @@ func (evm *EVM) precompile(addr common.Address) (contract.StatefulPrecompiledCon switch { case evm.chainRules.IsCancun: precompiles = PrecompiledContractsCancun - case evm.chainRules.IsSubnetEVM: - precompiles = PrecompiledContractsBerlin + case evm.chainRules.IsBanff: + precompiles = PrecompiledContractsBanff + case evm.chainRules.IsApricotPhase6: + precompiles = PrecompiledContractsApricotPhase6 + case evm.chainRules.IsApricotPhasePre6: + precompiles = PrecompiledContractsApricotPhasePre6 + case evm.chainRules.IsApricotPhase2: + precompiles = PrecompiledContractsApricotPhase2 case evm.chainRules.IsIstanbul: precompiles = PrecompiledContractsIstanbul case evm.chainRules.IsByzantium: @@ -107,8 +114,13 @@ type BlockContext struct { // CanTransfer returns whether the account contains // sufficient ether to transfer the value CanTransfer CanTransferFunc + // CanTransferMC returns whether the account contains + // sufficient multicoin balance to transfer the value + CanTransferMC CanTransferMCFunc // Transfer transfers ether from one account to the other Transfer TransferFunc + // TransferMultiCoin transfers multicoin from one account to the other + TransferMultiCoin TransferMCFunc // GetHash returns the hash corresponding to n GetHash GetHashFunc // PredicateResults are the results of predicate verification available throughout the EVM's execution. @@ -337,6 +349,84 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas return ret, gas, err } +// This allows the user transfer balance of a specified coinId in addition to a normal Call(). +func (evm *EVM) CallExpert(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int, coinID common.Hash, value2 *big.Int) (ret []byte, leftOverGas uint64, err error) { + // Fail if we're trying to execute above the call depth limit + if evm.depth > int(params.CallCreateDepth) { + return nil, gas, vmerrs.ErrDepth + } + + // Fail if we're trying to transfer more than the available balance + // Note: it is not possible for a negative value to be passed in here due to the fact + // that [value] will be popped from the stack and decoded to a *big.Int, which will + // always yield a positive result. + if value.Sign() != 0 && !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { + return nil, gas, vmerrs.ErrInsufficientBalance + } + + if value2.Sign() != 0 && !evm.Context.CanTransferMC(evm.StateDB, caller.Address(), addr, coinID, value2) { + return nil, gas, vmerrs.ErrInsufficientBalance + } + + snapshot := evm.StateDB.Snapshot() + //p, isPrecompile := evm.precompile(addr) + + if !evm.StateDB.Exist(addr) { + //if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 { + // // Calling a non existing account, don't do anything, but ping the tracer + // if evm.Config.Debug && evm.depth == 0 { + // evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value) + // evm.Config.Tracer.CaptureEnd(ret, 0, 0, nil) + // } + // return nil, gas, nil + //} + evm.StateDB.CreateAccount(addr) + } + evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value) + evm.Context.TransferMultiCoin(evm.StateDB, caller.Address(), addr, coinID, value2) + + // Capture the tracer start/end events in debug mode + debug := evm.Config.Tracer != nil + if debug && evm.depth == 0 { + evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value) + defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters + evm.Config.Tracer.CaptureEnd(ret, startGas-gas, err) + }(gas, time.Now()) + } + + //if isPrecompile { + // ret, gas, err = RunPrecompiledContract(p, input, gas) + //} else { + // Initialise a new contract and set the code that is to be used by the EVM. + // The contract is a scoped environment for this execution context only. + code := evm.StateDB.GetCode(addr) + if len(code) == 0 { + ret, err = nil, nil // gas is unchanged + } else { + addrCopy := addr + // If the account has no code, we can abort here + // The depth-check is already done, and precompiles handled above + contract := NewContract(caller, AccountRef(addrCopy), value, gas) + contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code) + ret, err = evm.interpreter.Run(contract, input, false) + gas = contract.Gas + } + //} + // When an error was returned by the EVM or when setting the creation code + // above we revert to the snapshot and consume any gas remaining. Additionally + // when we're in homestead this also counts for code storage gas errors. + if err != nil { + evm.StateDB.RevertToSnapshot(snapshot) + if err != vmerrs.ErrExecutionReverted { + gas = 0 + } + // TODO: consider clearing up unused snapshots: + //} else { + // evm.StateDB.DiscardSnapshot(snapshot) + } + return ret, gas, err +} + // CallCode executes the contract associated with the addr with the given input // as parameters. It also handles any necessary value transfer required and takes // the necessary steps to create accounts and reverses the state in case of an @@ -527,7 +617,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, evm.StateDB.SetNonce(caller.Address(), nonce+1) // We add this to the access list _before_ taking a snapshot. Even if the creation fails, // the access-list change should not be rolled back - if evm.chainRules.IsSubnetEVM { + if evm.chainRules.IsApricotPhase2 { evm.StateDB.AddAddressToAccessList(address) } // Ensure there's no existing contract already at the designated address @@ -535,14 +625,6 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) { return nil, common.Address{}, 0, vmerrs.ErrContractAddressCollision } - // If the allow list is enabled, check that [evm.TxContext.Origin] has permission to deploy a contract. - if evm.chainRules.IsPrecompileEnabled(deployerallowlist.ContractAddress) { - allowListRole := deployerallowlist.GetContractDeployerAllowListStatus(evm.StateDB, evm.TxContext.Origin) - if !allowListRole.IsEnabled() { - return nil, common.Address{}, 0, fmt.Errorf("tx.origin %s is not authorized to deploy a contract", evm.TxContext.Origin) - } - } - // Create a new account on the state snapshot := evm.StateDB.Snapshot() evm.StateDB.CreateAccount(address) @@ -572,7 +654,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } // Reject code starting with 0xEF if EIP-3541 is enabled. - if err == nil && len(ret) >= 1 && ret[0] == 0xEF && evm.chainRules.IsSubnetEVM { + if err == nil && len(ret) >= 1 && ret[0] == 0xEF && evm.chainRules.IsApricotPhase3 { err = vmerrs.ErrInvalidCode } @@ -630,3 +712,57 @@ func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig } // GetChainConfig implements AccessibleState func (evm *EVM) GetChainConfig() precompileconfig.ChainConfig { return evm.chainConfig } + +func (evm *EVM) NativeAssetCall(caller common.Address, input []byte, suppliedGas uint64, gasCost uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { + if suppliedGas < gasCost { + return nil, 0, vmerrs.ErrOutOfGas + } + remainingGas = suppliedGas - gasCost + + if readOnly { + return nil, remainingGas, vmerrs.ErrExecutionReverted + } + + to, assetID, assetAmount, callData, err := UnpackNativeAssetCallInput(input) + if err != nil { + return nil, remainingGas, vmerrs.ErrExecutionReverted + } + + // Note: it is not possible for a negative assetAmount to be passed in here due to the fact that decoding a + // byte slice into a *big.Int type will always return a positive value. + if assetAmount.Sign() != 0 && !evm.Context.CanTransferMC(evm.StateDB, caller, to, assetID, assetAmount) { + return nil, remainingGas, vmerrs.ErrInsufficientBalance + } + + snapshot := evm.StateDB.Snapshot() + + if !evm.StateDB.Exist(to) { + if remainingGas < params.CallNewAccountGas { + return nil, 0, vmerrs.ErrOutOfGas + } + remainingGas -= params.CallNewAccountGas + evm.StateDB.CreateAccount(to) + } + + // Increment the call depth which is restricted to 1024 + evm.depth++ + defer func() { evm.depth-- }() + + // Send [assetAmount] of [assetID] to [to] address + evm.Context.TransferMultiCoin(evm.StateDB, caller, to, assetID, assetAmount) + ret, remainingGas, err = evm.Call(AccountRef(caller), to, callData, remainingGas, new(big.Int)) + + // When an error was returned by the EVM or when setting the creation code + // above we revert to the snapshot and consume any gas remaining. Additionally + // when we're in homestead this also counts for code storage gas errors. + if err != nil { + evm.StateDB.RevertToSnapshot(snapshot) + if err != vmerrs.ErrExecutionReverted { + remainingGas = 0 + } + // TODO: consider clearing up unused snapshots: + //} else { + // evm.StateDB.DiscardSnapshot(snapshot) + } + return ret, remainingGas, err +} diff --git a/core/vm/evm_test.go b/core/vm/evm_test.go index a69b1a04fe..5a7e72f3f4 100644 --- a/core/vm/evm_test.go +++ b/core/vm/evm_test.go @@ -11,12 +11,11 @@ import ( ) func TestIsProhibited(t *testing.T) { - // reserved addresses (coreth) + // reserved addresses assert.True(t, IsProhibited(common.HexToAddress("0x0100000000000000000000000000000000000000"))) assert.True(t, IsProhibited(common.HexToAddress("0x0100000000000000000000000000000000000010"))) assert.True(t, IsProhibited(common.HexToAddress("0x01000000000000000000000000000000000000f0"))) assert.True(t, IsProhibited(common.HexToAddress("0x01000000000000000000000000000000000000ff"))) - // reserved addresses (subnet-evm) assert.True(t, IsProhibited(common.HexToAddress("0x0200000000000000000000000000000000000000"))) assert.True(t, IsProhibited(common.HexToAddress("0x0200000000000000000000000000000000000010"))) assert.True(t, IsProhibited(common.HexToAddress("0x02000000000000000000000000000000000000f0"))) diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 9cfe0abc70..0b9037c4bc 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -233,6 +233,41 @@ func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m return params.SloadGasEIP2200, nil // dirty update (2.2) } +// gasSStoreAP1 simplifies the dynamic gas cost of SSTORE by removing all refund logic +// +// 0. If *gasleft* is less than or equal to 2300, fail the current call. +// 1. If current value equals new value (this is a no-op), SLOAD_GAS is deducted. +// 2. If current value does not equal new value: +// 2.1. If original value equals current value (this storage slot has not been changed by the current execution context): +// 2.1.1. If original value is 0, SSTORE_SET_GAS (20K) gas is deducted. +// 2.1.2. Otherwise, SSTORE_RESET_GAS gas is deducted. If new value is 0, add SSTORE_CLEARS_SCHEDULE to refund counter. +// 2.2. If original value does not equal current value (this storage slot is dirty), SLOAD_GAS gas is deducted. Apply both of the following clauses: +func gasSStoreAP1(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + // If we fail the minimum gas availability invariant, fail (0) + if contract.Gas <= params.SstoreSentryGasEIP2200 { + return 0, errors.New("not enough gas for reentrancy sentry") + } + // Gas sentry honoured, do the actual gas calculation based on the stored value + var ( + y, x = stack.Back(1), stack.Back(0) + current = evm.StateDB.GetState(contract.Address(), x.Bytes32()) + ) + value := common.Hash(y.Bytes32()) + + if current == value { // noop (1) + return params.SloadGasEIP2200, nil + } + original := evm.StateDB.GetCommittedStateAP1(contract.Address(), x.Bytes32()) + if original == current { + if original == (common.Hash{}) { // create slot (2.1.1) + return params.SstoreSetGasEIP2200, nil + } + return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2) + } + + return params.SloadGasEIP2200, nil // dirty update (2.2) +} + func makeGasLog(n uint64) gasFunc { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { requestedSize, overflow := stack.Back(1).Uint64WithOverflow() @@ -409,6 +444,45 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize return gas, nil } +func gasCallExpertAP1(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + var ( + gas uint64 + transfersValue = !stack.Back(2).IsZero() + multiCoinTransfersValue = !stack.Back(4).IsZero() + address = common.Address(stack.Back(1).Bytes20()) + ) + if evm.chainRules.IsEIP158 { + if (transfersValue || multiCoinTransfersValue) && evm.StateDB.Empty(address) { + gas += params.CallNewAccountGas + } + } else if !evm.StateDB.Exist(address) { + gas += params.CallNewAccountGas + } + if transfersValue { + gas += params.CallValueTransferGas + } + if multiCoinTransfersValue { + gas += params.CallValueTransferGas + } + memoryGas, err := memoryGasCost(mem, memorySize) + if err != nil { + return 0, err + } + var overflow bool + if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { + return 0, vmerrs.ErrGasUintOverflow + } + + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) + if err != nil { + return 0, err + } + if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { + return 0, vmerrs.ErrGasUintOverflow + } + return gas, nil +} + func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { memoryGas, err := memoryGasCost(mem, memorySize) if err != nil { @@ -488,3 +562,23 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me } return gas, nil } + +func gasSelfdestructAP1(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + var gas uint64 + // EIP150 homestead gas reprice fork: + if evm.chainRules.IsEIP150 { + gas = params.SelfdestructGasEIP150 + var address = common.Address(stack.Back(0).Bytes20()) + + if evm.chainRules.IsEIP158 { + // if empty and transfers value + if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { + gas += params.CreateBySelfdestructGas + } + } else if !evm.StateDB.Exist(address) { + gas += params.CreateBySelfdestructGas + } + } + + return gas, nil +} diff --git a/core/vm/gas_table_test.go b/core/vm/gas_table_test.go index 0102f71ec3..da94ae0ac5 100644 --- a/core/vm/gas_table_test.go +++ b/core/vm/gas_table_test.go @@ -161,9 +161,9 @@ func TestCreateGas(t *testing.T) { config.ExtraEips = []int{3860} } - // Note: we use TestSubnetEVMChainConfig instead of AllEthashProtocolChanges (upstream) + // Note: we use Cortina instead of AllEthashProtocolChanges (upstream) // because it is the last fork before the activation of EIP-3860 - vmenv := NewEVM(vmctx, TxContext{}, statedb, params.TestSubnetEVMChainConfig, config) + vmenv := NewEVM(vmctx, TxContext{}, statedb, params.TestCortinaChainConfig, config) var startGas = uint64(testGas) ret, gas, err := vmenv.Call(AccountRef(common.Address{}), address, nil, startGas, new(big.Int)) if err != nil { diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 65f659aa32..1b7debbbdd 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -27,6 +27,8 @@ package vm import ( + "errors" + "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/vmerrs" "github.com/ethereum/go-ethereum/common" @@ -274,6 +276,17 @@ func opBalance(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] return nil, nil } +func opBalanceMultiCoin(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + addr, cid := scope.Stack.pop(), scope.Stack.pop() + res, err := uint256.FromBig(interpreter.evm.StateDB.GetBalanceMultiCoin( + common.BigToAddress(addr.ToBig()), common.BigToHash(cid.ToBig()))) + if err { + return nil, errors.New("balance overflow") + } + scope.Stack.push(res) + return nil, nil +} + func opOrigin(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { scope.Stack.push(new(uint256.Int).SetBytes(interpreter.evm.Origin.Bytes())) return nil, nil @@ -705,6 +718,57 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt return ret, nil } +// Note: opCallExpert was de-activated in ApricotPhase2. +func opCallExpert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + stack := scope.Stack + // Pop gas. The actual gas in interpreter.evm.callGasTemp. + // We can use this as a temporary value + temp := stack.pop() + gas := interpreter.evm.callGasTemp + // Pop other call parameters. + addr, value, cid, value2, inOffset, inSize, retOffset, retSize := stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop(), stack.pop() + toAddr := common.Address(addr.Bytes20()) + coinID := common.BigToHash(cid.ToBig()) + // Get the arguments from the memory. + args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) + + // Note: this code fails to check that value2 is zero, which was a bug when CALLEX was active. + // The CALLEX opcode was de-activated in ApricotPhase2 resolving this issue. + if interpreter.readOnly && !value.IsZero() { + return nil, vmerrs.ErrWriteProtection + } + var bigVal = big0 + //TODO: use uint256.Int instead of converting with toBig() + // By using big0 here, we save an alloc for the most common case (non-ether-transferring contract calls), + // but it would make more sense to extend the usage of uint256.Int + if !value.IsZero() { + gas += params.CallStipend + bigVal = value.ToBig() + } + + var bigVal2 = big0 + //TODO: use uint256.Int instead of converting with toBig() + // By using big0 here, we save an alloc for the most common case (non-ether-transferring contract calls), + // but it would make more sense to extend the usage of uint256.Int + if !value2.IsZero() { + bigVal2 = value2.ToBig() + } + + ret, returnGas, err := interpreter.evm.CallExpert(scope.Contract, toAddr, args, gas, bigVal, coinID, bigVal2) + if err != nil { + temp.Clear() + } else { + temp.SetOne() + } + stack.push(&temp) + if err == nil || err == vmerrs.ErrExecutionReverted { + scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) + } + scope.Contract.Gas += returnGas + + interpreter.returnData = ret + return ret, nil +} func opCallCode(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { // Pop gas. The actual gas is in interpreter.evm.callGasTemp. stack := scope.Stack diff --git a/core/vm/instructions_test.go b/core/vm/instructions_test.go index ce36b18bc8..57cc58d727 100644 --- a/core/vm/instructions_test.go +++ b/core/vm/instructions_test.go @@ -729,7 +729,7 @@ func TestCreate2Addreses(t *testing.T) { // TestRandom is a test for the RANDOM opcode in geth, which we do not support. Therefore, we use this test to check that DIFFICULTY opcode continues to work // the same as the RANDOM opcode except using the Difficulty value in the block context. -// Note: this should not be used as a source of randomness in subnet-evm since the difficulty is required to be 1. +// Note: this should not be used as a source of randomness in coreth since the difficulty is required to be 1. func TestRandom(t *testing.T) { type testcase struct { name string diff --git a/core/vm/interface.go b/core/vm/interface.go index 34b3e714da..340d3b44ac 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -42,6 +42,10 @@ type StateDB interface { AddBalance(common.Address, *big.Int) GetBalance(common.Address) *big.Int + SubBalanceMultiCoin(common.Address, common.Hash, *big.Int) + AddBalanceMultiCoin(common.Address, common.Hash, *big.Int) + GetBalanceMultiCoin(common.Address, common.Hash) *big.Int + GetNonce(common.Address) uint64 SetNonce(common.Address, uint64) @@ -55,6 +59,7 @@ type StateDB interface { GetRefund() uint64 GetCommittedState(common.Address, common.Hash) common.Hash + GetCommittedStateAP1(common.Address, common.Hash) common.Hash GetState(common.Address, common.Hash) common.Hash SetState(common.Address, common.Hash, common.Hash) diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 34eb46425f..e6ccd330d0 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -34,6 +34,11 @@ import ( "github.com/ethereum/go-ethereum/log" ) +var BuiltinAddr = common.Address{ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +} + // Config are the configuration options for the Interpreter type Config struct { Tracer EVMLogger // Opcode logger @@ -71,8 +76,12 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter { table = &cancunInstructionSet case evm.chainRules.IsDurango: table = &durangoInstructionSet - case evm.chainRules.IsSubnetEVM: - table = &subnetEVMInstructionSet + case evm.chainRules.IsApricotPhase3: + table = &apricotPhase3InstructionSet + case evm.chainRules.IsApricotPhase2: + table = &apricotPhase2InstructionSet + case evm.chainRules.IsApricotPhase1: + table = &apricotPhase1InstructionSet case evm.chainRules.IsIstanbul: table = &istanbulInstructionSet case evm.chainRules.IsConstantinople: @@ -112,6 +121,18 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter { // considered a revert-and-consume-all-gas operation except for // ErrExecutionReverted which means revert-and-keep-gas-left. func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) { + // Deprecate special handling of [BuiltinAddr] as of ApricotPhase2. + // In ApricotPhase2, the contract deployed in the genesis is overridden by a deprecated precompiled + // contract which will return an error immediately if its ever called. Therefore, this function should + // never be called after ApricotPhase2 with [BuiltinAddr] as the contract address. + if !in.evm.chainRules.IsApricotPhase2 && contract.Address() == BuiltinAddr { + self := AccountRef(contract.Caller()) + if _, ok := contract.caller.(*Contract); ok { + contract = contract.AsDelegate() + } + contract.self = self + } + // Increment the call depth which is restricted to 1024 in.evm.depth++ defer func() { in.evm.depth-- }() diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index a97e274ccd..264d1539d9 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -62,7 +62,9 @@ var ( byzantiumInstructionSet = newByzantiumInstructionSet() constantinopleInstructionSet = newConstantinopleInstructionSet() istanbulInstructionSet = newIstanbulInstructionSet() - subnetEVMInstructionSet = newSubnetEVMInstructionSet() + apricotPhase1InstructionSet = newApricotPhase1InstructionSet() + apricotPhase2InstructionSet = newApricotPhase2InstructionSet() + apricotPhase3InstructionSet = newApricotPhase3InstructionSet() durangoInstructionSet = newDurangoInstructionSet() cancunInstructionSet = newCancunInstructionSet() ) @@ -99,24 +101,46 @@ func newCancunInstructionSet() JumpTable { } // newDurangoInstructionSet returns the frontier, homestead, byzantium, -// constantinople, istanbul, petersburg, subnet-evm, durango instructions. +// constantinople, istanbul, petersburg, apricotPhase1, 2, and 3, durango instructions. func newDurangoInstructionSet() JumpTable { - instructionSet := newSubnetEVMInstructionSet() + instructionSet := newApricotPhase3InstructionSet() enable3855(&instructionSet) // PUSH0 instruction enable3860(&instructionSet) // Limit and meter initcode return validate(instructionSet) } -// newSubnetEVMInstructionSet returns the frontier, homestead, byzantium, -// constantinople, istanbul, petersburg, subnet-evm instructions. -func newSubnetEVMInstructionSet() JumpTable { - instructionSet := newIstanbulInstructionSet() - enable2929(&instructionSet) +// newApricotPhase3InstructionSet returns the frontier, homestead, byzantium, +// constantinople, istanbul, petersburg, apricotPhase1, 2, and 3 instructions. +func newApricotPhase3InstructionSet() JumpTable { + instructionSet := newApricotPhase2InstructionSet() enable3198(&instructionSet) // Base fee opcode https://eips.ethereum.org/EIPS/eip-3198 return validate(instructionSet) } +// newApricotPhase2InstructionSet returns the frontier, +// homestead, byzantium, constantinople petersburg, +// istanbul, and apricotPhase1 instructions. +func newApricotPhase2InstructionSet() JumpTable { + instructionSet := newApricotPhase1InstructionSet() + + enable2929(&instructionSet) + enableAP2(&instructionSet) + + return validate(instructionSet) +} + +// newApricotPhase1InstructionSet returns the frontier, +// homestead, byzantium, constantinople petersburg, +// and istanbul instructions. +func newApricotPhase1InstructionSet() JumpTable { + instructionSet := newIstanbulInstructionSet() + + enableAP1(&instructionSet) + + return validate(instructionSet) +} + // newIstanbulInstructionSet returns the frontier, // homestead, byzantium, constantinople and petersburg instructions. func newIstanbulInstructionSet() JumpTable { @@ -219,6 +243,7 @@ func newTangerineWhistleInstructionSet() JumpTable { instructionSet[SLOAD].constantGas = params.SloadGasEIP150 instructionSet[EXTCODECOPY].constantGas = params.ExtcodeCopyBaseEIP150 instructionSet[CALL].constantGas = params.CallGasEIP150 + instructionSet[CALLEX].constantGas = params.CallGasEIP150 instructionSet[CALLCODE].constantGas = params.CallGasEIP150 instructionSet[DELEGATECALL].constantGas = params.CallGasEIP150 return validate(instructionSet) @@ -401,6 +426,12 @@ func newFrontierInstructionSet() JumpTable { minStack: minStack(1, 1), maxStack: maxStack(1, 1), }, + BALANCEMC: { + execute: opBalanceMultiCoin, + constantGas: params.BalanceGasFrontier, + minStack: minStack(2, 1), + maxStack: maxStack(2, 1), + }, ORIGIN: { execute: opOrigin, constantGas: GasQuickStep, @@ -1022,6 +1053,14 @@ func newFrontierInstructionSet() JumpTable { maxStack: maxStack(7, 1), memorySize: memoryCall, }, + CALLEX: { + execute: opCallExpert, + constantGas: params.CallGasFrontier, + dynamicGas: gasCall, + minStack: minStack(9, 1), + maxStack: maxStack(9, 1), + memorySize: memoryCallExpert, + }, CALLCODE: { execute: opCallCode, constantGas: params.CallGasFrontier, diff --git a/core/vm/jump_table_export.go b/core/vm/jump_table_export.go index 96a4d5efe1..f899c3ee0a 100644 --- a/core/vm/jump_table_export.go +++ b/core/vm/jump_table_export.go @@ -28,8 +28,15 @@ func LookupInstructionSet(rules params.Rules) (JumpTable, error) { return newCancunInstructionSet(), nil case rules.IsDurango: return newDurangoInstructionSet(), nil - case rules.IsSubnetEVM: - return newSubnetEVMInstructionSet(), nil + case rules.IsApricotPhase3, rules.IsApricotPhase4, + rules.IsApricotPhase5, rules.IsApricotPhasePre6, + rules.IsApricotPhase6, rules.IsApricotPhasePost6, + rules.IsBanff, rules.IsCortina: + return newApricotPhase3InstructionSet(), nil + case rules.IsApricotPhase2: + return newApricotPhase2InstructionSet(), nil + case rules.IsApricotPhase1: + return newApricotPhase1InstructionSet(), nil case rules.IsIstanbul: return newIstanbulInstructionSet(), nil case rules.IsConstantinople: diff --git a/core/vm/memory_table.go b/core/vm/memory_table.go index 0a2fbe1f9e..8d0d80406c 100644 --- a/core/vm/memory_table.go +++ b/core/vm/memory_table.go @@ -88,6 +88,22 @@ func memoryCall(stack *Stack) (uint64, bool) { } return y, false } + +func memoryCallExpert(stack *Stack) (uint64, bool) { + x, overflow := calcMemSize64(stack.Back(7), stack.Back(8)) + if overflow { + return 0, true + } + y, overflow := calcMemSize64(stack.Back(5), stack.Back(6)) + if overflow { + return 0, true + } + if x > y { + return x, false + } + return y, false +} + func memoryDelegateCall(stack *Stack) (uint64, bool) { x, overflow := calcMemSize64(stack.Back(4), stack.Back(5)) if overflow { diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index c4e99b0669..a32119a0f4 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -217,6 +217,11 @@ const ( LOG4 ) +const ( + BALANCEMC = 0xcd + CALLEX = 0xcf +) + // 0xf0 range - closures. const ( CREATE OpCode = 0xf0 @@ -269,6 +274,7 @@ var opCodeToString = [256]string{ // 0x30 range - closure state. ADDRESS: "ADDRESS", BALANCE: "BALANCE", + BALANCEMC: "BALANCEMC", ORIGIN: "ORIGIN", CALLER: "CALLER", CALLVALUE: "CALLVALUE", @@ -395,6 +401,7 @@ var opCodeToString = [256]string{ // 0xf0 range - closures. CREATE: "CREATE", CALL: "CALL", + CALLEX: "CALLEX", RETURN: "RETURN", CALLCODE: "CALLCODE", DELEGATECALL: "DELEGATECALL", @@ -442,6 +449,7 @@ var stringToOp = map[string]OpCode{ "KECCAK256": KECCAK256, "ADDRESS": ADDRESS, "BALANCE": BALANCE, + "BALANCEMC": BALANCEMC, "ORIGIN": ORIGIN, "CALLER": CALLER, "CALLVALUE": CALLVALUE, @@ -557,6 +565,7 @@ var stringToOp = map[string]OpCode{ "CREATE": CREATE, "CREATE2": CREATE2, "CALL": CALL, + "CALLEX": CALLEX, "RETURN": RETURN, "CALLCODE": CALLCODE, "REVERT": REVERT, diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 7d8aec3741..bd4e8cfdf5 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -67,7 +67,7 @@ func makeGasSStoreFunc() gasFunc { // return params.SloadGasEIP2200, nil return cost + params.WarmStorageReadCostEIP2929, nil // SLOAD_GAS } - original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) + original := evm.StateDB.GetCommittedStateAP1(contract.Address(), x.Bytes32()) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) return cost + params.SstoreSetGasEIP2200, nil diff --git a/core/vm/runtime/env.go b/core/vm/runtime/env.go index 31a86b971f..b9bbaef2cd 100644 --- a/core/vm/runtime/env.go +++ b/core/vm/runtime/env.go @@ -39,16 +39,18 @@ func NewEnv(cfg *Config) *vm.EVM { BlobFeeCap: cfg.BlobFeeCap, } blockContext := vm.BlockContext{ - CanTransfer: core.CanTransfer, - Transfer: core.Transfer, - GetHash: cfg.GetHashFn, - Coinbase: cfg.Coinbase, - BlockNumber: cfg.BlockNumber, - Time: cfg.Time, - Difficulty: cfg.Difficulty, - GasLimit: cfg.GasLimit, - BaseFee: cfg.BaseFee, - BlobBaseFee: cfg.BlobBaseFee, + CanTransfer: core.CanTransfer, + CanTransferMC: core.CanTransferMC, + Transfer: core.Transfer, + TransferMultiCoin: core.TransferMultiCoin, + GetHash: cfg.GetHashFn, + Coinbase: cfg.Coinbase, + BlockNumber: cfg.BlockNumber, + Time: cfg.Time, + Difficulty: cfg.Difficulty, + GasLimit: cfg.GasLimit, + BaseFee: cfg.BaseFee, + BlobBaseFee: cfg.BlobBaseFee, } return vm.NewEVM(blockContext, txContext, cfg.State, cfg.ChainConfig, cfg.EVMConfig) diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index 52668a5c2c..45c8ec490c 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -69,6 +69,8 @@ func setDefaults(cfg *Config) { cfg.ChainConfig = ¶ms.ChainConfig{ ChainID: big.NewInt(1), HomesteadBlock: new(big.Int), + DAOForkBlock: new(big.Int), + DAOForkSupport: false, EIP150Block: new(big.Int), EIP155Block: new(big.Int), EIP158Block: new(big.Int), @@ -78,7 +80,10 @@ func setDefaults(cfg *Config) { IstanbulBlock: new(big.Int), MuirGlacierBlock: new(big.Int), NetworkUpgrades: params.NetworkUpgrades{ - SubnetEVMTimestamp: new(uint64), + ApricotPhase1BlockTimestamp: new(uint64), + ApricotPhase2BlockTimestamp: new(uint64), + ApricotPhase3BlockTimestamp: new(uint64), + ApricotPhase4BlockTimestamp: new(uint64), }, } } @@ -104,7 +109,7 @@ func setDefaults(cfg *Config) { } } if cfg.BaseFee == nil { - cfg.BaseFee = new(big.Int).Set(params.DefaultFeeConfig.MinBaseFee) + cfg.BaseFee = big.NewInt(params.ApricotPhase3InitialBaseFee) } if cfg.BlobBaseFee == nil { cfg.BlobBaseFee = big.NewInt(params.BlobTxMinBlobGasprice) @@ -132,7 +137,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Time) ) // Execute the preparatory steps for state transition which includes: - // - prepare accessList(post-berlin) + // - prepare accessList(post-berlin/ApricotPhase2) // - reset transient storage(eip 1153) cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil) @@ -166,7 +171,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Time) ) // Execute the preparatory steps for state transition which includes: - // - prepare accessList(post-berlin) + // - prepare accessList(post-berlin/ApricotPhase2) // - reset transient storage(eip 1153) cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, nil, vm.ActivePrecompiles(rules), nil) @@ -195,7 +200,7 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er rules = cfg.ChainConfig.Rules(vmenv.Context.BlockNumber, vmenv.Context.Time) ) // Execute the preparatory steps for state transition which includes: - // - prepare accessList(post-berlin) + // - prepare accessList(post-berlin/ApricotPhase2) // - reset transient storage(eip 1153) statedb.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil) diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 0ee9411f18..95af47ab56 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -189,6 +189,8 @@ func benchmarkEVM_Create(bench *testing.B, code string) { HomesteadBlock: new(big.Int), ByzantiumBlock: new(big.Int), ConstantinopleBlock: new(big.Int), + DAOForkBlock: new(big.Int), + DAOForkSupport: false, EIP150Block: new(big.Int), EIP155Block: new(big.Int), EIP158Block: new(big.Int), diff --git a/docs/audits/Avalanche Warp Messaging - OpenZeppelin (November 16th 2023).pdf b/docs/audits/Avalanche Warp Messaging - OpenZeppelin (November 16th 2023).pdf deleted file mode 100644 index fcec34d8b4e8bc555057f36ac6a8cdf05afc4556..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 198844 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 JHFYoT{Xf1_bnE~C diff --git a/docs/audits/Bridge Smart Contracts - Least Authority (July 7th 2023).pdf b/docs/audits/Bridge Smart Contracts - Least Authority (July 7th 2023).pdf deleted file mode 100644 index f416fc0d0214a8c9e277655ecf0a8d2f969eb1b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 285080 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¬~GEz)sQ53c7GfFi*;{bsH%V#~0Z6uYO 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℞9h>jc}({`hU{G2LK#^exUgJ5E?^Mjh!Qn0jZg}Qw=w~`LJTB`9MizqQ6Qhi z&9GJ6B6waJP(4o({e@eZZOS^}OI>*E{BVL7RwXq;VSRa)0p5xIj=FKiiQZ&?Z9RTN zqt*@_!a-(Vf#%diiRLmcWRHwgRPiviRyDLNkH40CXcGUEJp@Z3)f+)7%jlW z>DRCF1uLY^DN#s{0yc%NCT1+T_PA>*8JNEZtHnE|onfF=CqYKLcq!-TH{r;RIh952Nk-(7uJU6g zszUK$oDh9W%ZpHNeeR!*%d(+?a*SG1T=jfsykM*A!l1$`6Hf$iXRdMjDVt>1lJ^y; zjjYr86-K*H*C|1RsmJ$Q$_mE~1n`1g3*LkrE9W_d0cAFlf+)6{6(_-kwT8!@4@XS==ASOn;J?o{x;P@equ0Yo76T#gv*%-5+`&sU=Ms6}QS7 z@fiOq!@Ce$Jp?~Iis}?jJn%Glls;W@H%{6K-LeW%rELv0sFtTl zwg}sIk!k*9&-zk?Gma8AFfc@~4(L)Gq_~Fb!k*pkxEb&K-r6ni{JzU`#p}`glD<4< zIOSa%A7A<`mNyI=OI_1ErQT*?7J_wnJ|%<{$#YbjMwZJ2Z4DH1^X@!L!55(|p5Pfu zW65y6&OY+BZIo&21ud(YWR@#H?xV(@{^z$Fn@Da)XHAfPzL1|22Bx?4$v73g4JR#; zYE~{UXkVk3gSD05GmaRu zYr?OW5gZEqd<_w34|7BeNgF-a4xJlh6RjqiW5Uunv zwjBPU7k_dDJq}%;vP`fIq5&JJ9!R`gea3H&a=ZEC$NKK)X;|U$CPV z@FUb*s}A$N9sBBS-oiBEcc@8@k|XVOXr278YSdrkZs5-()ANRsb>?s@-ua zkg8Y9P<%hMaB)x|=_!fdb(Vh;=|#e2ViKd|QgUqqtuXl)YSRs?H;E?0uo5m)@~cl- zV zF5WCi`Ke(vmD7GyA5QqUZ!^~xZbvP|pMa8tn`uHo3dDlG{edfNq&j?cDylc(my77l zJ6Y7jY+d(M!hT{(Nq8`Etb8kJbye6DqV5;wX;NaBab-ilfLFv&b`CbNlgqM!|7eaT z)dLX$#8wn$2fPTj8z>=i@LdN@c=KM-NGou}%#B;09Pqym`CfmCM53{V1x8xeXik(BjbJg(Bmu|PW(kn6x(M$$aLX&7OI z@?gilKpTB#POz;3Fro}K*-_rA^wCaHWSEN9H+$mLB|YcI!aj9WGIyIM>svvW?#lX5 zV~3iZmEMAsp6|r%(x~^WeqA7E9|k8_e$`1n&F-~R zi^2@2#csyAHZM7h4CQAL4)iQ~I>`}p8SI!2TPht?YFo_d2N*k*1yN%gq9k?-bJp#E zTUIn_K42j9y@mTCp)(oH*OlaU;Qi3!(F@1l)2l1 zsbmY)#dF{?W10d9)1}7T5!tH{yvq`p6xI?XQft04RZq@OVz5+UVRu!TjW0UuQCxvE zJmL`zzC3h^TgRwk(zrb2*XG)Sk*)m&E9p64O^EvN!jMNtjs;NMKNL0|hB2UeYy9BG zJq?&C{Z11t#>vjl#8V4i}6QZLEOot!7nsqeea3x7{V-!~6xe z8HuG|hN;AJP(~p8n8c6`yKBo>t~w=Pn>L89pWt^M<*Z_DStGFRa3n&9&_wA<_b z;Pb=bsZ`*7NPxiS{ZHR*X{=eq9DnT5K(`OtM0)T}R1`cPz)lBmXslC(^O14@raRjR z#(?l?1cBPxqMqt!HtHrbnwj-F_*>F+UTqNT@8Bs)n8@|$l+mJx1D?LCQ(NvY)CiMB zJYLgy(NU*K(y|{3Z23QK9yWm~HFDeGc2`E!=&ooBU^jRTziT!pU?NAZVDW^`s z(_lQ!;6#O{0gB5XLhpV^n<2?Pn~5o!ZHdPcgc#--lPNb3TQA$?k5ocjLqDXa=}1(l z*u#*j7~FP4Ke+~952;z2+_*jQ2;&1>#cX2CPVy13Ic=I4QEl2n@v`3aGHq6q&6N9H zV-G$!fn6~2nZqG^S~bAHi!S)>CuVaY_7Ijjd>=zqs~?z+){Z5;$N27!R$BXh5|g4b z--3x}2ziJR)(nwiX&O72;kX(;axTX2V2d1IUVY6Dkl6Vr97bNgJay9FJ`XPKv!d3^_da`9Ia#8kNOCz~8 z`F$x-#2G75X~C?M;_-&Zv3{=`K#|kaF3DV=P)BsR+6YtqO^GgGkDWbYL zNHEAXw)w7?+!gDij}#s#jZZHw>A_Pqwd^IbdMcVK@>vv$CPO96+=ALIn75XfPn%@? z)cVHm)o;@MY&I1|XX!;SWn08!pan!sHS+a4PN3Kr0Y82m1J|!k zq@UzQ*D<`ve>x!c%_FA3uQ{3tbcD<>fYb8>VK3fP$(?T;^YRQ6$bBW1@s)_&BXw(v z7fTCK5cipLz4oWhZkpcIihF2*8S04Y^Fpm!QAoKRR#M~S7Zu=JX_4}^ml5qYL-Tdd zz?4NSX4cpa=149q@k>s5)r{{dZdD_ftcO9THz`jY7r5pRT&MEIE9SYJ>ng|8jGa$$ z-{nl&`A+bgIDWF~c?(A4RXFrq)D_cpi&nr)5uXkSszF4sUlq(eI06Mcgfv$u{sgu` zsGYbD5Y>k>Pwc}yho6UdL)1JEQJaO(lZ3!MHwENbf}Y|E^%=M?LdZ`PFk~f(z}~$s zd6O-!-=2M>-HAJlM6|q&gS+i(73$&YSOw?Hn_w>&X{&ilp`)#7!!}8%Nzu9(?cajr zaKO=S@82s(7Sm@zEan~5Z&)(TgXvV)&epG4+L%RJhiS2|?ldiu!v|4|k0mQw-V8y` z$&yL>8`YU4HhK25?#?rsK;e#RD45xg&)%9KWS2Q)D#b`O*(L4IXem|>{4ya`Rc|HJ zuP2$&7zyMLEKKAzN-Jq2PMiQIR_p*ps&~E}lgkKC;Fr3|By#v6Qu}p2D{h%wgLxuI+ukcnavm>qd zLB*Pxr5XoJ1qp_%Xe!FF^iHzs|w?15RNvIUY(3avTwVsv|vkZHOJv%(WF#(Ce3 z?HuAJJIOdJTKI$2%U!?thtpl=w$X2x(M>jO1*w$utSOnV(?da9+nS`tS|(LlHxxvv zzo6!(@EuGdE#sAZV74fRE#mDtgI}Mghw3D2Js?2spN87YoDiouu zPnYC*Qx!03yQzQQZ;pPq!dcC+g7{WTyw&VBnn@+L!KYlMyHa%BUW>8ub{LJk&)gZ1 zvZh9`WNS`iHC#}2!5wa+4Jj_*?7cas_vk*^UH(@KfhrVS z5Z*B|`c)4kxDcbWbay8R{ielLidA6kf+u`L5gHEn+{x23XSp7~a&-vAf}Oj-pnNIR z!Ehx_3J)`Z5Jt6&m$5OFNgM{FGrxOYjV4pQjWTWWF^2|}5tT@Gn!lAQ->GZMs1CcM zW1GU_)VufMyTkJ#_;N+l)eo3VcO!%{4@%ni8JL}hkvUhX#9MG?96^t2Pcj2@-R~nP z?%nE6(X_gsbjYi=o945AKe%{_B}ak{V+Pq+Y-WJ0jZFhzNwSo+ydON44us)BYi;P5 zI)g2tAM|AMOv@djL&z;Ido0kVZw((XdiE;@wHTTMYeWhIwVgg=b6FhMz`P&Xa8{s(t&8CA!c z@9T!3!GpWIGl7Y_TX2HAYjAgWm*DR1?ryN`%ErUMH$9=X!l8YxzD%4Fivd7{Qu4Z+6br`HT4rTcg^UvGoM-?Y-gclaV)Jc`fv<5hU`3%Gn@T9b?ZX@{GHk*4Wv=tEWKJBl~Scs>#H z1?2DoKV9CrKJ$$eR~z|2o?%b|aFx(;)B9pjzk>;^%+-!Xx^MHj6Pb0EuoefROeM@R zuoLOUcBEt7TuxsdfFH8H( zzJBdG*qHbchR#yWz7RhL=xmF1HaMAxu($~h;oh_)m_)PC@|x?;RKg~f+%P#1avB{D zt{rrUgvm;>lMb$@2r4tPZvRD4BE>EdqNY<1#^#|(1OY`C+_s^8KRswy5giI`=H0)3 z;H!@BS!d*ZCUZ(OdSv=xF>%(#Y*0|&>{$bT@!=B}Sy_dpm^8+Pi)csyCnmicCGN?@ zD{kr8ioTFa$owdq6lWLFB(B=$Z<%`rZMMfPqfE45AND`Jl__v6^%K`E*J!29SwHj2 zK(DKH1@Jk_ijE%PuN<#XH1U%r&lA_PyK~2gZV$G4qX0fiH;?xcKyUueF|V4J+aiz9bBC+A5q!A`8d^-g|#)a)DqOLB>xmS7agf!`8?R2PstZqOkR(0?lx`j=tRH<)@VoI%aLE z!p6=%L8dOu^^IMTJ#IR^DjFZLqEO7g22f+cmTeWn)l}Z*M?BB)idE5Tgp3Sp77;Am zMPFJ*y|*0I!Kk*p3BI^b{Nmf&pXab|v&E-+2vkAPo_*9Lv#K>-_^#<T!p;hY0J}$z{-}U38hVH>U}yJmW6z~XxXPU8pD#uHPZky7~>SPX`A{1BKGRb85gE? zi!dge(;Fx6URv-Y(O}%FJY_~{E3HzuJQM^dwy}*Z`?yt zFtVwvqE}tq{*En~rb0)H;NC!>eF)&$K#W~jnYCl25nEv>rkO1j_)?Mket!Xf|44Ng zhmf#_0#&ZucL4+Oh`e0W7R~yc3UE1X$aE^9ogEIFgxB6$ob9wl*2+#k=0S zR37k+id+(@ZUvqG9Il!lj%QtY8)9D=&oou5b5t2=mTdgI1wR-08A;U38tdB5uyYa- zDs}h{jj+d&Y=v7Lo4g=U?6VsXdn`;ToR@#L4Q3n@P~DBb4fdwzr2)saY5BZWGWM4@(7T9exSF#@}heLg@Qoj3}$&bnVQcCW5?Czcg=h-8o2IHB<+HSL%^pQnp) z@m?RRTe^Rl(Eir50^cBt;Svm#yU8fm{g%?dbDW~GUPDz@8~V&?|BQBo0F(3+exKO_Pdkt5^U@@^8(t`9%1=y<^J}qD5$Y-p z8VJci!yj#_+Y%B729k6`syE%j`BiClAHV7(!DMc*yQQ#Do#pNWJPL*T)a*DmAHywFj#kl?hw3wF<`@1*ru_D zt-xeW!DO*$9)G-X>@S^Zpb}z-!C>DXiqtsSEG8u`1OHcpIT>4qaH!M$b+>IoOpDEx zw;MtV9)b15oV5aLT_(7-qAugxcVhFp0|=Y+BvS+942ei#Oi*eTDh%IZ-e8qNtr0wKq@}klHD6V^9Eu~v7onkx#FfZSj7BE^<++=l zLI4imQ-c+1OT`gV1aIY2NKr&tjRB~zTiKLVkzpN;AzX`BJ^QgjX4-3u)<7u|ibpSP zkp`1M&55SL#%chFNV+MwPJ5!DP)Z4;iaIZ)_Mu>q<22R)2=G=$`uG%%R0VZ({WQ>I zMi-}JDYUuz880US_FX!x{HcD?0~>C3hi`gfzD~-_L^PT@W~^+rpmpzk)%)7hd5x=r zM3X~Z7+SU_PKBiCE^_u?ZYWcNbskqV#p#2n5mtLe!1-%-<&x4=g)R9Xd9pTx@FK{a zN@*1AL>LqbLYzITBbMI=;>j_7Z8w;t4d5|GAce8Op20%SQL{>TXe2p9mS$tMF*foq zAeblRx;P*>Jj7W8;Xv5lD3Q&cy~#ZChj8{`>g@fHY`UkOjyJLdbvUssFS5YioT=V|ycgTSg-X2YP1^5C1*yn$?(z z)zH`w0Aym<2O6_+asrtQjX4aA*g=yTK%k+Ck&z+5gbl#LXl!U^Ze*j+D59inYiVrr zyB5yO$xbiB%=u>&{C}$Z0XhDJf~8~dh9~b{&>dvnw2~S;H7KbQR9O6$}SbqS`&p2U5*w#s4obbH8!rsS#biWNbm$x>Duebd8 zZ{z$R-S72ysl8v<`(bS(y}JL*_N^;nzjX-zUGN6u*4E3HyOh7#e$61;5AQeYPJ$bg zXRJBKhqe0h;_z&8c#QT6^S0YL+q)&;xu>d%x!yT@Q$F}e}ACHBt z@9lR(_FP1&hl|o)MyvqZ9pSnQX=3BX_d^Sc&g5d=HfpER`4$I7aZ+bb`w7@lMjyoE z52ea>7?mW=XY**OouVuZtzZ_C@GSb4Mpksox};X_6%39B6SpnNHY1d!F;2nXt> zS?f?<^53D)N*L}0S(vjCGjM~TceN*kfxR*j z#K1?9^Y;Kvz&4>?2Y;xVeQUCeyx4qSKiK_&BGFGd8ICusV0NnEG)1c}FeFl3s38oH z!nh6%?r)YK;lcbMgxwm=L_8hn$1$cE+QsC)4=rw?vw+H=aE+Em^u6~4R~fq9Jg=)RR{L}J52Sn$YNzEe6m<_*7o%N1{%QBOE{UB-y^TR`P*$0C}lnK7ct zYw2C+`FaFy&7KI=ffmeGpJq-XgH9Huza8*F-)|BxU0zocCIH|+1WWovUR*w=I=Iu~ zb=g>ybj_w;C8~wMR8t}KZcQpK$yzk~m%*=$rm?bQk}2=}ssI)A0z0b?!bQD$`OrE+ z2JsS<@=K;}u)!i$1E>&!2rx#3k7iZMYB|IM4SxLXGlt5UC;D808LQ4;XbBbd0@6RO zovoH{_p{iz86eyIOqRoyO?4B7yQ#F`QIU-qso)$mW6mriH@jF4YgCynuS#xf8FCK6 zlDWgBkG}7V3&RC z`D!hDtINybZKL|_;dU65|9-c$LFj$|MCfw9c}hUA1*hRvB;v6AsDI)9`m8fS>u?#V z@s3}>ukR~IeiJ_zL62rN|BhA=x8-l9-A421@N+HE)_!E#9CBTrTwIvxv~U?q32YJt ziGv!Q^9UZLk@$pEIQ83c!ZA{kFk!GF$I5LJGi2S! zApP`1{sP%>Lqt@dIX@PFiOx8QmnPL~Qz1hPw*`t7kU@5)gSy#^zAK7MdUYdG{l#c@ zDI%0vKXGV}B*kjR2Tz!ikheUTb2tfc$CRHX<-pF{I93wzqp!I(W_b@)LLa!fNiA4R zhs01{cG{rpv~&j)vzEi9y@(h3bKOiPeNT??5dkKaTtyaaKJE0cV%MuIu>Kmood5(q zYSEQvI$9Pj^6&mPH=V*+7gS#+KQO~Md6G2rcf7CQFvugCjDNh~IGOP|@wlMf}bGLjF?7sd6iwxl6ZrfLC6F$MUuxAM-KW3lsKpt|Ph2!co zYS~j54X0$^mr0$Z8}xZ>%@`)0?<%S1s_<^_qrj_;pf4*M#!#|XB&qw{o^z;Ig}c}| z8Qb)nlcs7!yv*nrnaM2kg-&yV!6cHH8N0(b=DgL_$T6E387g zFb(kIcPAzbWD-~V68V<;l($lS! z$PI(#{lS<&Dk;`OHk#BpLMM|jfCg?m6j^d+*uoH*c>nO7?WxNO)MWTD}7$0+kgK^BaV*c9XGdHEMM2*4%Rb+EWId^h(7wlw2P+`oim_o10 zl6>!}PXFC!ry}ykI%w3=eFl9x_q)lY85;Q)v0Le_qfWb z0I}YjQy#1-Dez!gck)}5Z)6K-SIk5_(I1*)caiOPE$l;2P#=Skiri70jK&2x3{7Qd zBs&dr3t7q^X_Diok@fw)ZK;YL&~m=;RXNQ+5A@;go+>M4zM`@{4K!GM3OWkrQK*+A zFtaStHkR}dZayx{F7%e7?_2UD>dX~?LUUv{Jjkj$sxvn`w+-(>0**>osH$76#j}f8 z1n;pfjwqzQxFmNnj~~4-(r}wgfQsjfM;23vbbCITh7%d~D6b~xwkMyoRD9k(7`aWJ zbfN2ZxSr(HP%6#UExZkRXo3pb83mKKP>sscU#VqfP7E~ft;MiSH+sNz4QazHNG{px znyAq>85ewKEF98Ddn2>(iRhdpp4CHVGsLi*A1c!R7VmgB=t6TFH58AmDBfZXUUn=R zU`NJ%jv>xU)B_DW{cLzhRVogj*h{oxC*{H8d73^1j7!lmWM2$|Kd%nR!ndfhy zh6)wMk0TE~HQG}uZE+q`VscUZ3;#XdW@);uXh?vz2oZI3<2f+k&%=WoJpM z;@iRLZ|%J;*}syNT4fn-*rziqV22z$YUv5e!}vkzqvIwW?DS;yK!Dw{YiIIN^y|lZ zhjje(;_2itqQKH6JEBs?!4ZPlOpxf~t)3;X|ufwvmXGfM{ z3o8OVdSUh?M)Sxd4bfrD9&z@1#dLCrhHw9C&0|lrcZ}N+Vbj;hVBJiuPkN!^!48_# zUOzi;oGgRFn|T89Ey@ET@T8MOPuG>Z#$j?q!InJ>!V{&mlYU5=I%kQRGLRYa2J0(8 zTb9zNG~8H!QYIWa#Y_~$f}7j0g^pxK!ws*ireiIlT=OoAP$R{f2q|;Gr8voe$O@uo zF$@o8gU~H=J_qV=c+2(IHKQ8!*5 zH%bz^Qx)%(fV#&g?_3k@RfJ=o{4aU2!V}^T5gbZZZS+m-u39^#Rh$kA+}<2hj@+C^ zs|d!15R_OKt|%K}1b9spV>A}>=}s%o{1sKv*I#KSUgN@Olr3gT+FCTm%53Dh9Xwyb z<;IE96=@qFC*0Mt>S>V?66_q&&EOmjsp+sCqhe=x-k|WfpEh$cAHPAhgSXW?V5Vt? zEvSSiEATN6bID60=3oiQE|eV!G~?&7bny(~X8M%(wp+|s7(U)lqR_E1RnwO)XC$fP zdz^u-mb9+U)C5)$d_X9!sICV}C#1P7YZ>!#8&;&E&haeBS>y+{ zj$fwJnyav4OM-RNwTlKNNbNdbDMsCj?(omXPf5)=$ESKU_MM%aDs1r=>|apWcD`ru zKGSQS@?p;LFUTq!6WvcFmh=L0AEie!EZcAk*(h1KiT*QUX zu038jkSkaq!zedh*b^|zJOyGS;OA*-nM)nOU<;QIVC90ua{8-#tqaka^v+5i#D0c8r4h#dH-NHwE*;v7m zd>W){dh0jiY23(D$s{c4vM4DmB zzhu^j_qC7~%iXYwT$wqJ_BkY%{^tW!qFG3H)MkP6Cw9&fql3>Yt41gUP@V!qMqQXDa_BXyx>9ZapuXEka=Z_WzY!61VXSRjBk{dr(|}sD zrVox=`{nQ0ulp%?e|%J{W+-{hka|XzR%3WITMLdGp+NV| zV;iIvbJUOZ^@yELaQO5h>oT3RaP}L&w)D;5^hJT6sOkc1)A2n7E(ObTye==7R}mp$ zqw)o6^bYp??OBm{PFI-pr|Gy!aauHzk+lehP)#)XsfBm6I8&594yRja#=e>Q98Zv^ z-!MQ&!x>J!Tz}l*LvoLL2ZbO{~#B=fe>~0OqmJFI9vus1G6!mL(7cA?JjUZiv?qSyT z1Ba$Sz~HhK{lFF$OVUJ!Sn@c*BX_$Mg+D@7IWbo1OQiU8wOq3h{_dW;^pL2nrnSrN z;Tb#2$Z;pz@p~?xX|yR*+@tC}`cHun!B<6SKk2`v6HwwO)uip^&WC{Iiexo&^J670 zrQ5zfoPM+2>S}#ETYB?++AcnSdpbTt;(vaLX?HnyMv6I?o8Wk0x_9yMW|SGM;88Nq z%8Kesi{LKN8H-F3an0YZ2|%D`5RFWP_mlL|_r>wM6Q9gX>KG!`>KgGT7Ry`b~=|O0aQY>w{caW>xCagLvl)#x|-lYo=gYs8lZV^q}M!d#}!;W=} zyugBXTmGUoT2jt7n6n{}Yy%2Y`rv!9a%mi0gfxj*ae!78bzi(Qu6bIVZ1B7x z@BlW4Q1tHF>^!%1Iq&aS;1pSV(j($475vk5cu&$5fqMh z+Ncg1>1WV7^-3%2V3EFyAO|sWr39A?&&}b?o>H2574K{P9vQ;s!r{^Aw;H(M*oX;N zo~PE_uLJ4iVBLeaBlu>`Tyzl6C;0_<|2YNfX4)kjP)#CmqQ#NcaO zMsmpmF`yiOD0Z@_+{LUi*7f@u7rtl^Ezy`b_!OSIIUhNfDBp0Cl}Q}2-|AFvn7e(F zn>&kVm*L~gQB%Wa59Q;+_wz}`V7LEKF#i>dnvP(ah2~T2uMEE7`GV_Ib)g|P1~^IO zVUfA#`>sm!LhTb0Ubt5wQSOgI*JkSWb0et}qq%ex4&SWA>``U7lDiZ}lw)IcgRs&c zO%pE@m7-g?L_Ut(Z&l;1G{!FxGXO)43-FF*T9dZvMo^XB-7A_oWwdRM$7-3BQh#kV zR_W`H#skTVN09O~6OdX#sX2w-l*O#lh4YW@OM6202^7ie1^WdV0eI+pD%e!zl&ULq zvep%cwk2vx)cIYxKSedyrLc*RyXOp14fU1D?X!EiMl7ob(t^U_8_g2wE|yep)rjQ&_4gA5?H)pigmb$1%~w zFG%icoPIg571{B1Pf~5o;AQp$-;1C3&(W>5Y%>XuF*JCsfJ;Ts&$Fa=`hZS_u4~r+ z`8!qhy$pT0Y#5WXKyv@g0&iJeg_p!eSa@QkKX%Dj3$#U;DBLJzL*&u{n@%MT>@H+m zvfG6)#X6GY1&RXTXQfCBn&pI(SFp#IEl&iZs?E;~W2YW~_2e5gVxK7O;Ow(d`tWB$ z2A}8c4fZ|hDQ-Q1=;Iu++l*{qBKE~9Z_jr>7+v1LFA*KA{ws*_&wGl%za{YjIY95? ze{&Lv04$&+J^&LNGZDz;`u*7d>?QpFqJ;7vlKAwEO!R>!9GvV%#s)@AK!83oC&(i+ z;otxz^Klw5u^Te605}W)?2L|otFQc9l;0nbet${gV*~stst_dG{jF{Ce=2f<&Jd6t zz{0{p#K!U0tU_i^CI%)ZRt^9WsK4|7-rKWfFfw*#v^O?kbogRyNc!6^)71x+XWIWI zn)XkW<)1eX|6`9?ng4()po~S(Hvk*RPh|!CJ2F52VUMjr0la@pQgm=Ka4@ttHvn~o z{+6J~{-|c)kzqjar*yn#f&-Op9nf(vo3ff*a22KtZHV#lP zz#nevzjw|5$Iq|7CW-#}`seujd!Fr|htvP*y!<6uFi+E4zo57OZDyCOmrm|nSF z2>EBLUZ1l0Pj7wh_jL(3Uv^x4Ua~{DvcgY7m(O1xuKF2~x`*L!mgM_D;qc%gT$jUJ zr{3IOc^-Sk_wi22u8>C`rnTtWSdFHFT+m|Rva;D)%re*p{k$Ym=t)69I$ku?eiPRHiOi~_7NxEl!O>c*4JrJIB_ zjV>5OYz2kL>V#;&Go^4o3^rg=5WMa5Q^ndm!=<{ug=`PRWD{*(bM?9?7nSnA8Q*m}ob$ibJUur<{}hDriGl0F5kIIs5r0PJDZ20`_;n>w z=1^>fl}lLc)H1c_O5k>WM{4s0-5>HDE8>pd@MyGyBo1_riv7l@9Z`^ZoLYAdYs|nM zs@}&4y{@@kpJz`$SWcuBi6B<{A4)W_?}9joI8ce<GwC zHG0uG1@^v%tx8-)BgNXiH>r~uccrd0W=b*qQMQ}j@bwGS#$b*t*O=cltB?RIC-e8O z>m2W?AvoQ7=YP~fI{HUS-LAa5+8*ww;WU-WobrWV2#z!F@w%MD6rdOnf92AG|Dss) z!#;SNGAAgeQz=-}#s; z=fl}+(hK2@H*gV#2@aV)0`3LKodfTCl;-&E%a2c1p7w&%EiwrB2 zBG^HqR@kaI0=*4G8$Ww`AP-T+woKb__vj7ymN9~#>4-T7FLX(stVE{#@fSAK1x&h> zqIC*Q78?Nvxvxo;q{Uf@#4n2WN}5eVHvG-Xbtma+`cG+Y6zEz`0t=66opX1&g1hZe zi580^ciy=uN3brEPGIwF8Ily8(?rB-*D*zhjvE|crzC-{)b68DnwP@CAnO`OI>B4p zq#hv<1={bftJkriVGoD*Q8`;iAVOSzqN~AtwfeAMKc|f~tOPR~rPoHLHcAem;lj?o z+!xqQc2i&q;lnG@>=du{oeCquNimM;BLn3vSuSr*ghWOiE}sO=AsT4HRx>Sxq?MrH zzG*ARJ_^xa$#*d+Dp(Q}JiFg(XLS}t=_m$e=z2D&;g~x8fv+RK*{L^-(KtKB$A(g8 zNqa-cFi}$%LCvmIcFuIk`O6vc2(|XP{g&(+@d%ts?MQkP_ph1$9!_Y0nkRZN>YZ2= zbPr*zM7&iv^nvr{i&$|S?7MHdMRd3vBx`{u1t=4^u>@AAida+}0iRMi7&%0S;i69v zS;HtI;tW2-v0q$xqM);g+HFWeMt^^UF)ix!Qi&>2tcF@$bsji4<@UtM?43MdX9lj^ zWk92tUN~OIiU-Nc3YSD;Al=KnBfC_r3iX2*U-slf3GX4yF7*_XVO2NaZGgBsFMN2i<%H{Wp#yfP(jsC9Myk5p- zMKi{d6UstBm-B;5GTGtIr3tG^2_?9ao3=P2M&+N95e^rOnTEdp{||r?6E7I z63egRU;=OinT@<79u)4@d;9D1JXmd&QTq-eQi1n;Syhfhc$Nfyzb@detKya5fpSXR zWp1C7$i51Y`lHf~x(Q#_Y-y}a3*W5Qse0*T7tZy=PHG);DKw(9N6J4QS~+f~LYLOA zNiPJ|jJzYhhJsP1tuj{FQOGUio91ZbtG~~L+1ADeaQFYXf&?n9bT6sTd|dp(W4n)( zDcOdj6>+rOm1FtKE;$;;+FK@026Fv^Q|-GH{)?Y5J&$L*{P9=}cu2|J2I1?k;;pW? z*NrFsw_~5T>$`9KZ(H5YZ@1$-i~S}s zFFk+up6X!^GNL?wHVd_3Y-f*E>9kJ>9QoPM(j7({mB~ESdGm!=;TY4SMT6tY=7C`>6oL6}BeT{o$cP{-^! z3#GM2vJIUGUhSei+sx%?^WLQ|yhGuw)||S8tp0)!pY3wND(uo$LD5fX<1Z?z1~z(w zOB(Q{it__wwdIju7?d{kaa>;#C93G$=JT%Ys-GpqZr;zBpTCAy&rOMLq41O<3&gJ4 zy9k~(;7~HsG4h}>Nw>xbIcKx>F0H*#0F$M*MT`4;`~8+Zbg*bMX&!%0CJW)~Ur7BZ zPig}20ZqAbBD6S-JK-!vgk(@gaw1|j8s78mE$iDWTeq0hVBb)`yE#5fV2KEPklFQz zi@RTDn>`ougm$jRKgJ6W&H}epW<-EfscR(eHmGlVcij%##$fJFlM<(m!S2`LWVT5% zhOD6#aM^*7rbB!3vjV~FM$^4GXEA@eyQM1_Drq%P&;m7h^=F=HRjE>GCf&5d~uy^PSnGdDpQ87uX1}B8c6wheK?9+_)P?dOnQ zYpdBn+|kh19WCpC&L~m1Lm==+6GNM2aAGOY_siudMDXfLxqmJqPnv7?(3R!I9>P=J z0$I7-%~q4TV3pLe#BWOrSQ(c1tuccRI$dJpj7ns3^jK_2j91>~B4Z@B*$wKU zg@o65JV>JGF*uMsyLl#2s`Spn6(~Y!0hQ0q)w+6{MXl3F%qkF70Pk*lfV94hNnlX2 zTQURRXRPb-Fg0quP08%4@}i9qWm3%2k#ni(B5sp^qhbBss=b2=kxv@HA-JWC5{BZP zJYh}R&WSd{mPCA+ad)rvgYjlxK5~g<;k{^$pm^8yVx{^`ZYXkRFNu#5$@kKEhyY@k zd9SxoGzC2pb*mxg3|Kthl>n@c(5Mt8*JU7M@VH(U+u(<8e7c&^b|$ny9ygp5w|X5G zfNkte+a|W2A+3|FZdg4YxAqLmnvYw5=O>KN9`V|mAMbM}hrV%t;jh>V7M`=Qm8$GG zIJIlsY)(BA^%^z!!A|f&(Qca@098GhHfwJ)LJ-=iG`1KlSk=5}SA)jwIV#9f9G**G zcL7jNGugKN`B_V%zM6FyH=p8Jxn}MALWF z=24pdn1}zZaX_=)A!F#(I6&IHIU?bRr71Z+vRZZ3B8*sZi-1MQ=iv3a|#bkIA7RPSuMCLf2iZ=W%1?N4t)uGt-; z&YV*4js2t1TO&SsvpuAzMZgKj`$Q}7g3%>$!h5m2G<)**uXO0-d4`n7+o~k&*=vjF z{6KiSA+VwuyaXz^fIXrw?7Vn=-Y7xYR$}}1Df4dpOEME0R(v!j%60u1T&?sd<5!k-Eayd! zXRj(3ZS87%++}W3$+BNZOPi-P^>M6@;Je>XD^WjF(`R}a@Lsroopur02%5eVgI3dI zP5cG+xvaEN2KQ`hOJggWR|aV@S=O|9Yn7gqc|n?^^9TNZ`WC>xsCM=>r|7P=nE7?F z)TpG=J$);fCjND8?I|LB{$LO*dpNH8>=(lxI`7I|nZgx(v$}uothuGQVHG(qWyzcO|JAB3O1? z@~$(6*HE)_NZT9_k5(r#p^LkV+$|j&>zEGz6wPiw%KWn=lG0(GomuodsFEz^3*Xo; zO2v~Y8fPmP#>Vd)lTjP@`HJvH13mbifA)_Vo?(21qbR5^eyxEBf+NTLYblSag}cln z)n$P@N2tw2kWxk~S=~ky%mkp!tYhB!! z17HnA6Pit+0ddY5L8=@~X*E8hXU~+bh0j+~b`ks6b-$Qc=H3l-bo$v;ehn-!8IMZS zylEF9;7|>@*5o!nD5+&9fNay2nr7CgV}@^sIKS8r;eWNcOFy$Ndc+i$V^Kw+d*yyi zE9S52ls&x}E~5awmhj9m!M6XtFDA5ij!2xi z7X3uROb7L|poWOQ_gCpWlRu>1Ts7Is(moFy>$IJo3Y(+z*3TRxtck^Se!2bdRv5TS zfZXxM$hGrlue}qQtFE?mjA`bj;e^p4vKR0K2U-3S%k5nZWZ6NWbRR+cYhU} zyBoXI1wnN5(ES5%N#A>G9l(AP5(cuScrN)VaF*51DMJd^B#W&JDd>uZ%`Tqom9p1@P=QrY8-RoJwC>Sp(2 zuHrW=Mq5^kz_@WVZWg_Sop*O%3+BtA7=)aS^eJ?z3ETb;Xww z#>bBsL1@TD5^h?E2nFxWjBHcF@uO}bjWA4BN^XW_iPB`;pQp?)&)oZUda>c_n3H?* zdQ!MEg~&p_+o&w6p?`u`vI-LYGF@Vw>(bqPg1q2QjdVcMg6<-JLgFx50v%+|MC_0A zKe*)-Fq9nWeWp6DC4)Z?3uN3lXT_}NaVOrVF(NijKTTCmDh~VaJflZpov;o?xv@mf zpv+ayLTU+`V}j{Dp)ZK34i>Wxs#ARZ`0878us0A%2<8@svTs6zX;RoXaOjMyKUK%% zbBM)IyFB#^Q?(d+bRey?jVv05_efVTp&FUQPlxuOZhqAFc=zpNUP|HJb z&nqTABe%jS<;KXlgGNdtUT_$5zbaz_&+HE_A!M!?RoK5&N${rRUh zl)XC7p?dgU10-owF65S99(@mBQng=i??U*sE?7P`5PeUD<>z`wWJWnw%SS30!aoSx zbHC}K?f!6Oj1`!SRHYLY*poUL#YE(}Mymf<3Fh0f_P&R~3GCw9Z5Hv}j$@`S;ZeP# z-zNbJy0@LoQ0wuSB^J4Bc80f|uJ)*QmppnQkO^A}4~(69Y{AjHWA6x(b}s$}da2I`r#GIuw&|N93XsB8IeOZe~jCo3xl>)-KD z{FP|j;a^_e{xOp8n%k@Rexsk3yVOXM5IdX_Cq#l)I}n!-r4;)pA%j)Kt>a0JC`8pO z+;dJYnX=2GFHh(P{6U~nNkYQc2jsdFkY!p<*jM%PvQh04Lil=<5>o9W+ur^Blv}w! z#{I5fqr1!J{>P8z$KcyiC>9*=SbqXEb{}-(TE> zXgMPbTlid_Z6hHuIgmn93k0@2-ERv&(Lo6j=QTXt?{Ba(^L4&C-~F;hQupggQ}QtB zes{6qO&k;nPrYEC%Xfy!5h|jr@d+;D4o>&&mC)z;Fx|Gx`>y!y`7Xxi?f&KMwf<=k z$>(MFY|YjsM(&)DN^c@QuxP)F_G!QCY0?8SfcYxx6F8ckuaIB4uenwzp_y7NsM0(u z%QX9uny8o_l|IugAV+^~YB%b83XzfJO4I*7cedp_ zhOcyjL26}UB;0Z!`u?{q?kQ6LfRqOw2HY>-Or_@zHK|h|+lnZ!jy9-})n{3Obtf-* z+EgC?-jAcTn2<%dH|SKDzKNbZ@d;>sw_)(CJxBq;^BOrpjHGr<8gOn`s4t~GWiDNp+&|91^DVa{_nkf^DCKKmkcf-qN6z|vdd=YHAqmky+ zZ&O5kP?V*bo@Da*0GB|IxF?rTq^j~x8HG8HZIliBH z$WN-T)z`5IQDF!=&|W8zcnCU_Wuf&a>p4;FVuS0)Pi1nVPKDiUn3$KLY@ukPzw7~; zH7hDu0dzn>{wh0a8W_;9lnx_lv5V;|BuSPcH<&yjl6z`N1Wew#<=&T@T%3WcWMtal zQ1b9`7gk;*B~!0ixmYyV#98u5tsbs7p#k|CjH9I+3S8n;WDI*I6$?%bUYGqKT9r%) ze?3_*W~|aNn(V#?Rrw?leUXsRXpNi@{`!87REacOgKXK|&?BqFWz(BRN0V52-%>{K zre937jeJR_G=dLv{;kq+Y|^bjH<=tHP7~kQ`9_tPYDV^yaPbr-6@VP99--7pX`hCG{aD&|191XQ%HSC0fU*b zmHw;yQcpbCH(ZuK+N)_BG9h?&;Gjkm-=rGpmHKwd;>h4kx=6{YrfFHye(Q7N&4r?S zm9}&}l3jb&uY4AYzQvEbUlL`v^|rftHC9g?uJXYaa_V2yAm4uuU*e$Y8c9`nZ+^eP z-Y*PW7n};W)M`GA;VkDu4Fq@VMswTywG&jK+A#5=Zj$HjVF`K@8dO-fv*O*6d`lQ! zqt_?xtibdZki|?*ALl3fpq(Vq-jr*@xovd{9!_&bLGjAzyn9YQctBPPRBC{ zd#4-;gSPjBd)<50wJW`&M$oIvrn68%tjRC{hH+GmGS#y zT>A$j;BH!2H&kbbeKIPqTGAM5PN~%yp>XTb!=rVywbun%7(6N-25kv0wVlb^*IXL9 z;LXAX$E<{OQs&u1E#G^2`U+2YKQ=pFH8D5b=UiLUMy(c_Fz@b+pj<3tH-_tY7N@se z%Q}>iy0@>y?Z8)P{FJ_tvz~DfS)A$zjr&-VVy0xjH07J`jZI*^`d`l$V$UIX-nwI9 zwLDbZNg0wj$deb)UV1p+j7X6z!>yUhWrs1NSyExGC|ETPS~*;LIB@Mb2Tj4jIub0A z?0U3e*v=h(-ct!*AH8}oD4M$=%9MNtW^|$LJs}j2hmyn4FeFU47R_0f#HH&vsxVui=9fE6ccMGn;CAhl=mjrircXxMpC&-)I*IDP> zQ*YnB_Py`CDryupse&4U&*r`Hc?jDmf1dEH_O6id*ih$Zz;8}XlH@5P*uuD;Oz5+yD8CT@ZFUi z;s7xr5v0L=^yittk&kH7J&;7UH}ee+@ao6@`Hb9`V*2DOr5QMyd0*~#V^eolGgxaK zGbjjFZi+GYS1e580!xW0a2?UK!<*R!=n$8k;T`vfS9e%&9|0ft8vXs@UQ6c|3`Y^} zKJsI!nG@3ND zPr>bK6dUSpKl^*=4b|xGeZ0l;H*+fJX!X1Nj0c(G%Y7Qp*mD0bP^J4wD~RcZ{k(QE^SX)U4yrJjA45~7ciZQX=Qm&loc)XfNDn0BiGlD7jc4QT-NN+#qlm+)XE5nJ z&aEO=u)@>v3-({e5><%8gwA}6s@_tc*Oz?>dgl@B&KA{Oiz0~GKV%7gJ{^&^;r!g> zrLe|DT3qBv>?Cu(B$&=!t>a#b;&O3DN6uy_G4Rw-M?dK<5z~teaF6OMDVm31HZ~u= zgp@1Gd}8=sjOvAi_3HoO`iUZoEhGMLFihKEnU5{{rVmk(YkLv3^?UBXy=B5@r?3@T z-4!dZ^oQ&-N27S7z4)7(^77(l5i8oSJY@3P=SakF?U!C-Aaj7mI4>9a~a;N|}_PK$;+D*7y$%w<~Zm9;GpwT9HCv6?L4n27>& zg5EE^;O7NHnC3wrc(Uxp9ohr=hh}YTN6MLok64@oB-^0RE?74T|i>WdU2>_Q_HWL8ME{nC@lv$$9By%aU=_ABRIVm{-*`ogRa=Q*XAR_@ec4N& zy5~ArmHVl7nDnEA2nO%_!^ve>^W&@wt}wltHuc-~*yvf(Uf7g!w#V7nsASsA5D&ph_K!pcnCI)3wQ#13_htj|go#geWSKa+N3<^!S3e6IN4S2Ho#ji# z=%_M3zilCPEWa9uo7i+TVhzm#K7{)`2)n;*Y0{OQh*9O2(x{CHzWual{>FF2*G z*UIxUxG%aEUlxR=3nN{)O!;gVWHu{tB{3ocS)|R-Uk9WqU zbx167iWHK4)oFBG#5SBi(wm{3Ay;6OnMg#2)0`C@BHO++j?$}6O-MEq35OgfZAeX{ z%=uX6o;T?J029>QYatVT-Ahwl7srB-lR|LXWZi8^Sfv4?=(ab}JU=DTrwz81QD4@w zed|ZR%WT2!{9sO>FsDBn+`Q9r?tq)pzL?C4_yxgx)*%e?f`;1RnoTT^XAl@d&h%Qd zs~CkDW0|-y$9HOD5Zg!cJPzVFvy z$m#Zi<5T{6ht%~{i(e8a1IJ8sHiefs@0u%BsVbq|+qFwMfU%1h)Oku3y7Yw;S52EE zi)Wqd@z{-WtKDgv6aI^>%5)mDzG~M58Rco2oH1a(o}Uqp(4}gxF46ta9b*)`SX2zfPwFUFsR3QovU>M zc}YbX07i^#xtOCr+ZnM9#0>l)O4DVM{n6N%za0*Sl$jMpQz@*&yI=SX8!(NlSK5>0CqdAV+{d^=j7N2H4SI9qi zN-6U*HG?^e>B#WQ`JT+lxl#vQ_AD9ZC$+IfjmXf=&#?q{jAzR-MS6kavA+Hx;h@ZVaUJ@|G{>W++J$rjzLFo# z$hYiYIi7JNa1<)0X_CV!DL!N?#eGG#<~U6%OWih-n{97s@+e+Z9a-&$7|zFfJ`}^* zOH3&ksG>$uUH&iyb8(9;p$tS{r>3NcHmNd89=~Zeeb>AV@$4QJ4cCJfujiK;f^(q1 z*$kF(!;9PeR%O!b5o(8AhkN!5e$t1FT=3?u9k#VYqjml@bNOu9{_dl<=VEkt*VKTI z&*SSyH8cl45PJ!1rs+t7!AguExbh=@ND&K28Ob1(>GgyDoZw`X_15$-%(L$&t~~!HDrUzeWi-Zq35ZNzvKF+2k*vHNfw* zZuURY;9&)@{8hPQ0mdMJ#5pD|B39rawtrT+GqZ3qcQ#}I&Xr^QUj*HLbB~7BwuX%V zj#4qR0FiXR7svldPlyE=CHafc`J4G}z%0@KXUiqxVC(28Vh+rk3EA0MTNoMqIaS9f zrX&Jj5_YgKHZ%F{_kUS%tgOEiTK`CAiG|~DQt_W^3E*U5Wg}wY{O6_O{~HVLA4^!) z1ej2?F$4NBj<(jMG5}6`8MfcAyWdz+|E)aD&dl;3W&Kdr2E{g*Cr_VaOA9mmI`mN9 z;`uJx-}lZpTq(YtFKoR(#=6{Xba>XRyl*vRov(ZDxCp!+lt&e9yNaH=bO14kJ8KQl zwjtw<=4AzM@(fNhi|34+k-r3FY&{>{9`;3sdBR$v1=goT^#EMVx(P0XCp&tIO#DUu zJ}!js?H*QVufY|rgzxSW^Oa*sI$p)NIl+L4gzh9M90_d!;llPbo@Oo5TW_~?dX$@9 z``vYC(j_XPC=K22Ai?Z zpaV-vy1Z2+N#Bi9)}dOhiIYgfN1LuU_U>_dboINK2(k~=OpAt%PB740R%`mUQ1%l+ z%{j3p!(3zWMKkyy#?7cq!XE&nT#gv_RA0o~PZGb$_LAfbe7UidyC{w~vL3k#*}7{L z0EE+0n5JucBqgm^3|7f#o(f?*RiCYkO4jcuV;z-MAelrijUZ|$@crt#!%@{6^X?1i zhp*y{Yi9N8mDE+{+ZjE?MvY6b1q8!KWgBlu+l)a! zFK_)K@Naj?lQ-E5aS(aK36yA6o{R|S7MlUAmsIeRJQjlaQW+v&Ny2x-97A?(z6bcT z1}8#I?^6=6ODkw*kLuVv9DN2zug-HBn;`$VPIo6S%Wz{yz6qK1Wex;Qf~prl>7!hi z27j0qM1=}t)h8@)Hnz|gossa&lunKytis@bHjn%Oha5YN&QBo0l(v{$8K{rR?9PjsC-vTI9~8u7Mg_f0Yy?`!gaN7MLh#~*&EI=y!28xr zkMZ%$eJG_SXi}&^7)izG%8!EW124nI9w5gRmq0*BXL?kdYnT3UGM6Y{7pzk;iUQ9> zkoR_#a%p*7FCq=i%JwRhOax>Y>3J$ ztKRX^0Tf1|H%$SFx&>PNOV(i3gFg);?|yZ$gNf-+xN**{Yuk z*W!q*a~)?JeFYVnUdG}R9`a|SPp}$Dx?OvNig}BuE{x4Ib*m+92%8x! ze7@{k1lyflHJ37klpJ-PkOQ23Ss<)pnONf_M8z=^8{qlVFu15F+a4G54p)gjMKW^l z&#>K4{4e(4Fx6RF8@jf&@6J<7*thz-YsR0JgR5 zxhjW|MaePdG?kPZ+PIffT8B=RNWa?MI>2(!4!62Ci!#X`D}mEzYfzgXDS#5Na0RWF zV`SJ^@Gs%{LuG!+G~{{aYiI;B2W3>N1{3?j=!`%>1tKtt>5jg`4kNC87wg3{#*20T z)T>aCflg7+mClu;UatXe+=FEWE^Dq4Ad@}ztpK^Wo#nftDSwX5)aIi7YP!4LvCp}b zPl+!@(z6jC+7!lxoV-&VJGNCML!wK5hk7PcY!$ov3i~+PCuC(tW(_p_&U?D^)-{wt z$QZB?6-6FitZ$)rU#%+J6&MW9E`Cw^lMhf{Qg$iEnSE1EE|t)L$?2x>b?KAALWNjB z<0rS6=QcO`1S&}1fT0z|ot~9)&AfzP6UpNGa>}#xLqN#<%3E-WH!}h4A-*BY$^kq8 z#NP80T7d5G61AyMX38N!Kd*n&Zl(K7!cYSIiPEX8^*C&A3X!-TGws)5Psz}CxYJ>W zLbzRNiTR~Z@W{M?b!Wu;nKO;KG84K{+UczDIXX*GlH6k2b+36gI5qh=Q?b4`xDpMg z1zM2hTdBcQ;atdB+i_9Qs;NxyQ7Ca`eUgf`CQH=)fFD<%s`>z@Fm&~=Vi+;=xnKnR zdT4GD5{F7dzNR&3&%%cSpme+uAqbulp7_wL65E{pvfS7KJ3FPA z41sZaosDGYq%8#tqA0-(rgq>;s%^~xIJg^YWuJHvPjDNnatz!K8{g`FRu{uqQ>Hv@ zu1}XmRUpa|e~W-Jp1>xUIHabCGf^~%$8kMSfw4vw+)HVRGrL9RV&j2g6@T(Br1^$6 zlJV6tspd&af#lvkUeE6Nyh>vG!+}5I&E~ zV9U80wF_pp+J+ZGmsFzkb7#^9k6Lr4W9Uw_%C?0QVCS93NM6(5k)B8uw2#->LJ*?R zX7cj&nmYK>nmg7#JpF>EGqEg5EY8_RyYFEd+T#0i6?-X!jhu!35k{wCV6WeAV4mE0 z{4(RT=UYYhihs!d)nIrtxp&}ZjEt0`y{k{5Se7no@(_;girGL75mA_$cYLXKE+Sjo zMQqDkxr&)Cy3A3I<2R#I<`Lu0=pT9`LgBGnfX$@3kb5TfCcDew*3~O6L!sdEPZ=+Y z_8<#$zih6QvhTxBK{o*t>dzSsoX`pli>6*2ZXaaC`aO<3QdcA)Luw$)mV}GsX(O9L6hd=QC=p`Z~pxVu*<~oXNR> zsdg&O9j~M^&^|}1F=>K%Uw=Fd*FrtX5y9kRh77fg9^Muc{p@9g)hU+RHe}lvkX08Q zN%?lT7O!_PYc|eBm<5FIKO~+}JQkOiq<`F6ZBFLGHwb^P}P?DuQfqe))AbNdq3_jn=;07<`}IiRh~* z`mTsA&gvujj7h|xOKW)YdcTG#>|A^YY+9?OkF7n9o98SXb`0K?Cgcvu<^4EI${UoL zCLyjNkxd6)Seo|C*(|0@Hm%c?>{Vm_UxnT44>dTKalwUXMk+(Gk(%q5I6 z&QAeS@P@JC)y&Utp|i@}!?x#fYS4t?)wsXfTSY#BX&Qx*GsLq0c&X)*`o01>v(jAS z%$Fb>yXDT*l>6dd!go$TpIR>6SwI_YM-}v#!M;AB>lO&$QaamjMfA-svsL7?__6fX zc!qWMG0E4d#wRMNvD(;(7s2k{9D0XV%z(e z>JtDl6|d|2WOlM$#B)J?@i%WhnDh_zf+^~{Q2+vRhoaqAAyI6OW03RKyx-?y z$*nh2WvPWmwQIwiwRbZNyCla!4!Gn%pt-=DK5w1d`YgZFR1?-)eVvyGiXDAhVSc|H zApcb$7g;x>^E1ox>rn=)Rrd~jx2xs%9`rHSv5oc3HhQ)U01$o0S6*yc1_DGwduyX6 z90Bc{vgDE;RD-Y*3lw|RJK==*J+uRc-Tt_bTcpfTK*_ zq(fjge#__TX6)+CfHH!HrnE-RkHoFask;tNtC+`~S%ctX2Yz^B!bjm(%eV8L7q4n5%J23aijNxnA9d8bUG&GSU5ncCk<-Mk zX=HBs;;9%`$0w}%9J5S&koP*VA_<;TgEsgeT<5Y@JO^PY>9pB?lW{u!Jt_Xpxf1|P zGr0j7;Ysl~xQUNsU$L}mODcva00r;bMFOfNzookIN5_E+2B#t)YvIL2N{gE1UJ$lsLF=cmVN7wqD_Jf^ z+#%1|J1@f*i$1I*2OOiMPUq2n z(m44wR}PvDu3%IX1fK*ZmaIQ-*K6h-UX?&u#@hzOBp=?#2h=!OPPdTrgsNQP^A?L$ z;wjZoo)Yp>MvtL@SyR#*0a+;prTIv%H#8CS*o_A7JkfN<5c+kj=D90G0rjJb>ckjf zR+PZ#CtfC= z5BSAO%@b&I(W0O%FAi!=Xos5zSr>25wS?>%c{%mt;Z=D#lcGJBjsZbudf=RLBA4Y_ z{I=N!9$9-ktZrFk{GJ{UK8_|}690uh&hf9eumYG^8MrvOfO;D{E0AgSXCe{+$nIii zWd(2%v9tY^lJrls_!`?9IsQoivoN-BatAgtn=#lrm@%3F(N;_hoD2XaMjIfg7YOyD z|5G2cffIwd(--UiC@pgQ>usq2xGh$|U-4r=)DZ^*C$K#iC}jiwX>Inuzbj@gMn~Yy zn$Vj$TNs=CSGV+=w$y*w$p8TUmcIZp*Z%5c09gJV`HO#On}2aKSPa;WnM@3rSb>X$ z-H;X7RtzvPF=aJi9>ozuXCmBqx61z^Nx%4EpGV#3a5WN5<0#>U0T#K~r0 zz-h?xzutlWsQ&N#3g+KiJREX^<2AV3I^r66&SuoaKPto(EE4u8a_T|LZOcs)W`mdL(fu5pwZxX8vlM_#`Lr!5v z6FllZ4H|cxtDX<3&z7$FEo{Ps{S8ZTO!8^s-(T$mWjjIW3EXpb1OWxrBx5(ho_HUE zU`S28Ne1lWI#c+YEXeo>CHOt#)Wt9p+z&$1cyE%T8EFfrO=fxlgi=cPH+MO+Q@}w6!R?Ml9j$H@$n%9+>W?=zI-z@0w^9bEEHa3N| zp4C|EFN?kE%lm4Ce+{y$9+A++TpoClsp5@YS1=TY~vAAn(cR=^eTMz<*m2bvh8zFLpYg9CzWS{^F-h4Ypj;rw7WDsRJAYqV^ z>zD*TVk9Q)Y99Z|{eI>n#|S9n<$%w4iUkw&q$beXxDiy+7HY3i% zFc4f>X@=|W3qt7i-C#b*m9)d(B*fOc6A9ecgvNqdp^&ng(c(K#YeaW9guTID4_;oP zWJ%O#X1dRFR;BG8%x05xiXfi?ig^VKB!vhNIKTr;d1_wcT9LiqZ8El`nXSd4V}UwYK1mK$Di^>& z%gw7_4}6Go4k<}E{qg=;3Lj$BK<`eYDh=hBq@Z?@&)8r%Bgy(Xj5|8I6A~=F+a)p< zLFB98u9rx7dMjg7GY0IhWXZvwsh(?Mez->j6tu&1P@p-Kd3PDwnn$OagY@}lif}Me zCIkZV=U|l;3!R-JJ8+_FrftP3xsfNxw49uF5Doe*kt3exb%4|{lKW6iVnvUAvv5y? zzz@u^EnRO3s8s8Am!*}r-U%3Dc|AdgaemUc%Zo%S2_XpF_r0A^Qy8?HNA3y@y8zX6 z46+c0pPWSP$6^gQrf45J3+okRF&->--^7?H}Q~VYXvMsRw*;!OUHSxf@zFyJaD!XV^; zVOeRqn}Xx<1gx7zs9bc1YA>JZBKb(v&(I-_Q^H?NW4(D1-o6b6F=wAI;}l^;GOnBB z{+n}SuCna#VwH}{BmCOArd7BtcNtMv)o1lFD4h*tX2~~+2BXHVSJXP~q2X;Beo^lX zt3#C09tdOk$-8mC7<7#z*i-naN4aM!#NAhOx)!LNf@qw&X2 z-By__-*)O?c{V7vY74>Y^1pts_Hw;;*NzxtXB@RHxtP>8hV>oo)Elr_za^%}h1J+W z*>Gb;#{c-Cn+ADPZXkQo-%^_cKY&&LQkavRKqyq#_5`B(r*b}qgZ9IDfY)TK_X9V_ zg%G|VqYXf+B$NURQ4D3$w$FDz)tmKni^5uMB%)c zKBAGn$Vb63M{H`{0fdM2?3IC#De;99ojnE_;oM0IKYE475+IL+aPw3wsY-N>qy*$cO52f@!}2yfBIFrPY!p2(Jb zQ`YF$*`~0whJKo{R0qC?it*uw!%q}wg032ze7weGsbB~6d3@r|w+R3*qi7|FJvM&~3V_pPgpC<>}P zlm$gV=95HzsK=?kgOz{2LyT%x`!PBhPTAQUUg5pd#UKPDmA}ZJ+q__J^=&V0XPsS1 zWBQe=XH?g+oJSJTZ` zD4B|PvC2wjpr_KU;ViZ%w4q|T@teG~Wzu$WG;M5H=}E&QjbVy4J3Tn}Buj=W?Wb^I zvtYS|rTbi^h~(#_v*bShLi4EDKe3IbNwNA~3&vm*iIO3f9YTrm{jkjtLi9RBQEH$q zY?)h&(&$ViNFP9WA);9u`fIbsrqDW)lB7>2lV+ijx<#7%_8Y=&86gox!{UdIN6_tG zLm_eCClt#AU>C<7P>&loKQ@KMVQuP}`mUtCtrz704(rf{M$ckG0krC|~l z+=tsSd!fy>wv_uSZVrbGgtTD zh72fsku)>`)E6=Q;q}7GL$1Z3N!_L{*J=bp-3Ucs7sqIb?R%nUc8g@oj}!zC=YR(Xf4QfvUt4JkqKjGrhU@GM8(; z0|CZU3*2r5$(qd$*0*(A=3pO!fBV=1OAN;GJyW5>+vVbuUMt=_mfL5)wrz2RQk9E# zpM|+RaBBptqQT|GcxHL50#IV#TySxpy0uT$!9}~D%W4unhuo{L<`0s&8Ot@@Te5)c z#lgWMZH2RYHKK}>iN;2MAZ;IdL&-^F<;}!Bx3#%N-ck-sVMW}oXsPfmL<^Ix3%WXw z)rq7fF;l$-Yc-3QG>aLV}!lYDVir!{l5Dv zO%yA(iP~{Zylj#7J(d&KE#XPk=P5yjC6`*8Xkas|xc;zkiNCGz5Tq@$YG5(`>$6w5$k4w~{J z0ipnMcol;(S(m(~_hFovHsI6e!~Mis2ZwM!)`yDJ_`q@K4A#kaQPiwQA_QJ(a1Cs# zXSSSWNoW$}o%zkl{wB(uL{#67qf|JWN};Q^yY z^qah#{|DM_Wr5tg4ykTRN(1+Iunj7Jw7i>zLq%ibP?F2wI`OYBTQi7DJjOm~G4uqn zVd~|V)ivSKO;&`QJrsM5*1H`ArhEM_JNj07Iq2NOF}f|u*(}tisF0F0iIa+{CQvT!`fzX8y`dYNsx4G7 zID6O>6-vy;urOan^N;kVH;M{c{6-EQw(nzVAieR4RMGsLg7v(=KKFDGzA*$Ce0lW& z>sDELXME!o-g=6*VoI29w3L%T1UdBM>9Y&fURxp$q};(zL|$vs_M0LWKTy-hwi+Qe z)f%s>q`>tOizm?93Om$@k z=fK zVR{*qsy85gY?F+hf~pFt95Z)+ls@_11-oDO%&KR6UUVX_0|gK4c&=*L<<2P-UPxLg z+}~S2xe_>cM#SGc=`-Y}rE;vIbcQgQ)Nb<3)d=aZQM!?|;fxSAUX0jMajW=LQUO*d zElR3GiNXB2?_Em|eT_Nu6WFl2q^^-y%0UX&sph>YXJ+8a7IH>F3FUTKz~xu@$<#q1 z6`i&ntdExN*HsQp^SM1nk1O#F?e*2PCQmndS>6?Iz9XurAtl)~!1t{g(eB3^XH@ot z$;?lW1MC*#E|8`MW5|0R(US8QbzdRg+|6{vEZ2^Ix~Tej5q-YkB@5<8rY474!2y zwPMVG--x}{hb^2_g%^UZ^1_W zL)ZU{SNCsp3jXu1{3AU(_P?`~{uB!VaB_0760!eVSW5o~OUA+cyEXPlT6aL-=dX0G ze;C5-%>O42vVVsm{Ci7<^N+Od*nsT9zktmCk=0@YvJ3zHC)odHwC(>zR_ia3EfrfU z6Pv#zkC=adp#70X9vhH=_@62WBq07zuHbKyZ-1#E+wZsQZ*0APxha8->2I(&RxaQH z$Ii^kLBz)V?|{Yq{($@Ut-=3PK@L`W8IIqXqny7(kr+{r6D~{RjP8mRzdi9Wivs%sqiD@b^Ss7lD`Nr-y#gWq$m(z2M120iRz#S6;7w zO{BZ(HJu;4TT6M2wu1{UyuR)17mabee7jBne54^{`&6*kGSR$&$WM@A-=ztc`JmwK zta3&NlA3{6h}hjOvQ@GxtK@~@&vqzWKQl259Jsb`KEh-q5|U~EGLx$EmD8>I;`qws z=3Hd|e62nD(;Jo_8XN_W>!2lBk7Lh|#r;=cb8?5Pp54Jk$EogHQz*p&gvqe= zhA)zmND>bJCxi)Ma9&9SlFx>O)3}mkYNsM(4NXWyKagnIdiOa9(#;fbfO)?v$0KFi zICA2>olKwEOFGuYhp{Zw4fA)EB%Dh_aQL$ww-}t<$(RBtTE#tr0;nTphA+759T~o- z=tVJ+5Sd+vp^D4fm^DZQ`2O`FOZ_)K4XJbD(TjuldxKQ(C}wdmu_-FpKPVQEDAj%O zR@)6y41!*J4?AoZy=YR^KZIm_m{f^oO3k2n{y( zhU}G@O0R07=dNZZ<8P++VkWR72eIFaNw@<-*|pS)+y~h8xWnv)fy2l!(@`5mT;@N3 zc;f|_N$1J=U$k7yfVjJI3dv}}VsFL-x^BFk{Mu-L++OQJ@_BjqrKjutvO&0}=lwjO z*TUFwN|2SckA@fx%-=FZGVDiKFjCy+<0em77rH^3VU3;{Cc%MSyVjs8!NTT?fe^(=dpTIt5-^2IC(t}- zUQs9%(qmH96bRxY@4ew+&Wj(jF+StFTOmX<>SywhtcRRPL5mVhK{#+RQVOB7H{~on zBX1n)G{sX8fRx%i0l6iqNX^ZAC0cJ3tJh%^BA{@l>v(o2rUR{=-~wCKZ*!M;oe z^K&@7bXu*&VU-TDC{#z99A?$CiInp#KW8u>+iNZeHG4c+-|2rkHu4U{XM)&Xn{8-d8ri8(Nr zP;C@fp*@7YFd6n!s(Z_c^i6gAy1s|0{!m1%wWztXMsQpYXCBpt9oa_w#Kw0EW0y*j zo3PObNRb6$(}m?=I(U-L?WtLHOHjaN;v^q(ZD3k>>RnX60x}3)usymINjLv}f%vtgN&`6>Bu3(UL-Kw!NR! zYO+n1!v$#$dvlbz)U+-4lxzhx9LpIOMf@LYyLje+aJYK{*VLhUfD*L2@Ehdv`If@YkaAe*SZ}mgw&63A{)q|MBCnJeO`iN z-dR1)9ky&lYTypQOP)C!(MB^UHT$Q`V7U97ss7sYtMX;Hx&9J%UAEDmksHnIm>m4Q z9Ars!3Nwi>5L4ouWgVnzhY*9BAZ3JOlqw))O&?@D7)0w_P(k&wDYE*8Yx6~3@2hi=*!p!$m3sNSrDBMb@`c$Hw!i#c}@HuF4 z_nDisH@o$N!YC60m6asGsuIz-B~DZ5sHBG~FO&u|*ZYUSeekxv^ITTO^8x%&6KN(yL~QRW?Tttw9fEB$_783yBGhD&J{I^g5t-xp zg{ig8vbs!x;oQ2eD+=H(2^m@6O;9TLV5`{Ts#T-4VLY`j z%ND-uuOWoGs~qVK^?YNnS~T`7W2ET9hiVqw`FiTbg}0;P z0*Z;Y*upwPRlC8kZOUDo_xBj$wJ@z(AynwM zP<&e7`qhgUl<(0W0C!z?sNG18AKoJPd#NN8@-gfV8oFBeT|Pq)Fem%FMlSj*Frbo; z>1dC+Ek;3HU$Z%cLbRn_4 zQCnxY4THa~#FzHw`Pv9v5#PCnA{9G@t)Xv)lTqoMf<9Gp9m+X=JA zYEF z2Bo35PpyaOCh*9|3G<$>oR())e}KZHmeY6{8dEd`9Xb6X$8a?1{$o73r9Xb#@D7r4 zlfm616x+dK+@%?lw#jiPo`A<-ROpfa{Mff@jp2 z*{pAiu=i8kVvQ|c141WF0avPMZJrp99Y+LnO}Ns1sxv9=(txt{2fP3k)a=^@kol>A z&oCB~Mq}*S311F90Vzb(FLz&~R*t@_k34NRymH-4t5e7htHu)B*D;wdIfYZxp4VvK zO#hT@9>tBOZ?WXnzo0}vBzaU}ASM^Pe2KCQQWy==RuzTZg~2!|QcBq=rmcD)LnMbF zAe4~EJw@4#upqIOhYjc3!!^06m*419xQ2x-O^b-6fEtwBT43Ms{K*7S8-dbS$a241rHqbRrL+ZG zvU*S5S4PDB4fj`3acQj^0{1Ad!q|_!`NpP@w`>;o#VzJ_k8g}oJQ*gWzGG`LI44!8 zGaD#hZkV$bo3(SGBL@SEB-`A-s3#T#IfNYKzp-hVvn5N*z*a;BtyvCBItmxCnb>tH zY8**RRZvL8<5_~O&rv0oj$`VGbpfY0=3Js0r=X-3)})1CpMrgJoPIBl8^g45_8Y>X z%Ft3t%M87on!~C1K77MlzUCW2ONs>&6c`!uJ-BY7V<;NDG{!`IjpG(X=L-EG`1^WM zXvv`lE2WKbg_wUAiDjeNSsPw6?3;L<_L|KVnRmLqxzb3Tt_yk9P?uCb!(}eq)c1H2 zcB&q>PI*2hohpXLPm-)?qU6L;g;QUZNP^D{Yw0BMO#riEn0bU88(#CT6k+7@c}HS# zpoQsgZ5YEn!H!kvAdphAsf~OZ?K?i~)XFpjlTokWEcn+-xgUQGU4{{73|pPzQuQaC z_I|=%|1|YW@JS&?CzOLMjX-+^1`Tk?%&|sC-T83>|B@~Xxg&4K6q;Y$xTc2N8?Teh zWTKOcmIgo=NoZ-1SZzQEBVoeImJ4113cGnM`d}5*#R`(tEj1^3@|lC#l)_;~64!1? zoV}mYmrqU;md1c%-`Mxs79AMj}t+U%)k%MW>2i#+v&)!jhLXF3hkjAaX z)kWSxmgOkHE+*^&WtIu7jA&gbhN|$?1Lp+B5dG4TJLi%FC(-(>u;P;`T4RtD%hq7; z(xi7sL>8+&DSxC<1eJ&%@)!m#HQ4>Ejug?OA~BWb#tq|4JduS86yLra9DmR!Vx`3T z$aF8IV<3OMIqIOLq_BLN!r~AodO;WFamz9U5txEqE9X$(P6lM#4>*^D-!%9gygOIlx`Du3t27@Fl#x$gfVIyRp?W7N&#aLckp0 z5!L;WTmPt1`wrPqo&v~Xf_ah*2cfhN8$)6|2OWJ)r!Lo|?5G#B62o*xr|~7I!cx3dJ z8N);)k+#Frynv>7wTsZ$Z%Rx1Wp}Kdc=3Hw+aZ2D>Hb1+U#7fqvT<}UZJ8m*iqg2~ z84hdI5Gt~p)XY5 z65ErJD39x;L|xZgdw+f=nwOfJiiXT8lsetSj>A(+r zmJS37`|;3ZFx?Rn`sqHf00zWWFffmY$p88*RFG%Qz6}DQ z08@b!q5KKccKS77yEx4jR0g%ku}-3PgE4=ebH}%Z-=47 zO!@wOZU}k_{E=?3#UE0m(&Xdi!~@pc)ADebAyduIl9mSX&i{wCcZ?Aw?6!5=wr$(C zZQHhO+g@$kw%xtjwr%6AZ=c+oy_1uZ+~g)RsZ`bfO1)!_F&--g&P()*_MeLh~!Kl7Y86}WspkCA6Sr%Fx2;g5&d{l2-Q z$WnCfPw%dG|C3C3FT;o5{rQTlc5Kx+eJ?pg_NqKg*7{SDRYvaTAr!`c9rtO+TTpkn zY9p&bE-TOXd6;Yjgg1YXO3uY6QOZQI(Cs%q20nl?n9S9ec-SsMp~2U2F8Sf(^YEMv zLe;pI=^j@O$!xz}&lv76P8w6?A$|Bk3VJE^CpTn6YgbfR$}oY|k&W&U#0~g6~~p^ItjsA?JOVYwfH99ld-Kr9hxF)YobmU=}A(*1$DUDd3z( z_#M(ZsKwR2Blu!s_t zTKXIYPO+qH$&7E~lO+&Sh=q=93$0N=UE+v5BIln5j~vHRA4hGb78XnyKB?x3H6b;^ z8k5|NP*E`7j;+$j{kwI9Be)%YQZyS4wGvR1ZsAlv@I)mHxrBe6puKSbpuB>=E*7QC zL4t+9kw9`wb1tMpThH<4 z0JgFR{O=$;2&xSU;c|EbwuD_puL!Swg)|Tn#Y0|!QGLqD<~^{ zpGi7d{3xUF>mUt===AN7gS|`L8l5rvQG9cQko55cFN8EAMX__lN(8@KF>c)RzZA11 z45DlKgv(1IY0XV{ra)=5`P+*_ZEOlO`R5ST^BV*C7J}m%{9wBx(jI65XktL|DGNdc zYj-3x1w1gED;xm9)$bK*qJ`yW zuGF3okQxrxsU{9l5c7zDE<6-*z6ICC)KIeo4Yh9qJTr1yudNwaQTEb2nqE^lRJ}x& z5n=u33b;(jJeL5;fHva+jkv#sbP15c3skUvT*Bz`$=G6y9cy41oTxhaTr!SJX3*&3 z-i2Wj(aaYivJx`#Bkyd6V%y-%Jd}CCK?Pmc>3)y|SPVd*7T4%*JT~{&!jmig=G#)U znHemd0^xWX&kR1$IHhD>eDL#djCkL3xbtWw;>u3vEe$4Rkr)VV)P01MZhxw!(KgAn zQC{NOgCncx-`EC(i+6_F5^1+aJ!c`FOGnTh2S?_JTLue@w*(Ee_K9<4DVZ8s0}0qs zV%&-I%DC>p#CO~?wC0FY0P*@*fJfMihgha_N+0(O6%@fZw*=;%K82V9qr;ly_?1H~ z5sBfPf$=4I%4`Va>q8J>JX_5ZVTB3?a%9bk#RVF;Kp8~K)KyHi#QU?5sj)!QvTQxCkLn6R!*$nObV4y|62(|KmAMXe{U?MDsos>H9my7p; z2YIhfOf>s#=cS1jift>1m;b6jT*EcZaN;x99LZ6h-0*Y2j{g4n-Df?%Ecz1|3||YyNpE-s_hdTgaF}bLwkx zZ}Q+kwV6w=X80pAQ%VeP9z21Gw=*o#6(o-YI_aeAGoNRKfSL>O<>z1Cw`Fwj09|TYfWG)Z z)*@V?%9WyjhSP^koz0}46bZ^BT!SJ2^$3b@-?uNW5w0OTT_r?&#T1@pCbe51rqRx# zPOK*&R0B=YZPt;Pg12-IUMbW2^)9~iu?i|O67ZQ{%4fFIY3r&^p<=-;YUG5HK)8ui zM8v*`|8A2u`v?BJtpfjtfF=DPL0dQ4jZuLWbT)R-XB)2xUCFWxu@{!EdI41u$BIm} zW$3e2sN(M3LcS)tz0Hnp6SL(B5sASgHl9eTY$s|pdt}#_mJD4KV&ClE-CXt_O21n) z*!-@W*U*Uwefa1W40hL{BKxiqFWR)MoB*wfe-a2sJC^wC)lX1Tz8xD- zj$8Vb!JS{F;0`N(vRJgxn9WhWTi87J$ik`RoDEHhcyz0I=W2=_wV~z@Q&0{wCWH#g z9`)4IUgBW9oTf50SycMv@i(g@*y*CL#}1_+RB1WjO1e;7TtaleB@}cm0&UujMD`o0 z?l-c=){x^A)UPwSqd5Zsd&!PG;2=j;Z)f3MV5fA?aQD3g5DKoM#-4d|P!2I1CFM$j zgn%X>nnAW#eXLN{?&QB_3euAG8;f+MuB~CeG9lLq9G9jC##7mtSt_JT?qw?W6A%tk zs3?1=TxGR0cLJ9ZBD!SCkVCy9rNTr!?h}b*AtG7Tfrf8)FrV#$HuO1J8BU>r9Cwa4q zMa=fEZK!g*5F&#oX1fx4qVlHSGCJ0G?9-y*H6ftz?)z=I#L{stjMwm9v(RmP7T<^{ zyr$P$UCQyKQT0+Zr&Hj4ByYkhStG&i#QemG26te^b#RAHd?0QktKXYRwG8oZS-cLk zvrK67EyE5TZO2M|v$A}jL)%i~!_*$Js)`rNSTYEL_kZ*iyD$*p?Dvb}ydJoySMMj9 z*)D3eBHi#Em*|H6iK)T*E$9oh7Pd`aIF?8wu5TV=RabjEHpnOL0pjUwFQ(^#o8Jzq z{>;|F=2PU3^@Itr?woSJ2--9J)m(_^)-JIz4h79(W&Ca1rADY>WhiU}aa=?8`c41Pr*Bu9*d-SwQ3sDa-NMqfOV8S~;I#6o7V9(2E`uEq zT`5MeM~$9F%YG$zvZj>AFz5id;pQ^^9FpZ86|FEG(Ak1V85UI###H0_5CipA06}bX z$1A^V80I*hrNkO`ZDX^nblj0+ho7@pQE|AbMSZ4EJIdW!=cXt+dXguQQpYTE_AJqo zEvQahu4D2k>YC`#hM4@SxFVP4`Dmpea7(7JYZ_lnEgUbhhunXHf?qfC=k}zeW0I^~ z_p#m7hfraARp<{=OI5h0_iIm$1uueTp#PY?Y%Nj->^E{p(bnf5-GZC;_Q2L;?kv}( za~!A`f9HG8+!cHHP!jsrtkUL!Uu>g5EmajYAvW@}|m2T-0aLzNv@p_WeMqjUH9v98uJ27f%%4 zgHj{KO>}kP>0wkVY;sQDsay)66!PjoTXe`l8;`P*mj{YB>_6M$yLL0sO~i-@3$G-{ zI=kwtktZ%|&@_{^*V(8(t(`lKW5<+u4-57uH4$?zy;npQ+T0&XYgj{=tYuMkAV4g6 zQ0~^Qaej6eE@#joYTurH%Gh+?c|~U1YghQC(vi|T^u3jt8B_Z?npjvai6 zGCX_d%4y&_|1J<&X8URt3WG}OQm0aHfCamB?+Jaaw-sDC0W(l(8`V;c?)Z*5&Tov4 zIsuDMlU0@+f4KhdFr$wqM2b3oF3WaTn)QU^is->xAuMowE*Tu?WIB6{)s5ogy_{^G zJ;5wplX^7!G-p+PWV`?ft5UcA^`Ap&McV0iZ5d>gLxyiz z5%n%qVc!A3eV(kuzIcLXglR}H{3R8H9V#Gz>?s5uGEuuaL@+*KwpgNjx5=FPit@cC z*k!!!OsVH`MWzO^nVj$*(Q&}E%%&`F_PP}s!ls~F5_*%hu;R=pqU23fdX)3NW?>|% z4L;#H_jPM4gKcQL_MXT!NJVGWaQUuJuOJxJ9F>~8ZIt#h%Y{>KR@q*9Uw`VJG$irI3|$fXtMfF_k-YIf)|mf(e@d3id=fA-yi+o%YWRq{qS4<-}~Et z+}J*!&-dRSzqfGxpF@0j{vQtyub)ORDqh(0RsWwsj=7f3#sF*o$Z>4Skjnks#nn7= zGAX$H>=(Hw*XD7ZfNx&$?~BEV6^J&ATns)qG5f=36T6Sq#(wL7$-G3t;MC+8V<8vj z-rdQq7XZ2rdE8CtP1NKdXvYFyzNF4h6*E6Ta=sakhB1EtnSaMYn9E}1adSR;z7+9s zabzfoq7_4U;d$Kd5Mn0y>0$LT@&q}9)brv)P>^|uFUvLp$M=c;ARP!r1&|~5OLMNzVA56O+ z2VvM!WdQw?G(q|rh<~Y)WtDO8tSU&th*H(NNh0wjzGC+g4s?Kq6UwnqSpn~9gU9bM z^aTF$9xI;V2yMMcq0x)6LSl%RvFv|RB{cbRQyWcR}TgKeS9iS{JP_6oJ5;C{QoQ=X0& z#oS!*M&K2|no5B~ogHz4n-aivl(6&`KO^XKwJ;V?S1P`J4bR@-$Xs!y*G%7pg!>qrGF0 z!{da>l07}p_0Tnv*QtS)avO$fZbUl$ije2CIt1Wy{mwKa|T>bOYPm zX`CqKifC4=L3%W$B2|+25=2(Q-g3rD`=JaCdi=eXMuFpIX!p@*Ut;0y=^5-+UbqhH_0I%u=y4w=26QN2ymnEGTJrg|E zkE)k`wQ3%%gsIG_+Byyy9$3@7IHjAK)}s%;Xi-8HyBWla!IW&KqQ-k^RuPm*0pu06 z!#8H1c{~|W#ClOmgMXTi-;}Zxd{$x_Zu4?i{2|-UthXy!Cp8Unf)401w%h(VJPcl% zPdr5*oWKPgo*>NVDu168%*vfIfpxDsZ{K-eh;TQ#z4An9uXpk}p!F8*8WaEd6d}{L zYKv3)3+Xub7#?tmI{-1#yeRhV!kW@p`tQqQ>QN)GypGc|3LlxdwjR@CY?Glpz1 zK)pGz7?7d%1Y>r4+VS=L+vnLXHp_3Ha=<3KW~JrbKDen8M=dAW@vZ?iaOOsO@`Y<5 zATE;8n(hdBMENji!p$hifYXYLlU=1k-+AyZO#0(RPeB>8avyAQ>qQQOPtPByqu!kB zT^AZb&vy{!3M_!&c`F_3t+Az?lIg}4)?#<2*mai0bXwyx3{-YzHlV@o@4=_Rwkcwv zit3WS_pLZU=zk6cg)?2NuVWauDj?4+6SAjPk>7w4Of3Ncs9cqwXyw*$O?`!a3Shca z+={gcL^qFrRZcJ8eH_kU*Q?uGWHJ+vqj!;2%7|22dt9JTyx&rX)4T0#NfV~Rq?V^Q z&+rk*Ei0;YRo3OgT2F__mV3&+W!JgMy`3x*+lnV{Z*h0`g9a*4ic&wdJ5ii*U!SdB z=CD+DJP)IT!Am+?1;Zxor;*}zKs<<}KI?`67(M9gVHZEX>d1cI+~T0tC?ov0SZL-m zcql2ZYo_?=#u`W|Z-}x7=>qtD0h1ocE5CF_V(mNe1A`}#&o9t+sI51drXfk+3Yi- zKmNZ%JToHW!#S<%C9#v<>pwlxjdAQbJ>v6U!&3*2@64#bn!3h@&g{tj^$ipe-;wco zVFvKXy*$HnVid(u;Wj#9Om~v>vAd@;?U;{W%;v^?t-*}W?C>)Y_^$6tcf!(5Cyryv zI~#L;V6wB#3>*GbixbT#r{J@nkopAHD-HuxlM*B~d#`0b?ghhP(M-zDzJ zYv1*R;@g1Pz%xW{GDp;Qs?yTING>N)NkA#GnaDvZgyaIuXqUOio2`$E=b*RC(80w2pdS*YLeCcML-04jC`yzqMOyr9Hk)# zrhElg@uUWjdgLnsX1x@mwAH%CyR*HH$KIqJ$!#v%0Re`OdeV-43ejx2UBPsNk_hq2 z;fXwp`!cW2VI=2!R^Xw59kDc@i1BM5#2+9^dNM0*S$pC;A-Lks%l|SrB0J^`sxB0Q!A6T46xxum>btnCKG~1iTHdE;R!z z&C`3J6N}V|md*^wWOk&^n|mqBjLH@+iTglXq~R*4C=#YN>^q!aZTamSPx%7y1(}Db zF7SQKzRBiFxy-6!;Ux{PL-0-@bxQh1(gmN~niZES-d`mv3;2tU#&Z%cr42%=oh&3@ z)g`2BT(78Y%!gUasB8X>rA0AoGuC0*SMD$qcCE`zN$Vin#D%!ZUF}r$E{s5xvWnW= zTCqS0Njj?5e4HiF?o^-ckM0{w*1VuvAofBM4kon>GBKrfSQg;K5}6T8`QB4eM|zeM zo^F)FAJ?6SsAt#z1q@H!jccLJTA`Z@1D`DiQ;0`aW|tbH%PeiVP!nW`c6a_6r_k=^ zbYZK4dRQ02+>F%z)=#BNU`r?OJ-l_=l|9?^cU*bF3p>;U;$+OsEEcC`p!8;V7P3@{_YH#r!xk9};M zXp@$3kA)Hvxb{3rrADOXZl3N}qy4s*rW((02}y*t)C!@kBW@84bqln_gp*7ZLT+^? zteIL)jwt95{pA6S#jY7Khlp7}`J3XqWYVfm0U?F$Ccej*H0Y<^lsoHJnsG#d-g~)! za0(Xu3Um7ZZ2oxtU{yFReBbjWg=7-&BJ9sNI0p?&6*#Xv9L<>_x#I{G=s^r{(Sj=N z1qICL{&FFk{!)Ac!GwuE$Em%k_lk0mF(?M-biz_8e!C2jMFR;`HEY*&yH+D+`MLKRT@i_yt* z9@m=$a9zXv`k9W`hIV%!7H%@?Q-Wklb~GGEQbgELIxd^H@lDN}LDy1ZE;T~A_a^`! zhyJ^=yy}drKt*aDxHCyP!*nc6#@Nz<9QJo4WxwTwG|@ANuNXHG)A<`_&kRa($tX->WoX@HIa_8ne~vJ?w45?Z)P(ZI zzlb?Fl&c%2ULTz!VIIJRtiXMMc9B?<{c@1Zo9mWHt4WEbX&oRPl!|z8;&$_NcN;m| z7}`LE3;<}V6i=C>cxrwmcpxECcw;7;*E*d0M$>8u@DZC_NZs;qz;>4(1^#%$KkM~E=asAA@yKQ;+UoVHq zc=b>JV+ zaQdFpujT#YTlgcOa2Jqkmp_yDUQT>L=JG?Za%0$kk3#@0do^&ci{6UfDvGxp0K8ucm;tNl{roY zpwEcZp&bX<3h=pw!a)*X2*%>FuB0QE$oP5P;6-E}DjgyH*b0C$bYFsIyd2fu2e)C1 zlR&;p4q&F^ctos{Grc_ND3N5(F}W}b!6_UEEje}o92lpN_^?8RebHY~_7bFlfU{f3 zS%|Pfk%b-j5(G072N8E9{A~zrqpz zNVu&Df1oli?)`wOES0bds@XnB;%OMr##ov_cwo0cb!NV&4yfaT#2iX+v5Yv|Mew8PbO1N%LIpPNDgoq= z=UgJEBpTv-Fb@2>2gwCB(vMUc{(@XkhU-WuM|{l}76X{Vm5p$OM)STc@5l}>SI$hLIUq30Ig8bOb;FOPJ+j^m=A-%JJgDZbn_GJaMu+PYM`)cv!fd>5AwRNtd9VKhsY zzIv0tHq=yh*%Cl@qj@DM(>>LyO>Ac?rc01nQ>S)0V|xn{1y}jGng27Is&SSMjd^tr z3mdC-rsmnx{bf@f^>N^I0-K7Ci>dt`sQ_^#%u-yjfhW(6-{i$YyUJD~fC9$GG1_Cb!xx1iCTu($<)J+TiX}PtkaBUN6buQ1KMU4*#u__f z>u1%YLAqvStJSM(wHXa9_+a^DvBsxSD?3`rfewyoykk7pshDTwz7zm3k_JYyykGuz zH_R>QvcU#mEc7ub1YI#9BvXS`J?V?JpnYf?W*?NOywaem!?h!S@MzDbHTdp-tgO(ZMWy%$B86GXndVi~zSl%z?GmICqYJhGvRIFdSUecq-+2M8NG zY85$Hq%7@I{kHwL`RiNHkJc=5-iOg;fVMVFKN1Jc{rXJTdbAc7#dMa`c+->awnbYm z^H`Rx@?Bfwq16nPlbgcid}b$uI6qxPF}K@TKgQ%CsmO+NrM{bgTgQYT-KqZR%7%v4 z=z$JE<0IM5Tkd%Dd!`cDw?)OsjzETVIEg&&Zkak_E*MD^WdXJx*yoIxi6pXbtWn?d zVtq0RZNQ_*STMWJd6$b>ntf>!_@$#cVQRt zlBAN!V8b0UQoOuwG&17SoeMQ2D_%s?JYPuPYfjGOx1HCE?3-vR6~$4fFWJxOAikUa z5c$v36j6%lJBLk}{IEWus_pHAk@j$Ts|8u(7PZq%_+$V1UWIgF`3^>Mxx)!1Jx#pI zeH}rYW||jKnFjP@*L-_ZOd&lWIaAeQZQ34i+(NR{X(?($;)iuAtn4TPJ ztpgn*mkv=Q60vcNYj2%*ioK5AT){Na*$6}a&Iqy_-752l_gMWoPcTc zD6G*KO*BbPUG4>}P%s@_6p@`MgCzQqomr1udxW=Kp-#qZB1%8c1hL@?3RgO+>Xo== z$3d)-3*rmZUBI;Wu%eC<7QF0>mY%OuZ0s2JpZ#=$Y4Imyu!=;b^KDmzB9=|UTI|xy zvM&{5#;IH;G=9P}>oA>)p~knPdX7yvLip{7`)-TG?14J{rNqx-<*9;rm5`=dXFb=V)})zXnJ5!sC_ z09UPyv@}G`EJ18tTV~;s%Z~(8?21@ju4x~t5~TL_Wj3`aO+#K5PkuDC%g07+8G)#$ zAS%N7Y|S;-hI-rFSXK>8nx(+iXInaPzbo5@@Ny}EFjrfh+%yA}O7-$Y_0998NHCAI zWaQ4n`XumRau&wu<6zphsjwps-8%UDPOHp(m1mV;)r^j#jfk|xlhCsiwJ0KFX*98h zQ)zvOWmu!>2AIALZ)Y)9yJ>TJjngF@X@ED0VOMN^fTZEzim1W3`Zhl3@%Rld>^Ui$ z^EHj>pZ}~60E{izdg{$xn&!$O8wfcA^Db3bC8*w+*mGL~{aSm}VYq9kX*%1p>XZf{ zovm^RnsJjhENKbW_fSQKq^j3D?k+{c8Z?_F(cGZ!){DYOpFjcHUWlz)!y!aud+5VK z?DBNORnqNEehLE*Lvb~w*`%Cf*%eG75<-GT!I^&CtzkaAOqZ}TnA*0mfyIAQV0~v} zDZWJ5^y#MCwKmv|`22c((APak6qoo3wS%JR@*ZY^#hb?;m4X{KLx{xm-A!Rlr_)9( z%vsh2ji%~XN`<475>uH91|J6UqNR(pf$?SsMA>2yyjvKfR-JJqUe~QO9g_^}BB25= z8%(74jbz2I4l2~!ngYY*vIOc6caBfrc}vlnynaskKL6y6y>w`>}SJ*Yy! zHciKb+8Eq6e!b8Mf1*XJxN}tesYO@xS>%X_ectGd#29A7URYpPxv2;~6&8wSU8Byd z)ramk83*J)pIO~o|0h+vkJe<@xw<4E3_7s5(xasGj5iGWjH1`4 z$+Ku~5G?XTkG9Hh*@L;1atB4Z@a!7)p4PvV*x+bqFM4|h&a4Dynzf?RScUd>;jriJ zfO)>+dE=I2FXZas(PFdatFZ*v?h89M{$poidHc{N&i{sY+Fqrb1nQ3l&L6T0X z;o$pRfUKiUqEv6mmCFC>FcAlLK-~210og-Z-3beLJ{uiyZXo#z3R+*u&58@p?fv$PlBBMp1 zPP|U!SV-G^;j0=MkZRWkvc=8=+GxfJoKl%f*fja1ri1Dg1f4h{hjtW9$>U3K1`*k; zwPDS}{D;w_q_v0u+tQb($UuXWcl7as*-P@!+{gMmCMKc+PLWB=o-nrtOI~2T@cS96 zsH>!5c823^vD}qeTSqb53`YoWAZZ>J>-KH}JC5?~w;873>mbUrM99G`Pv{pM?`?+) z>1n5$Xcxhd58Tt%9V}AZhPWq0#Ww9a4FH3SdpvHPo-e0NuVu}qaZ?0|#-|vtoKDL( zAv?sO6m!uSXrG8ITC8!~XpLI!MXs1)uvA%?eDLO@gkggiIp4RHxm0?}^s^Ok&8Wp% zVoicZu-oduvjb}q5Mc?;n3d<%eQ?=F75^Jnar4Jn!CEAgzw2OyipDUO>`UY}Su~2w zK&W%D<6{53%jaBTP!cW~f;zs@K%Gt%e>1H+1*d3mVTeN@RVj)R05a=$oToAjnBq*!#TDJ%DDESmE$G%Ks^wk0PvaT z!q(>=F0@2MtsIdoFl{a*3CNMeH*V~d4CV}==0ngva)Xpq;ALgyVO!CW3;-I*$@;;K za+*MjpNJ#J?bgy4%Gy?*%a^Mta~AOxEQo4vO=239JDUF?ThIUJ+pAH4Vvv8>ppG=% z*NNNp@9I^uKLMd}L_!DJ!i4sscWHiE1X@mLT#AeJbiW8yr!8%+oX>6%YyK)`tBOqy zgTb_gU5U+2aH1~WwhOD6HFG*r8uMaW-D z@t{e@KrJWQrgectr9jm)FIGpOsrM48je`wkh!h}GdCe79NoP&3#HbkAsLB{0o=E~` zT_GAhj$R$J`sQk6Lc8Idm0+y}{(G|T-<`kg^M6t>{{JkIU}0ybW9DS|7dB`5Pd^Sj zD<>NP3kU1}e)j(%yl-P_=SAH<^p7_3U!-YgQ)Acv+|d4`X*;{xxLDem)Bn%z z70&-w=J0>hVmKMu8UBYBqpKZnBI)Rtr~fyeZQ;aBWQR0_rHOe689pP`LkuH-ftkT>K8?cB^*YyGGe&ib3>;56{R^q2j%=i%m4KRmtfYmVDjPs@k!;O-%wzvuI@Yj=|U%wAcV?mV4K?&IjS z&*S~Gihgb&^s1+9s88sii~e(VWWkM}9{qPZM<)UQ9nDSC|H6@^Z$$45EA-)@d|U$e zSmWtXej<41~}&d`t%$6aVp;5PyDo`_(&>Mzbqa9mn{1JPfUFh3}f%t+bwXxwd~{1|zoMa8%v7tej((7EJOLD=Kl4u0;qSh0o> z+z@fumJYPe2Qhyg`&f!_n0oy91mOf@>0+`(8LgBK@M~MS?;8J|oEllNORgnN7x~D@ zzXgxiv)8*sZH*qxl`$_}oNxOCySNM2HjS}kql{Br@agut-*c5Zc~{R_?UkP%t_^0n z`7Q-MT9(Gnx>V7SP&oXU>6BBgHP_7e*vm+%R-J)UO&E#xlDF#gSPM0235H%@1xMZl zIp^va_{1$9$QYs63%c>A1@D@F>XpUB-@Mxj`CE8+AwsI6?L(-^6e&!GEKEj8BX^@Kk(06~UzQh?q z%Mx~xIG@dx_T@WmMjmL12RJ#BhGYc<=hoZPEJyMr6}b1^eeWxn(lrX7Hk7$(g&=A5 zW>Dkf7b|FU=2dFI!y}olFT>;vB^#tFlciGupN@KUd2&yud;cp9?Z|f$36xHNCr(ga zdGH)>6?r$<#<~OuLQl7LORc)iD_g8=pePc0e*V7dq4Y@n8!7yozy9jwvOb$mtNLNI zUR}dizS46RmtJ;G%r-@#ok%?N`Z#KNxs`Q;00mu*xc2n2Bw{fWD#cXx93BQaPa#1# znXTjxQmVp@m#D>|ATuN!u?~4I{Z-#~tpE3cG-Y1g*0BB&-hqfRgdj=(XDwS<<87G*nFk6q6% z>jZZyWJJV2%JhM?f0-hW1TJ7Ub9ujF(%xAJAue+D>NztRURFGT^W``g*5o#RFAM>v z6;lvHkcJw!!o)znZ9&Iat9|x=LDGXT(!N!h38&j@@%VsWro`t+ zt)Q9sm-L0dNJL8pBkbyUrn5~iKBhWi8#o8 z?hem*j7r6vWDO|^0`S11YYiz|3s8=zgEcHn2TPm2LGs{F6%0I;>o)5F3D_hSBCgcP z#$iznpokCxywEU*Vek5Wig8qPdX#_p17z`Tc=pI%$w7xZI4+3mp94C8PZ}`bbX;Tf zhs`8rK3jcVD8$t%fB?Th|B5+#hSA4b&biEy#=!C8vhjODbr*hnBIDkNica?RXY)=1WHQo1+!+;<{*%71jOid^_rtQ(P92`}fh^!Qtol&b` z;^WZAfWU~kY;!Cce>w>^(%HAKF(v!95!70;sZ@}St5nmd_Jcsf(UFFEPD1`e&Xa$rh#wkp6fQuXzB0$?yx;`*S(+() z>4<8W!V_?8t+1!+4z)xUbTBQNJhdk}Du=||#%&2#FUFxam(IytL1wK+;GYD&f*-)& z5fOC|5UUWD}Yb;6u&**ozLQeOp041UT9UJ69MNfJ@Xdv4gjN<;7;iTDpEIoBVw z6R)^cAw2{7LIE?z_njuPO5 zQ2)DfLi&i5z>XFNlMIW#k06uym?Y9PsM2;OP&`*nD z4S&vxE&aSX%w5t&c?#gLcV4ZPq-IrOSoD~)Su`W-lw7|=r7voaCQ3q<*(<}rl)uYa z1SoGrqv-sQS|ahzvFSUPK=E@a5pGlJ2A`!CR)VzoB`%_i0 z&;mjL+tewEx^h2Ge(dKzVJWuayze^eqF$tJi1ZNc$s2d&-&T6PhQZDEHd2P$D_Qngh_hm5tO|hZaR3lD{t{KR&~> zf)lAf*%JRjk$kzGLOZ@=geL*J;nT7(Z%a8O%#BGgh?f(I5;(uAtwKcoQ6n~;3I23? zHq5M{3@(q>E_Vb==WPMG55K|AR(!gfkbToGw`W|4@kV{Z( z%DO@})sB{;E2nxmY3i2hKKMP*>kaf33f{W-hi-QuPBA5LmFYoToe3uWJsulg1Y9vG z8lozC8k)Jz4Ujhb7zERue9fUV8EduVGEp^+u`Dm=xRpnlMAbGW85$s|6^d5@m2*W4 zMCBLQV;0S#!5A-ANx~>h`?0c1OprX z(0v{$=}5OqT;#-|am#~m+#!(lB0{!{S0ga{esqoP5zji*TsFW4EF$Lyz*jT-3nky4 zD5|E`g&>oQm7x9i&8r-T6}B5-(1Tfr5{lAU3?nK1p;JG*yfl$TXUa;;&Op`-g#p8q zZ4TrmRCfjy0wxBW8SM;*0a0p^OIj2s@$hHejs+Nf(I76o)vX-j_;OPbMW=&MixXvXo3bCa<@E$M>jP98;>KXPgamZD79R%#Id?bEBgOm(@ zAPzfovQ=78Ku>fEMg_5MR;)j+8aBLbewMQF1&KIHfPAu|Ql%c*RiFpWpA?r^z7gni z#2WDPpI|<8;rTrY+`Vm$@$X3;-9=x0LN%Dz6fF&wrLtW+DxD2vZe=T$wj(9BbJk7B zY~ETxMTliZlYGmCpy^4_>9`@w$}^s93YqsU7}7~A*i{>*7SL2Zvn`t$rwTh7U#>$} z$K+AIgjH=$jeyQ7fk+yWs_%=C7Z6Z0SeA5{n?oKSmkTHRho^l)+%OWb?av zl_yRl#m|Og)XT7R5OH%V4#ivyCkG3US&wDZ1WjT zrl&C{C}miX;{_VDpXO|lq$Iaer4aew$xcYf#G_R{d%c!L)tw5+F@~(|n2GIdJd=f8 zfhXowsob;__!w=<>Q#>+((@_$Deh#Ud<=k;^dG zG2T%9ptL(97kTT&lBRwV5?+rR?`f~G*jDmUDKbVDY1Bj)0283NakWGh#)M&~G&p_y zdR2Rp6Gnd@|GJBE2O6N}3wioPKc$*M`3ysUD?ST11H7plcDpK zJH0J*`Vg_a--4U^5#egRh$?s+Z266J(3+h;D*hva=nKN)YC>!3FXp*oGYFDwt!hWP z7mFJo?S^IfoXqL+NtM9HoPD3tgG?zSuzijuSEuKz{x?hy`KR4638mUMf5o>&i%JO? zev}_LQicl80`JAyA^*@d#d#6n;Tv^5dZn)GT3zF^&N{==fHONqYq%PP8Bychv23#% zWcjY5wGCttGW(bhOm$F#@i_a6DL}1h^++tKD6x`$KuluZKop2Ud8qD8U$-tgyJERC z$;(KRE4z+W9bIMuwM*Bb7F=nZ&3ZPeFawN{sH4;DAgLm9v|?QVucXlD(sr~9-mPGs z!{!e3tc{Pj^EQK}pyLB3`NaC^?#a2OrN0L-QsuCB2$j3r$UT2@AmN`7*pXA*fDWSy zOel6~Lz<28LJLU*ur!dR8`QbxUChy>zel#fD;_ZARaJ~^i(ShJ6j0*Nqf{~O*(a09 zd^=W8w?@N+smanO&a_LG3T}ooGh#p>j;h#u*K!IiAu%25a0X@2tY+* z*d1{KDElnDNtOd*z15Q88%mWKaMD}LhCHAnMqsvt{JZ1B%zf0tgwatYOc5b~kLPAf z2`UCrB?$qE;bEnDj*nl?qE*!f!lddwnUL;^@b>on`aO;CgrZi!iZ%{ zNFiv7{v{%RL0GDemEh?VOKcWdaBW1zSDNtLE5InOkLDz%{>L^xMCI1+p_o!e-y~Sk z(oxx{lTMS{*TpvhT&Bi@wJW)Pv8f-NIO*Rf>RE@-2Rz|%JJY22W@zks+`M9fJV?N| z)+f`A$!D5MwjY}fi~{~_;|~p$Tm7!YwGp~Y^c>%;G989Y+4;KOaiq^B%uJZ6iH5&4 z6+iPk>4)=rH8Yf_veOId@_e}{tP@Oa&W8Y8Ir4xDs=JDb^uTf18>8}&3wMeVCUGr) zoZt2i@MsO|Vi=xjDpaw`_OyD^mD7iXX^s*w3=h3~#qSfz1nz3Dpuose^voAO%9`4d ztUUtCavML4Oqa>WL(FFWX62mx%+A}>Uz<4l9?YkWOmg00Moh`B-imQ1lo0iFjndjU zZ`U6wm|~~@64^b~_^c5PvC0D$Kx{;u>Sg|BEk(d&yCl+BwB*v?^ z#HOcBE9r@L^lmAEH6Ir>Z6i5)xTLNoCL4?=!}I-?+J-@9X%6b)ocT;D@4Z{`lA2NSefFLnY5ak1w?&I0wby;ysWy;{YXTqn zW@~42zZm7}p(nFH&*#w@ZT@z8m+F%v2|L#Gu=~Gua^+~3WjzA07W;?}2Hs-e@XaIN zmIh*2$07Ql z!mKMhIND;IS_jo;9g%ltN?>-E9w6VRUB%ye``Qw#6l`y?l#?HOKVOwKw8j0aMySn& zk`(T@uzA#Y4`usPwF74){1@xaSAAGE^q%;XAo-&`b9=TGrT$%!MeGNbFQ(1Dst=dIz zZkt`46KUQY)hqM;*5w}kEWRkHD@Z)Cvoj;xfas`F>e!rSV7Ic2^D_Q)4+ItXyxi=O z!hUDjFoO;5o%V zIptMp$VD#UHaBW)&o(KJXuAgFZQZM!A+EG+_!1^(dzhuzD-Y$3q38RrFF!VH^lNnw z>WUW(8(1}8WALS(k!&yn?G$r^7n9B`r>@cRgoh*Fxz%4@)RuUWF_<)>Q4IXXb&}{~reULfC4omqL#0QO^Rh@6Ymh1iV(+2@KHeAWZT3(n6&@aI#J_jH z72C7>0pC42>bQ=^odBL)SlfZ6&n0#BkNd$FYkY{TBhEX3JJ;ND zG71B3(y6@{>^o!}yZa@pD*WQIGl@}q@!eu60rNhWFEW#p)W8Uy&fB1K;q@tglw$|4 zz8SQjjdx}xRa&1zu}0+qk1grYi%*6-9E0*yB;}QZ60l|-_8SjIn&TJe#SPAVa2ypo z8hZBPI-2{ZwEJqzkJkz6PZYrhs+{Pi_L$z?1+jB4N(TDUgUA^F+PKAIqLz*R~QetOAJ+6eaMi$ydzKfqfuc$4DwMbIEOrp@D z0}G>s#2fBFXlO*ZPN7mlq`~cdL~Fs|&J+k^JN6&WbF}7`>=S=@M$V zfkx11S2-%JEyki+=BN1KRCy&aD`Tzf*sjD6g)N9x=Wi%((YYuS5%is1x-E3O`3fk= zt76+8@F`5%UHa2o{l7dP-84HM)=sI`EBHG1u4b*bN{<7nIpfwIuRyl9rAcqb z1L}U~uFkaRn$Lb`*L!bpGc#UF2wn1u z)A^JX8(KKu#r*lAb*86rbLM|93+6xn@nfXNsZGN0mYMkH8(B}%S0t+kX*N@fhnK0` z&^}ds%o0reP-OdfDKvM~G+qPtVSy!I%>WvhvHzUy`l}K6c}wy>UX|W`OKjbdU1!4| z$?P}1kdhX=J9`nT?18U;{EYqi35jYb@RrIeo?}Htte;yG@;2y!8ki>+iH1 zcTU+X^M&MV>PK6<6IHjw4|VUm+{eBWJTFSsIP{3~oYHXGijl=Pe*RP}3u$f~?o**X zU=7Fi@7l1Dl=1MyWoNvLn?<|JL(;daJMTZ9FQFNnZfBb*TY5|8b^oK?=DF80o5XF4 z{5IAHHO?)QxTe(@+_k}J<*Bee)_M8EQQ2dqr&|O4j(r=t`pcTAv#8-!-jZ%?F@yAi zG=}c_X6_GHWL^wULc15}tu9#_HSdnr^2^OH@>GwcX1h37o-~@vxM06nnbP(GMlYUQ zJs>8VqxwPbUE5roK;>nCQES><|ET)gdsb}@&A77YUF0)(s+H2P)k1R#*PY&+;taF8 zoTt%An+l>+;m}ZPX9=^%&0cmL3gw45wvn2l)-uKT_&Txlb=CRW6aY|sTr)5+>Mxrey|A(hx;-bApl8x5; zhX>VfB$F(f67m+zJ%2j4Ve?3>M2o?(-<@Y1$zE@r>t2MQ??N}buJb90D31?U__A#M z_rc`@X50FoIc{@UC|Hm14Wu8Ozr7%WEXAl$4n`=Ke)cW6?tB(Khmg_3-g(b{;D%g@ zb=irAwWqX@9AnEtY~J&5dcxN){StE)$9@_>+N`-0z>ixCIna7*Z{P>-EY_B)qk$f~ zvvlQM16>kxB{O28&#$i1{b{5byFdShMJd$eM734gA^C5sPMz66h%MNN3W%QTXE_{l zs&`law_}D8UZ%~vpWsdqwlp}~M?R{NPl?jPEq=yw;8&S}b%-r`w!fvl-BKUUJH5Uk zc$1)df!WEiGPiHH(*kxxCM1Qkh^+2J8>u}Cch;Gu725lvTWj|fNElcFPgJ<}sU_V8 zMyr!kb$FXPFtmpoo_Mvw7JmHaj^dY^ww33;StQ(4{ix#iZdB|B_`CNY>(|EarM$i9 z7s+1P^f=v173ozvVj5ccJI)RJ2Mu(KA1l;-ug*QSK>5Bg+cHbLc{n`Gdz zRO|Q2!GmK`0}BFoUEIM>bvsgVsO44R{FdEmL7a`%_K$2s^ci2t8`jc{9OvY16C2iz z&DFwONbbZ6(@G^srAXOGR+7AgLW=e-t;kQ$ym6E^L?nssp!fMkE`jMX(BxDe>C3mprK3dnZ$4 zU+sDyKbt#O7rFPU>5HXHk8XX!Mc!Dk=7oct2jyH#e7w!|)8YjQSx&dY0>T;-``Tjf zW_5kL_b5-*(qsAYIZlD8-ZzT{1{D@2=^hJyJDso?>pktp_+mXj_u$Bvk3SA}b#sSn zmyYIrJX*5W!LTcW>HfT1!w(Hqn7G(w{p7M zZU+b+$8j>YZTW7owUJSgOGj9z8MSq6z2&-4C4Ae_r?(G>Yh8K#pwK0y_E$lZZL(`G zlj|Jb5oLAN(BW$iI&EO+aB9N)Pc8jkEy24wk*?<&N)LB0A}G0i7AO{%^_veLO(Ay@ z8{OOPk3|paF!Gl*2|hPGrq}&qee6FX8?c=gZy#|dlM}k{ZN1XoR>Qk{E0FK4_aqIM z4h<-oeJOT*=heCILR4?jsbeOJ-NSjn%_RC0R*HL>ZG+l|;`=M+7aRO)X1oR)^~pul zSBFpv6Dt_FoUUxxM|fcK(#OzC%@%t;Zod(Kc4$jY-Sa0!#0SedR@m;bSBs1rcpjMZq;QJ`B>y_N z^@+BrIz~6!Up`~bp@ot^oZIgN24vc^yo<)J^oGt^mf^AGS$)JFz=J<8W`|XPE*&r0>oIp@Goh_|#q3m%utRW{<2CJ>e>fAt?qhH^aJ$+rJ z90LU_-t3kl$ph2fz4FaB#xS0w(Be5NLMxvFa)N*-?MIh+u{+7I2 ziC<55+s#|qpX+f?{I`Ep-&ZG}2ZtgfUEdj(%-2}(_%TF#+5GP2l?jO>qpGhCu5PTj zIM$PQ@Sw~Q`St65?>ivlFf?%Sz;D*b%YDB`8si$0lGguzD?R!YCYY1c+xz~a`N&A4 z{F;OaXB%{ZOvpjHT{o1EH5)l(*7YmAP4B=^cVnD7dOgN}sJlm+=h5(E4lQc^*`f;N zfSezfE~t-w-ghzkl)~VXq&2_C#2KCu4dm`?dD4#C)IDDfiRqtr&`?_+WHF@ZKlUbx zGomB8t^3*;lZ!2aUvo7r%0HW}{}$3ic6_$DYtT^rt^Dim*z&s`5+M$xoJ6}@=jD@C z%s=Jqe6(Bjt?kQIzklXyC7Qu1D_0|>;p^3u#nLnav)+b(`L$(!*Lgip=ct2boe#cB zqx%e)g5#8m*7R7bFdvV3uYT%;YaWo>vdeJqK=uy6V@8pyEXR?h<%hx!(6dXy}dF#0I_v{m4CoQ_K&xu@PuR3URNlQm8LyOp= z{YVdu)H?aDD`+s;;qFH-nL68phTjBk&&94j>0JNw)l+oZ>El{xL~xRPaDTcYZ|u`Z zvKvUe>$QZjrnlzrKGW`Z&p~Df53f&9JDV}5qUW{4@EY+OXXm)AxtFxL@TP61U|^ss z$lXIT+~7*Fw2DOA{N-7j`-@H+Z*c0nbD8F-aQ3lX$}#5UXGW)F%Pdp7^h z!Lz2rH6gnqKReDl|1ja=xBM7S67x)--1#`d4Q+qm&6>;P7O&-TmO=9Jhwergc)xsi zJ4t@oeOk#oWI_7xlvD)elOB>7uL9V?l!3@FK=sh{CQ^#6LR>@ zgZA%n$E@;BE^LPx1yfQ#_W9H8?H{<5u8Ati3JNc5Au}o?hIW_@>d4poyvfM0{fNA# zBDa)tY(dX%d&%BSJEQo;sy}jli|;-^9^%P~8xE=AtTK4AL9=MF?W&OCZ`IE3oV(&H z^&5KYgMAJZ9TImFAlN_FT!@22IwtwSDM{+S2eaN@7LR5VOPYUwcr7V=Mk3$ozEus& zf8MA@P>L6>Y59V$IE?-xi$w)$t({jH=dI*+7t){DgU!Ehe6;l*y%XZUHGarV%)zk% zxSCJ(Rzls;QZUhoV{eqRcSyKlQsqIh)9&+C;k%f8t3Jt>d?P2dOuSuj=vnWR@5}TT znIs>|8ZmF&{&eUucR23g*uC{*pFj9L8~l~@Z1jiv@8;LQVg93}XTyUHV`E>Q{75hl z_|4CB{>7>-U4NkJxm$)?KTT(swkFvA|f|bZ%G5oIR0C2~E3u4p5Y#y_*c3 z7N+vB-p#-2Q=Y4AYyDvNW|d4FKdtnF`+`c6eo53OGUMoy8z+8J_o{%er`z-?#-bN! zh?Am&mG$OdLaWQR=^IAaRq2HhRjJC`GiC0s%(>Wfc#hsf4m^ss_L{g|--jHl5SXK{JqvnZAyJ!s%UX^MblY#u9gC$7OO^Yc=e~+7e}}64s<5Z$PC2_a)nLV1ZJAxHfjM$QTHs(peEkCAv1mqjbT$EM$+Plv5 zSu*X|R>YIDD~C8TU(ZD<^qm-(_mnDq$G)TZYE*^$8mx}o8TPNU^+_weax9U{)jnU( zG^fW4D9hB-Xiyk0C4|^RE zsItWPx1wFYElge^(_Y?j5|?po_CodN{_xE_+aZtggmlrnb z?N&YXH84;z)8!^>kL>qzHCOa6>9Xn$UvP4BB8fX)8{GX_wa3$W%jG7M7z@8-!M5Ve z())TfgkK&tg*|UJ>bF|RJb~p_r(8XVVv=^*lXEU_+)QB}d2mg+AUnvB$={Lxob&x< zI{Kux>zOv8;1vv$R9TKdEqbWI}dO>4tp+ZEe$pm)0QY%o|%Jo`}(PCE4(Eath+gfunTip-^e5v^~~wiJ8S!{l&7s? zOCI`Ot zTcmyS`nO{lcU~b8ey3Iu9|u%k&#Lz5k+Rvt-1)YwI2KtAt2nJX7NK~-;3&E0W!Lkg zUwj|=h8|eEFvO|G`3=bb%KN~qt~W^^*zQ@}WjmP!n=usY3zh%HQDRfwgV@iFmxCWQ zsE1;g29%_SBe$*loFC?=HG0>q!TZqLp{Jjy9}Af!ap8YG2y1!D% z{)&}n%a~HJ*MOG(<@~w)>kg;c8}c%1uJ7!+@}s;`Oy`j2_0;HalBCs()}Quko!1&l znVeM_2neC=(|f3mdYpD_iBe%klwj|ZIMA2nH>)2hOB#Dn{me#kA8Iqr)t2wpZ@qXh z%H*Po)Ppl$e|eR>T84_;QSExZm8UdPK6gJ}ENpl25NP`)Xj*EUto!wvr}4G8D5n3M zh*80QM1&7v;o`ZfPw8BD`pe5`!&rg5vR-U$?)jxr=s=^IC}frkGP7t7be_YRd)XD6 zqDRxVMRdpv&vnh}p$A(qZigK!%aF!A{qQ!Uhx#PD+z?yHya2J?Q4C(f{+Lo;T%5K4 z((nA0mU@~xa?iebzT|Kk{8Z@iT(C1VTYli0l7x0iWyS4e#qR|yj~5HK+6080fpNuM zmVSR#h6y_r67_Ucb7SF?5&hpFtg@bZ(dT0aDzDrAd%F|Hpp5mho$o*1F%kjR(w>>Kt5IADBIAaani6z>d4BE3jnE zHBU|s-;?V(x-+5!H%GGhO@XodrIm*qhT9vmdlrVp%&UpwJjT?1rpZINNm7#Cnf#j( zrlaA*Ja@I|>ay^ITF3kG3HADCT*ViZ__4|_?0UacD?g{?$on;$bvl6>)rAqa^7Y;vS&=neJ zx6dQCw|^K5Ksz=ctW%YK-Co(z5_#g;h0E`eKNreQ)5x3#7rE5-0@og(e(PJ87on1$Fm zFWu)yor=0#6$O3o=+ysW6*%2SJ?X=-haTL5-;8e6+VLbrRt;w*EoqipUA@3m zn=HAhFzjl_qxFx_K1a^5r0M&Pd`iW?Ty#@b@4}n6&yVdX?dhA-Gj=t4#oVMTS9Ggf zd!%$$Y>c?6t?~0=$o6o(d-dx&rFQ6}Zay?U|NW@XhladYW@*}y>D`ZY{Y^G9cm7;O z2#+k_mF;u0J)>GZ{5>%OwxYGG=-biC``fw$@7u+;tShrfazccfS-s6^Q9SyLm=xFg z>*yuFa#eAL!#s(#-IfKV8BlPUn1ne#azvmq=_mvZuEt$!MxCof$hX!=JmA=OVQ>@Cz}?(ft`kPhfmzLwy9l}ymP_W zk9mIvGtl={!drqtYrqpokOpvY^X`vnZ#B_*em_8MhzEimUGN;zCru3P&RRJ zgTk%6$b+dagHKbKuPRkK-}^W}bS@|Mt}5AhW~*V5%Kpb6&0chIHgCfvD}t?8prbEo zUwmA;0tGx&L;i|S`N6F_HfE*W*kP}ycwg#y;!D@H-+3x+35^AD4~7CQ=NoC}6i1vm zyNn*CrRc|78?rlebJ{jr$GZk;O6!&=ueH+Bwb~aJ^9?y)0X(Aad<>DGb07p(lNqUN z;JAqvolcI;j!|fBOt|&tmd?-(odU!XcSbAqQ0=lig4IFDjzlTi+QUYUC*8joRGH-} zIy?OMn4wNm%(Pj!&eTKWf!2NKI%}#K^XvJKZN8T?pRJC&FnA)7AM8Vve_e3=i?;NF zP3We}&4@~x5#GABg%ZAydU;pBj#S^CaBewWFP!B5})US6%MW0NqY=P4~vswQj|AoP}N@D)h`r0 z_BQTX&XK78mFVo4bz47OJ$=xex^f=cT$hB|m(i)~QGD*q^~9Q*u!mbKBC{5!#XL@` z=05$9nE9~hN}xiZ+eKoQB6cli1(~IDreVFKY;sm}B+v79Q<2Rof>lFw=#|#z4}TRe zjXc}aXm%kj@{^iw;zBiY4Ycodl96NR(PKVR-c+>d0V|n~`SSFI#dk4p_ov5LsWOdJ zzP+*ex^k1&Mg1S;@n6?nm*{iOof`!I7Q#_4-ME*h{jfdyT;`mWl(tv9-7nVoy$!kS zeRClyHR_au{bfvV?`41adVA8=Zz~)^&+wmYdU$PEP+dkZPdJ}&D1nX>+`DUaJ8WU< z@iqzMEob`|ueRR{yJDRC;`zsvyD*o|3&Gf;>vN{aNQVQY*D%_e?-a{jq1E9sFS`M#jm@t<43fr}D@~Rs{*LxstHfXkA4BWD< zCtB8s6#MqRyapPks5zJS!lsg?6SVsH($8vdBzh0@$JTK(`<}d==dnI@70h+#`%_(G z^P6rg?&ZMygMNlREH?Xe@`le@?Z-y9 z7JwEPA#2_ReK!H#p>jH}WohvaQt}~xD z99A&we&(1wt~I-r)}HaxkMI9!WpcFihO$r5X6T6huG%phi0LJjeXu>p_Yu4DF?mTp z*0}8r+_icyM6fm@oP59W%i)S$5>j(xzXlE&rml$J-jZC=>Y15hvR3T)@*Pm7?8B7`z-=t4JJ2rumMpE`ZYy#V(qfRMVLu*f zTK0j#N0Ft$T&Nedt)~{pU zh8H$InEQ%xe+lI6Q`zPVi;o6uY~~z)G%Srh)=mtnit-4UXXBHXIph(&oBFh$y{uVq zswAer}OFhL`+gbAFjx-s8r) zFna1&SX|nxZ0iuc7YOfymB+F+`#3k8UfjDT38Ag_qix{XPAD%gcRRUDLFvuylCav{ zCWrN&j~YuWJidO?Lb)dCikkl#a`rW`1sx{Z@;~HXy}PGo*FWD88Zg${G!j*0*wS8T zTI1=&_jb`8_M10|wM~rt6&dga~Isb?HaZhA-^B0xOPh%m)70;vieTY(}iXR z31R{VCyI2{y0a(aSCpj1tE*X+-Po^gGv6K4bSk_i8fV{hJGQpM#_J*7Zi{7zcxNg9 zvV+PG$2j7*JEyeBUBUJ~~AV%lxLzU%uNo`2m~n-$#t zPTPGj9dQL|ADb&LS35r`{I*OV=jM>qCgp*XG2i)&SJp~hQWrjKbv%Hf*7)(` zs0f|nJczpg5XC8ps43Z2WT|sso7Z(T@tLfx{sL8~LWAQkBv|T|=C^hMqE zmfs@HQk?7;u7BIywu^6M@P>bL^O$PWYw4S$D~ySr2|F6grD~fLT;0x;I%a! z%3W0)B3@T-^QN!Vj9a7_)hBM)?>Z7`Rcd5=XrF*HGUyakr0DO$%^M>d#zHx^j*nkd z(j889Z0rEHDO#89la0a+6`pvNWzhThKxB>mLch-oAApScob}(1bdt}W&n8FVf2<#L zdDzerp?B1iiX8M=d*PJknRqq*mGS|#m-v;Rer=C~Fn!qpq*)wJ=H> zOujfq7b)z6td+XUI8CJEoizACDzyv117EY#x3erGpMM;^ST`?s<*5iy%vED9v+0>p z0n_kV#K8+6dzCKSO0hp$xv#e{>(Wtu&nrg`c&ODDF7@<%o%3a=w49IpYV^`tD!#5c zzAH2GJQrH~VDB+);29rrPif1SJ5*xq;Gs3|*HuCT80B>Hj=@s%4hN}q=jU|ymB@rt zoq^wdG;}n<(`|`(-Rdgq;yB6e_(L5J;>;=zg}mX6!kZ;!R#A5=*WY%jUa;oAX{@(U{{sxqs4reoKdnZFR*CoGd;tN*_xCa$?xUe9&U_1U{k++Q2HhR-I<~f zAqS*Xw;YIm4ZZwRtMZ6A+C3m=rQEh4ie*X_hVij8%KSr}ep&(fN208~QDOMS;nTam z6|xyhO}3W%(K2VR^u@-#IcX3dzuGt#%v-jy{O-3rWt-f+i;G->k_`{Od1Q8euZ%Jx zWu>`~uz zW3MLc()@K@`^B$We{6ew^6mG~Sj383*Y+nUH^##_j!J5B3JzNL-(9J=7CX8iiqu*d z-2nQ6&8piE$+?u#;}}4T!+tTcwTZe=at0?nsmma}?W~V`gWYz{+HL6L`a=(t*4pfM z`*rs8<98|EZ?7CRm^i2#;rZ?Y4(gySXW(@O;j=5bot90`{on0QM67>X+ z1}ST0#1!VgYq-Kce@8mT6nq@AAIz@2ONrk9K5=u4%T@cdHyyl)SpA&OC-%NS@N>QY zuY#_*rL8=H_y1b~|86 zcpOXkiwxm-1fD!KjLxg{;0Ul>m13b=_26&uE4KPCV zyK!1Hz{1f2e>rXl;9~qc$atqTAfi?vqE;ZHRv@BQAfi?vqE;ZHRv@BQprTfwqE?`y zR-mF*prTfwqE?`yR-mF*prTfwqE=v{R$!u5V4_xF1dUD$Yo)2A0a`}D$urvDcl zdDi*;w`Ub5Vhbi>3npR{{x`PZGqMGloh|r(AX~u7fFm@37vQZ-^NggwD<& z;{Qhs{)$~Hoz23kZbASK4X6VW>U z1R~&fqR9b|-#P*|PLX;5{uea_28kvJ3=%a328r4NgT@=1JS$+(c*E1D1u^@yfI-5k z{STa%NfG$JF&ALa1h*oo185_W>x2K0Gk@rrKLpJmislbV0~XB`_zzJtp=!VrvIab% zYrqr21}suGQ|3gZa;DD-@*n18ToC=|&B=df9z^19DlaqoDryoh9P!bIr~kid;^CQ}I--&g)d(B#DDZv+Xqp#MCACTscsPSl#n zi@+0!5qKgq0#Bqy;ECJ_JdqrM|DGLzXHJj6lk=nLr2dB{NdFxoMVS>)FPPX)MHv%##62TTZB!djo^vA5j>GNf+sRZuqbuZ0E-ev;Qw(CokAoqUw~-; z8A;F>?5~vPNEcx+- za7rBnEXuO}M)c(LWjqy{Bniipt^Wn0h2@?UbFU6OC z!R`OlTK`PIpK~vIbJgF%DP*z^pEg|4dCK2hohdw#VFTwafZNy+y?{fC@i4409;Wn)f7G@9Jh&NG#ec;Y$aoFrU$6|GIK&X;sKRrSi4A(< zlmR?ZX$MdA2P`W30{hK)NeyLR_v)k28 z0|7h%9039W?IDpU69i276$t1f!GwbIfpFM32#ky*%mRUfz$QS-CtN_|5D1ygMDy4v z5W1Y_64D+59149VS z{E6|xdUH4wJQt1v(PqOS&6+;I2M%wr31;zEF8e#VBzHaIUXXP3TLxPbRv$2(bzMwx~B04M^44SsXkx^ zlY?r zHw+XHq3|dS5E~B6&&+mU5cCw@;9Mq!ihy82EaI$o0NpvIZx9HHA@HU0X)KP9#-52d zG<`fMqzO>K_>*fd0*l5aKm<@4m(`w7q3$7RuUCXj%y zPBumMC4w+e77Gr?j33O;2tojkHyK2y5Me%k6cmXvyCH;d@l6^Nl;^|t69~W%A|7zo zS$YSD0JHli2$)?i*+)PkVpvc===6(c5Q!#vEE`7W_`*0Cfv|uwD+n~Tdqg&#NkJgNToQ1l1^|g< z)3jAMz9~{l}rYw1sACtkLu`nE@ zVD{++Lq&1y&)5t{;Jol41`O>(1mxErg8si)9|)8emk42bQOG`!*+Em*2f>7Z^g;My zy`XdwRX9}>slzl&2>4VcxCk#M2H33`AOWECW&y#057YT;7XX4Y353Oa6R=)f3Kr0= zv!P(nNhqLyXaNU=_TiGCRN>w>bN}G~7HG=mK``Ocf#3({p&3};f3O6a>V3XQZ!j4S zhj92r{;b`D!6$nTmXj8T z0Oz3p9Kc^>|Zc~2_C^hd7~j-Wh&1K`gL2zH87@Mdt344uek( z6TUnY-5U42GVn>$w!J0Kwoh0Ct2TX=Yph9tbg6#Q`N8 zKAw!{6EMI>srk-sY1-;w(!op!lHrTv(q``-6ggE{p?L^zCJ2LJdcntI%Z%O!0fOI@ zbqInXys>^bECo5fJZ1y|C%5D4@Xt-2PbFiyU^)jx=L>!I9|}N!x}R{WaNZ~+gFwOH zh~pu178rDDAmcINFcywW zcf^@3A*St9Y>*EdjfHc#J|y8voEZd%qoxEhJe~~2)BF%92wfO5X8{Qd4U?iDf&d2t zF^S>LpbHn(%pe#NHVGso-HVI&!yveR2&T6%%b5iRg-n7GOgK;oUO@Gt;F-|bI|!Qw zBGUL?1P&ZTgYbpoU=~Z$c3(iXf2Q;e~NxW)M*Aoni@)mRn_bS|Dx z#qy!RRk=NXl&z=l1XDH;f(e^}K$4kM1jWx!nC?x~y8jChl7ir(X=EQVhc1-Pvw)__ zI4IVSgogQY!Dxn%^;tlmsfvsR^@j4uByT2reD%%@LQJa)F~B??#fJ@O9=cG*%?yGA z`~2jfL=&dUI0k}%M+v=e77(Dw{sIX}hma^7j(|bs;k=o%!vID07mQ%S!$P@eHV;jv zGKEe%i>+xOB#0%zc*CI_7GIcz%?uJ2j3@7J6cmg?0kPm9Kvd5Pf&!KO$si<-i4&j^ zbT|VdboH4nLC{l^93%;aWfLd@26MdBH46yviOH5wFh3NSP4MQzu|mN(3kX<=e}ROg zQ&3D_G9JdK^O?dD)Wo&(|H5gh5F~Km091#7&%OSaK_oQU*N;a+`VfRU*er&oIXaT; zizRqtFjSJzk!HpqrtPB)1eD3blXwU}%It=w`#Azh6cC64EP^bo=+0sZj+|V860!%w zFd1wJ9|Z@|g$c{d9fU)tmY{G@CIbY-y|LtyY{9~B%tRWZV7 zU@{8D#QVUgLYJM{5)4oWfB6K#gzgQa($Gu+4oVPiIJ1C2Qx&5h!i$FSLla1-@yugp z5EPi*zxDwke+VepkHq1F`EdH|qnf^bc(Z6kDuNBA&CcF*2oc01V<=cOhK&_U=b5_) zK}`*qG$b1i!*YD^0+w*Yo;YZnwlrXXbpC5h1QRCK2Z@415M(AwD6M7&O^*`3Y;QcB z=L-}yV2s&8K$0`r5(VvpMnlMG2#+b;qh_`Q0p!kKAR*;!6cGp%U^0us7AnSxW7BD) znqC0K@t7b#9t({o^J)LXplO9_pwbFlEaVde5T?*0X6fFvLbVr^!KHcOXlRZw$j*#G z0M)z6a?A%sp-~xVES>EqoZXp0aM0A94NhYFFd-Nyng$Y4#tlK+jGF;n^e-0tVrEp)f+>GP5P1Vls(9q5pA+Jen^RA)vDb!UE$gFyLCj^#7L(}BtBkn9CRpz%Ho0*5Dz=rdb_ zz^5)=mcWmTWRg%^9B({O%$S!?kFq=_gYU=nLDMnd@z6XYXyPndcrkeVFqMb*hQYZ$ z0tA90%!?+jy-agHC<0i$FyTkei$H!@G#SI9dPC?~_^coZaA^cMk^d7!!GZ*M8VT=B zAq%DA#2dJ#S(>=d@+XLZ!g@2f2o{Em7AmBfK?n#CX3>9H;!@Fm1QL>g#W2Sgl<4y0 zG)pkx=;x2_O}>F6WNpgDAu9|EL-F$wUN-Up-d+z8y`xNS%|<+vCH!{$BzbZR!pmwC zE!g9!J}f{70p}zz;KJ2EJr)}VN5H2%7JIEMYwtVukF52fN_O2g4+yFySYq1l6C}qx>NO~_W@i`rq88k-k!zlW2&Q{D*jwh`nv@2yHe+~R1Ya*GiBw4k3-*6e;04C z8MX_?_F5etGd>=BQRVR91<;RWhk{;t@#TB>Uw%dck{EAK+>VtSS_c-La1P%#|JU#P zZKkDRePWpxaAPrBTuq0ha~vf7^D+)nK>ObJ%Wdek(!e#fyr@j$i77t!Uw+}@{Q27! z8f3GPy)G8}2InjN7>oR7-mYbILVw}*2Rjn`Y-C+@I*s*JBzC>ntY3ncyZ!0Fs-ou{ zL25_SmcdMG*rNA2o3s`Uxi&pNSijVz-=HaD;7f5M;qtlqh5)bb^Vstsz4b3kEL{{} z@GGYnZf|IBOBO4UVxM6*?Uu-uFGp+nJe_YzwOM0)9KSAjS4yap zwtfup(DoIc7?@08nT*(`gYyE*oW+){lMgH-ik)<6I_ubxvXHdy=+5dR?vBGL@?Vwe z=Je@4mHnz|Hn&ekio`RmU)X2$OEX!$K;9}~k$UjZ%U>@Y*N0v_GQ=KPTexT&S*3H`k-$cDPpwUk{pYXcT0LJ;bdHNYwe<=@8bTjUhPIm*oHb{9(RFQsI7PR%m zbFDawVTIBm&C-4i!l2Q+1yvg_Z>F0ynWdjnCA`g{crNW+-scd0c-JE>{yV9A;(hPa z*vlOESiO^yxOp@JaVatEp_Bw9C@Hkkx!Pp)jrS()hDv>(Lx=8L2P_rXF*A z^U@Cvx~9EL^;fI|%kMASmJu7da+Phh0&SHY{ox#g9BrBEzJTO_GtwsKemR;Q=1H|n z?yq!6M!vLjDt%y8fM;gbk?sbjSD>|hnjp%zT^=Z2PMCuSn-$aJPln3G9rt`1VE=@` zTJu>qG`K%J$M2A@ZRdNZxWEv#)3qwQNslVe1u6TUt7Vkl-$gR?tXoRKt+?iP!}!TY z={M&TJqtbelu&Aq>Xt71)rY!iRY6milvlU-xTqv=(E=+_)!b#xw2OyQ#RAo9`6<*z zt&b$K)e9b1*Dbw$IYTM}p`I9$%eb{5bl1LI6YC3$e0I{FDcx<%`n;<$r&=fWh=T~wy;k_tFs^-S~9QKs2y*FoDMGE#1krWtkz4v<1*wT1~U19=(WWc^;MzifTXD(#nzj~5{r+k*AtdA1|ZwTcH`3>@w- z$mU7aD%|Go@IN2+oS<@I@mtH`ovopbw;WR8!^)?|wwo!MMmKR_) z9y2XEQ=om$hQBS>Z24Zj1B*^GJQ4>#dTGxaHI@Bl^9g2$eY-qm4zD%j6L<}1|30rB zWApmIulv~-)tGiQm*Zw8@1bJyj;OS=hZFNYta!NO#HVD~;Yhx@^77yZ)J*>u(lIp< zw5->jmsSqwy9;$750W0uNxqhvl&tpVl3SoBb=eagS|Ag=Op%Lm`t1-YXYvkUB0 zV3)3Ce@p-RPFRI)L0UOTHSv7QV@p+;3XEih=Akc_u;qr)y%A-By=C?Sc$9hMmV3Bz za!haUI_!}%^7(eI^3vTJu|Z;Op4;sl>N`TjdvlcMI~*|K;nnKLZi;O-IW%Nw@%Fro z{6~!u>Bu8Nrt_&!#8DRJ8FS(rZI-?8kUV|fLOG}Nb*}ugMJP+l>$~SB80J)2|5Sc2 zJDm5)#U%*obudE~*;s3-ChxfFckJAOy~<{?wB4HLHI6)A9cXsM>QGU~p>1NnxBjXF z^&FhjZ+YOXWZ{jN(gwXx3n;eu9r$HC3yl0N+h4599e8ya?O<#mX7-9#Xc(9j{8V8p zMlG(9vG&y3Lm%1IAB~>gQLZW*LNpw@3`DS`?^$a zgz!4yKoZl_!LB*MpZwj?^)rF&;n6|B*ErsV9}LgU9(;5Cx+(Glzy5LU=?{>-uIRec zap|>1A9UPnqk#wMlaQp7vfhf74$Xln$V9dON8DLQ)$Oc}8mG9sYmwq?+$rwv?(XjH z6pFMIcZyToT?$2uI~4cg&JFz?z32PRy8m6uVr3_jN#@OD_Ua_h^S%h*B zrXH~BkmU9*UZ%lvAleXwYsAVynQT<*jT>B-HE__?xURU%L zZ~JQI;TmZ3lyBu;g{=Q}>}-Dij91)w$W6xNiGeXG5^AHZxv@QePS#7OJGI|%gwI+= z${tp6PYzWrc06X1ce} zm-2oUc!81n(g{u5VMghn)Z3mhb}Q~C+Fu1`wzO09l1t^ruDR~)ea>(slzV)UT-lc5 z@M{kq@iw_c?LAs|-G4SR6?=jQ$ZiV1*oHp3Bh1VcC~AN9#ZQ?^PWv9Z4Pq`=vJq{4 zfvn8UItAf=qo!|=&!9gZYb@g{(`7EW7b{4geCv#RG>C-TsU^TBub zYts(^tmrt=3QE5{Z+G| zZe|8ymXmFz)xRXnB;6jqpVE<`pDG5wepX5N zb0{mdz2T4$0ppx&U%%K}es=x@wYUgjmJ+tYsG(tG^l4;9k0!c=Xq7&j zLINwz6R9rw8+=Vfdl<6NlOH;ybFGS5mxS?-JfuHo>iU_kOQUaSjK z&@6UnX^A#n?J%|~O?kgf@b8ik0`B8g)0_Hor@F0s?YVg!7r(5%KAnRv;u=K<4N12> z3~@a+T*d8dRkgBQ+-syPK1sT2a&nU=*iyuskR6NLx2*P_J;xsRiv-b27lFc4x5mSi zJI|OJSXbZ&9{M?APfw+*@p^H!_4RCWqqX{{c5;<&d?<-BE4tBd$7K;@)xss-@zc|- zmq&IkYmvdGrSQy5)%LLIZxi20TZBpAUBy(_8cX2t(_314WxcIBCLu;6Zhw<8;$zpa z>bzf$nmHs-oJFJLMAqf-r%xX`^s3Y*O4l!E2q<1k&jr(^!vnyVUXMk!No=`yrJ zxf$~FMDvT&Ox>j6{HATm7)@Cf{RqvxX0HRNC|#%47lgO%=C+O3W{{PronhIaJr!Gu zMgNn?tC<+cu9#9n`(rvCUA<)LsQC_r_l`Q~25}+Gc;CU2@8DYG?GgQjpS@aDBhwcA&Bz);*t$E_CWi5^%}j zNYZ)ZB0UlFvW;0jw3*gOf9(3DxfvR^#A=NmXKnK(Z7z*Gej0wa?mD_6dz!?bmSr+4 zrv{Tl4DxI7-M0b@$AS7e-ZQ>s9>76{?_sAz2ENt=GNVa#@v&s(fH-Org6B3Nxso=?15oLpa#2lN0 zXTTE>3H2DFfVue9xxVWj$d9xs)`mk8`c7l;dU`AixoZU-GrADNp#cfGVRA%GV>eg^ zd+!Do;`v#~U&F@6!otQzBY`OkTv}?{T0Vk;PeO@4S-M)Q8zGfa$ErvtGjVxGvz24z z-h-~3Q_yH^reJ%|)oMTdAoy}K19rx1T6v#z3E4MLyVNJKFyn{va6+N~@sZs0{+Ehv zs>*K}erP?kM~h`Y>+5vBm|8_?9VF{slxD<6%y{icY?dBVe@av?ei3L!DN*623O(vu zxp1J{a5M&Ay=U#VhAu3t7&oT%p=N6ny? zUl>3`ibNChcsO;+6EG|yAPjzv|IIIruc{ICUhIaR?pEbKjJhqMs7gMosx)CuU(4bf zl9ofHz1SJ=%}b-mm&6O1?Hew9QoiBNp2zK7ZzjPTG3+5S)vfOYzrsuud37N*KA(0t zeV@M{nL@_W5=V{Tb9Y--BvUbnx2q~tB!R2{-B?u)>2GlVcF7I<5p0yG3dnYr(?O8y zQxUd@^Wx#MLHN|zwVQJc`*LNV)tSC+-mGPxO2DucWXLEVt;AO|Y4D8m12oy#CMee} zh&WZ`(KLlKs#S?|iooPR>NEdje&=iUU1#)V*!emD?0q5w`~M(w{6l2?+jP?_FbhmA znK%Ql_B%NL1<=YH0dIga3IF+_mjep8m^nKexiTo5S-9C6y%w@HvTz{gi^rtmX(RHy@jnADe&?2 z_#%Hijts94x`3Uz0p$Ro6a>^tEWniE>vmus%ZwG6`uc-q{_E7&e-M_hrT^OJpKStm z{PQY1Jd=o#i`i?5|JpVcH)Gd7qDxV!_pgOtWss~uE2Q45I!H*pmjkAknWRjChFZCL z{aLNz1w6pDld?B=cr{l7F|u-Tb@n2q5eBr9X0-533eKix&Q|soq%?ou4Opq-=ICf^ zX7?H|W}rn(8m}Hp9S{IwbFy&ikpk`4;RHx8P8M!GAZ!4M#m1%wgamlR3Gj5UOB}DI zfsLvO#zI8a5fvs$S zVM&J#(6MlE0ei6lR!26#mca@*-2mJVnEqt`qk7=r0!sjP!wwu21iU%P2~?@W@kbZ|0xSriKacxgp~^o^ z;CkJMm4#IgxZLkGNI5vU{^c6~kwX2g$NsnZOg656o6gkxIAR~dj4F2H8}{j#yk}s5 zT{-kUvE;jZ#h?CQK~mAvxLXNg&mHEBa)sIu`=0t90~ijacH1pVeGVNC*X+?IyV?q$ zo(~AnVR_sbm7^?M%`>82Mw^NAxS&oQJ|&K&@Lo?^Q@wvAF8?&S)+ai74eK4dsj>F) zbXkhe*Zw-_X3prgh^<*^h-O?&W-wvDnOOgTE$VYGwi?0Fil#Y{E*Wh>DL5PCG=i zm@bFPB-1)>mCaI%NqFL0rX4o>uBn-KR$40Qyz78zf*20pv)9;;$H9$;GVbJZXAsYK zB6_MxWJXZ22EJE?nB0k|Oo;J!UBvxji|pBp4wccFhidt@-ZqJ!FmCZDHUgKum>@sF zypECoVd!~%O!6NC&+C2gx4Z52HUox(|EF72%-+Po^!1MV$BoD!`Sr!0AIj@LS5ZX_+s#Ctwpv_A~Im5s4^f)RmQ zwA_n~>UCy=_yGv4;f^2C*oxB9kJRiU8n73i#CPMtBBpA7^TbLfnQxg99SmewVoducKWm<lH7K9KaoA6dl#d4A1jD=||C!Htey%kD#?zMqb#s^+IXz_r))^lcg^To} zzz9QP|E`E{`4GZSxH+j<-bL;`X5%|=K~4wN6PbLHOvp_zz9#yfNU*lPZIK{FiCcQr z*kC0=$_ea82r!lR2NY{JNU8wjO~5^I5m{-Mz>W!>STXcN zD}gCHJor{kl73z>0XI1IR;>VIL^1FdPcitJ%O6i4NmZl=n+&Zh=A+CBIldgOP}Z7A zQ(9$?@a}3mZ@#N(#rk_nGIs6yFjBT2-hF-nq*aUX7#9WM5z-+Rdaq;zvo}*BAMmx5 zD5l}fz&S)G_-eZet>eqr4T;c}tpdN~xF3x%yT8qYXw|LM89p2+Ai%JfMg1_VOHq{# zzZ2Ma(PbbsX|dvls5HvpG+OV$)KvDB$YEV;D5uPD;8A_G1UGAdtWLG zc#w=LZ&M0l%a*I1`r`fk`A4czUD5uhMD$+s-)Ss$KGKvGcZ_%oGQ_(#Y59)tZL}{? zrtYoYYa>`@q7vlh*LPhOh$kLt*_rd=(QQpUl!Ae~476n=#K$)nd6t^I8O5G>CxEEn za#^%s`UUIf5!k3MdklQ<&wY%){SpOT?bWikO4OEkGuyuzjkdaG0gBk@pui{;X|>@E z$b}<*?h4;*Sz=Gqa*xd(7YNpqvAaujN8&VfR3V6GOJ{TDSv;F4;y}0Pjbo6r)6Hm3 zinE#cs*nm=(zRqf;`uhQ{GHEzUXoxfFvZbC)yTbVA^hHaTWFPO#8_q=xvDgNcud~L z`qw@?UwRQU0X6P!mLXv}Se6GzJ)G0ZCh-LNdd_9%d-(Z>X+jZbM~0s~c%sl3*@f zNUS^pgEuAYMqpdW3{#ikIDpOlUeZ>gk4(my3Qma6aA7Yk#)K#&{1 zQCNa@BRY6gBx`+}ld?y>$?JYFiLxE{Ig=B7JMKFHh`xqBO5Z_JAnx%7hR|+$jlN#RpbNYOUTou!q zU`+rY{XCG{o-dM;2iL2GeV4%-+BMF{o=4Wl)CZ#Ln+p<{ zxzy6boR{vv+rV8&dzc1r>op#l@skrh`AS7*ESL(eW zmfof*Puh9TCW*7i^_7?P7KdRqU8bkq`*Ah5_JK<0pMKt9IjEgHRV6QzGY2elBV;Hd z7oq{5s;ZHVl6s|k3BnZK2;vyU4aMHlu%6Utc$;<(O(wdKYlxr3^ZlssjXf2k>Q}I& z#(diT>m(jdrtqn_p?Wj+igVa1L&DZe0zTv#2-6scqg8I|ZF z1TBfOf@E`o_@==Gt1aw@d`6aO2(dG)lJ}D0Y+(&0p$2eP5Ou|Q+&wxaq3mK2cTO}~ z>$#mcbZGP&#Fq#NlZf3PQZT@;fn4m#-?PO>!^|xU4MosO6)!3IetzusC%&|gBWaYw zj(-&L^e5K-&M7*6E;E7pEX5v~AzYE`KqH7<(bt)$b}KRGBmg#SFv)=x5e=4=@@RdD z=wnZXFM=#$74FPCb;ey1_<*nV9vQSj9N6uG3@w@lZ8G)ybPp^!d=Ozf;F;n^(MQsb z*u`P&rJwit;$i$$u=AzSUxdf|yUHL_PHw^%uf;7^e3_1%)oH;8r&gu17n@t+n_qKz z?JpI-lN2!xLbYQ)Z5Oodre?a3nknPK(?UfNln9cp6muuz=-%ieB>0+3FF_~73)6jd zc{W(tW;$_B-Hm(M+FjJme@e<|J*he8eLv3nvaj1YYuS0xnE!I-KYS%Oy4x=PqM!ey zqj$57ckuab!j(gb$?8)5gLT~9m|O2v{-KW7imnC;Z)NQEl3t_kA!)#*nFm&DywIoF z`YE?>)_$w+gDP?N5S*;|%XXS?9LSnQHK5M369PaIPe{vawUex7!)hW z+VatKA$kX4OK?!eK_MeUj=e(tlwH}z+k@5TMw(%)<+6xnLg8v)z0y#09>z^>Fh5#* z3Spvez{+XD`Fayfa7zQ9A;B1zxhEQ|ncT|S$S>bqkGc!3t55=S= zycrtyEr@t;bQ96WbUCFmDfF2MT>LO_Co{JSp`bFMMf3ngYDXv_N8e&zLXXrfY???p zK(aSMSl>d@wU9+0-iILH!jQ!IOqCQL?4t{AIn3Y=?s|(D;M8xEu{xNXrVE&t&2czRsNH{wW!H~eJ$Q1H;Xh;wd%N613%u%xY!2`k{s zCt$7~>qKk0wiEJ@;8Vg0Zw_|u@pf8}Kmkphi0tgbS_y)X-(V<0MiA}1KJ4x5fO`aG z2|6M{6Lh_W;JgKwS0VJ*&Xix9+9|Hjb^a3bZmxQ{lW;*%JTajwI}mY2JFnRpGM`q8PIP{5auQpzGTQI z`lrSlcj|;DQOah>cVF+B32$den5*v%z1uZpZ3;w$i8T+lyMr@CjnA9F$Kw(V92|$t zz{b~B2nnk2;Oxmk6Yq|{qyQV&)iL#rBy!@&rq)U=+qkK<^KEAMn2XZv&^*eyTB*K# zHM0mYY*oqB__e-)l;d5~a(G<>>a0pto0d!0fOR#s4kET>6ro&{IK7OzrB37o3v<0<=H;K3QNZuIQsm~Qem7qpFceM@e>Al8go;yg?3`Uc6a zx5RYU1JTct742SbKND@Y_e7`%d6l{8D+&iVZR{nbrDWf5;D@}oah3f>OT)!NM#ard zORdemS%AsbYNEbc-9to%o$h@7PV0U+ODyIF%iQho@iOoVQnHL+bGcQbw18g^hHs_b zrJ-P$%)^%|eQMPa+2ipnR3hlzGUh4Pvhlo!yrN+EIi z(9WMN(P{a?d1X5QM?TM+GzxvvIZXhcG-^OJ`;Fy$PfZFae6nOv9tC@{34sl{Ww#h- zQCNc$JAk#S`7|Y54nTHnN!rKwsa@ zwQx_lk7VKGS^U`h%=}FvJJ-`I-tInjCey(0p=+uf$h7QZOvSvaX*0{ub>BaH8_L(NikQfOAwj78jIRhFDMBkb3n{9q*`1%?yIa8 z9f+Es?>DIdXb2nC;n%|X;ni2_L`S*`viXAOC@L9?mv1!Y$1F(tGXdT|rWQP;PSZVc zPUfuSE0Zbxej0pXT3O_Dm2Tv86tuSK>&o~F+UXMxEiqQP4Bw&@F;)q=^8U#4H1{~q zFeo{Dy-{;_WBxI=s(@vxOt1JySLdeZ1yx4Kp_%+X-Eoq5Pe%KMwm_616gm@Ocd1G< zj0~AE^h<5Kb=_V$&-~Yx_un8Lk_U)V1~%d(xL+WZ(Vx%27<13Wr01N_?;%|6-eiu9 zADz)7mqmrV!}KTVNSN<_Z^-dh&c zi5VO~s+7Lhs^0qC#Wo?9^FH)P4c)p7)>&cZo9JETBxT%}_k-p3X>+A&`Xff8Q$N%B zCyS6WMNhSRtkOa`y9j^lPqk{fBw@W>^!S*|yAvh1e>42GTEW*a`xdfL`}^r-P`5yq zUe9~@6)FMkR%D+M6eiWWK76?*(+U9=VTVYwjIya3hV0=7-Oh(!bB|ZqC%U`R7ctoe zvg()>n!|}vQ!M+GG=Y7vO+@x&e&m_goflQ~bA@Fd4!RxCpN}BH1Tm09ilmdsFu-tg z8_-1{%(Mhn$7tP2H!PWG2+HIo?Gq#gcADr)btM1-|;_`qa&)}Pj z>5}c|h=aLGc;2r(W!pXW>}Atf#dmY2^3YijAhiItW;myJ!W75^!~eE-7lriBXB{C- zoTi`Xj%`0=lZb?ZZ>R=igErn%n;_URYjiDr_bE+TLfa=cSUw!LC|$I^A<}s$MHx{Y zgvLBM9O-GQ&b-yuEHK1CJ)UH2T;ZuT`7y3itG{sUhH2T~ez}~Akuok-SAuiQ;QFz_ zO-vU;D>Y&=nzkz7;m${t(21o1`fX>czoqrYFckv)8)FWEO|~Elzb@$m^@#GF+-?=$ zn(*R@O;27WQW$&!mAGTYn2sN-hWRwN;`B@FTxdG3boL8(7ZRD=rrWf;VPudF~GaMvu#<1s+ z9z*9H4)04t(SoTfxgj|Bf}ux3_8i0sSNjTFGki-M=af z^PTGR8~xJBf00?`(8m93WoPtSj?4MVdOOCY!PfuQmxZLkcX11gzOo965qIC&2?ej{ zs{7oc4{=$KM%^==lF(`MW+4-XyvVM*pO=XR`7hUcgxMiQJXGTi-PO$gB*J z0apZL09FkZC_xOpYHYW!Qg|vG8AYJd#;^EW-i=g6uKH_z^mY#@m0ro>YwBhmmM8{P zTVaq`Do-l9?{TruPw_ELvM18aZUx3Ax|QT%$IC}K`j|qU;A7K;y<@p|g$N4%a?%+$ zF*}AcsdpIWa-saZ81A!b>y2-wo009yzL9tNz!wN4>z$4o=jzKTiY3>2mf`JxOuEbw zN&Jov`Qv@prwO*CP3tDtUlvjy$11u7VEwzuiZF70IEb4)7i(++pgPX{pvQV?=>@x_ zswBr8*5;|7lxq&&?ni(efG-y5Y#8YW^8CTguSsLc?*8!Af#04%Qr{2dNMXFNiXnGk z=fe9^*DFpp?|FaA@7E?}XLLTV57GzQRk=Pd=O9eoX+-5z(ewycKBLqGDN0Eg?e`fv z$yg6)*4Je*F-_H9U2U=wR9&eDYqu^gHBBo$q*9&O?82X$ZNIV4tfftp(sxz2$Fb!t z;6g$C4Hg1Z|3c8}C%R_yXEMK;a;lRyxuX0Wa~tlW`1(uM(lSli=HW|1@E>e)ZNIW$P`nRKMm6wR z`<2Rbu`|OL-SIPubsfzYm7O6e4$z^m;4ECV@S6Q0)IA~%T?7=)_YR1|ZK*F{$X|9y zBSupqEmBju6+P+j%EwW1@gp}6t4Tle6rn;k1TriLcJsaI zbcnuC88x9AkJe+X?6I$1f*ed49CV+~pSO(H>4$cH=Nd%dED+c`E?V(JTNvtZV97c03 z+T!k$@NvP%M6CSV(Xq@im-8<^w;CbdT-7Km+HUXQAq(U=u5I;T(YAVp)D;JMl7DEL zD;-8*Y?b|bJc}j*8$x)*O&Da;^?*i_gd!RWRd>7PQ40lAncidYZevhE?K>y7zz>-t zFiBG=HD1!qrn6L+L9wfn3aF&8sX4WQtv)^YwLOTewv_v!k$*pD?%?*L!w<_<|MFIN7ut z#YiFyg^#bq-c{HzYat#ri+vC-1DmsVFk?s!TWlVo%HwcVHoG_q+ABp@Md}-zoJB0? zA2B!akGfR)^n>nxF|fmoq}}I?+yhM^|MKbmy=PZM?df5eWt#%eZ`$zesHxk;%lh;I zpP+%=d197aG-R?+?j{I+t0xcjw82^@OS3ZBL%Wo_Z%86{)J+Y;d7(IJ>nnyR$KRc- z*5G_E6w0+4p-`IEGeS*bWb~Kc`N@0ptYO+37|?QMuP^UO*jHt@`ht+U#nW<@&OeU} zOa&Pes2`IuiLG;o!uZLJe-^fZr7?_!Gx;Jgumpqhb1`q2tZG9`u{NEU2`a?nq@m7h zC$m@C3(8R$5$|jrhC_GB(_%8Jor$m01%voy^3DFq4FMIZ%<1Xs?%JAvNtu z({wTk!yd`gyFCv@>k26A;}ET@J*f!8H>G*0pCm7NpwIS*sQULh&=RGw;*5n;M+8dZ z*CMFq>ic}A7|u@!m`?ols@22|5{Rwfs5zUf=uO-SJxyS5pkpc}+LFA!Fc3->Rt9&E zkY1t(su?32#eZ*v`O&37DdssxwOipg=D^hA`H(NGRU`m+{`@xF#GMGDN9MD)osv0& z=R6N8evs^{i-794Xy=V__^!2Nc+$_xNSN`OSkN#57?W?(4SI5s^6j${+=&wy(`Bcc zA*U)?AB7hVmwTn_k10J4m8jPY&$ptPKAT&^B{y}s{=VgrTsLd?w4xpdrksdOD> ztwU>%MkXVku4=UjyA9SPPmP}FCmUWvj*DFG;8E*1G9dpx7-BCib!*UAz%aGraHdB4 z8>aCy--d4wo}4z<_{YO7^um3xKv(3SDw&uT1p&n|M102QLWT7R(fV;YA2m=h-B|fq znKx5)Tcd4s+lPIYLyng;E@C{&jg^Ed3FF249kW!lP%u2{eZh;?IW*+gXB`&%OwaXB zIMxtmM3E+SBfct`?9^G=nKu%$byF5qw^(X#@1tS-JnAS1djkL9bLZ=Akff7Gt3cXi z-Qb-T#Zq&=A^OzUS85`4PzjsvDojmrF9;gaJJf4|;pM_>dQJB9F2(Dqs?kV%~fgbb2=R(hHgqyU#T2T2HmVJP+n zWqtDV!8TPQF_WQ{U#IenE5&KP_s7}NoJ_U+GdK0Yqm(;Lgq@!$`9*%hJ5GttB%Tx9 z&fzdB?ZJXyc0#7a3GKomgYwy^awIz0z<%9Z#*BcA`a5B7}*{ zDs%Usy|wqbi@#MT&8j9v=8L6JaZ`t1wo$M6os5WOFS~2saUw+EP|DLL2#l9a$K=Mx5X7twVXeg2$MPF#d} z4RR`YKe!tHd9y1h%aLF>sC(~w2#7l|ll+S+E5)SC$?46hU<*@wg@W&S*T!1F_%Tz~ z<3MXm+?R|Eul|&$pcBzT@MQ;~8%0$T2AB7ok-6;AMdEKtkw}+fvS2P6gO`!08%&>B zYE%?4E_&Ma+#(WJ3eM@K(#!H}T+~XJ%wpwI1W{FBWGZ&5avK#gvdU@i4yP`wbdWOd z7SH99MpoNhTz{QI`rV)EAjj_X$*GT(-ri3+P-JJd*}H7ygmG=$^x}AIW;g2Rsl!Ab zSG~tiTo~QR=d7-uq!Wk^o2h2dqyWPQpFyTZ(mnhAk()Nk{6M#V$e_8zC+YVx?IXKJ zvT$dJ*Xc|G6u;L+HlRe2CHXu(5296<_ld2DBPH)0* z-uW`o34$vJWaejs?`sGxch+6n9)w~uP#2tAag|!7b3p2-6uA<8YWZdBnNI>=#5#&e z9?epozgFYZwTGWpnRm3aAkADp zk5iCT^Dj5rv_2P$Wn1qGs&NR#%hNLu1!cF8@rP9ThQEAf*p4Z29+Kr#wmyCbW!VTm z^dzSG3$tI>I;7g{n_%&TVl?D9M{~5& zg&W2oELB6AaevVgaQi%E^8V2(DPn)QJf5l2UaOuT7i*VNUVbpdw5jpwoJCzXdp8k0 z$>&zJ>K&6dC3VXSp&8j6SIsqJP|gP@v%^ci_~%EoT{02(+`cP5rP%uVC7!B2az3ft zyrfvZ?e3qx?p*g&^fg;xEyXC%&lfH0gJBDg2OcLvDMIHtCU2a@M7j>L<0bo>$ffKR zUV1V~d=|3-yF!gKK-({7R8gFnPbhV}`c)lwt>Q2qWS)58}5m4%KWz&*S$ySDurMC2@`2Td0rL z^O6C@?Pdf~tZh+Nf`y5yr#n$9D$+9SDr4<0a{CRz?N9e71}&L;#FC$OxJlk*k89KV zG4gMs7_6?BYreqM3KZln^K(*T*l3ajR7v2L!Om!cJMeCi;F5j4^ejt7bAC`=Fa>?~ zmQZ-sZ1LNtSFmWWvxSXv+7OSS7KkQ$e7D@D?00#toyYX7x?}ha&hbnllgL-d5DT`J zL1TBUduIi!8B(|*(H%!h{<=0NcVoFCpf9+eUV4bSPgZI9RZ6I*h8^yz8 z+_v;AbW6-)D1G3y6nSUIcf&(Pxrv^aD*3bN0>h9sZz@KWNI(%Tn7oc+>>_1;>! z;-@xqJDI2%4O~#D;yyc~7loBOQusa9bn+2gghx}jmk=DDK9c(*aofCwgo5aAe!cjD z+-zOUY}fs$>_r5Fds;n({x9*`e71LSKN@qf6Oow&0|W{)o5kj0n=ceH(WeR}`p1lp z7w_y47d0i_u}$tf*D=Aas)E`krkMPB+HdgUa17l_Np|3ef^Z5LM{>Z~O!uUqwq@5< zD>p)8sz~|@Etov$>f;O?PcUg+%paRW*inzeSlzh%M$dg(U<%1JqW7m0``&q z+DGC`tMr#EdFZ)@&ANAA(@80PA=Cufr^%Ro^5gH?Z&Yu z(9pn$lCM>?sq)k--H(KENEd(b)o$+YuP@~qrBd@BDpu3qz^@&$?8}6m_f=Zs2{e9- z?B+c<^A=g)2QT(^-p=>nAUgN>{I>|V-Ao#VM4p=|5+9z!-b(XUCO|d~-Q{1%v%dK$QM*z$?}(kB?CSbmz-4{@>*XkvBJM|} zZ`h?rM?aO_qH0%IM|Efii=MWOd!#}hYVtaBXgJH!(=RJcB{kJZ3h6M|1u^*z^C-H; zy%_M$;V&5p85tt{beSxYShnm3a;B+Iqa&Zb2XUG(o-Fk0CCpO$K3u1*%{{97-->Te zBTsqkc77+mJ%-*dz6R?C-{)NZU87|4c~Oms(>B2$W3BKEA8eJ6vn`lFf6Y@zvQ2b% z#8#K6s|?cf3f>dCJ-*vUnuYRhA;b^uGp=B{ft>dy>@&aVdSOdLV3`KyiEhugLtyC^ zjk0alZVoSg!E%-NPKOc{embeRdwN{r#8`hi{;vMbfD1|4Xb$;Xz{RJ3UTyK&J@%>g9=Y}B z(3NeF?=s3~`y#lIZ?<|5oiq@C+8+L_plu3TfIuC{NkOg^8SxYz>KO{AMTmeIGBS;z z&WA3+bqXQ0L(6ieK^4pOTr}P=1*<8=H;zycD&gHE6-Of{?A!cKn0OwWtKFJLQ?HZ) zUKmTmMkk0z{kC7brpTa?(T=zNsKf7O8Ec8BeyzHS^ks&qo!9lCg&~II$+Wh}Dk?5q zkkiNZfGsyJdT=ydu*2$bHcDmhrI$6rw*O{6%AOZiU2=LcH>7(tHw3UQ;GVUF6{lMS@`;zP^ zA<~j0M4UY+iY^~Zl@%G6Gm@88mfcmWy3%lQua;SXO#s&e<|cQCkN9RNXzLA^*!)+T zjs&(y-q?uW-Fnzn`t6=uc!5su)-nhoJ&heZ@X4^o28rekxEpptzO#N7gkieMf0A+1 z*(s|NNosNuVLuW?S_)tX;{su)yMLgv7?3v>o?*6TqTM`koSb=^Ya0^|_F*pCX8T07 zqt}g8EWKnD7aIMT!d(PCI+bVoyg5hPhAri#QC?az*_{D4jUzZ0?Xgo(H1~Pw?&(3$ z|E)*BbQ>+3OUL6!SAOs0C4X@dWhUR7 z@(e@{%=~?y_?DzbQz%eb7L3NE8+|^i6#VSGmLG|Y{ReRl9*_S0+)qoyxPdHj3QP9$ zb@eM8*g7>W^BvtzeY)*) z8yT6DVuSOkxOC>vXb}wAZT_*WNc>JT`UTZOy_?-NWbF9LvhVZH|FFijB+Vbp{seoZIy4&7z%7q`@5Jdv=SI*BJt(Zc^e0xv)4-L9-ppTc;<}wnE30NtG=>%$Q7@ znw5$B)k~qlL0_#7$$>$;KFyX%cNk~;g6mfyr~cB_;|cJ$lFsLF^afd34w2ao{IfBi z5~F^MBGLCz%pG0Llmt5bb@98P2a`Vffp}x@$+K9gwycbjBs5q_ta< zyE39xNzzGr%hsr72V1A*_|=DGF;V}vVJ z0jJIUfs3Rf-|1~%sAp>e%J|H|ODY0!;Z6!uDC)k`Pn~G|CjE+qrx4H0Hq_E@sBsBn zeH6N4Y`7-3OWUI%lH8JVuHu&%RyF-W@KWu`=SuIYSvUgXP-5moM0BqjiGo*5gJjl9 z0sHYm?FVfCqWklOR(@q+-ppCz?e^ab;w&RhOKL_->K<(z8d!~T(dO94AN?0*82omM zye>E^_GXIgZip{@%4O}AI2!GE_L(W_XFfXBT70k(e8=ZD@EMP-pst{Op6?+ZGc~kusyzElg&1+!flBqJb z?F2Wk+ip~uiTHeBD{C3oMXSPH9l# z{-neHk5}mb$+`T+z5K=Qi2TLwC_5NCxH|k7ZikhVk&}&`?ceB~zl&=A&hP*-XjWEM z4iJ#y?_W?JK+VX_#t9_0`rmmTPQb0p1*jca0ICN_FT>5v0lbjU@|s_T9oQ3KSYBmn zAXYZ^f5&|OO8@<@2*_*Rs{d&Ce_}p=0;K$}48i{rhJQc@{~r(^c0hN<1}I1YY6bvz z09=QW6A13B1&9+6+_JO2=3wLiRN5e}*L+<-3QR^I-4YN&HbBV532;_iz!G5A19AMx zI`XPi=i+7ua@qnaB}P^tQxFG0`T%k#PA(u(BkQZ)lM6T+fa(DHP3AvG1+t<8bP))! zCB258ofODwt;5O=Y~}*42|z;t=K$ga&hFK3&G9O@{ByB5SXmhXuP{&*$E%+CPxTyt zMT(Q-HESmT_59T+Ae9^IUp)$7iU1$e>mdM$2~br1@g=bW*`~NTUz-a81ZbSVaae%H zaxij(Ud^>2HZDeH;1KMjT!0oFP(=fc{pY?MK;{+@kctdA`fK=r`u}|XM*v^7u7CsT z^=yDpZ~(Ca&h_tojRWY4tbi1WlaveC4Is&Y*#7gt0Efi=>P2Mb0;r}}p6WG|77OQJ z=`de&CcH+NiQsFH|F#1V zG@!A-HG2&;$7{<;S%KyPU=(oeK!BzA^)j*l%T_MHSI+)d&Ho4$2)K&?W()MM75^jh zY(RBvf36D%h{;73QfRX{-?)7^9V;#^*fJ~x5F9Cx4s-=4U_v$p}{1e~TPi(;80h|ut*J1+- zakBx9d}YL5p)_C%C(tE<$N?#3xLy}Yx&B`IZzR|&8E0k>V06~-fEf~~?r(#@f8%n1 z`1~uCqnG&ol>-Yu^@k_Q3$8-RkcSC?#1{BNy^u$@dxsNItm6YymXQR zNoJ17Y)E#o9i_O>ls}4a41N4yl$l?owb`Fjpz0oR#!Y6dPzBrjk1VoBHNT_;eZwhu z46OT34hp)B_mRdMC*MxI=GtC#@Hu=&tsTTZ!Y$sK{{)ic`%))j@rA++^abeye4fjQ z1B}Ee#P1aWx{^sPwY~D5oFd~#I(m3GpEoxxOj^3-R}cO9o5sD;Dd7(*qoyKix6few zf*E=L`R((+ke2@#N&f@i`d^0L|5tPi7!CkT>;FQx{;2l;wNJcuy1(^_f2z;8S%I&! z|JFqSpz+_yEkJ#ys+M}w{__0twcz8Th#DPPK{R;`>Kn?0o$d(^0=aZ9#7>rFcg&TbZDqasl~1w zr=_`=kNxhvSo685xLNgB)APqd1!FcD=wc}<@nq*68wT5Rx2?cua~r-SrdT3@iX4GR z5RXmsUt-`)#mfRqc!3eRmQBk_T1u$>?MC?FiMh&IODyw}n_ld8{0&Ko?##O3?)?k$ zU`Nzh9kd(5r-U*4I00-oIL(= z87Fm-H`YNz&52|}g6ax%BR%aQuI`ad;0{%QP)26Q!wjVg?9#Yp+&FZ*SVycMsFGML zp9{aYam6qR($V?rJXu>T@N_-o>g@dQ+PA_8R2)0G!E6>R3crqhq)X&nGkh! z7c&utB26I5ol&0%;F$9%YO`HqEnir@%_EYscA znjyu=lF+B)SWg&6Jl7gMfO(Ynnlv`xb9VL+p6xBJMR&vYq0XTzEu2o z#cn@JaTG!Shq$v0s-xW!bqH=DxNC5Cx8UyX?(R;2;O_43?(XjH1a~Jm+|8FWN9N4T zovQoe7Q2exyY+3ln_X)?Ydr~AlZr{ozeJ{PUl|Xk}~vuCVAPU zgkdvxB?ryV))|~mQOd>^gkz*x?J+4ao4W9DR1QBbx1WJ&$t#Uvr>FLqAA6Qs)7V_8 zjcGy`ZX}JFx1jewqU*&kDc%t87GA5Z?UxL9+detBkH`1AQgKfgEt0h*&~mcW76vcE z$P0tn0#%Yp3D#lvJ5x)MUrU{R$7o{#AjFMn#wV@``hyK*oTYoqRvBEQwVx(uPV=HC zO?DYD9>atx@sSJX3+$p6xerv;$iGk@02XTR1}tQ?zhgJ^05kTJbD&4638u{Cwhz6k z#%_EBL#5fqkV(v5Qn1%9F8wMiB2Dx3$FJOi@h*?sy67)xw5B5hV%iy70_9PzEhrdAOg8~0beVxP(#ostynTTz2W+I9oFeFm{KHCgYp07-7?!Nr7^aTlPYB`pqV$)EeV)AKH!qrZ;X2DL?r2VRDE86nx zHVz;}8aky$=3<*~Y7#d7M?`A6Az=CAOSZ(dIaz;2J{Sp?ebos z*XWiUE^pZtT!JOe?g_w%hd!CNoI=iuhrkPxxk!hAwNKR`sHR(U2e{-m_CEuy7?>RHZqHXPq1r;Qly!Bta#2c{Cg%AY|FkofANPhT`W4`rL+ntwMq}sgRB+%-5;x zB!`|8g?%TMF#<)uuMuyDq`S*h2Zkk0^l?2Lm#Ap=-(m5A?a)%222k06yUEtt{x+$T0qqg% z2Q*O(Nic?*py_^NTjK|_*MV;24?_1ja)DZ7Mz7*QeU^j>?{27mi0DoHRU}~1973gB zy+^*y$T0QU{&Q#7vi)ai9UR;T2zBw#ncrSAwmkap^Pac-BW~$DB#3p`g`VHj>tN(p zoo5#1Yz55XE4;Mz9Fw#X14jmtA!iK?845Nj-ixV~G9#-8EqZTr&V|;s*7sESEzk>k zdGI{38+Qk9^`CfRwHA$!CI=&-^8@KTQ)J$x^WBboE*_n(xZv)(=-s%ho;l9AbRcM! zKhFtw&jeg-;np}kM8^V8hRg(Tzwo%A4OEL^4%DpQVf#z`hT**9zc4>J}5t==qNczJ-H61Gd~{d6IGgg=l2O_*L|57v>Q z3UwzqW?!4UPc9Eqq4$0^Zr9lZ_4}sE^WedspZA;IDO4kh?C-AZ4?@4E;XY@Ah>P?; z-Mtrc*>b%JVq2EQ5t$$AQ=mZ~PM>J~n#YtRz6np+DEf{$i5z_*fU^mPfs|;%NYc+Q zFB}K;>FcN2AUT@c9}MkwRWg2pVWgbZPnB&?I^u1ljApllhn*O#Lr08kM2Psr1Z=}! zx8rI&dXMrw-i_aC1>W)FU%dD{xm8<#|15ht&nvA=HHobZORy|7JUpSfxsQ*D!w54E z(=rq7^Z%~Trsgrx^*zCZp~(^|zd@i@ay%=+<6h~&{wKB++rt~SgQ?>?2^XD$G>M0V zR5ZT)0@KsQZ_m}YfcJhjN7!AW7;bA6Aq$`NH_SsB!(tWfGY##?6GVUC6v%`ZiIk&; zl<|@rxx|w&9-AzZWJR7IBb_6~t2_~+ZtUk2?}YE_u_uuEz1(i0X{_oj%NIAeJo0pi zWM~Kv(lfwbepiX36i0gfU((9yYZQeV+;|uCp5s|JMITEXw(y)gn0$<2CcO1z@Tt;R z^!h4?gC1F`b#H|A`f5op=}t&Ged%Hm2iz>%HPgnKb5Ri4nXoI0PTb5=CJw7(BCr{0 z-Q1#sVE4jcoI6%6!BXc#khY5ny3v!-L&(XPYsXVAh%eIE5cwTF!D4>$q;Zvtu`hmo zSEhm#=xDkTxbRZZT5==cihktAI$uLqma-ntiUagz< zj;Uz+{E1Dkmt$J`4RlGjKv_)cXe$U(mf5j?Q$Lb7qrsIZHa*so-pv6Xn>e??`ex-+ zqpo?V84@9$W=KYO>Z>WKd*KR2*&O$K6I+K>x6$~u#tWBnLqnS(ZM$15PpVc^;(!v? zX8Q}y0M4ptL%S?R^(Lp|)p#?Yxn=_{R?|pYkRyu4^}yOxBk&1`03&{wBmL*$mkM6H z&%%gyZPuTXPdt%^psc@C#sjMs6QOaLf&7LPqG|}wlu$CxWuX(-$Ti4KSP5tzM0Yjo zOIZD`?S=b2Gv_+ZaNBhSiGqjqbdoSIwV|$WaFI&$vFMQvkuII*)f%!T{e=8r5bYJW z)9yTyj!JBH1XlWFhuN}Cc!cP_%xX#OC<4sx+H>S>s^;=2Ta^Su>&3Tv6i2SnuUeS7 zVq*kC=A5k7U8c6^E403En2rbsGy|W>S9u7TuQII44qaC0#FOr*iG%gS)ff+2`=S#G62+ zAS@28>Ybwm1*{i&Io*f%6%p(f;*hNqf;M~GPq2(MiVh`)q?rq?Bl*ufBd5mjc1Y75pQ1FMTP`Nbbenwak!`NiD=bFonyDSrfi*y|0 zNFGRt@aP5jXkvn@x-`;*w{>v)JI!iin`~}c+m+{}_-m3NuYB9%J&7l^n;ztSb&&05{7EoO)2m9iAZ+mwJv%MCGg(&(w zsw7szUmP}mT&%(Nqq){$m_u&>h0dNCWIhG9d!Kv0S7nr`Fl#?cFyrz;N1-;6xBJ+p z13z`M(~O=^Hur_8T||M3^u))WW?S}v5w8S^i_-JVvdWs+vZ=*P>xVUkB{UIk;0u#s zQB^>Dr=#<)K`ApmdR|*hhbYM*r=~=(^rtUlHe2OWmDF7fuTS>(Av||xE)FInPOe*W z=C9r!*X)!BAS4sZc>gYYojWJ=GN86g!}mhcV{QA#Gw34QbKG zdd(_p0#)RSQu?0j`>9#Y7VikXcR`!F`B_8M*J9b;@nE(Rr;vi;oXa>Csi0y-0CRNM zw{V28W#=T&9_|Gh`QRclmMAs(b^?0XUod?2F_HywDua>y#{wrZii>Jx-IOZvj%o>8 zIt!CTz?8+W9i+to+`^vie%qQVqZ4>C86jyqLe)dMz11@2IUH(Rw`BAlxhASKW<$5_ ziUtmKa+LaqcelBk>^q=J^7I%p6zn$r{Y!rF$Tgwah?pfRgjL=^4Znc&FwJJ#v6knr zijgsuUOjoOodPK#!0-5)LXUOXS`}A-F2)OELgP3w(zZyyD+rbugj8BG*hpw@J#tCX zPeg-$E00Ah0bI8QqdvpvE^@x?dBTe6zLcpsA}&VsBP5U4iMT0-v)ypaJdX_Nc>|AD z^w#TkF3&kRjgpSiUvePFnh2t!H*jy=!}>O;sy8!m*zBG*7SW$YhhNl=n2x4^^x7y) zoMrT-6;E~i%_{Z8kE2+Bx#tV6q|+uFfsARI?h6G9$OKEN5l;6N5jB`{$>8p)YMgVP zk1p9w5ALyujmvA!9Ft_!;WCgYQ*plep1E$)K3!*Za1iZVS|4=E%DqvAYJ3mH`_8%B z<6Mmo@ihV#c|+DBB2bWh#Fbh+BhLEFXT#3xEk3rKgaLd_ARr#L5VY*a8h;?{82SX% zh|rIRz0WE&J?|H@Xu5{81oM9F9F0U@d|11E(ay7wev|&VqQy+;!k&VO?3%Sf@q(4(CbmLm`tD|KCwMp#V+Rak+l_C~}9&g@za&rT}_ z(dmBG^8*$rwU`5Y7TQOv2lJ4qjYP1<_B-#eWbju-1{SrFCKp+R>>xE6MoNQF%jZed zb1B!u@IhQr4NtrH92J~$kFkc|3i2;&kAtO7b#*nm=D%s#-BS{)wcR~fW|YD1SE;A( zpgsK#N9!fyu+83oBL6f*FJ|fui*UK*m=l6G5$IZ)U%x^aMk65R7Au*r(6hu}jk(}? zzI@fK=B{zV9CRAg9nd<9G8>-Vh&Y6Vb&1c%_YyjU?bk{zl}<#Xf?~>MY%)AX$jTSY z{z)B%Q72fmMJQO*UZkmslg0*A^dyS>YJ-v>y|Z* z%Bi7gM3kbKW$0z)S+vp+PBAT|riX;JMjHmbM1x81T>oByNhJMMO4EGQ7w2a1P~fdT zF>PYz&X$V$k{tgk5`@Ndj!XGuQYyQ%Z9j^GHHX9V8^)ueck5K5hJ}0OM`sYw;MXf{ z=)lP)ZytTP=Z^K>JZHZc{!If08R)Ppu`9@F91|q?-_!y&aB+kVD|ojCzHc^0*3@`d zdm5PSF00a|4GneNsdC<3YyvBV97{O7k`9Z0V_nuqpKcYTs*9t18a7gr=d0c4`XvPM z6j@D+dpLqaMPbo7_#N61ba&e$wz3p*3#IG428#OAY5G)My3l|qn7?FGUZK%Vrx=eH z9>9*5XZ9Cuaz6H%pAu@y$tgM5Tf79Y8N-o^&5lyM?a`Z?7FDeA&zyiY6~0>Bn~F_x z@mgu}9(BF#j65xabJx^7`H&%a+VUjVrcLm%3xjg!^F%{Wb1S)&!~d2wxuH!Dh9C?8 zg6FZPM!XV)va8yT&dJ#+lF_EmnV6>3I1^bz+V&qBbu0~m4&EM^;qw62s?%1(mvyX} zO{cX}kPP>F1by}gvdey7vC?*}akM-2Givc>;Hpc7fMo~2u%Kz{K)EFl)rhyp_SogcYeV#Yr(%!u5@*mYb`Tie_8om-q=JeP79F;q*Xrak{IIX@Ns`RwL4 z?IPi{L%&dT1fJ~bNgS(oujq_ha106U7?i2N-3p3_2gV_wt%mR?W4iF19FA*^oF(Z=q8dnM2U4aRm1E zIMNj=UyWb+Xki$pJKdqw$JATrq_@ddI9;vL=)yw-iJs1u&yG(j`V(Uu;1Qh88A*AV z6{)YAVtuzi#;ho`2Q`MWSC71&qCJ3Kmy6+3g*|hQeU4X!a(QHz`I=nx*g4v4!LA$` zWgV`0Cnec=eg&DApcTb(pL3*&kW<_pNBJ~LNV*W77hq^9RvhbFrpca7w8J7LlZz`HfzJir6>X zDSi7o?-N8c^`%+GxOAClN*W$IOYr74CmO54HZ<!gO z#phAc1EEKXYlhS4p7x;46Ea@d@)yd?Rqf*AwaDOX!Rnt# z4*loFJ!NtuLZ^xvsOd5XR|Io0%GOQhaFqB558lodOegM3XD(#(kmP{1r^4mf2lAZI zPMAWpNf>iw<@w(#n-UvDfV(0=E;6EpnlBeb&W618A)EDzkRBINfcL)mq_Q$w&JC(d zoh~)WX6m>a(Uz{Iq==~8B`AbuqWM=4=?fnXu&dl>$5S__CSUVxwizfJTbC2MePD7QtW((x=TLY|^&VF8eu-=D zc3s+8dISu##NRkuxqA~MM*W@3Gia~}TeyoV6+RyG(+et9c!S`-XV&Ne`Psi`u|Cph zcz^Xg{ja$W{(s>*{-76tWY=HqSU-BO{zpI7{|(mxXz%$aBm1vCTK`M1gNhE&0`;S< z;@?`e0+@xrF$n*Hb};{qGWhS=j{j^gqWdR%(LV-3~T_{-~+}0<_w)+qGf(hV*1Z?kP&wuAj0828kQPX}9 z1oVu6BmLlm0ILJG`Zp-$AD^-^F;cSujvRnG0G6cvkYWLj2jJTLizoOP)*pBU00J-o zhz{Bh-UDEy1qjOks0Dye05Bjd0RHH&9WpTiW&;=mfconLQ~}^H41nbTtzk#QqBy@Nsbe8!&+We`*k$BrFGsPm~v}a+^Kl^{F~=PL3|Y7Ujir97Zxv z?Xv@Cr29B)MXJ7jo$*yGrXh63-3lrAtcxSU5<2yYK(LOOcqc-~-qbSbm;N(OS2T>- zNLf{S6z`vhyX#{(zs)#%^^rQ+K&4Wl-5*L;yUylUaM8M4269XEpq{3V@@p&CKT!_U zNapfsJi8*Aoug;gf)Ym|+Ibi zL!CsH3y8BLjQ(Z_Ih{Z%I6|rsqFzcDF5j>eZR8Ut^Q^FCG!l$3 zQ)X%}x%`VnJG`vn_pb3eVRqP0D1;Fy1z}^#FS}`TH^A7uW#D+Dqwnhb9+(kr?5O*` zi9p~rVSJcP>!=(n`DU(OKz4D3g#Z35`{!o8|9m(9{jU5sOyIx1i~rY@z+WAq{}Uzf zSF`7TrUd?pUHAtjK+pVtY48dV%>w!m;{kd(8|qp@xul(`LEEIx$9LSnxv@&UL|Lj{ zsn?Vx;>HlDO7kEr*BA^oP!3D$`B@qaI#v0J!c-%w;DLUoq&jmd$QMU-N|4D~r-B`= znF>fyZIMVSKr#VG!_#ysgv8GSfdE>Yd+$8ZdUXP<3lZtW9_uc2-x+B?$+p>iu<@=! z-vg2DP(%m;`U>k4gK>rlVXK73g#*)A4}DQCk4NbIjqn7b!Fd6h(>6&+x z2f@rHyb>Fp0yth7h9NLNT1KcN1j4ybSyv&DeRG z^uUc>(m z)j@&O^5!AO-YNjLt)=r#6asxhDCK#!WD+leL&{i7brk&YX6T;8o6OmOAEaK zih;h0u)As*^>>5Cp+h8O7G@^a2nJoP&mBe>)kxlb76U=Fn+&T_KTm(yRz4efg|A2l?~+ zJ#$gv585RghB-JO-Kt&LqD-)!L&mRQsXF&v)W~!GXn8)4I7OaI;ErGYB-R2}v(CG@ zWbU`Mik@wG=5k$-jxEs~fC9|3JHR)A@7f=3Ug1bxYd3n=MO}2^GqH zvU*g&_f{A-R8mDONnhy-7q10;^ev*T#vPei&z>c!kz5aYCBM$(+xkN(jNurjsW(VB zSquHHXZ>nP-cg=V%P$UAh^sLmIAkVpp_b@jsXG9tOOc9&cy>Bf14DK?WkYS2{}!i4 zIPsQ%Mc@q`=%g)UfSyfA!1UL;3jby+lgMm%MyQ+eu6FDU4qXfIDZDxg6kF%&)UvRN`Vu@Z^(_e{PcT!U@pg}!h-V`C13O(M}d;8#9XnG{&l?UMfeTSLLpp)BRs4S5Wn$J+|8*UlNT5O!M z7-8{OM0As6f|t@uZg;Piv9-a_47pz*E<2wElQeDjcN7=X;kU*qJn%Tle`|{;svI@dOS3=fKN$b09!n;aUZIIywfHZ-S`;~Jv zD=hlkj^CtBf~sYpMnI7KZ2Zac^B~(B%E1$l6SYx|mE4Z#6uMr?C5dUTC(u@1nT!mQ zg6DGza)f**fHfZLIq7+3gwA&R*(gwP;6JN#WLk9$H}#AgN9jS(Heo@Ubf5~!wVoSF zHkMg=`3u=8M=Y4T9V0cS$Aj>~lO<=2K&Hs@N@GHj#Dzi#ZAawr4PBf`qKUO#PYHch zT8FhnD1FET5nF%ir~$Gi#R2l=zGol@%)M{)aDvb7XBU*uqJ3BdpO7XA2`LN^5T-i3 zWx1;p_7I14##1kqa#a1X%Eb6ZDI4+rymaz0$y5Tqs{};%(T5Qx=-N>Bis+*YA&k<- zs04IZ!}#f=gXyA!4bsCz7^0m37VDvhAseM#sDjal9i^3P>ivOM#;X>fIzn5>FeVC_ zREur|+AD#!4-=3aw2atarkBKkzHbn~7Otx`N^1{O`%_nIkhYL^j6Q%ZFnbS;nW#6x z4u=`Cmsod64!wa1MjeHp$hc_tAstlVq`ShU;=2qRUY6=eYL+;Z6W&yS2{X_Wo_Rhe zE2&4&stT4-@Wo1KA==Xf!P60HLE`0#qR3scXeSJ)QeJ8Yl3UO;MQCu568;Wq&Pr&` z2@eUEU<3(*a!t?^5BPe|s}W>6n=k^UsHybDDTbJpUpP!q%x465d6G)<F8^Bb&J-OB$Tlp*(=kcdW*2fOaznU$QG9{~l&P{I9y|oVWKXARV zdq|>s$6=iotRb!Lz@(n@9cnEK%NDy(m?Xex2bTQ)G;P&>mRyH|Mg zeDC@!R9M?E$4lp?Cn;23oZqHLk#Jdf$bzDS|HM@L)n)}*pWi`#XD1JSApgoX+|B~x z!l&82wO>&K$!K zAX8#`LINs~pyEEAQvZ(RG5!I#fQwIQ{Efmr&jFehzdUuV%Z3fiLR?Gr9K`iSC%W6q z%iY1#Z(L{?m^F{?w3ZFhyeBUDJ$3A%b}xh7OB~6tpu3MSdP6$eeY!R}7AeTJu!H)+ zw94_o+gH`qh)XD6*2<_>*0Ab*8jI?1=}n>HIu$Yobd&-4yPf1(}OO*oxCrm#yUd2(dzYd_n{I%g8vh{)kJSxZ|+J#l^Zfm6XO? z=2WiMC$oZ4CS?AE5)@01A&%I(?yoZ`H!Mi;aR-Kb@nGu{T8S;R8cE6~6f^M$;t@~bptLC=X5njt4dA|9Z4nv`l@ebL0pU|d&rV0Dkr2S5o zD9UFz_ddPgCCj>mc?s)fdd;)UU;(FR!(BA*_Y5xQz_D~I>DJ}7b6e+>BkBa-eGOjC zDv^`s@=>iWRn+F{>Z<{f9VS7^=5fP#*-|Do)rGWG1&46Ts@8H#XTL#9xqhO(6-rhAN*$_H57(^M6^SLxLEooeX+n4w|E(rE*!+2Skp>fE&E6M-PQ{ zrudc~iTp%tai7HDWKE>Vx&B;o<2b_fC47ynMB*pvX&c)T?d*)3Cf9%m>{|j*V&jTs z_BAXQj<~r`?k+iezPGeO0=2b@HJq}70QoHTnNfX9L)=@Jur-DUG#ey*pVjUNh%|XT zIVd!u(WO517BicK1;cHb&}s+8#d4CLO{~yS|0T2Byaz1W4ryq zC=ia_60$0NFQ`WFN$NQ%!^uEt#i3}z?~S{&g=8I@f_WZQZJr}r0jh+e-f639z_okC zQtHU1PCVG)8S1>@IC|kJPgTyr((L5me0J#&7kK(xMisq$w7azZ6FpRGa(Gl^iJ}Q| zysiIK4aHX8)`i29_+5v(`QP?Ikxl0rri{=rA$*n3TEO0lwc&=?04V7_Swqa{em6uzw_9PqY&IwWc z#(u(W%D9Q{paR8dlHG8`T|QN0(OHG zYBe=!#n5C{MX<&oV^l^;Iacv2a;vC}A}sRC-*7)IU<~GkQ-cxBu{(pgn`{`xlPbif zCO5}XMpWxP2z1}nDYwmx0hPXikA2~5)F6ElRJE}%3;oNp6wfm*C_ zwH3xFzWdt* ztVzE?yG{reh8Flv$3^#E|}@qPYL*J?%yAsGrxr` z<(0UQq<52eB^bE~ z;KjA;qqt1MV#_Rc%s@FhJ)T8xYlnBeoUU4$2POk=N=bM3?0?59D-ZG!FI66(R_3z3 zr9NrrM)7N}tA5j@c)3_o&c6sON>s?6QQTL~z*KWRAEmUJEsP#iS`@_5ZQgh^S1dy3 z9-RojL4ARPOrtq1#4Rv(s4kl~m_!XGo~VrpA4P6zeIw|2rSu;d^1yuGIYzrgf07*T zNA$p8A~QSbkf z^d@xeKLV~Dg0Jo9ikkfnUV)ig`|oimdO%$D??J(T1fu>CQ2rBO{!=R{@Nb?oNkaou zN6Y_+s{`Uh{}ERI?=)wCnyh~*j{JY~oB_02|EegW{|E*DEyVec(foUm{I5~}OOX7( zAKTwUa{nI!<$r0;7yyp2KbkXu^X$W4_EB)iN)L#U|M8sxYSdT&i+mKAG0*`7W`C4t zYyfQ;9UvK?&c+0YebaqxfR*uME3;0&n) zN(uo}f5d2jI>$d^G?tHG{9j_UkGX%uXn;aE<_~omAWjV^a{H(a{7|H+)3X4A>mQYq z>hyob)d5lZzp@h_d;T-;k35a(BRKhIwZG(P41fdvBTxGn&R_Dhzg%vAN-xV+EAGecT8imkFR; z7qIL9MNAxU5dR$F)QlYt+4)l}husPnuBf#O;@d3_!!{)5DO10(-al`0vJ`dI2xqp!4mAj!W^>E#j7mm0(J)f>^oWg`k5=TdPp z!17H<9Or&6VJMCiBOT3ZS@X?m(W>P=@X9Wjle5&wMvRWeub36@kaD*^gvcVFLt zO77GNP;K&jI$96<_a`0Gf0;b|pS)`S{UhOj8+HHdarB>R^ZvTx{#mOANNfH568w*- zJ0Lm60H`AWA8Yd%=^6j|k~~X=bWsp&7<^Z3WT}ag`awxJ)o41yn^N_Yt9xqsQ}|HK z4^uFFAA!#`qAHqM=z{n;cz#7qTlD0|q%1&m-;spoWDOAEoguPvCxWsrnzv$}-|n4w z(eBzGH}3D;GarjmZ%XAA3#I1E)$bjcAisW6gGaeVi_xZ4!HEX{WQ%If0fSE024VTN z`rX!5xO?I77Dc)-U@~m{m?2nX?2%9 z)J-S3rT1?MxFwXC9a|m1=}cMPY=dZOs6-(7KJ%}bu`)S?70?hf_$cvaLWy0j*qoQ zO5@YBRS8d@b3GSMLsW|c11!N~*tB+`X&0z_+QRa>fPz&30gDM{Tkwt^-ZgNJlk~#~ zI;=;UQ)5F_x@X=CgNOHcgNF#SnMoDsXfpa?CcW@wscK^utpHAvj?ITxrU{u!`}DPG z#zCvLGQ-+YV!re`o8OE@vBt_8p!GF+0Oj%Q$o|+5w~3~OJV(z>(pu>t^oROy%eqQD z4sG%&(Vg4`=A-wR-YKU)dy_On?S`|r=QS`k7f>Kj3F|p$iTK+x*QGYwRWlNSZ0;GtuYQ& z>%98J8qsg=Il%!Hp=?q=_FXmpBZ zUY6$nj4KasF$0>p(MGJ9LqwkH0P0m?CDNFtuP>;s=I?;Xga=+r2WprUpi8=sQ>N1# z!o;A3;EcsF*r*C_iY|-&n5pEB;;%sDuzSL6s$k?ywG3tL7)>D6Vb6ZiH=xw6~~L4=+RJg+zg3T|KBP2AZ~bSbt4 zbKcNV^oy^F-Kh-wPP;H17_K`|AI1* z$)y)~JM9k24IjMCP86wyv%vI9q?W%0HIVGMME(?nKVykq2se9=m!VIYwb+Wj*LYdd zd*dQ4pelXjy&#PbsfkX9YR~mM1Y2xJS{wsB8J8pY&=<%eIT-blwe&i-WLrXA$d=z#ewXAW8+mh1MqxdR(!1@m6l{jqIOQmOd)92aM_--lC*%iB+ zP8T^khlob8so%tws#0)U=_+ao4+k}sZN$q~!h^U2K|2(ekmoi}l32X)v}^R6@#v#n zc$@P1PQA7lwKN5!74jB`$`lz58?UqNBp!txqZ6%ytC}dWDYZf; z>HFxr=@;!f_Yg-(7Hk(5%W+o;EM_fPmX9s8SDsgD&%T|0J5yd#UTIu{V;5`YaNb7e zbl)(o`mUOTL^`>}ygV91ct6sWPY?8c%9yFb;kVbW**hoXCdae%o}<%$6%78kDo*ZA+!w(<9l%*EUG+aNgPaQ>Z6`eylt4N`h%0TA=Z#=Ks9kR$AR2==ycfE6PNXV zR^4A;QhJNyeX4^r(_G|aFm?^VH1j%w68C_;BvZZc_EIK$zHRKGV(Avu0Jme%#4b{*^QrtX<3~w4?VWBx$H-i6 zaqIeN77C%#erXD+B6MB%4S6ZF*v(M|F+IU#tTKG`1~LK1C#nd8cIT-!N$Ek{EKG8%64K1)>pKrT6=pAiL?{vPj`-ZBhMcGs@%y=VQq+ zo%c1BPQ5C+K!v5u6qhXY#GVwFWvGn^&o-#_7WtpAG+4ggxdE@B6wUV&;WRRnE+dkT zN*9}kAIn0s5IxT4-KfvBF0ktJL#xly7l;Oja4Q$Ht{RyYG-o46u(#IqBt;+SSwTpq z=h+bx;cUoz10%o6auxH8Cw=y9ue0NhCQ}P-n=vykkhQ*DEGw3ZRC{G=0_G&)=IJab zD;`KVe!&PZJB_VX4?i<@7(bYunF*uPo7`d8!>S#(%e<|)InEno7%isXxz+_)Y@m!< zP9ME+!i0NBVCNyO$pRHl_&GvOOuX2$xsEzzP&{3;BO#tTEhK&^F}i%7k8{2|z8_o_ zzJHnV8f-hzNM1BHC-CAus{wCDLwSu9fu){jktWw**U%#~U+l2X!5;Uxft^5FX}=9t3U+!4nU zCW6Sqi$)D+?~i91g?RXJU^TRUfHxlUv z=|l<#EGJdVu@jQ$(Yfp zBRz|YQ=8u#JIW)hwnn(X7ZU9f4Uv-FB50KlkSCT=l3S3sC`gxOh^ z!4F?QU5(BLFPBIH51#nKmjO{fgqW%}U{p5QEP&Gs7)%I3*^7Z2(zkWy+(~V@Pr*wKnt3 zVp5}Vhj@qF>V|)sNQ0ke+F1HnYwbGudrPr?n)qaJ{A#lkbq$B%S3_3A&xS<(#Eqk0 z>h~&G*v!|nRTVQ!ZYc$w`@?nZqz&1nsm9Znwf#M zu~cLkb-9lt048VmWf``pGR4M!IG=(5-uH$U!#iHT8FM(<-C=sg%h>DfdQ{B(?WMBo zvD0H3O4sOaHapX#ck6SSJLc0rh() z1UJc(J(+J$=VROUC+{4HbA;|ffhsYOc7h~wUu;1cx~#bdq|O>o2J@VE!ZP^bAJ(F!3DAwj{W{Qdrq4 z4?mpjFwgR0(;dF@f60-^I?YPUJ4C`P=djtPkQ|6wC-WmkBn>4aA!D{@Cm};%NG)iZ zPE)xr5QSqAvm>F|!F61pIkP^HuzgD7kB=uKC}>C9(6Xj^#^=r$^lMTPwnz`!xVF4? z-W19el!A6m)d+U-_*$BB9baF#3b2G|)^8kWuvzw{n^^y<%&aJhjAtXVllB!~s47ZtEyf{{YN78lTu0hQh9`J=!A=F#Db%n-=^_PLTml_i#||l ze^&oo`b^f9xW~6c$iP0LDK#?xnkrIdBr^`;Y@3`CPx;+qM0n0}6|QE!PznKE+lqxv zWfkK^VbfO+{}j(YTrsnsO;lZKlGHne4z{A{Q{g4~!vpueZV>RTw+jZ}F4}Gvcs~VS z-GC8&2^o#Rre%FsmRS+loFu#AY)@}!s45b0&(Y)^rA2Dj%#wMIz3R`_@*t8z-dzl( z38$2it279$gc3r(6La#ylTjfVNOF?tPpsRCc$}%Tzw}$K@;f%ctx-p<5jXT+ab%=l zsW>I2kW$O}$b_aWqanvIp&hNAx;IIF8Mg<{42wS9y6LJ<#lxX=0pGc8@$*+%Bi%xj zSLe*yqzR5**!(0~X(k;Y!}`^pGRfUTKr|e<87Sjw+#@zDY|D#E(h1ydC%v|2d^~ke zN+YfVb%@)9i~((f=Xt9mDKs7WMCBCSzL}+g8T5 zGPZ5owvl1Rwrx9O+eXHCSMr>*cQ(#`_qYGGzEt<>uBz^??rW|7)qUUjC@5zBE~(|! zE2`>dt8q!L1vCsbKE}_ohOA0Pn2BL_^HW7wCbRV>(*_Tt2Rx1BlZ@+r-#x72FB{S9 zWl?17?gIPcvcESSQI08P=0`*amwd3U5`Pe!!@!lAxqF1C=xsV+WH7dhpRYr;Pra6v zaVq{&OJp2ht8Y zIJpplTm-2GCJg_iJS2Jpsz<`;>}mtbzS#2VFbjbM{VsMYxUwWhpa)6JBJB3JHh9(Y0oqUr4Z?*+U zU$Gp7j2YGpSo31nwg}zvGET+3BB31!-LpBCu()pD1##m&r{hn9wG6y(=ih;WC5>|6 zz8Tu#J;kAA;{q_=E*|t$ryuS&1K64&4QQepzzm@1@F5_Bcz=3Po${HVFIJigQD!cp|D>@zC-Z>D{km z3(;F7RCv|{{B{@>_{lqJuVf{**sf3&#^aFJWLEtI*DO>d%6bsZVHN=A5yECn))?=e z$R~s{dRMTdICEP@SJI^FpETFt=L2?j*d;%Eo4^`l?)5-ivxc8-v6^uxhrz1CJYrtp z;kKNvb<0TJBY0w?bt2zB;`C!bJ-a}g55qLYd#vsvHHCX1I>&o=bM4BLWDT_M3a^To zHD#6IZwOG&$ERI>XvTm$x&Zv~F6;ECCdum08veq`KffbqS2}!9<+~a*__OhfGZRmn zt{Gb>cG`V z<6*8M!uy=eRbxAzhB#{-EY}Z{C)V3i*B@ZFL9VGSNa1vY-$2({9$~eJHu|Nnx!MFf ztgnIFB)vkEhk75UgThh?g?H^OD4yn-$kQ;y?6%{4lN|JJhdcw-mvIjn=odg-w6tE8TFs4?e(7M8UI$C*6zCnWh`J# zp>N-EMxWVu^$t4%12h0d|LTl3<9%^(yJ0Q)UT*&snBs+B@a`t*U60Y{fd1aeyNZHw z^w9YVoMV(g(7V4BRaAtkd$YbOKl^TR-6`oLC?XOt?rtwLt$TCID=GP~KCqvu*R$UY zl0I?S9y!j%-n-8ZR9(I0j+N#f29tIGUS~GPV6mR&Zid1#8EY~_;$f|CFJsxW&yE=y zYWurpy+zJJJoaYC#WvRagwHIK(skx&xGWdrT=h_C2W&-VzuTCta)w2hu47{U^@TWK2O@`)eTcOC{mhoYaeDxCYx~`X@FHiU$VP`|&whJz zVF#e^!bV05Rj$_O+|W&ceF7@4vn%rnAVLkyA*({?!u{KwY2% zk(A)SDjonVcqb$WUszh47js%8e-Up!J5yZ? zDTRN-x#^f`7y!B)0Qvq^fuWTHz`+;b49iZ>LH0+5!XC z0*q_v|C28;K&S)|9sN@KP%| z6L9|F{P_2y{WnDXw`Js?mcajf?ElX=I3vK(R2^`@zgRFGpvVHyMbQByRP_HtbXl4I zR5>yH!TJ6ZKVxA0N0n2Ilvy7gbl^RAVA4$gX6OnHD8G&fMK^^1Pq{may8P zGfIwLWQwY-?E6sC;Rg5PHVM29@)R7Thza!`;xYqx(DZb)M4%JNmXhl|&vb!0geDrTy@Xi5s*_>~>85dPn@m@Be1f#NRIX zuW>{7pN7VN%|m~S8vjv+^!s}K0}uT}==i@plpZjF;Qz;~6ktsJPbwr=4`iV`Z^bpP z_+iPo%;87ItzjvhJ&gg&?x|7iyCCrDOc9r+nL)&wJ z*56vXyJeJ~N8iX(G5(>2>BEgNU8m=M4ywqLoh{mjqrR7r;{x`dGo4}Xh+Q_HN8gwV z&G?W4c!>%atJWC`E9zU$FN>2u>KXbp1vJ+lC-3g>-+lz|z8d=0Tw7|S$@cW5Ah{8J zc5|TT%RcwOwx6uS?P}#Y8kb>CXybvgf`vB6JW+<}UiP9+mUke<#;bRCP6$z!*_LB~3X0NQH*wsnCQ|M9kL!6(Xr|$XyNYY=1oyQ$geb0QBT&*%k1d+@3aN9SQtmF za1@F_KcUhmG(NuQ$vSv8*q}iAbnxiWzFmUQpD5_yi~4w9sA|7e-%Kwl ze+t?o+7;kBOGw%bXDjOVafuox9xS^N_>Em?%3XjeOytiPjWrEs zq-{*pdDdKy&B2_h3_WV0CJ^y#xF}v1h#bsmMEY1cZz=#qooC~TNH@vEVpaCnj7ufVeq=tC3{5VasW z0mBhFCOx-IMR$b|IRvs3N?R)4YF#L7Ls_VfrVwB04Yl7IIOd9G|;1yNowpwqNR$@n4Bh?X%yV8N}>#6#PvlNU} zH6VVZ{x~805N^wX;#L#e^BAzcMk1wY5hi7L)imwA?!323x2L z1t5SPJ-Z*dKDnVHA3@0i6T1(M(6<@ok77(hYDgy|v6qoeC`e-D4kRYyg-aXIGYN=M z3@eObghBBnLCavO9++S$zeyrg^nY{b*I9eGHpUZs$SWq$vcLSJ1b&_s84x_$)7*o&er5%666Am6MvygQ{|s!Pv-1*Y0A&xdLr zQ`Bco61Lmk;7^(iPb!|x+KG`IiZmLbT8a>EvPjd$dfX`y_{e4GW2?AGubC>|(5My{ zH+_&(?`?79IUu{#yL4Rq40r%bU9zcy2CR_7e6yR03>Hh$qH*Ztyq06C!}Y zIDDom)LgIZch~vSv2sb>-$*tEJ5zg!eOj3%8r)qYF`Zz=8=mLhmuSOCtL<}uY!pG# zrMZoG*w7~o$tn@Ot64>e#=3&WHOD=WNU|zJyZ7lLC6!f9BIv&t~4-oMlq~oBw$>|&lCveKg z(K(b7(QY%WBJqys>3fJRT;kg%vn!Np->HJw0e78Cs zcg=TH)#*8Y1qjP?#N-Fd#QF;iEe~ypG)Jkzh2^<3i{JY5EeR;MICg2|m;+N|>BzFJ zq77vEXGSvyw>$Yzqw#`Omtsvp9zk52h5fgd+~b00tiesESR!%U^iSM_F?o2!VRkt^ zLo)eQEq$(C!H-B{6Q$B!rJrOMD4CMe-xDHRSD&d}eQ*1bA|z0#bUic*ppcGx0$MJ? zK{fCFK0Y?HPs=&^Q<6Kn8k(x^%_11eTho4HtIl)j`GmJ#*b#Q(A%i49$+fH0ht(MEc$MVr!p=!%P54PeIIfYY$sR65^JOePIlAor} z5NK-hzWOHG1&F$mH~YzR6kF!XBlZv17e-gJlL_${5*=?bq~xgWY_}eCg*lXSfnD_- ziwRH(-9KW zL&>smdk-#fT&7umA$Y9V_pK=zsE09+qUE^bUvqahjmGN~VzX`@vc_F3!hF6v*=Ge( zjR3#IivH$OfQ;Bp^fjb4H?|C~u0|h3Rp#&|Dt?!0Ux60?h66M%HbsfRAX<);x2Uu5 zRBR?uI3IyTNzda`X+$!Y7~f1Z5;jrpkU@m7n2_>UyUch(d~2UkA+OrzY@_vATdC#Y zeA4+Be{`$#<~qga!-0eFRni1jN%BtJk)eIfInvCmpDsp0HM@_K-0K`p zRTc&FuGqWkX$OE#_slYs5j|m{R2vOf`g0y3PN_=JJXFD-+IMFQ& z7|jg8tIp8?^+)Kh&SqPltpO47i?Ys!5g$cKcY;c;PU=)XC*PwMb~9$Xgj#>7k;~X&=WpFa2D=t{q90cPO#tnPE_xWuW3pMmT2u^Fw@iTOh*JOOa$Bi z)0}tZQ9m7N44%Y~2<+qMmk*?U!>8eFbtfoi%wN7!3LxiS+G~f>dJR+z0F`oK#^E6; zmE65qku+&4ZAVqH%~djUMCrjnW6%khwG2je08M6a-H_rfj|!;2p5hF3EuGP9a)m}} z^5oVu`{HEL87n>JrD6tUgsxZiLC*>!o9PE(o{1Y#iHwF7v#uETMnsz8FB|kav64@p ztH8}c7{_zPworw63B{C!pr;WE&` zr|E6q{mzE+wR;~#uWylZmmq11&cHBt%;iTy0YOUyd>98~vZUZ}pHGTMVbud8=oos+ z7)k8)k%K$0HI@tc%9Ks(9v}RjgD11ZiVTt{lcU*l>pHPxt60!{sQWp3s%S=tHRnp0 zek?F!LT|qu(2l*)VvNyrR}fPx2juud3W8xvEQ%cL*LJ?UfKL}yO2!HADBKszA}h28 z5S81~+Jb22tX|~g+UC&&q%&>CL}51Ai$)IJT!=Ctk@Y%dAc zrOqIU<)@~I?w8u-1S+cfA^5cBSQ(Oqa*c_F`Kk(}4n&An2t?QxTzkg#cHqG+R0&ot zL^+<$aj){T#ugce^2Y7d@7!%rHCt0IT|FQX*3~*(W|l0Ai0K}TH4ev+zBwAHjt=4~ zdEG9B+>pw)v`(d*9(Z-3@N5}BrjyiYpAccZT!2pvx(h#ijAt+?W&>GMgQ&^-XyDT0 zc9@|)V1*^4jgKL*(Rzk^=<+j5gSlGmE6q@+XYJnY^zyOLPi`voq#czl+38U{K4>tX zZbtkR2nd4*hbv83!E?$d?(CQNM9Unq@62&cqC{S%WU7K{qG@-+ehm+bmSRA?`;Yg6 zgU$52PAxYtA8ln9RLU`2w^zl)QW(1HDXyOVc^HP$6EoFdgkcjsxXX}jfILx09qD~aplTJpt4=NM)f#I_Nab=dv9yFY*zMXq%s+@4PGMumTGD0b^@C7}3sO?wdoCG24-Jzdu&>ORS8CAbhES0oN#MLR2M79*PZAU>?$MnHs^LQOr0VZY(j4siJ zfFA0`@3=J%r^7$jf8)<(5%?TokL-I8Pr@9RC6geEQJr54>cpjV>L!4_K0_de55yY# zGBetNveIkT6o+1xPCxcoZ4RlS{t)q2gG={o&R zxK=p7(LBj~unsE7efhwQxPDBSDTmGaJCUwzLD&|;nh7hR`MwtM=6q7|#^<`DQj*$@ zoxRnE=J4!F&BUe5a4J;+63dBW(!5|MCm2L16Bb?jDhTdQ>ipYIPjTl)gfM^Ku? zG79o%FX1m)^2=ngHc?@UQ?`AtFCUOzWEbp(6+?7zzS4ma{Sb>75>nHGu1Epe&g;RC ztJFkOceERtil|-~Br-J@{n#AM?ZB<8W4y~jR9MCTdCbc+U;U8gC>q}XU?XO6)Qv~R z&S%8LC8zlO1#8@y72i)Ch?6_$kYc>6K&4*9XG8ur!+Yu431mKE`Y^Ax<(w!~*+^!x z`FM_WVf}F`q=jSHp(Q^u=Jbkwr_!mBv&ziYbTkh{QeV{p_<#w6r5J*|crE?f6v}3V zI%UkmFNMy^FN8yhi(hnaMm?1O4ng;AM#upAn=gbTFsie`mTLqHrEbmOcl`}yVUI%X z{(i{leq52<{cYn77v+eHq9d{IPY^@nS80_#9>VXMQd$-7F)2Zx254|n)-FnvGgaEJ zhU=X#I&SXHDpBFk6P$MxY`vOGwYO2`B+wRUCXQOY*29>`#g<$vice)It56R_lQQ|O zy^6#m@Y|mWec69#2OJtx+DJR7Mgo2+D!iR&)sP`VkluTKM;R3Lam!W}7lAU|ZWKX| zh5<1p+8Lz|Sm$B8aEQ?iD4I^MZpmbLoC2J5!m*Ggu_e!ui3py7?S zvuY_B%g5KdXG!NuPu-U(mIj6xN*2oD%gt!zAgN9UDxP|VFN-bTZ#Y+5R zgLx6=Ax-LR&5v!KFX}9s@j{%geLb(s&GlE`H{=5nBEBlc??m=M1Yz;kKvi=YED4EQ z7BbINbKY>;N|QOq%~*|NmlSpN*xt=PQCIi6AJX)XPM;UAl~2~n;ELa3K?9KsEW&Z- zZOC+wBXSqSp1})!rr3o*ww?9=LS&FmNq`Pwb2p2O2mR0_Z zk#3ZW&hAHm7?O$HO`^Zje0{@wc}q!^>3N)x+SSj6`{6MnM?w*;;6?QF*PwIWqcf8O zwOO)_VhV93{+F-zgT3?ijfHmL8)s|Sa6d8jnm+CXvmF>}2(1DuRch5( zu6QbAKR**f2zV#Et@ZryQ>#Q9IkZ$ryn`oX4+#PIpgo+xX)vVHIZ-NvoxzH_SFhoF zww@CPt!>aGuZQ1e_BhdAIoVx;#wTJk?dz`ZGhpkWQ}@M=+3e=BrLT)SU(VB>h@5jz z_EkH5cn7V5vQhb0lEA?5hsE6AY16-FYr_BF(8zCXVeoJG+J9ru2r%;dh1Y&XZ2rmD z{)^a6fJxx*c*uXSW@HEKNdGI8@c)Y41V|hINp<>%Z`$9F`rk0N--c^{f|P%E4*rH+ z{vYT~|H$0@B^_n>--J^@eBs}%78wEQhs?jy9RWg6fRGaqkqKDA`a3i7cSI%|z{BxZ z3MxSR2@rt-d;;(H1 zL7RXSOh8otr7H^!z$)=qQYGN}Ffshfto*A!7J2{}W%?DE$^x)x{3~}6;9mxat^_1y z{;#(qAU^f4%t}^(AtazZ7G}VTUyg|YU8?%89s%Id@3G4WAg+Ja_Pejl>@EMO#-yo@HgsOH)KkAFdgSH*| zx4eE3!7L0?L#?T~+)$pEk}u*#HD-$D!08X(Up(xtei)aoAs&3xRA@k;GGCa|LEf>91^+1GB0un0P#yVl3p>C+j|-e-pohQ8vk6uc1N zDB=o3vH7bY)vSvBl>_cFKu3Y#B7PJEfz<(5pLTEt3T^U0o_G^K4M4}q6G_DOHzGxd zCh;3&t{d*h?9!39yS^t-7GB5yQU;z&bQws;cEU91iv<`-XhWge9 zhLE&>Z3FNNW&MW%bCwFFp{Up~=xu)C$rhaa89p|+1um0!ueiuW-0lIu|#7K2^5AxtF(l8{>We;&tZl?V@*G_ox24j{PYg+Ea(3yvU9 zO|2F7=1l+Ds49~xn~-_6dTng|A%S9EVsa8onbUSDKlmfsYwtxWduN$AXZm9DE9P_X zn1;sAmxGzN-Veb@Rf!W7B`G|Hqc4(p2|Jc6lq0B$_?W(&-XC8xP;QbYGOu({CiB|{ zo}NZjZ<0?RiaI|+>v2_ouW7TGfBuOkrT1=gXRK6{y@HMvu7_l+wD*!q%x4_}s`VWV zJcYq}YpiBA`G=ls_45a2L$9SciZc*Lj#u-;3pH7mPR_fL?9kKJILq2nRGJd{=S`fX zi<+m>lF?GgupV@C=#!7;I^~)%l1arjynJi{rZpBn1HX?WlZ*y5g&QL(Wi?@G<3r8p z=w~4vrHD6{CfcroMds?_>`hOmK9ff5oaL$w60#nK?%=-Av75kc>+1!F`6oEU)s4K;V*ePAPGQ?OA6G%rnh`FoaQE-_Ah0m$Ktr7Ym?*bJN$A?4^C z{}Ri*?#g7o8cJnQd-@WK&P2z}$<65tpS~-S-11W-8fI|oM}wOAv`-py;}bBy>TpQd zF#?3MHZ*qJx8gC)DtBA+ll?+Xao51%$di}f1f z$|U7VnbMSWg}j(EB7W4Doa z?Nk|4atd6U`dD;R!Iurtf$Q`cTl)ICumuO^ihilpaJ3_5tg}f>7CWmhmx2=saaIh%ElZ=my@vh zr};A@*4plu+{ZvyztUp+aipf4VzL4T?Z^{bu=AaWlPUyIdB*kL=ZADkw4q((*qlN( z3269Y0!@RkP`$Vm5(>dc6^aUk#?UXeSuVWjIkD56v|6YaVUvBjS18H@ZflTAJ;<6z zrFmq<;;9T;#j;ik#|b}2H^^9IeZuR)u#xWS^BM7J z1@`!Ov8eb=^lb60cxQNLewv1D(}aEU7*rK_(sp&*$Yl7%D_C>e)q`xlD0buvy_~D$ zDq&0UbV}))(rF#awh^%eeIFaK_^TQfYWTE(r^e@gISVDGJ!_MQjVpSbGwmH)jA4=h3PLnY2$_J@T5vw)yEBqTfwFgkdzgX=(TA2hs}E z$Pt%%q0^oCd#a0Me!1dwYNO3h+$hmuGKt0Lg&N_BYJrxG54=R-R}JFp-cxra2PL(M z=Oc_6>$a?VNLc}v=%pQgN81Y?PmScmY|5%3U~>n32a7}%vlO%9!p(xyef1uw^SN@= zQ+~1jQpY5mwDTp_VFnhnNg*KH(lhSc1Rh9L!YkkBR~YKG_Djnz!soV`YgO6(boWb- zXXhcWd{BE2uEn|A=QOf)%61;WiqOqBb;x}ucP!hq-DOvR9up4an+LY_u28a4%$vo( z>n*tP(p%BK+&jYQu-0klD{R4vcyXFS>AvVS;jZ-n3-+6<%x&|~+M6X(IBs7Kf zinEDZpdi1Nx3osU7#1=oVA8PcWgY1^iRY)CNXo!*8NWvs7LL!%Y>}tGd`(VF-M((o z9BANd93?G~qA+@#=X4GoWg{EiF;JCANR(CD-eK?Ai>w&4$H#w7xwD(mJZGD~R_z(7GbpK-vfx9X|{9+bLCon|PR zu5daY9v)3ACPgoJE_n|PeEyk`O0@+~Sz7%zmTtM8!x!M{o<^Gdvz(DNt2Z|DzHt?k|D3pNm@B(Lo!0_~;A)p$ zpC*;~u||O+eXb&{E40g<+~~WNdZb6xZ>2S41GU#H$6?R5WR4*GKKO-iVPHb;ODs*O3PuzwVkl2**X^S_0JcV5ks}X9t zhA}qf!;F+S~G>}mqRmbfpjle6r`7L78l}tJr-5M0YO59mVOMwLh0H5jTojB}gWZC8gsv%YJlms zA@~kO+PQw%6Hma|VuI*P(4-sU6)f|~JqTucMx)scG`hQj-VM?|V-Q6Q#El~QA&=8D zWz!Xb?12k}%iRu&um>9_OXc0+Br==S3R>v9i~%cA5>ZE|VXvuum zfk|@NwUn^x!gpMmpP@Hcx3~^j0!)V+C)`3VEhigC&wyN5#wXS%*Qczw`6H>@mQKF= zBkoA8Xrjji$4t6cR8I!~l|tI-gCi)78gng9Mwu$^c>e-oshRM{kulYQ)?F%nhfJf| zb3~7LiU>+;oayBNYMG6aQN?E~(_1{5mPt@6-)%N)4(PgTsJ76m3tlQ(aSC zx{ensC-ciE^NS}HH4Bjq3z2mThRY{G%O{}ACqK>{hwoZGxocXx=Mna7IXKt{u=#z4 z74Z?UCxc}p@Z8susrjj#IP{?~5d<(jOx1}3wVX%Uwi$U#)&?iy+${U6N&+EdWI3Z8 zTd$#q`tDsQUPtuuDtk`|nc!AJx(UGIJ$uqhnk9%B4f8G(4Rry^RvkheW{;%fT#EcI z6utE5iS6WB3(1*z|4DtPY_;ueJ`PckdLn8f3LVAE$!lHadtJtGA=Q^hXv*}ay3kgQ zRg;Cu>RPovvkb-Z+LGz^NO#Eaut-w3WONLr?o-XLCabEusamT2I^izPHp6`Ow;=ZNnUMd%P%<^HWEC} z2`A8<+}mQKw8JBZ3-+{6qMh7a_nG_38>e^sbFFKnlXFy_sVftP24ZLcKi@VQ&4;4H zA3|th4#PZqam(j%^ILHw#$Wr%aofj9XB~f#&Zd$-8mqcW+toNnIaW$iRXF1n25bZw z;Ov!Q65n$%8OpVXG#SC@ zmEXeZ+MBp5A0N(6E1_Lzm6oRcEDgP@I4l(&7-SBwKhu_9gJ$|>T(jP z7}$p(*Yl$Zq7R7M6@m^#%INT84kwD=8%%^$R56lQ%$6O>vDE%#-R}$=uXji%g9q6=d`K-5L7KNTM# z#Do!yUXyHK-S=%H7tSsAi(5R+HAi1A+!ETCqo8n3s4D}Yr_{k1aMmR_Db(iz0`(x-5$ z+kFa(A*7$Jdn3h*=M=F+4vJkumv*E3!Ra?0$3DaWM~HS%l^!gYr_6x6WPu9|r|L7k z+q|2cF!Z?wzIj*0W!QFzbWlRN3*C+Ya!8DAfiqw@PKa%iGlZOss8El}m3!cu_9jpS zX*j(vRkxsB>TY0)ptJ~rD?&jGnuL~L@xE8WHc$<8|1R|KH!^yjK`KeYQCoa=teIiR zVQ~$?iSGX0d@|w#@d*J8c3j3GkQ7s|pujlA6vH=^PF{A^$faF^VN8X%WL?;1bo*g# zahh(<8+odJk83+Byvmq1M5tly?2(sG%3El=3<_z_RiDF$O63H!{6~k(t5e#fuiajL zyg*rXAzlkp5nIQ#VJOGcH~n})Z`@Sgg&bD5MRCPS`i8sZL`8Z+bkW7UTWMR~jOM)U zc1<(fY76WNXGP>KaNgY>cfVzH?KKSBNaB)|-atySTD4ZwN5ElqN*)0aClAE@2kkM%R-r%d3%=m$56; zg6ADlI1GZOxZV51DIEfJV!Q zCkA&rdf$AJuo*^l za~CZRtU8S|fOUxWio{jr8ho9gvxj4M!DZovF-qLa|9;5&s$&&yblyg@0(O_NyZf?Onp*)d%e$9JA zL7}LWOk!##2{YFkr?^yOikKmfxD>9STB_G=XWkLv&~#i<0q!G5s+XYI6GHKk2w?Z5 zR4)~?C*9(sVDE+k+_+RPQL`uf;v=y90^EdDFIlrEzP~M0#TVelq8k4vdukKO_^mGP-w z8MC)1`2`!B3l5zH?@&zbLS}B^v$v?lN7P?u)bk1&MWw7`Q`fJ(MQ1wSwR4X|09{H< zef!xdG~@l|-l9G8n)>!GHRCKHwL#77**JTv`E|xwOlpIe*>hoX{x$=!Brdf<&g^*r zIBhHW1=fJ>YMMPc75`p%$6atxd9NiuhjC(Sxt>J#dKRlCB%-S-^}kdnttDh4EBals z$D?9v`dxwwU?kebZGAl3u;2rSgE>3Kdzl~hn`&2YtbpyY&Y(i)N}u?vgglLapotx@ z@kJGVn}D$O3wWm^1F4&ckfS5K*NFjwjgW}*@6s9qF4D{#-$Qx45J9$b8U^G? z%(>rn8|t(yto$y)43_ou{M8E7`$s42kc@wQoy@@ahfn0+5zoH|TOxmm#;E^F9{am? z?9c46|3*9pFz);-1d8z==@vl!^{?u&e-0mGre~$0`=vemQ$I$}2%sZ>$ANOaEZ2`hT2@|7VQrzYvfCQnvmpki+m#ASb39 z0CJ#%F1#SIR|IU4mV5C&2@B%~m*+0wbX6zLRxJwK!n!^WTOH~EVeP)Z_gN1Yc$#NK z;_(chz!E~wWArT1<{`L!E=LlA7&vZne4LMmN~0|o-Oa&+8ZiI2=j~Ww zcYk|XOtEGJA;;a4>=v6jpp3^tYXzReRstt9T2iOvgZ-`bb)@9Cd+2;Lf{FO#82>!* z`7L{y?qI?E8z?y?YrB8FD*&#me>+ou4vW8C-CryJK{)n*42S=gvoQWvH~tf6`7K=h zL-EM?hvM;nISV~I0~??t|B17(0+8aLVD8KV$^~h#{(Y>%Vz{O{mRhx8H#jqCH`!u1 z?xn#HtCMa~#S32Y1D1t_{yIF}C;kwzraUH77b4g#GC!S0B^`+8ZoVPf zd^<3|YVhzlO=KE+$Gld&L35@l%`|+0;UBXY%F#uLX$ z7YMGeLtMfDyxru<%)v_X%C)bCbSS=&u-VLxtHyZ=$^(lP)5#Y*ux0CeP!?;D6H#Ga zF$BFiDj;F}^hwDPc9YG8=3|*@w93fm0tWp)+0$YR(Vj9NyO`*ufv%98;vSSqKG;*( zB%~nVWUxJTtTym75mU74?^$^$A}8=2$(;HuHR5?0P=&IY z5&|J*@k{s|i?GM}Jx#clze{w8UW(kB!gBuXnJK=kL!$O86x$_tk8z~4uA-L>k643U zz5&CPf_f(c*UvG9Om{7!Fp-SQ9)nIV&0YvB!Y$`#%~qeDoZ`EMcY_8fh(16>f3LKG z&WxC213B0MI_(E|VNLb}(yb~7?zgxeyk79UfOsM|x~#W5x12BK8vMUYJQIpAjYIqp zlKX~kkey2+>ca=hn5W8%PE_n$hXU>|359@^FZ_|2f^PX<@bY9fN%1@}d0@y7;f^>SutlS{b7L~QNQ;xeho?V&4qq%e z4nlQ!e(6R5KD_gtP?XTou;SNUSCw}tDy?Z23QjF_Gd_|934p`4t7DRx!v}Ujk)evu zAtS~?On}RZflG@?mK>^$NtTt_J4#F#mhJN+mO#sW%S6ezkz3&1=#gf^A{hB#mTQV6 zbz-U}H1mVF6s?l?v93Lf5s|q+$FiCUs19Zg`oKXU3bAh)kUE(o zBa&^?72}J*{*k;II^w3^OOy+n2l^%8^HnJko4yb^!FMhr%QFAFs3nDY3T}jDisG|J z`>i2yb6q9w3MDqFZt?F#h(v)}L>;7h3Ec;44ZP&e60IWKm8+7Hf{Dtx2TbT13q#|2F;XJnHIu>F|c56j`iWN zH;iH_;z&|9-_T<|Xd2jhGw+vKAmTL}7-~7BRqDW|DHyl-Hnj2}bJ_%ib_cqob|yV4 ze+~Q^VCn!Z(a534WS=k&Q>!=@fqN@y8J2-Un|h*WA*9ioGZ~yVrbuytk!m#KRrcQXMG8 zZn}Gpjb>*V2>#}2$+@%v{&TZ@H`P!Ft3qbdd!W0Gb_9-r9@oPk+d@!mVNRs;vH|m0 zC$UQUofK!R@MR&6V=MBs^>Q)z2Y=E%{*Ck2bNQYN;v7TdrIthfK_}4$M#ZJYc;bp@ z7FY+&a7wT8Mff8~3Ct1;RP!eEqojw{m8F5MQ`CVY^ZJ`W9NBDhTm=4e^NF_qS^eie z;l5H>9w$fa6t=v!OzunCL)Pz9TH*>Hr zWMTfbuoEg*5q|O?je*j|Y$&!OI_KRsT0ORUp8CCCCtl8ZU2O(FU8l*dGuLy3L)&16 zezplpC-q``V+Ol{W$+Y1z3`y*d#R1)AtZVmNQt`c-#|~iWfjhUz{-6`<9RBb&pAht z=+oW~&^{cpRvOz?F_{#6jhKz(W+KNn3~tbR1A5nf5yiWoOYGmK4J5KUPyR%6@`!jd0I}9#?%BVaOyjc>{R|(V&f@d`+L-o4o9Ou|~iE z?#uGK!B*3LQ0k2+#ciV2GJBgL(;Tp4{o78#TE}Qxy~7V zOfDlHHiQ|+Ehi*{7o_NCw4Yo@xSrXd(+T_F>+y$NnxixH0Ie%X3sV8*)WM6Y+(<1a z*b#Wd4>Vb~C_4@)jXmWb9E-;lRx5ZH#OV23vh^>iA3?h_e?nNIXlP3>14CSAZE5dE z`$T$nb&hsIyN1yv;2dNf@E{8(zk>}`eAK9aA)VqVw#|Ge283zu<-E&gp*R9#i1-Z7 zg1=)V4)deGD}Iuo^^+MM@9k$%-j_$NHvyBMDA=W+@`%W9U+A&>5#HYUYU*(Qu= zq7pr@I-f{7l#BQ*wm;8B}rE${eQ*1by$^8*EUQEf`EXe zNGQ^^o05x_BTuYivCJcz|*oT5?0_HMG%0pgJt=`*eA{&rKM+Q4W%abwZ9 z=+q4&OKfX}6dyk;Udx&ac&+!p9S7lmV~7`LIj;X~$? zJ5TR$IFb~5l9&<|?O%$~$#(fuEy(ggOrozHi$%(iok)!4)Woj zUaGvhgoT{Ct`hi{zR7cy`t<#}#o(Isa8~mParlhU1@VioP@hM4Cy4Cj&_aL4N`9<7 zpESG#qto39)Vv$kt|NLU=(`X}@rqdD=7;;m;_UQJ!&1&+fe*V1&p9{fF(2Ua z(5ym13;C@AO~Tjm1Js{OX}jToTAWs2Dj)PucF_Zu&D*t$Jd=+yNjZd8h&5U@g`n~Mfe{gx@usjy+M{<= zf^(ZnvXgHJ=RI*vPf{~*%2(N6J+koMxmQ8USnhe=rTFCy^>&n67Vdh^>j}N#B0?5Ihk=A>#O_RJKJ{$BQnx8jIch+eTp$; z3b-%>Y0L7*q-H4}yZT|HYn}Vhzo*c6^`#W^_>*R&j;g+uT_!F2J=rJB2W%6Om!m$; zn1>ib#G3Z{qM!M>-?fWO5iCVSxqj{&`Bu20_v_x5t&ukSi4P%)6^Uk=@o{BFw!G|S zX0}-D7VBe#&$uSrk19(=-g0H1(X>2_Hm%J?lWE&DDaIywRw-Lz#8ZfM|B`BY$b)E$ z4sXLYJWD`_VYN@4Z$$c`YjxY#v|^P3PVCN=3-!P)do$n%wsWtPc71nTW`nMob3$R* zmZRA!mj>1JB3pKYt`_izoCaMg;Kw!HqmRFS{q(72_SDN>)gSG2F14YYUcZfQ`HP@+ zfTon#x!vZ@^e#WuKnn{xk*N|}M#nu&de(;?6*9Ptm|1fyxJq%NG*s+j{31LUR%k|i zvgonvN)H55(!$kzk91+UMB8GrB29Gp;~HpZ;vyejXVFOxYN#AA_zxtg9K^7mvV{nV z4UQ@uEU=#9OQaJYliWH`WjRgED$QL{%vxzzJ}@KwWf{^OpVhB&z)1RwGUUQE#D`Zx zIDDSVFijkImr3=FB&6zP)*U(Jh%6RntU30ZpLvMW@n>x^eonA7Ymv@83Mq}7x5@lz zuH-eu+H4+D`eMEiM{Wyk3bz?A#Gb8nRC*UaDER&$94{{GK?dn*+ARG;G}sNy;>C=V zI~=lv6P*uv_<*Rf24BL7qCjCZTKgcPG-4A|!j^hW;{n+KeWP#e2kKr?Hmx*M{Yfp^ zk4GL6ia*xOFZ)p=%o=ttIWleRdR}X`hSco&ui9B7+Bm1i2bjm^(Fh<$)YkO+DCTpz zolExjs;}+IKF$!Asssj63r4s(n!M+wvD45`9$eX*`T2Bmfc!W$x;EsEADzAD$vC96 zW@Aw*q^m20){v0f@~Efdz1jQNo;7`o?yjEN&Um}Z7fa+9clICblO)OKQC3iC=(8?3 ze<{qdvXT^oWvk?heA`qG3R=K~Zf&r#Xx0|jnFM8z!l2wWb(*Ezc744mR%;YaL#^sbNv-HGS zc09E!pXZQyT`DJ|hj(y))KC<-VfOoc8u}yI&?PNSteB}K61@U<7s(?Mj>U*5H^pb< zsd;EGkE+`+BpF(j#QHsU%CArTl-ugnnp(lEJrEVP8@r^Q z>ilI+>l*#Ot&Y8J{GNTT#Lw=VpLI(2Xg)e+w#kXjt5#FIX&cD-X~oS-@ze#yZFKL*nY0QaX>OqDx>#0 z^Q|(oUf(Bo+_!035Z6S)%{d}eIxL_*uXn|oH%Z6MMQY2Q&ZP-?GEnL$$LKXm)-?p? zD$=bvHZ<7Tq&DXaHTBv%keV5k_t|qQ`h~?9no-)iNJf4pdrB91b8^|XxO9epxwNXP z^igqX`l&3ZQ?04Q!T}kh%hXQC`#rzUdsd)(Q>#%e@r0sGNn z#He~%;*9bWGE?C$H}6C8yooz17De70QOvx@ZX*d4qfcAz@O+)k>5BOvh&Ho=v03?Q z$%DzV?DgvD>rT4cx4nZqcnM6;8Nw6AB`Ff$MoY`sf8MRu3nGM#j`C~^le#>;`Mv9j zF34#KqcKUR0cS~qBqzxU<5&SKX#zc7GSRFik`nybgl<GUzseU6r@4FG7B3>{yziZ97(*vw2g7}?7Z0t>7~#HI_-rj`>Bs5u__mK| z=VQoFEfb?pVPq5q!C^Qd1p%=DVxJ)_80@Q$c-?1q*TnLAf;P(*n?&MW&Tv9M)oZP6 z1<>%Z;|{qlh*#%H5>GX zt_eOS@Q1_wr{p~sHzGM1&7Fc+&KgyZeCvKFL94n=@F$g+n7NVDo^I1B>Q~Wm)DsNDdxTFs@&`5Jqh4!S z4O^P$SPUA75D9(s!aWkRm}#WH^O&v5@7j$GN=-wS&>H!#t+#ukRo&n*-^zLU&D~#4 z89S&s%DWv;RDHSa+?b+;`?XyZ-x*5^gJO2!o}Kv#HvMjF;O3EvSo#ppTi$&DxOZHzW zbc*gj(&lXS`1qbY)f6|3@n=qgb0LUr?+mj|hXUuG?)U&zEnbRhrYA6QZWTk_+j1Hph_U!3CxC zjmFckv>&DYK9cK|YPr+&F}%-bKJ%JTS}wnL+v$!D?N3btR~(<0)|VM~+JAUpuuJUZ zrRL=P`s_qgvEAQ;Ux5>+Yy1PoQM{5i_G%%?yx(oEo`AiRYo60}*sfjr6!NZ&jGRU9 z3~sLvmf*H!z=8y*YmHz6kO$XP^IBs^veD`T*)s~0eYZ;960KN2L$AFbO}uV^*Z6AG zJG*~wqMCCk_lcEFQ&Z;Lk@{^uj^S}}|I)g;IxDfw++7EM@gkL1nt|*RMWEMO1?pe) z$jb{n@TLUe#$P5{yS8f|7~>LN;O`6lgdSsa-5G`NMyTW(3~${wmQD3L#qT3A295{? zKD3jQs?tVPHkgXO#wS*-6*d%j*{dAGE9u6wnKXu9g%-CmQ`Mk``-@<&lXx*aY^k}& zFRKAulF}BXF6)D3@KT-XdR3(Ro|$U>b4j{1$56ihskHSz2)RN&7mS=UWlJk6-ogsD zy!bjALv`0NNys6IUn@HzG^U#@@U!jmK@rxWO4U79{rs?yS0far0=Z@+a<3oG+=K|f zaP1p&sDy{RnaaX?H)m{Q;Wl4yvDICEE;LKq-x1KQ|5T)#r8Il#`}E}?X<07qPNy5O z&E-o)3$N_Zr(N$h7~-wEMmMMPM~*u8$WNXU9?O_%74T4WOGR$?ba4BQKgSYJ#!P+t zf-24Xcm*>k<;~15;_NxcU7PIofDdS9Hy;)g?O*KoZ@Oii4h;B@;M52{-_{!!2v8T5 zYfRH&Eit&=`prS*TYkJpY7u*%rLr8O#h4;(L*D#%e`W>YUDa*x+FF9;rk?X8&*A{j zi_KRbUe6c>-rXa?H3_aZ$$s4Ci#8aB^W{X@qcWHNTgf*aCT59WKF1o#T~t(gbZ$$E zw-3K|mgnc`>0uf9 zhb7I&t_hV>6F82HBe9pCUtY#E6f%~ZS!{C#{j<4PB9-|)62>6)_2f7 zTk&*McvnRgmOCM8km`@a59-@rdSbugmcjj@T{l(NXl`U_4FQ?lvP;kErfkyjayGnlVGglYYk8?Q7ElxvD@D9 z5YV%kde+cwR?~1PP+9fvAaH7mS~gT0x@@j9R9X26|J4l3!_NreX8lVY-5pKs;lUj{ z)>paYzK73cK6R!ETd$1GY!mOKRlSNbWs^-wiwkXn`wN-S&e|Njz2A_OknB@`kMv_z z6Iz)b4%+ftB3~*}obpqjb0Tp(c(@=}y&qPl-{4Q3v-#G|AF$=6Rpwnk=(C6`F2OoNynq%QMPr6 zs@1~6twKbycO129u3K0R`RVcb^`{fd$6Bv?=+k0<7O}03!#}vp2TCz4SE((4wu~Ry z$jR9pvfhirxIckc9L*imfjKzFKv26LTGB^aSS`fKFs<%vb5DVaRgKhTv?}@Qq-k)M zR>I91$^W^c_aOgM?=gMHVN~@0DRl`x3!N9aL#Pnu|oY#eu?m*qknxQ^W1*|m zjK(T{HpFjb989vdk?F!=Eb}0CBbQNK1!IBGer$!(Be00u&!p|iH$RL*PGN(k&W@*b z5Xb{@VeJI)OP#UXmQ6$fG3+}X3v#>KZWI;QZN8LmPrpptsq;tt5hndBs?PIsh4c^JeNU;Lp|%6=@w5;F@txj zh=EXM>5&MtT_jry?g2ITvwKH6QvA}+n#B>~D;ZDwbTb#~`f<-kqQ}bIUR-V*=ii27Y)$_D@Ws^OyVUM0< z!J7?F@a6iq%wHeSn4^FHqHqtfLhzN?B6;&!B`b4~)@v~?CMUwd_5MdZ-}~D1^aWZr z`#PRBB-#ayZ!+$93=j ze?-E+F#~@gUj7I|`zN&FD%A3Sw}}3qHvffq`JY<$Z&1Ylg=^ivX@n71_q72QVBkJA zJrDFx8etefp^LcU_5FuWm=E9+27dX?Ckz32f59LqkPG%-*R4S?K=NO_!a#rXg052h z!!CUFm*4Edzi~jk0P!!+6_xKl{KC+ybi6?Mz%^~)8y|4JoQD_q_7BrA3~}{Ppi+>3 z?u=iN3||osBfwW=#J^d6fq2hXtigbc?~0cAHw7^iko`kJ%nNWMUqy)ihl2Pj?QaTV zDCp`N?CKsmz%~5Ozkg)^P!GedAS{UA$$wK1UvVY>Lp^-;=zpk(e}4m@Cpxg-#Kf@Q z9LN8r9_Hhv2kxr_X?XxSoDWV91R@50}Y261kWv1FZNSJ_7$G#fkZ@Q z5Ha`#NSch~^}WL~fx^`-OeN_CX&n((!GlenpZ(4`53O@UXm%Nc{!RVwzpMBe$$2dW=egK25eC?yi|! zc%FN3sNe5-X?^NmR+F<{yq+5x{_Lx8&REq2D}w-m!3LYy*y_~f>Yaud*91t8WA=T_ z9D2;I#0o?9L}$W+hh*=Jx>R4ITMfKf<@{lj#O7vBGVDjH8oohgZRN*oJ&vQ(I?Tq@ zD@c?HN%K3RkH{t+Kcr@HbCil5$+-Pe8|Er5%apv77QG`BLmDcE_si0ayoiY`lM{ciOwH7f zZ3o7uj~(p(3XeilrboPdOV$ay)nE2L{2`iiV{vPBbAQ_UcHfE10?o4EB*HdzV;@@9 z(@kFW>jHhx|1qxXcSH>&T-Wbl8GqXGA850Gz*HH zOP>4}F8zDyo)^E)mpdpwgE*)?u^7twy5g(%Otd2BCKz&9n7&!meP{){r+_sIUVp{;#3>BbJDi1y_`b+dfBtb(B?{{ilW z1O3!UXpu#Q(S-wL4S`5|h0O}{09Qb@t)ZK-O|Kb4k9-~e_6F_k&DDD|efq&h;_S1e zH(j65wCIa*eN4k43U?+5I;_!&Frqy1{+JLR9-+vAZ({UuQFXy2<+BMU>)EmBFDKvO z(Sgt3wf4~PO)QPjyFamfS2P&~A&z(@L#wu)U$?xqyY?Boh@yp-vVC6AYC6i`^XYc) zP}YoGzSs3-wIT~D^FUE-HftU5!~SY{G0K|fs%}IJgf8-7hEtSGR615hyG_FrebY~H zMi*l*1mdH}*M0VH)W7lY*<#+Xt zmYx_3W}6IU_Rr7C2!8y?53)NU45v+1GJArQ*+!I`tjX8vU+yyR-u;%K$tHN=c0XcU z&$Ol@Gm}JH1(%&G;k}(M`{#i=?2q^K-fPLSms7c=wor1i9NhvJ3U(jN=Nhsz6dYE`(?7x1sEPmf^ zHApHaOA)Kb+OB$$$RJ%c$vS6s@+mhhuMAU~(i#b_5MH;>k4{UO-WM5d@`^&_U~ zE^Wwv>^tw2 zI67PJX=@)}aM^(Tu^Y+5Fo-cz4_=k}3T`{gsfR4J(RY?=^rs%iQ17w{tRAXx2M!GT z%|y?I@Rd%DXz)x(zUKvLWz!oKxRS~WE8T88HMURpr3xIIq!^`9C@Nr@2^AoJ zkb=gAiO)2_gQkzOBLBXWqn>ei2AlWIJA3H|qZo}CWmFl6K@H$+bmQxQ5av^|S)z|Z zzU}A==o@+8(A;=l@W_pz^OO9VMAr{l$Sn9GK!~A>vw{u62t&g{%SDes^FkYl&nU*8 zCI5)-3-+}^W4=haesFn+>4i4E5-R?yGRI_W2rb>KH?<%AyWH?YLh0j=Zj>KWhL2Qt z42E7b9f+JQ;!Nz%MCahltVd|wB5c5xqz!ChBRhK1(_Ti;S^?U?$0nwI&S~w#Uyzgn zo4Ei_kb9>hJoAX32t?Y4D@9#*B(J!&v5>8Gq0Sq?J^Ef$yCxdA!y+IMg;w0 ziwNpfFw1?L`}S?fsu%f-iofhp2jU-OhK zqaNnmekyUvXS}Y#N+6k2QA5dB)M|0+Wy0@5A396Q{=+8CTZ#aiCixOCA(%1EY)heB zGh%0X@a$FhLQO;3W&pO}eZgu$lgd8eyubpH) zUe-AFGab7m-?>%~Dfu`!LH~ZjP4yGcMMo0_1$A|W2pO&$2^s`lixR6&NpLf>gdhz% zmwIhCFIv%^k$Fd2ovI57MJ>U4`pd8Ng8c{cXZl1K;%{BCA31dQAx73rrLuze2R{npDAaS z94!R z$)JT2&Xc3_X@;SpA1m&%*!Gw{jI(0ul(}-~w>Fa*Xa(g=e8uw}UX_`0hFPd0#Dhuh z1o<*2ZdG482`+O}E2;M#_LGCUEOzMiZ)FnKMXh#lPS}b|xKCr3ed2muey1=KR>3l9 z4}*18X|e`?@?7%h?db8X+mK3#Mi%F6CK z!}z2rAb=@+BE8^LRplwqls9DhT}S84c7Y+h|BJxap$}6>nZavkttLmDToj3AcgbJt z>exn(SJ$Ru62%_Kj`$8$UE}q+W@~b54Eq+ur6-8v8EN*!?VI7T3t9=1BIh8IDak=s zIZrc)OPcW0Chm!&i4P|{1tT8W+EA519;p)3d4?(LYzL>s4k#jp0H@GeC}2J65u1*I zGHtxzIqj{DZyENWYYZyR?&=%9j`9Qp8Hc*CLZf1%Ik8d7miM1EJ#R19MF-t zERJmBb9^Ad=dx!zHn4<*-wrb)dJ|`4bK(&ontsI4$VZf@H|o>5ZEwYToK{#FK^<-f zQ?E}o=!oeyTgS9f6zFDu#I!KDRkA?tY4$KemR@tqp_u48mbFq;-m%}%YPmK?`)J9u z1z3Z804_RsdzKJF;izbvmt{;DN^-w$c}sDSdeq8YRn_$(E?&yroY8O-gM(N6nR1F& z0f~%5oBP3*&LCa6)*#&n`IIKlhJxq=G_H=|R1DojUcwaGAR{B`U^PbK%liH71Zce8 z*<4lOx+47=!+$#`iO>f{u#p#(xT3_anJzJC;@117f z_EMfIGP2d?8VD$VFnJt5Fo}EDc9G%-j#~1bSMX0&W!zb19cg3W2}yA_s<_sN zZIV$Pu~Iz2FT>lilrjPKhPZ$sttWYc5hM44{d%*g@f=2JY;mNb*rTtzb#cP`rfoFa z;A}Pm>AkG8Pp2RUHc2ThT!u?zMP=3vA~Qk0bT?LT6^@N$R32#^_})(|k6&Sa!!#=p zuPAFKn)wJZzO@o9JF;S3SNOwgIbw9mY{%h4b+&icW{IdqlmeH*vF3o$j7EO`&lfGU zYOh+m<$HsQTerwY;P!;pfp!|BMJ(WyXF<*`=c2@n>)s;Nbf&$Z-Vf8d*t-zgvK>ei z@E)(`aCf;GB(*q{G#tz5OyuybN0q#>3K|k*j*^jD*RW6Mtacp^mo_8O{gmvMS6>fT zYG%C_+Gwz3Q&Qk@OkX(ndV1*zdn8w(?cta$4l6$`4!<8g18!Zv%g=va@o?~&Ay ztP#t@|LC-sAfJ zpZ8X=1zM)&*c#~QUZqPL?c8-nb97gJM#44!(wJW?#Q?T(Ls8%kqm!|0=&5$*Kw^Rg z>Gk!nSgzL4dXCv2@Ad@P0!FD~g7p2CwwkJ8UlQiel+R8MRt~ngQ>f(5R(z536JaJ}H&Fyx=?{0}bs0_5srt!Z&f*=v?M=)6^>O zsBc==NT~Hhg8eVT2+!{(huEE~9&A*n@C<4Us(Ib0rxDzzbT}alM5vOy8jK@pm}^iR z5xlKZw(sgaH1uBK<3`3amoj{fdkb=SwqRv49lDJ=?#7qyBG-uf`*T4RD@^4g2{9>9 zxgU#5w_8QU>=d?&9^KPj^aa>-Of9ZcO=VO+Q+iat)hzG)WACnaXJ-zD+|qLyOO>xy z*Vd-)B+9sWiN9NGH@gQm{_3Qbit+dvEv9=~=)_~pR#9tL&sX2?uf=|4cft=9Y5uHI zp?#mU)n$F$l9X@1@(uIXw_ML2-Zfy^Ab6o)N|;uq879qU%*QwsYhEMPXc_nz@7=rl zFD6Bu9(wa+MWzN5v^yU9G&9aE>$q4C^b3}Dv6l=@RrRK8Axlpds$6a`&bL~(xE^9N#l6e(<0uK!OOnxKn7r~bo zSOX--3i03MoE`~k2xxwD?K5}cwXI%#5$zM6k(egrf2qwD6Fx#kEq;EFzlDRX=KtPcp3r_SjYzB_y=sgL7Oj~N(XKzXCVvt*-7>h}*Wtw&bLwzE z-b`tHNKDJ7f|Jp#SdPQ0^G#Z6)s`vbCBsOdLQa=#pS!FaSERH4(>gDOlqUr>)p<&< zxrsQ5g{NnW(D<@=niD5*aB5tsE}*}4vzrVd8Ijss4Mg*O0fqNc3*boS&x$Il z7eZtl)UBmjJnbbfdFFKvPt)#hI_6ZQw&dts6D<|dYh4Z+k>({{JKsP`s@?}w>7m2BS5IT}QY7bq}PdC1h{y;r?A znzw+^4VpOe22Qs%H1vM$Hm`)936&J&=NFfpx{>d_(5=@Pd&__^UEe5iwF1 zi;n($GR*yo4sDn9sj(ktWhhrp`48uNjLdsB?_&^Wu28A1c$H{NOc~2L(gO55g}f&_ zDYN#S4lF)~A{;8zQQ7UAf_UyxldE1O&%@@IbJdt=irDm+iRH$1?HYL41cSNa?bz8T z7j=B5%$FLGY}n!3B~wDrlcZyJFMi4GCGUKK zh?N~YKEi4rVM|kCTHkj&HG?ebKGqt4WO4jROY2&YL2XIJ+wk@U551Bb({szRvR*uM zW_pZUF89jC@5bCzC(31(Fm_O+r;L~W0x5SZuH5gOH%=L{OId>+uO5!1`rR%zk7GI~ zefQm@lFr7Vgg7)uH1l1zMNqQe#JIIc+pmluvvO({ep`cKhcS%#>Y-q$y#&*as?&C# zffzULz;^J5H~mx*`VV`VcZVWgp_@A0C@Mua_m8Z8YJ0Ps<)oe(Q}!PIbrpj<+@Kvd zAMe)Jx+Sr)Z@m{|m{%&hZ+A_pvNLlD&_t+Q&z%b_)`ov4GMY;T>bv=q->!YS+&_CZYW(&2EvRAK82?94 zHyk#%@$;K31A41vIih1)*A+B5V$T(eDqRZU z7w6}%6@)joyz#yoHdOu0r#*hHZ;+H;I8W&+!R6+0pCswgz17{?vmcN+y!cONG&b)v zLL>^)`UuPH>oh9@EBtQVxKU=IavUJ^>aB|UBQ$rpuT?qi6^Uh(H_LlNZf3|}FPu(y z^=9rlwmX@aKqC@qQfVBERN1=stlBd6)nh(L-6!vS8?B!XEmB)qVRUTmvPfUIn)078 zKi9b!rnI!e!!p=3aA2ggT>vGiqc<-(hEGObG=H(2nj_d8UJs#Z{idpygU#D>p zMBjWvxuS{Zi^sB5%x+Z8XEbpRjSj`nj-FS^KXzji&Vb}|lV3=Kl-a6k0x0@LKi)sh zVpG%7xmAGo#03tIkF9>s#gWmURrxaI4s)C)b1qY0nOfN!K}s_hMeZWzPYL;#;Ol-P zQEexmVwe?G7}95%=1tCNtY210%`aSkk)n|17bnc@v=b2fc`QHJEb~4^ztq5TDJ8y9 zctY>o>*dHdmcsf}(ZygLY!>So2G!k}w`C?yTe}izr_XPtw*awt8X^U$(oMiO4MkkY zlEhLKS+NR&$Y=4dsogZ9cz5|rgdY+&mi{>~YA&E}tOImdNK$kw!unNb?8CL^`k9AplquZZE}xX_m34^`rJY?;<K#X$-?kNV_;bY|&uzpFR(7s9`XXEte@bi+8*{aM>lmr(m%JZ(QcGk>?qyV*Tj*mbz; zKA0ffcO9}i*VC=)oqr56tdzGv!~0`#bYd*aUOZ{#)6<=*cMFKWPxUSOI(Y)|US<8w z+XoV5=!AjE%&m7K`m!cRTk)$Zw6r`-UKr#YILKqG`#jvAc4(a5exB=)o27VCl%5#}gMt~GC9=gO>)PCHcu(vmD5+^!<|aYR?Rmzs{V$uKIKQam zlj$!CzCHWP*?L1mvp2a-?4ukj;=2rM=-G`2dGp^V1n-_;{#?k(iF#t6Nh39QH2T4C z;KSQS3m+q%VESZL9|^NJ6gw{c61lFqE6u0ZidF+PpYtExU~zdas!2&+2*XwpeS|KD zZ+Q2efodz3Ps6U#!nwWw<(~e2=F-Y@6@$yL9YcVj+t0_2DCgRs>Y&iU@U3^AbjcCV zRLZ#d55=>%`6b+W4!aleO~VmIx7S}Z3Gfj*a>VL=*`nOI$qaEQzOT#3sy?yF;xlso zRF+)Wfl$RFoA$W&@sFjN^A>ip$K3RBu+#4oS=vI1nr~GU)ezfYN{)j&fpT0>IIbBR zY+WC-Qx;pWrl&{h!s~|OcU}VfL#x)-um_SJl7X_kS{WU>)%fz&bgW#@9Bw$OoiIGt z-Cbp1S8yLJmvj)x)U@NI9n`H{noCgZJw{mk00w=G!?$JAN`CjZyN74XrrFwL4F_Fqfq<)H-G^+Is*U= z3Pw-N=(%P7jK?o;Z)eY`VrJoDYvc$dvNf`Bq6gyZKm3Q|8VrK*{1epuPu^=DAaFh( z5QzO>yw?aY7YGOw4+n^_{{U)$|KH>C!~PYP1R&G?*F))fcwt-!;Pm_|F8{wuM+#eV zbwKzhhyyr5|BnZm$RLj2TKWsb@xSQe|00e934H*=cxbBgMQc_F>3y| zgMJ{o{+|b;deM`1{#OV6?jgF*D9B`Zt4KuGEZH5({o9VvViO6iut-;~*?)MQUiJ0f zwbjaB0@dZ|6z!rlxAte`4H97Gxg%WzV>6HBH$+F(Jw+iS--cwjbX-SfPt3 zQ@+coww>Qxr(uRx_C7qM@Yp-&$?kZ6+J!K1x2kI-Xu0X~mbtBo!zAQ(%4*JS{lbXE zZPQ6nt>*&0I)(1yUVWJ0Rn3g#dJP!iWBJ^#8ha{sifvyj1(T$-f9;17RmiD6*Jul% zv*lf$6Vj1>Ixw#6{OrDy6xeGID>}l@LTQ7=k4u3Fcz3>Whw;^;Bl@4uFtpS|MBI#m zq=Qq>;1+123RDeDqjp%AHd|nPwNA13jH!v)nh|%5uoj5hZiQKDiEt@tVAY8*V(hU5 zf~6k1u}HrCB8L70j7?M|*@Ku(q-4>OT7nE-?00UYfA^`S?=0SX9ubNYYFz(ClQs}H^1KQb^Mi1#X#&Yv>iI~YJ8 z{Uw6{;2{WLxk%|CAQTx7;tHAYS2`#kj1L&~f6BmoU=RYp6Z|Oy=qM1pfPVjyL1BDg zBpC<>f&%!QztRCR7y>}H{h1e#0VMP&G9Cc&gq)5S2)=|Q1A`!_vMb;eaylpgyh4`2 zuDa*y^DjMvU|=`^HbR#1py(6^=0%{SgMd)<41<7C%K(^NQ1il2%YeWUC^`jvH~_r* zyUq|^D2kpTd;mKKQab1r)C^e$kS3tW082oT!BOlF29SQB=oHEeMrj+A5Ad}}<-$N$ zWAy6t-@1oEu6Q1hWY8;e5+oTMjG7JsT);z4$MYY`;6pA0uyKG31vxJt4-9#H0N^kH z825J>aG=YPbp;2;C9+-sLKYBmd*LA1)nxfs+u$HL%9w$Jc)-XyfP=1pY=7ki+QExl zXJF032S%z31jY+Rt{-3oJOD!Qujc`9Ie@a}Zy5*Tj2D3-1A|fQ3IPOoLv9=5 zKiCTZtwb4v2rxizgIq2I#WsNUqS!M8=<%z-aaW&zj}HVd5NHlr#s`9+%24J75CSlJ z)O0Wa%ZXG5ADkCOFMI$W5lT8<0HXPKUcfVfk?n;C1coD*0dN2z`>?B!V#sy?z*G4E z*ep^Re85zRB0~U}Pvmrfk)z1?uBec%KL6G!F9_8q@`4~J_QDGgDk1BI7vNAr)(bBP z0Z<|#m%$4`))g;^54pV{Ucmg2^#^!Uls@7GLjk-iQW;?QRXn$=&wsZKjNn1i0bo@S z6o1Og$9J`K`719U2v~5S$N+{N6d5oHpvX|{8N>$y$fQux@u18zd?3Cn`1s#-0fSKH zGCnW_Wt;<^4#mF!z8z)W=L41psAce=_;$W4t3<8~1cdCH0G*=FgTUSmIUR@(uxBJa z19?$=1khR#K*05v4geYS3J3nDUH}=eZbXssA+PJeKu;piJ%9{>JZ}Lql(_=T2ZJEb z@nA6Q3XuJGUBCdb5^~!B)g$|7FtFM^>4q)Ml+;3nAKwgIYJRrU( zimv!zyvTM6h5*4lk;ezHd`9*Wz>*q<+?Rli2ieyFGQfEv=>-ZxS$l&4ayJzHL4nRh z8gIbt0Rf>r4*{$^k;?#f@<{#w7zrr*05Fv2>hi$f{RZU$=-*IeKtdE5AAqk%%F7Ez z9y36l;Q;pj?{t6(A+ICAFhB{&?S(<1$m=j59kAIzejXIn#sSSEud~5Gmm}{302wdx z`U(tS^^yH4AcF#T36RNo5fH95SUN{JXJRbo=0fFp?fLj}2lraM^m?6)X!0>>h z*ai@27TJ#j?}kwO7mk3T_#8N}^+TyM58x4y+6ycRQS1-SgR&L?c1)lvLc*)hzvdo5 z21T)P;NlDj+5Y$tz={$nF9NU)cTQjywwHOnCD6p#V#?(h0Rz+fKa zb^tPzc?MW0!%*K908R&Ky#Y+{Amn)!kU@~gAmEAt4~SG3AQiGNfk2@MJBM z7Yc(T`%?%M*bE_!K?oEu7^Hp!WGMRzVBdqXpN7DIcNxfeVLX6EBh>}Q!;Ad>1=us8 z_6Lj?s31~aI1G+F-XL(m-6NL)xJhK62<*Xlk$nyXAmoH2_YpA6k;f&F4wx~K>Vg0? zgXCj@w+ArfeKH_Jpp?OLHG?2Mj|Z5Jkj4kFMh6~&u6=z;ggGIr+n^gwaHUqU-M8#y}ves>0h9tJ~s@faB; I6eaQgAL*~nW&i*H diff --git a/eth/api_backend.go b/eth/api_backend.go index 92aab3fbb6..c4a815b88b 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -33,7 +33,6 @@ import ( "time" "github.com/ava-labs/subnet-evm/accounts" - "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/consensus" "github.com/ava-labs/subnet-evm/consensus/dummy" "github.com/ava-labs/subnet-evm/core" @@ -107,10 +106,6 @@ func (b *EthAPIBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumb return b.eth.blockchain.GetHeaderByNumber(uint64(number)), nil } -func (b *EthAPIBackend) GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error) { - return b.eth.blockchain.GetFeeConfigAt(parent) -} - func (b *EthAPIBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { if err := ctx.Err(); err != nil { return nil, err diff --git a/eth/api_debug_test.go b/eth/api_debug_test.go index 4e9591e317..d3d95abfc0 100644 --- a/eth/api_debug_test.go +++ b/eth/api_debug_test.go @@ -173,22 +173,27 @@ func TestStorageRangeAt(t *testing.T) { // Create a state where account 0x010000... has a few storage entries. var ( - db = state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), &trie.Config{Preimages: true}) - sdb, _ = state.New(types.EmptyRootHash, db, nil) - addr = common.Address{0x01} - keys = []common.Hash{ // hashes of Keys of storage - common.HexToHash("340dd630ad21bf010b4e676dbfa9ba9a02175262d1fa356232cfde6cb5b47ef2"), - common.HexToHash("426fcb404ab2d5d8e61a3d918108006bbb0a9be65e92235bb10eefbdb6dcd053"), - common.HexToHash("48078cfed56339ea54962e72c37c7f588fc4f8e5bc173827ba75cb10a63a96a5"), - common.HexToHash("5723d2c3a83af9b735e3b7f21531e5623d183a9095a56604ead41f3582fdfb75"), - } - storage = storageMap{ - keys[0]: {Key: &common.Hash{0x02}, Value: common.Hash{0x01}}, - keys[1]: {Key: &common.Hash{0x04}, Value: common.Hash{0x02}}, - keys[2]: {Key: &common.Hash{0x01}, Value: common.Hash{0x03}}, - keys[3]: {Key: &common.Hash{0x03}, Value: common.Hash{0x04}}, + db = state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), &trie.Config{Preimages: true}) + sdb, _ = state.New(types.EmptyRootHash, db, nil) + addr = common.Address{0x01} + keys = []common.Hash{} + storage = storageMap{} + storageList = []storageEntry{ + {Key: &common.Hash{0x00, 0x02}, Value: common.Hash{0x01}}, + {Key: &common.Hash{0x00, 0x04}, Value: common.Hash{0x02}}, + {Key: &common.Hash{0x00, 0x01}, Value: common.Hash{0x03}}, + {Key: &common.Hash{0x00, 0x03}, Value: common.Hash{0x04}}, } ) + // Note: This test is modified compared to upstream since coreth normalizes + // state keys before storing them. This means the original values cannot be + // used, and the keys array and storage map must be re-calculated. + for _, entry := range storageList { + k := crypto.Keccak256Hash(entry.Key.Bytes()) + keys = append(keys, k) + storage[k] = entry + } + slices.SortFunc(keys, common.Hash.Cmp) for _, entry := range storage { sdb.SetState(addr, *entry.Key, entry.Value) } diff --git a/eth/backend.go b/eth/backend.go index 85da1b318f..12cd249deb 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -127,6 +127,7 @@ func roundUpCacheSize(input int, allocSize int) int { func New( stack *node.Node, config *Config, + cb dummy.ConsensusCallbacks, gossiper PushGossiper, chainDb ethdb.Database, settings Settings, @@ -176,7 +177,7 @@ func New( chainDb: chainDb, eventMux: new(event.TypeMux), accountManager: stack.AccountManager(), - engine: dummy.NewFakerWithClock(clock), + engine: dummy.NewFakerWithClock(cb, clock), closeBloomHandler: make(chan struct{}), networkID: networkID, etherbase: config.Miner.Etherbase, @@ -194,7 +195,7 @@ func New( if !config.SkipBcVersionCheck { if bcVersion != nil && *bcVersion > core.BlockChainVersion { - return nil, fmt.Errorf("database version is v%d, Subnet-EVM %s only supports v%d", *bcVersion, params.VersionWithMeta, core.BlockChainVersion) + return nil, fmt.Errorf("database version is v%d, Coreth %s only supports v%d", *bcVersion, params.VersionWithMeta, core.BlockChainVersion) } else if bcVersion == nil || *bcVersion < core.BlockChainVersion { log.Warn("Upgrade blockchain database version", "from", dbVer, "to", core.BlockChainVersion) rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion) @@ -222,7 +223,7 @@ func New( SnapshotNoBuild: config.SkipSnapshotRebuild, Preimages: config.Preimages, AcceptedCacheSize: config.AcceptedCacheSize, - TransactionHistory: config.TransactionHistory, + TxLookupLimit: config.TxLookupLimit, SkipTxIndexing: config.SkipTxIndexing, StateHistory: config.StateHistory, StateScheme: scheme, @@ -237,17 +238,14 @@ func New( return nil, err } - // Free airdrop data to save memory usage - defer func() { - config.Genesis.AirdropData = nil - }() - if err := eth.handleOfflinePruning(cacheConfig, config.Genesis, vmConfig, lastAcceptedHash); err != nil { return nil, err } eth.bloomIndexer.Start(eth.blockchain) + // Uncomment the following to enable the new blobpool + // config.BlobPool.Datadir = "" // blobPool := blobpool.New(config.BlobPool, &chainWithFinalBlock{eth.blockchain}) @@ -276,7 +274,6 @@ func New( log.Info("Unprotected transactions allowed") } gpoParams := config.GPO - gpoParams.MinPrice = new(big.Int).SetUint64(config.TxPool.PriceLimit) eth.APIBackend.gpo, err = gasprice.NewOracle(eth.APIBackend, gpoParams) if err != nil { return nil, err diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go index 0c451d6241..5204941ba2 100644 --- a/eth/ethconfig/config.go +++ b/eth/ethconfig/config.go @@ -157,10 +157,12 @@ type Config struct { // identical state with the pre-upgrade ruleset. SkipUpgradeCheck bool - // TransactionHistory is the maximum number of blocks from head whose tx indices + // TxLookupLimit is the maximum number of blocks from head whose tx indices // are reserved: // * 0: means no limit // * N: means N block limit [HEAD-N+1, HEAD] and delete extra indexes + // Deprecated, use 'TransactionHistory' instead. + TxLookupLimit uint64 `toml:",omitempty"` // The maximum number of blocks from head whose tx indices are reserved. TransactionHistory uint64 `toml:",omitempty"` // The maximum number of blocks from head whose tx indices are reserved. StateHistory uint64 `toml:",omitempty"` // The maximum number of blocks from head whose state histories are reserved. diff --git a/eth/filters/bench_test.go b/eth/filters/bench_test.go index 73b7255540..e14cca73be 100644 --- a/eth/filters/bench_test.go +++ b/eth/filters/bench_test.go @@ -72,7 +72,7 @@ const benchFilterCnt = 2000 func benchmarkBloomBits(b *testing.B, sectionSize uint64) { b.Skip("test disabled: this tests presume (and modify) an existing datadir.") - benchDataDir := b.TempDir() + "/subnet-evm/chaindata" + benchDataDir := b.TempDir() + "/coreth/chaindata" b.Log("Running bloombits benchmark section size:", sectionSize) db, err := rawdb.NewLevelDBDatabase(benchDataDir, 128, 1024, "", false) @@ -171,7 +171,7 @@ func clearBloomBits(db ethdb.Database) { func BenchmarkNoBloomBits(b *testing.B) { b.Skip("test disabled: this tests presume (and modify) an existing datadir.") - benchDataDir := b.TempDir() + "/subnet-evm/chaindata" + benchDataDir := b.TempDir() + "/coreth/chaindata" b.Log("Running benchmark without bloombits") db, err := rawdb.NewLevelDBDatabase(benchDataDir, 128, 1024, "", false) if err != nil { diff --git a/eth/filters/filter_system.go b/eth/filters/filter_system.go index c388632391..cf291091f1 100644 --- a/eth/filters/filter_system.go +++ b/eth/filters/filter_system.go @@ -520,15 +520,14 @@ func (es *EventSystem) handlePendingLogs(filters filterIndex, ev []*types.Log) { } } -func (es *EventSystem) handleTxsEvent(filters filterIndex, ev core.NewTxsEvent) { +func (es *EventSystem) handleTxsEvent(filters filterIndex, ev core.NewTxsEvent, accepted bool) { for _, f := range filters[PendingTransactionsSubscription] { f.txs <- ev.Txs } -} - -func (es *EventSystem) handleTxsAcceptedEvent(filters filterIndex, ev core.NewTxsEvent) { - for _, f := range filters[AcceptedTransactionsSubscription] { - f.txs <- ev.Txs + if accepted { + for _, f := range filters[AcceptedTransactionsSubscription] { + f.txs <- ev.Txs + } } } @@ -566,7 +565,7 @@ func (es *EventSystem) eventLoop() { for { select { case ev := <-es.txsCh: - es.handleTxsEvent(index, ev) + es.handleTxsEvent(index, ev, false) case ev := <-es.logsCh: es.handleLogs(index, ev) case ev := <-es.logsAcceptedCh: @@ -580,7 +579,7 @@ func (es *EventSystem) eventLoop() { case ev := <-es.chainAcceptedCh: es.handleChainAcceptedEvent(index, ev) case ev := <-es.txsAcceptedCh: - es.handleTxsAcceptedEvent(index, ev) + es.handleTxsEvent(index, ev, true) case f := <-es.install: if f.typ == MinedAndPendingLogsSubscription { diff --git a/eth/gasprice/fee_info_provider_test.go b/eth/gasprice/fee_info_provider_test.go index 1a128482a1..e87057b0f0 100644 --- a/eth/gasprice/fee_info_provider_test.go +++ b/eth/gasprice/fee_info_provider_test.go @@ -12,11 +12,12 @@ import ( "github.com/ava-labs/subnet-evm/core" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) func TestFeeInfoProvider(t *testing.T) { - backend := newTestBackend(t, params.TestChainConfig, 2, testGenBlock(t, 55, 370)) + backend := newTestBackend(t, params.TestChainConfig, 2, common.Big0, testGenBlock(t, 55, 370)) f, err := newFeeInfoProvider(backend, 1, 2) require.NoError(t, err) @@ -44,7 +45,7 @@ func TestFeeInfoProvider(t *testing.T) { func TestFeeInfoProviderCacheSize(t *testing.T) { size := 5 overflow := 3 - backend := newTestBackend(t, params.TestChainConfig, 0, testGenBlock(t, 55, 370)) + backend := newTestBackend(t, params.TestChainConfig, 0, common.Big0, testGenBlock(t, 55, 370)) f, err := newFeeInfoProvider(backend, 1, size) require.NoError(t, err) diff --git a/eth/gasprice/feehistory_test.go b/eth/gasprice/feehistory_test.go index 7ff971e2ed..d507507305 100644 --- a/eth/gasprice/feehistory_test.go +++ b/eth/gasprice/feehistory_test.go @@ -81,7 +81,7 @@ func TestFeeHistory(t *testing.T) { MaxBlockHistory: c.maxBlock, } tip := big.NewInt(1 * params.GWei) - backend := newTestBackendFakerEngine(t, params.TestChainConfig, 32, func(i int, b *core.BlockGen) { + backend := newTestBackendFakerEngine(t, params.TestChainConfig, 32, common.Big0, func(i int, b *core.BlockGen) { signer := types.LatestSigner(params.TestChainConfig) b.SetCoinbase(common.Address{1}) diff --git a/eth/gasprice/gasprice.go b/eth/gasprice/gasprice.go index 899c2eba0e..b03c611e31 100644 --- a/eth/gasprice/gasprice.go +++ b/eth/gasprice/gasprice.go @@ -28,17 +28,14 @@ package gasprice import ( "context" - "fmt" "math/big" "sync" "github.com/ava-labs/avalanchego/utils/timer/mockable" - "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/consensus/dummy" "github.com/ava-labs/subnet-evm/core" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile/contracts/feemanager" "github.com/ava-labs/subnet-evm/rpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/lru" @@ -67,7 +64,7 @@ const ( var ( DefaultMaxPrice = big.NewInt(150 * params.GWei) DefaultMinPrice = big.NewInt(0 * params.GWei) - DefaultMinBaseFee = big.NewInt(params.TestInitialBaseFee) + DefaultMinBaseFee = big.NewInt(params.ApricotPhase3InitialBaseFee) DefaultMinGasUsed = big.NewInt(6_000_000) // block gas limit is 8,000,000 DefaultMaxLookbackSeconds = uint64(80) ) @@ -102,7 +99,6 @@ type OracleBackend interface { SubscribeChainAcceptedEvent(ch chan<- core.ChainEvent) event.Subscription MinRequiredTip(ctx context.Context, header *types.Header) (*big.Int, error) LastAcceptedBlock() *types.Block - GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error) } // Oracle recommends gas prices based on the content of recent @@ -191,14 +187,6 @@ func NewOracle(backend OracleBackend, config Config) (*Oracle, error) { lastHead = ev.Block.Hash() } }() - feeConfig, _, err := backend.GetFeeConfigAt(backend.LastAcceptedBlock().Header()) - var minBaseFee *big.Int - if err != nil { - // resort back to chain config - return nil, fmt.Errorf("failed getting fee config in the oracle: %w", err) - } else { - minBaseFee = feeConfig.MinBaseFee - } feeInfoProvider, err := newFeeInfoProvider(backend, minGasUsed.Uint64(), config.Blocks) if err != nil { return nil, err @@ -206,7 +194,7 @@ func NewOracle(backend OracleBackend, config Config) (*Oracle, error) { return &Oracle{ backend: backend, lastPrice: minPrice, - lastBaseFee: new(big.Int).Set(minBaseFee), + lastBaseFee: DefaultMinBaseFee, minPrice: minPrice, maxPrice: maxPrice, checkBlocks: blocks, @@ -220,7 +208,7 @@ func NewOracle(backend OracleBackend, config Config) (*Oracle, error) { } // EstimateBaseFee returns an estimate of what the base fee will be on a block -// produced at the current time. If SubnetEVM has not been activated, it may +// produced at the current time. If ApricotPhase3 has not been activated, it may // return a nil value and a nil error. func (oracle *Oracle) EstimateBaseFee(ctx context.Context) (*big.Int, error) { _, baseFee, err := oracle.suggestDynamicFees(ctx) @@ -256,10 +244,6 @@ func (oracle *Oracle) estimateNextBaseFee(ctx context.Context) (*big.Int, error) if err != nil { return nil, err } - feeConfig, _, err := oracle.backend.GetFeeConfigAt(header) - if err != nil { - return nil, err - } // If the fetched block does not have a base fee, return nil as the base fee if header.BaseFee == nil { return nil, nil @@ -268,7 +252,7 @@ func (oracle *Oracle) estimateNextBaseFee(ctx context.Context) (*big.Int, error) // If the block does have a baseFee, calculate the next base fee // based on the current time and add it to the tip to estimate the // total gas price estimate. - _, nextBaseFee, err := dummy.EstimateNextBaseFee(oracle.backend.ChainConfig(), feeConfig, header, oracle.clock.Unix()) + _, nextBaseFee, err := dummy.EstimateNextBaseFee(oracle.backend.ChainConfig(), header, oracle.clock.Unix()) return nextBaseFee, err } @@ -314,17 +298,6 @@ func (oracle *Oracle) suggestDynamicFees(ctx context.Context) (*big.Int, *big.In return nil, nil, err } - var ( - feeLastChangedAt *big.Int - feeConfig commontype.FeeConfig - ) - if oracle.backend.ChainConfig().IsPrecompileEnabled(feemanager.ContractAddress, head.Time) { - feeConfig, feeLastChangedAt, err = oracle.backend.GetFeeConfigAt(head) - if err != nil { - return nil, nil, err - } - } - headHash := head.Hash() // If the latest gasprice is still available, return it. @@ -356,19 +329,6 @@ func (oracle *Oracle) suggestDynamicFees(ctx context.Context) (*big.Int, *big.In lowerBlockNumberLimit = latestBlockNumber - uint64(oracle.checkBlocks) } - // if fee config has changed at a more recent block, it should be the lower limit - if feeLastChangedAt != nil { - if lowerBlockNumberLimit < feeLastChangedAt.Uint64() { - lowerBlockNumberLimit = feeLastChangedAt.Uint64() - } - - // If the fee config has been increased in the latest block, increase the lastBaseFee to the - // new minimum base fee. - if feeLastChangedAt.Uint64() == latestBlockNumber && lastBaseFee.Cmp(feeConfig.MinBaseFee) < 0 { - lastBaseFee = feeConfig.MinBaseFee - } - } - // Process block headers in the range calculated for this gas price estimation. for i := latestBlockNumber; i > lowerBlockNumberLimit; i-- { feeInfo, err := oracle.getFeeInfo(ctx, i) diff --git a/eth/gasprice/gasprice_test.go b/eth/gasprice/gasprice_test.go index a41c9576a5..0bd54ef711 100644 --- a/eth/gasprice/gasprice_test.go +++ b/eth/gasprice/gasprice_test.go @@ -32,16 +32,14 @@ import ( "testing" "time" - "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/consensus/dummy" "github.com/ava-labs/subnet-evm/core" "github.com/ava-labs/subnet-evm/core/rawdb" + "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/core/vm" "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile/contracts/feemanager" "github.com/ava-labs/subnet-evm/rpc" - "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/event" @@ -90,15 +88,11 @@ func (b *testBackend) SubscribeChainAcceptedEvent(ch chan<- core.ChainEvent) eve return nil } -func (b *testBackend) GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error) { - return b.chain.GetFeeConfigAt(parent) -} - func (b *testBackend) teardown() { b.chain.Stop() } -func newTestBackendFakerEngine(t *testing.T, config *params.ChainConfig, numBlocks int, genBlocks func(i int, b *core.BlockGen)) *testBackend { +func newTestBackendFakerEngine(t *testing.T, config *params.ChainConfig, numBlocks int, extDataGasUsage *big.Int, genBlocks func(i int, b *core.BlockGen)) *testBackend { var gspec = &core.Genesis{ Config: config, Alloc: core.GenesisAlloc{addr: core.GenesisAccount{Balance: bal}}, @@ -125,13 +119,20 @@ func newTestBackendFakerEngine(t *testing.T, config *params.ChainConfig, numBloc // newTestBackend creates a test backend. OBS: don't forget to invoke tearDown // after use, otherwise the blockchain instance will mem-leak via goroutines. -func newTestBackend(t *testing.T, config *params.ChainConfig, numBlocks int, genBlocks func(i int, b *core.BlockGen)) *testBackend { +func newTestBackend(t *testing.T, config *params.ChainConfig, numBlocks int, extDataGasUsage *big.Int, genBlocks func(i int, b *core.BlockGen)) *testBackend { var gspec = &core.Genesis{ Config: config, Alloc: core.GenesisAlloc{addr: core.GenesisAccount{Balance: bal}}, } - engine := dummy.NewFaker() + engine := dummy.NewFakerWithCallbacks(dummy.ConsensusCallbacks{ + OnFinalizeAndAssemble: func(header *types.Header, state *state.StateDB, txs []*types.Transaction) ([]byte, *big.Int, *big.Int, error) { + return nil, common.Big0, extDataGasUsage, nil + }, + OnExtraStateChange: func(block *types.Block, state *state.StateDB) (*big.Int, *big.Int, error) { + return common.Big0, extDataGasUsage, nil + }, + }) // Generate testing blocks _, blocks, _, err := core.GenerateChainWithGenesis(gspec, engine, numBlocks, 1, genBlocks) @@ -170,10 +171,11 @@ func (b *testBackend) GetBlockByNumber(number uint64) *types.Block { } type suggestTipCapTest struct { - chainConfig *params.ChainConfig - numBlocks int - genBlock func(i int, b *core.BlockGen) - expectedTip *big.Int + chainConfig *params.ChainConfig + numBlocks int + extDataGasUsage *big.Int + genBlock func(i int, b *core.BlockGen) + expectedTip *big.Int } func defaultOracleConfig() Config { @@ -198,7 +200,7 @@ func applyGasPriceTest(t *testing.T, test suggestTipCapTest, config Config) { if test.genBlock == nil { test.genBlock = func(i int, b *core.BlockGen) {} } - backend := newTestBackend(t, test.chainConfig, test.numBlocks, test.genBlock) + backend := newTestBackend(t, test.chainConfig, test.numBlocks, test.extDataGasUsage, test.genBlock) oracle, err := NewOracle(backend, config) require.NoError(t, err) @@ -240,44 +242,42 @@ func testGenBlock(t *testing.T, tip int64, numTx int) func(int, *core.BlockGen) } } -func TestSuggestTipCapNetworkUpgrades(t *testing.T) { - tests := map[string]suggestTipCapTest{ - "subnet evm": { - chainConfig: params.TestChainConfig, - expectedTip: DefaultMinPrice, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - applyGasPriceTest(t, test, defaultOracleConfig()) - }) - } +func TestSuggestTipCapEmptyExtDataGasUsage(t *testing.T) { + applyGasPriceTest(t, suggestTipCapTest{ + chainConfig: params.TestChainConfig, + numBlocks: 3, + extDataGasUsage: nil, + genBlock: testGenBlock(t, 55, 370), + expectedTip: big.NewInt(5_713_963_963), + }, defaultOracleConfig()) } -func TestSuggestTipCap(t *testing.T) { +func TestSuggestTipCapSimple(t *testing.T) { applyGasPriceTest(t, suggestTipCapTest{ - chainConfig: params.TestChainConfig, - numBlocks: 3, - genBlock: testGenBlock(t, 55, 370), - expectedTip: big.NewInt(643_500_643), + chainConfig: params.TestChainConfig, + numBlocks: 3, + extDataGasUsage: common.Big0, + genBlock: testGenBlock(t, 55, 370), + expectedTip: big.NewInt(5_713_963_963), }, defaultOracleConfig()) } func TestSuggestTipCapSimpleFloor(t *testing.T) { applyGasPriceTest(t, suggestTipCapTest{ - chainConfig: params.TestChainConfig, - numBlocks: 1, - genBlock: testGenBlock(t, 55, 370), - expectedTip: common.Big0, + chainConfig: params.TestChainConfig, + numBlocks: 1, + extDataGasUsage: common.Big0, + genBlock: testGenBlock(t, 55, 370), + expectedTip: common.Big0, }, defaultOracleConfig()) } func TestSuggestTipCapSmallTips(t *testing.T) { tip := big.NewInt(550 * params.GWei) applyGasPriceTest(t, suggestTipCapTest{ - chainConfig: params.TestChainConfig, - numBlocks: 3, + chainConfig: params.TestChainConfig, + numBlocks: 3, + extDataGasUsage: common.Big0, genBlock: func(i int, b *core.BlockGen) { b.SetCoinbase(common.Address{1}) @@ -313,33 +313,45 @@ func TestSuggestTipCapSmallTips(t *testing.T) { b.AddTx(tx) } }, - expectedTip: big.NewInt(643_500_643), + // NOTE: small tips do not bias estimate + expectedTip: big.NewInt(5_713_963_963), + }, defaultOracleConfig()) +} + +func TestSuggestTipCapExtDataUsage(t *testing.T) { + applyGasPriceTest(t, suggestTipCapTest{ + chainConfig: params.TestChainConfig, + numBlocks: 3, + extDataGasUsage: big.NewInt(10_000), + genBlock: testGenBlock(t, 55, 370), + expectedTip: big.NewInt(5_706_726_649), }, defaultOracleConfig()) } func TestSuggestTipCapMinGas(t *testing.T) { applyGasPriceTest(t, suggestTipCapTest{ - chainConfig: params.TestChainConfig, - numBlocks: 3, - genBlock: testGenBlock(t, 500, 50), - expectedTip: big.NewInt(0), + chainConfig: params.TestChainConfig, + numBlocks: 3, + extDataGasUsage: common.Big0, + genBlock: testGenBlock(t, 500, 50), + expectedTip: big.NewInt(0), }, defaultOracleConfig()) } -// Regression test to ensure that SuggestPrice does not panic with activation of Subnet EVM +// Regression test to ensure that SuggestPrice does not panic prior to activation of ApricotPhase3 // Note: support for gas estimation without activated hard forks has been deprecated, but we still // ensure that the call does not panic. -func TestSuggestGasPriceSubnetEVM(t *testing.T) { +func TestSuggestGasPricePreAP3(t *testing.T) { config := Config{ Blocks: 20, Percentile: 60, } - backend := newTestBackend(t, params.TestChainConfig, 3, func(i int, b *core.BlockGen) { + backend := newTestBackend(t, params.TestApricotPhase2Config, 3, nil, func(i int, b *core.BlockGen) { b.SetCoinbase(common.Address{1}) - signer := types.LatestSigner(params.TestChainConfig) - gasPrice := big.NewInt(params.MinGasPrice) + signer := types.LatestSigner(params.TestApricotPhase2Config) + gasPrice := big.NewInt(params.ApricotPhase1MinGasPrice) for j := 0; j < 50; j++ { tx := types.NewTx(&types.LegacyTx{ Nonce: b.TxNonce(addr), @@ -364,82 +376,20 @@ func TestSuggestGasPriceSubnetEVM(t *testing.T) { func TestSuggestTipCapMaxBlocksLookback(t *testing.T) { applyGasPriceTest(t, suggestTipCapTest{ - chainConfig: params.TestChainConfig, - numBlocks: 20, - genBlock: testGenBlock(t, 550, 370), - expectedTip: big.NewInt(5_807_226_110), + chainConfig: params.TestChainConfig, + numBlocks: 20, + extDataGasUsage: common.Big0, + genBlock: testGenBlock(t, 550, 370), + expectedTip: big.NewInt(51_565_264_256), }, defaultOracleConfig()) } func TestSuggestTipCapMaxBlocksSecondsLookback(t *testing.T) { applyGasPriceTest(t, suggestTipCapTest{ - chainConfig: params.TestChainConfig, - numBlocks: 20, - genBlock: testGenBlock(t, 550, 370), - expectedTip: big.NewInt(10_384_877_851), + chainConfig: params.TestChainConfig, + numBlocks: 20, + extDataGasUsage: big.NewInt(1), + genBlock: testGenBlock(t, 550, 370), + expectedTip: big.NewInt(92_212_529_423), }, timeCrunchOracleConfig()) } - -// Regression test to ensure the last estimation of base fee is not used -// for the block immediately following a fee configuration update. -func TestSuggestGasPriceAfterFeeConfigUpdate(t *testing.T) { - require := require.New(t) - config := Config{ - Blocks: 20, - Percentile: 60, - } - - // create a chain config with fee manager enabled at genesis with [addr] as the admin - chainConfig := *params.TestChainConfig - chainConfig.GenesisPrecompiles = params.Precompiles{ - feemanager.ConfigKey: feemanager.NewConfig(utils.NewUint64(0), []common.Address{addr}, nil, nil, nil), - } - - // create a fee config with higher MinBaseFee and prepare it for inclusion in a tx - signer := types.LatestSigner(params.TestChainConfig) - highFeeConfig := chainConfig.FeeConfig - highFeeConfig.MinBaseFee = big.NewInt(28_000_000_000) - data, err := feemanager.PackSetFeeConfig(highFeeConfig) - require.NoError(err) - - // before issuing the block changing the fee into the chain, the fee estimation should - // follow the fee config in genesis. - backend := newTestBackend(t, &chainConfig, 0, func(i int, b *core.BlockGen) {}) - defer backend.teardown() - oracle, err := NewOracle(backend, config) - require.NoError(err) - got, err := oracle.SuggestPrice(context.Background()) - require.NoError(err) - require.Equal(chainConfig.FeeConfig.MinBaseFee, got) - - // issue the block with tx that changes the fee - genesis := backend.chain.Genesis() - engine := backend.chain.Engine() - db := rawdb.NewDatabase(backend.chain.StateCache().DiskDB()) - blocks, _, err := core.GenerateChain(&chainConfig, genesis, engine, db, 1, 0, func(i int, b *core.BlockGen) { - b.SetCoinbase(common.Address{1}) - - // admin issues tx to change fee config to higher MinBaseFee - tx := types.NewTx(&types.DynamicFeeTx{ - ChainID: chainConfig.ChainID, - Nonce: b.TxNonce(addr), - To: &feemanager.ContractAddress, - Gas: chainConfig.FeeConfig.GasLimit.Uint64(), - Value: common.Big0, - GasFeeCap: chainConfig.FeeConfig.MinBaseFee, // give low fee, it should work since we still haven't applied high fees - GasTipCap: common.Big0, - Data: data, - }) - tx, err = types.SignTx(tx, signer, key) - require.NoError(err, "failed to create tx") - b.AddTx(tx) - }) - require.NoError(err) - _, err = backend.chain.InsertChain(blocks) - require.NoError(err) - - // verify the suggested price follows the new fee config. - got, err = oracle.SuggestPrice(context.Background()) - require.NoError(err) - require.Equal(highFeeConfig.MinBaseFee, got) -} diff --git a/eth/tracers/api.go b/eth/tracers/api.go index deb04c2902..9e408fda7f 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -1059,24 +1059,50 @@ func overrideConfig(original *params.ChainConfig, override *params.ChainConfig) *copy = *original canon := true - if timestamp := override.SubnetEVMTimestamp; timestamp != nil { - copy.SubnetEVMTimestamp = timestamp + // Apply network upgrades (after Berlin) to the copy. + // Note in coreth, ApricotPhase2 is the "equivalent" to Berlin. + if timestamp := override.ApricotPhase2BlockTimestamp; timestamp != nil { + copy.ApricotPhase2BlockTimestamp = timestamp canon = false } - if timestamp := override.DurangoTimestamp; timestamp != nil { - copy.DurangoTimestamp = timestamp + if timestamp := override.ApricotPhase3BlockTimestamp; timestamp != nil { + copy.ApricotPhase3BlockTimestamp = timestamp canon = false } - if timestamp := override.EtnaTimestamp; timestamp != nil { - copy.EtnaTimestamp = timestamp + if timestamp := override.ApricotPhase4BlockTimestamp; timestamp != nil { + copy.ApricotPhase4BlockTimestamp = timestamp canon = false } - if timestamp := override.CancunTime; timestamp != nil { - copy.CancunTime = timestamp + if timestamp := override.ApricotPhase5BlockTimestamp; timestamp != nil { + copy.ApricotPhase5BlockTimestamp = timestamp + canon = false + } + if timestamp := override.ApricotPhasePre6BlockTimestamp; timestamp != nil { + copy.ApricotPhasePre6BlockTimestamp = timestamp + canon = false + } + if timestamp := override.ApricotPhase6BlockTimestamp; timestamp != nil { + copy.ApricotPhase6BlockTimestamp = timestamp + canon = false + } + if timestamp := override.ApricotPhasePost6BlockTimestamp; timestamp != nil { + copy.ApricotPhasePost6BlockTimestamp = timestamp canon = false } - if timestamp := override.VerkleTime; timestamp != nil { - copy.VerkleTime = timestamp + if timestamp := override.BanffBlockTimestamp; timestamp != nil { + copy.BanffBlockTimestamp = timestamp + canon = false + } + if timestamp := override.CortinaBlockTimestamp; timestamp != nil { + copy.CortinaBlockTimestamp = timestamp + canon = false + } + if timestamp := override.DurangoBlockTimestamp; timestamp != nil { + copy.DurangoBlockTimestamp = timestamp + canon = false + } + if timestamp := override.CancunTime; timestamp != nil { + copy.CancunTime = timestamp canon = false } diff --git a/eth/tracers/api_extra_test.go b/eth/tracers/api_extra_test.go deleted file mode 100644 index f5e07bfa27..0000000000 --- a/eth/tracers/api_extra_test.go +++ /dev/null @@ -1,417 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package tracers - -import ( - "context" - "encoding/json" - "fmt" - "math/big" - "reflect" - "sync/atomic" - "testing" - - "github.com/ava-labs/subnet-evm/core" - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/eth/tracers/logger" - "github.com/ava-labs/subnet-evm/internal/ethapi" - "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist" - "github.com/ava-labs/subnet-evm/rpc" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/common/math" - "github.com/stretchr/testify/require" -) - -func TestTraceBlockPrecompileActivation(t *testing.T) { - t.Parallel() - - // Initialize test accounts - accounts := newAccounts(3) - copyConfig := *params.TestChainConfig - genesis := &core.Genesis{ - Config: ©Config, - Alloc: core.GenesisAlloc{ - accounts[0].addr: {Balance: big.NewInt(params.Ether)}, - accounts[1].addr: {Balance: big.NewInt(params.Ether)}, - accounts[2].addr: {Balance: big.NewInt(params.Ether)}, - }, - } - activateAllowlistBlock := 3 - // assumes gap is 10 sec - activateAllowListTime := uint64(activateAllowlistBlock * 10) - activateTxAllowListConfig := params.PrecompileUpgrade{ - Config: txallowlist.NewConfig(&activateAllowListTime, []common.Address{accounts[0].addr}, nil, nil), - } - - deactivateAllowlistBlock := activateAllowlistBlock + 3 - deactivateAllowListTime := uint64(deactivateAllowlistBlock) * 10 - deactivateTxAllowListConfig := params.PrecompileUpgrade{ - Config: txallowlist.NewDisableConfig(&deactivateAllowListTime), - } - - genesis.Config.PrecompileUpgrades = []params.PrecompileUpgrade{ - activateTxAllowListConfig, - deactivateTxAllowListConfig, - } - genBlocks := 10 - signer := types.HomesteadSigner{} - txHashes := make([]common.Hash, genBlocks) - backend := newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) { - // Transfer from account[0] to account[1] - // value: 1000 wei - // fee: 0 wei - tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, b.BaseFee(), nil), signer, accounts[0].key) - b.AddTx(tx) - txHashes[i] = tx.Hash() - }) - defer backend.chain.Stop() - api := NewAPI(backend) - - testSuite := []struct { - blockNumber rpc.BlockNumber - config *TraceConfig - want string - expectErr error - }{ - // Trace head block - { - blockNumber: rpc.BlockNumber(genBlocks), - want: fmt.Sprintf(`[{"txHash":"%v","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}]`, txHashes[genBlocks-1]), - }, - // Trace block before activation - { - blockNumber: rpc.BlockNumber(activateAllowlistBlock - 1), - want: fmt.Sprintf(`[{"txHash":"%v","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}]`, txHashes[activateAllowlistBlock-2]), - }, - // Trace block activation - { - blockNumber: rpc.BlockNumber(activateAllowlistBlock), - want: fmt.Sprintf(`[{"txHash":"%v","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}]`, txHashes[activateAllowlistBlock-1]), - }, - // Trace block after activation - { - blockNumber: rpc.BlockNumber(activateAllowlistBlock + 1), - want: fmt.Sprintf(`[{"txHash":"%v","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}]`, txHashes[activateAllowlistBlock]), - }, - // Trace block deactivation - { - blockNumber: rpc.BlockNumber(deactivateAllowlistBlock), - want: fmt.Sprintf(`[{"txHash":"%v","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}]`, txHashes[deactivateAllowlistBlock-1]), - }, - // Trace block after deactivation - { - blockNumber: rpc.BlockNumber(deactivateAllowlistBlock + 1), - want: fmt.Sprintf(`[{"txHash":"%v","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}]`, txHashes[deactivateAllowlistBlock]), - }, - } - for i, tc := range testSuite { - result, err := api.TraceBlockByNumber(context.Background(), tc.blockNumber, tc.config) - if tc.expectErr != nil { - if err == nil { - t.Errorf("test %d, want error %v", i, tc.expectErr) - continue - } - if !reflect.DeepEqual(err, tc.expectErr) { - t.Errorf("test %d: error mismatch, want %v, get %v", i, tc.expectErr, err) - } - continue - } - if err != nil { - t.Errorf("test %d, want no error, have %v", i, err) - continue - } - have, _ := json.Marshal(result) - want := tc.want - if string(have) != want { - t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, string(have), want) - } - } -} - -func TestTraceTransactionPrecompileActivation(t *testing.T) { - t.Parallel() - - // Initialize test accounts - accounts := newAccounts(3) - copyConfig := *params.TestChainConfig - genesis := &core.Genesis{ - Config: ©Config, - Alloc: core.GenesisAlloc{ - accounts[0].addr: {Balance: big.NewInt(params.Ether)}, - accounts[1].addr: {Balance: big.NewInt(params.Ether)}, - accounts[2].addr: {Balance: big.NewInt(params.Ether)}, - }, - } - activateAllowlistBlock := uint64(2) - // assumes gap is 10 sec - activateAllowListTime := activateAllowlistBlock * 10 - activateTxAllowListConfig := params.PrecompileUpgrade{ - Config: txallowlist.NewConfig(&activateAllowListTime, []common.Address{accounts[0].addr}, nil, nil), - } - - deactivateAllowlistBlock := activateAllowlistBlock + 2 - deactivateAllowListTime := deactivateAllowlistBlock * 10 - deactivateTxAllowListConfig := params.PrecompileUpgrade{ - Config: txallowlist.NewDisableConfig(&deactivateAllowListTime), - } - - genesis.Config.PrecompileUpgrades = []params.PrecompileUpgrade{ - activateTxAllowListConfig, - deactivateTxAllowListConfig, - } - signer := types.HomesteadSigner{} - blockNoTxMap := make(map[uint64]common.Hash) - - backend := newTestBackend(t, 5, genesis, func(i int, b *core.BlockGen) { - // Transfer from account[0] to account[1] - // value: 1000 wei - // fee: 0 wei - tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, new(big.Int).Add(b.BaseFee(), big.NewInt(int64(500*params.GWei))), nil), signer, accounts[0].key) - b.AddTx(tx) - blockNoTxMap[uint64(i)] = tx.Hash() - }) - - defer backend.chain.Stop() - api := NewAPI(backend) - for i, target := range blockNoTxMap { - t.Run(fmt.Sprintf("blockNumber %d", i), func(t *testing.T) { - result, err := api.TraceTransaction(context.Background(), target, nil) - require := require.New(t) - require.NoError(err) - var have *logger.ExecutionResult - err = json.Unmarshal(result.(json.RawMessage), &have) - require.NoError(err) - expected := &logger.ExecutionResult{ - Gas: params.TxGas, - Failed: false, - ReturnValue: "", - StructLogs: []logger.StructLogRes{}, - } - eq := reflect.DeepEqual(have, expected) - require.True(eq, "have %v, want %v", have, expected) - }) - } -} - -func TestTraceChainPrecompileActivation(t *testing.T) { - // Initialize test accounts - // Note: the balances in this test have been increased compared to go-ethereum. - accounts := newAccounts(3) - copyConfig := *params.TestChainConfig - genesis := &core.Genesis{ - Config: ©Config, - Alloc: core.GenesisAlloc{ - accounts[0].addr: {Balance: big.NewInt(5 * params.Ether)}, - accounts[1].addr: {Balance: big.NewInt(5 * params.Ether)}, - accounts[2].addr: {Balance: big.NewInt(5 * params.Ether)}, - }, - } - activateAllowlistBlock := uint64(20) - // assumes gap is 10 sec - activateAllowListTime := activateAllowlistBlock * 10 - activateTxAllowListConfig := params.PrecompileUpgrade{ - Config: txallowlist.NewConfig(&activateAllowListTime, []common.Address{accounts[0].addr}, nil, nil), - } - - deactivateAllowlistBlock := activateAllowlistBlock + 10 - deactivateAllowListTime := deactivateAllowlistBlock * 10 - deactivateTxAllowListConfig := params.PrecompileUpgrade{ - Config: txallowlist.NewDisableConfig(&deactivateAllowListTime), - } - - genesis.Config.PrecompileUpgrades = []params.PrecompileUpgrade{ - activateTxAllowListConfig, - deactivateTxAllowListConfig, - } - genBlocks := 50 - signer := types.HomesteadSigner{} - - var ( - ref atomic.Uint32 // total refs has made - rel atomic.Uint32 // total rels has made - nonce uint64 - ) - backend := newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) { - // Transfer from account[0] to account[1] - // value: 1000 wei - // fee: 0 wei - for j := 0; j < i+1; j++ { - tx, _ := types.SignTx(types.NewTransaction(nonce, accounts[1].addr, big.NewInt(1000), params.TxGas, b.BaseFee(), nil), signer, accounts[0].key) - b.AddTx(tx) - nonce += 1 - } - }) - backend.refHook = func() { ref.Add(1) } - backend.relHook = func() { rel.Add(1) } - api := NewAPI(backend) - - single := `{"txHash":"0x0000000000000000000000000000000000000000000000000000000000000000","result":{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}}` - cases := []struct { - start uint64 - end uint64 - config *TraceConfig - }{ - {0, 50, nil}, // the entire chain range, blocks [1, 50] - {10, 20, nil}, // the middle chain range, blocks [11, 20] - } - for _, c := range cases { - ref.Store(0) - rel.Store(0) - - from, _ := api.blockByNumber(context.Background(), rpc.BlockNumber(c.start)) - to, _ := api.blockByNumber(context.Background(), rpc.BlockNumber(c.end)) - resCh := api.traceChain(from, to, c.config, nil) - - next := c.start + 1 - for result := range resCh { - if have, want := uint64(result.Block), next; have != want { - t.Fatalf("unexpected tracing block, have %d want %d", have, want) - } - if have, want := len(result.Traces), int(next); have != want { - t.Fatalf("unexpected result length, have %d want %d", have, want) - } - for _, trace := range result.Traces { - trace.TxHash = common.Hash{} - blob, _ := json.Marshal(trace) - if have, want := string(blob), single; have != want { - t.Fatalf("unexpected tracing result, have\n%v\nwant:\n%v", have, want) - } - } - next += 1 - } - if next != c.end+1 { - t.Error("Missing tracing block") - } - - if nref, nrel := ref.Load(), rel.Load(); nref != nrel { - t.Errorf("Ref and deref actions are not equal, ref %d rel %d", nref, nrel) - } - } -} - -func TestTraceCallWithOverridesStateUpgrade(t *testing.T) { - t.Parallel() - - // Initialize test accounts - accounts := newAccounts(3) - copyConfig := *params.TestChainConfig - genesis := &core.Genesis{ - Config: ©Config, - Alloc: core.GenesisAlloc{ - accounts[0].addr: {Balance: big.NewInt(5 * params.Ether)}, - accounts[1].addr: {Balance: big.NewInt(5 * params.Ether)}, - accounts[2].addr: {Balance: big.NewInt(5 * params.Ether)}, - }, - } - activateStateUpgradeBlock := uint64(2) - // assumes gap is 10 sec - activateStateUpgradeTime := activateStateUpgradeBlock * 10 - activateStateUpgradeConfig := params.StateUpgrade{ - BlockTimestamp: &activateStateUpgradeTime, - StateUpgradeAccounts: map[common.Address]params.StateUpgradeAccount{ - accounts[2].addr: { - // deplete all balance - BalanceChange: (*math.HexOrDecimal256)(new(big.Int).Neg(big.NewInt(5 * params.Ether))), - }, - }, - } - - genesis.Config.StateUpgrades = []params.StateUpgrade{ - activateStateUpgradeConfig, - } - genBlocks := 3 - // assumes gap is 10 sec - signer := types.HomesteadSigner{} - fastForwardTime := activateStateUpgradeTime + 10 - backend := newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) { - // Transfer from account[0] to account[1] - // value: 1000 wei - // fee: 0 wei - tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, b.BaseFee(), nil), signer, accounts[0].key) - b.AddTx(tx) - }) - defer backend.teardown() - api := NewAPI(backend) - testSuite := []struct { - blockNumber rpc.BlockNumber - call ethapi.TransactionArgs - config *TraceCallConfig - expectErr error - expect string - }{ - { - blockNumber: rpc.BlockNumber(0), - call: ethapi.TransactionArgs{ - From: &accounts[0].addr, - To: &accounts[1].addr, - Value: (*hexutil.Big)(big.NewInt(1000)), - }, - config: nil, - expectErr: nil, - expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`, - }, - { - blockNumber: rpc.BlockNumber(activateStateUpgradeBlock - 1), - call: ethapi.TransactionArgs{ - From: &accounts[2].addr, - To: &accounts[1].addr, - Value: (*hexutil.Big)(big.NewInt(1000)), - }, - config: nil, - expectErr: nil, - expect: `{"gas":21000,"failed":false,"returnValue":"","structLogs":[]}`, - }, - { - blockNumber: rpc.BlockNumber(activateStateUpgradeBlock + 1), - call: ethapi.TransactionArgs{ - From: &accounts[2].addr, - To: &accounts[1].addr, - Value: (*hexutil.Big)(big.NewInt(1000)), - }, - config: nil, - expectErr: core.ErrInsufficientFunds, - expect: `{"gas":21000,"failed":true,"returnValue":"","structLogs":[]}`, - }, - { - blockNumber: rpc.BlockNumber(activateStateUpgradeBlock - 1), - call: ethapi.TransactionArgs{ - From: &accounts[2].addr, - To: &accounts[1].addr, - Value: (*hexutil.Big)(big.NewInt(1000)), - }, - config: &TraceCallConfig{ - BlockOverrides: ðapi.BlockOverrides{ - Time: (*hexutil.Uint64)(&fastForwardTime), - }, - }, - expectErr: core.ErrInsufficientFunds, - expect: `{"gas":21000,"failed":true,"returnValue":"","structLogs":[]}`, - }, - } - for i, testspec := range testSuite { - result, err := api.TraceCall(context.Background(), testspec.call, rpc.BlockNumberOrHash{BlockNumber: &testspec.blockNumber}, testspec.config) - if testspec.expectErr != nil { - require.ErrorIs(t, err, testspec.expectErr, "test %d", i) - continue - } else { - if err != nil { - t.Errorf("test %d: expect no error, got %v", i, err) - continue - } - var have *logger.ExecutionResult - if err := json.Unmarshal(result.(json.RawMessage), &have); err != nil { - t.Errorf("test %d: failed to unmarshal result %v", i, err) - } - var want *logger.ExecutionResult - if err := json.Unmarshal([]byte(testspec.expect), &want); err != nil { - t.Errorf("test %d: failed to unmarshal result %v", i, err) - } - if !reflect.DeepEqual(have, want) { - t.Errorf("test %d: result mismatch, want %v, got %v", i, testspec.expect, string(result.(json.RawMessage))) - } - } - } -} diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 7b99d8e250..074de58872 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -75,7 +75,7 @@ type testBackend struct { func newTestBackend(t *testing.T, n int, gspec *core.Genesis, generator func(i int, b *core.BlockGen)) *testBackend { backend := &testBackend{ chainConfig: gspec.Config, - engine: dummy.NewFakerWithMode(dummy.Mode{ModeSkipBlockFee: true, ModeSkipCoinbase: true}), + engine: dummy.NewETHFaker(), chaindb: rawdb.NewMemoryDatabase(), } // Generate blocks for testing @@ -226,7 +226,7 @@ func TestTraceCall(t *testing.T) { // Initialize test accounts accounts := newAccounts(3) genesis := &core.Genesis{ - Config: params.TestChainConfig, + Config: params.TestBanffChainConfig, // TODO: go-ethereum has not enabled Shanghai yet, so we use Banff here so tests pass. Alloc: core.GenesisAlloc{ accounts[0].addr: {Balance: big.NewInt(params.Ether)}, accounts[1].addr: {Balance: big.NewInt(params.Ether)}, @@ -590,7 +590,7 @@ func TestTracingWithOverrides(t *testing.T) { accounts := newAccounts(3) storageAccount := common.Address{0x13, 37} genesis := &core.Genesis{ - Config: params.TestChainConfig, + Config: params.TestCortinaChainConfig, // TODO: go-ethereum has not enabled Shanghai yet, so we use Cortina here so tests pass. Alloc: core.GenesisAlloc{ accounts[0].addr: {Balance: big.NewInt(params.Ether)}, accounts[1].addr: {Balance: big.NewInt(params.Ether)}, diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go index 6c2aa1ab89..7c95d82e69 100644 --- a/eth/tracers/internal/tracetest/calltrace_test.go +++ b/eth/tracers/internal/tracetest/calltrace_test.go @@ -34,6 +34,8 @@ import ( "strings" "testing" + "github.com/ava-labs/avalanchego/upgrade" + "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/subnet-evm/core" "github.com/ava-labs/subnet-evm/core/rawdb" "github.com/ava-labs/subnet-evm/core/types" @@ -275,6 +277,7 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) { func TestInternals(t *testing.T) { var ( + config = params.GetChainConfig(upgrade.GetConfig(constants.MainnetID), params.AvalancheMainnetChainID) to = common.HexToAddress("0x00000000000000000000000000000000deadbeef") origin = common.HexToAddress("0x00000000000000000000000000000000feed") txContext = vm.TxContext{ @@ -388,7 +391,7 @@ func TestInternals(t *testing.T) { }, false, rawdb.HashScheme) defer triedb.Close() - evm := vm.NewEVM(context, txContext, statedb, params.TestPreSubnetEVMChainConfig, vm.Config{Tracer: tc.tracer}) + evm := vm.NewEVM(context, txContext, statedb, config, vm.Config{Tracer: tc.tracer}) msg := &core.Message{ To: &to, From: origin, diff --git a/eth/tracers/internal/tracetest/flat_calltrace_test.go b/eth/tracers/internal/tracetest/flat_calltrace_test.go index 3afa5d3080..1689e4b57d 100644 --- a/eth/tracers/internal/tracetest/flat_calltrace_test.go +++ b/eth/tracers/internal/tracetest/flat_calltrace_test.go @@ -7,7 +7,6 @@ // original code from which it is derived. // // Much love to the original authors for their work. -// ********** package tracetest import ( diff --git a/eth/tracers/internal/tracetest/prestate_test.go b/eth/tracers/internal/tracetest/prestate_test.go index 98a18aadba..a0ab36faaf 100644 --- a/eth/tracers/internal/tracetest/prestate_test.go +++ b/eth/tracers/internal/tracetest/prestate_test.go @@ -74,6 +74,10 @@ func TestPrestateWithDiffModeTracer(t *testing.T) { testPrestateDiffTracer("prestateTracer", "prestate_tracer_with_diff_mode", t) } +func TestPrestateWithDiffModeANTTracer(t *testing.T) { + testPrestateDiffTracer("prestateTracer", "prestate_tracer_ant", t) +} + func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) { files, err := os.ReadDir(filepath.Join("testdata", dirPath)) if err != nil { @@ -109,14 +113,16 @@ func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) { GasPrice: tx.GasPrice(), } context = vm.BlockContext{ - CanTransfer: core.CanTransfer, - Transfer: core.Transfer, - Coinbase: test.Context.Miner, - BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)), - Time: uint64(test.Context.Time), - Difficulty: (*big.Int)(test.Context.Difficulty), - GasLimit: uint64(test.Context.GasLimit), - BaseFee: test.Genesis.BaseFee, + CanTransfer: core.CanTransfer, + CanTransferMC: core.CanTransferMC, + Transfer: core.Transfer, + TransferMultiCoin: core.TransferMultiCoin, + Coinbase: test.Context.Miner, + BlockNumber: new(big.Int).SetUint64(uint64(test.Context.Number)), + Time: uint64(test.Context.Time), + Difficulty: (*big.Int)(test.Context.Difficulty), + GasLimit: uint64(test.Context.GasLimit), + BaseFee: test.Genesis.BaseFee, } triedb, _, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false, rawdb.HashScheme) ) diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/create.json b/eth/tracers/internal/tracetest/testdata/call_tracer/create.json index dfbba2d675..df0b2872b4 100644 --- a/eth/tracers/internal/tracetest/testdata/call_tracer/create.json +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/create.json @@ -24,6 +24,7 @@ "config": { "byzantiumBlock": 1700000, "chainId": 3, + "daoForkSupport": true, "eip150Block": 0, "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", "eip155Block": 10, diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/deep_calls.json b/eth/tracers/internal/tracetest/testdata/call_tracer/deep_calls.json index 473dbe4382..975616064a 100644 --- a/eth/tracers/internal/tracetest/testdata/call_tracer/deep_calls.json +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/deep_calls.json @@ -87,6 +87,7 @@ "config": { "byzantiumBlock": 1700000, "chainId": 3, + "daoForkSupport": true, "eip150Block": 0, "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", "eip155Block": 10, diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/delegatecall.json b/eth/tracers/internal/tracetest/testdata/call_tracer/delegatecall.json index 248ff04f84..6a2cda7dc9 100644 --- a/eth/tracers/internal/tracetest/testdata/call_tracer/delegatecall.json +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/delegatecall.json @@ -40,6 +40,7 @@ "config": { "byzantiumBlock": 1700000, "chainId": 3, + "daoForkSupport": true, "eip150Block": 0, "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", "eip155Block": 10, diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/inner_create_oog_outer_throw.json b/eth/tracers/internal/tracetest/testdata/call_tracer/inner_create_oog_outer_throw.json index f173489b14..bb16a4a430 100644 --- a/eth/tracers/internal/tracetest/testdata/call_tracer/inner_create_oog_outer_throw.json +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/inner_create_oog_outer_throw.json @@ -32,6 +32,7 @@ "config": { "byzantiumBlock": 1700000, "chainId": 3, + "daoForkSupport": true, "eip150Block": 0, "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", "eip155Block": 10, diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/inner_instafail.json b/eth/tracers/internal/tracetest/testdata/call_tracer/inner_instafail.json index c329019010..9b45b52fe9 100644 --- a/eth/tracers/internal/tracetest/testdata/call_tracer/inner_instafail.json +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/inner_instafail.json @@ -28,6 +28,7 @@ "config": { "chainId": 3, "homesteadBlock": 0, + "daoForkSupport": true, "eip150Block": 0, "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", "eip155Block": 10, diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/inner_revert_reason.json b/eth/tracers/internal/tracetest/testdata/call_tracer/inner_revert_reason.json index f4e4de5e75..e54129d4c2 100644 --- a/eth/tracers/internal/tracetest/testdata/call_tracer/inner_revert_reason.json +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/inner_revert_reason.json @@ -34,7 +34,9 @@ "muirGlacierBlock": 0, "berlinBlock": 0, "londonBlock": 0, - "subnetEVMTimestamp": 0 + "apricotPhase1BlockTimestamp": 0, + "apricotPhase2BlockTimestamp": 0, + "apricotPhase3BlockTimestamp": 0 } }, "context": { diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/inner_throw_outer_revert.json b/eth/tracers/internal/tracetest/testdata/call_tracer/inner_throw_outer_revert.json index 7599022e89..a023ed6d9b 100644 --- a/eth/tracers/internal/tracetest/testdata/call_tracer/inner_throw_outer_revert.json +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/inner_throw_outer_revert.json @@ -35,6 +35,7 @@ "config": { "byzantiumBlock": 1700000, "chainId": 3, + "daoForkSupport": true, "eip150Block": 0, "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", "eip155Block": 10, diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/oog.json b/eth/tracers/internal/tracetest/testdata/call_tracer/oog.json index 6f1be2cc6a..333bdd038c 100644 --- a/eth/tracers/internal/tracetest/testdata/call_tracer/oog.json +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/oog.json @@ -26,6 +26,7 @@ "config": { "byzantiumBlock": 1700000, "chainId": 3, + "daoForkSupport": true, "eip150Block": 0, "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", "eip155Block": 10, diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/revert.json b/eth/tracers/internal/tracetest/testdata/call_tracer/revert.json index 6296cfd5fb..3207a298a9 100644 --- a/eth/tracers/internal/tracetest/testdata/call_tracer/revert.json +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/revert.json @@ -24,6 +24,7 @@ "config": { "byzantiumBlock": 1700000, "chainId": 3, + "daoForkSupport": true, "eip150Block": 0, "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", "eip155Block": 10, diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/revert_reason.json b/eth/tracers/internal/tracetest/testdata/call_tracer/revert_reason.json index 1963a282a0..f02e5c6863 100644 --- a/eth/tracers/internal/tracetest/testdata/call_tracer/revert_reason.json +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/revert_reason.json @@ -29,6 +29,7 @@ "petersburgBlock": 0, "IstanbulBlock": 1561651, "chainId": 5, + "daoForkSupport": true, "eip150Block": 0, "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", "eip155Block": 10, diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/selfdestruct.json b/eth/tracers/internal/tracetest/testdata/call_tracer/selfdestruct.json index 58b3e9244b..620df1d614 100644 --- a/eth/tracers/internal/tracetest/testdata/call_tracer/selfdestruct.json +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/selfdestruct.json @@ -30,6 +30,7 @@ "config": { "byzantiumBlock": 1700000, "chainId": 3, + "daoForkSupport": true, "eip150Block": 0, "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", "eip155Block": 10, diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/simple.json b/eth/tracers/internal/tracetest/testdata/call_tracer/simple.json index 83339046f9..6c7d01de1f 100644 --- a/eth/tracers/internal/tracetest/testdata/call_tracer/simple.json +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/simple.json @@ -35,6 +35,7 @@ "config": { "byzantiumBlock": 1700000, "chainId": 3, + "daoForkSupport": true, "eip150Block": 0, "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", "eip155Block": 10, diff --git a/eth/tracers/internal/tracetest/testdata/call_tracer/throw.json b/eth/tracers/internal/tracetest/testdata/call_tracer/throw.json index ab8cf09027..499b449a6e 100644 --- a/eth/tracers/internal/tracetest/testdata/call_tracer/throw.json +++ b/eth/tracers/internal/tracetest/testdata/call_tracer/throw.json @@ -28,6 +28,7 @@ "config": { "byzantiumBlock": 1700000, "chainId": 3, + "daoForkSupport": true, "eip150Block": 0, "eip150Hash": "0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d", "eip155Block": 10, diff --git a/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/create_failed.json b/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/create_failed.json index 8a8786c37d..bb438af159 100644 --- a/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/create_failed.json +++ b/eth/tracers/internal/tracetest/testdata/prestate_tracer_with_diff_mode/create_failed.json @@ -56,7 +56,9 @@ "arrowGlacierBlock": 13773000, "grayGlacierBlock": 15050000, "terminalTotalDifficultyPassed": true, - "subnetEVMTimestamp": 0 + "apricotPhase1BlockTimestamp": 0, + "apricotPhase2BlockTimestamp": 0, + "apricotPhase3BlockTimestamp": 0 } }, "context": { diff --git a/eth/tracers/internal/tracetest/util.go b/eth/tracers/internal/tracetest/util.go index e9fbcd07ea..f2e866abf0 100644 --- a/eth/tracers/internal/tracetest/util.go +++ b/eth/tracers/internal/tracetest/util.go @@ -7,7 +7,6 @@ // original code from which it is derived. // // Much love to the original authors for their work. -// ********** package tracetest import ( diff --git a/eth/tracers/js/tracer_test.go b/eth/tracers/js/tracer_test.go index a1eafddb14..209fce01bc 100644 --- a/eth/tracers/js/tracer_test.go +++ b/eth/tracers/js/tracer_test.go @@ -239,7 +239,7 @@ func TestNoStepExec(t *testing.T) { } func TestIsPrecompile(t *testing.T) { - chaincfg := ¶ms.ChainConfig{ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), EIP150Block: big.NewInt(0), EIP155Block: big.NewInt(0), EIP158Block: big.NewInt(0), ByzantiumBlock: big.NewInt(100), ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(200), MuirGlacierBlock: big.NewInt(0)} + chaincfg := ¶ms.ChainConfig{ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), DAOForkBlock: nil, DAOForkSupport: false, EIP150Block: big.NewInt(0), EIP155Block: big.NewInt(0), EIP158Block: big.NewInt(0), ByzantiumBlock: big.NewInt(100), ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(200), MuirGlacierBlock: big.NewInt(0)} chaincfg.ByzantiumBlock = big.NewInt(100) chaincfg.IstanbulBlock = big.NewInt(200) txCtx := vm.TxContext{GasPrice: big.NewInt(100000)} diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index c7f0593fc7..654046b004 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -38,7 +38,6 @@ import ( "github.com/ava-labs/subnet-evm/accounts/abi/bind" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/interfaces" - "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/rpc" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" @@ -73,7 +72,6 @@ var ( type Client interface { Client() *rpc.Client Close() - ChainConfig(context.Context) (*params.ChainConfigWithUpgradesJSON, error) ChainID(context.Context) (*big.Int, error) BlockByHash(context.Context, common.Hash) (*types.Block, error) BlockByNumber(context.Context, *big.Int) (*types.Block, error) @@ -151,16 +149,6 @@ func (ec *client) Client() *rpc.Client { // Blockchain Access -// ChainConfig retrieves the current chain config. -func (ec *client) ChainConfig(ctx context.Context) (*params.ChainConfigWithUpgradesJSON, error) { - var result *params.ChainConfigWithUpgradesJSON - err := ec.c.CallContext(ctx, &result, "eth_getChainConfig") - if err != nil { - return nil, err - } - return result, err -} - // ChainID retrieves the current chain ID for transaction replay protection. func (ec *client) ChainID(ctx context.Context) (*big.Int, error) { var result hexutil.Big @@ -279,7 +267,7 @@ func (ec *client) getBlock(ctx context.Context, method string, args ...interface } txs[i] = tx.tx } - return types.NewBlockWithHeader(head).WithBody(txs, uncles), nil + return types.NewBlockWithHeader(head).WithBody(txs, uncles).WithExtData(body.Version, (*[]byte)(body.BlockExtraData)), nil } // HeaderByHash returns the block header with the given hash. diff --git a/ethclient/subnetevmclient/subnet_evm_client.go b/ethclient/subnetevmclient/subnet_evm_client.go deleted file mode 100644 index 08cce85959..0000000000 --- a/ethclient/subnetevmclient/subnet_evm_client.go +++ /dev/null @@ -1,234 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. -// -// This file is a derived work, based on the go-ethereum library whose original -// notices appear below. -// -// It is distributed under a license compatible with the licensing terms of the -// original code from which it is derived. -// -// Much love to the original authors for their work. -// ********** -// Copyright 2021 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Package subnetevmclient provides an RPC client for subnet-evm-specific APIs. -package subnetevmclient - -import ( - "context" - "math/big" - "runtime" - "runtime/debug" - - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/ethclient" - "github.com/ava-labs/subnet-evm/interfaces" - "github.com/ava-labs/subnet-evm/rpc" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" -) - -// Client is a wrapper around rpc.Client that implements geth-specific functionality. -// -// If you want to use the standardized Ethereum RPC functionality, use ethclient.Client instead. -type Client struct { - c *rpc.Client -} - -// New creates a client that uses the given RPC client. -func New(c *rpc.Client) *Client { - return &Client{c} -} - -// CreateAccessList tries to create an access list for a specific transaction based on the -// current pending state of the blockchain. -func (ec *Client) CreateAccessList(ctx context.Context, msg interfaces.CallMsg) (*types.AccessList, uint64, string, error) { - type accessListResult struct { - Accesslist *types.AccessList `json:"accessList"` - Error string `json:"error,omitempty"` - GasUsed hexutil.Uint64 `json:"gasUsed"` - } - var result accessListResult - if err := ec.c.CallContext(ctx, &result, "eth_createAccessList", toCallArg(msg)); err != nil { - return nil, 0, "", err - } - return result.Accesslist, uint64(result.GasUsed), result.Error, nil -} - -// AccountResult is the result of a GetProof operation. -type AccountResult struct { - Address common.Address `json:"address"` - AccountProof []string `json:"accountProof"` - Balance *big.Int `json:"balance"` - CodeHash common.Hash `json:"codeHash"` - Nonce uint64 `json:"nonce"` - StorageHash common.Hash `json:"storageHash"` - StorageProof []StorageResult `json:"storageProof"` -} - -// StorageResult provides a proof for a key-value pair. -type StorageResult struct { - Key string `json:"key"` - Value *big.Int `json:"value"` - Proof []string `json:"proof"` -} - -// GetProof returns the account and storage values of the specified account including the Merkle-proof. -// The block number can be nil, in which case the value is taken from the latest known block. -func (ec *Client) GetProof(ctx context.Context, account common.Address, keys []string, blockNumber *big.Int) (*AccountResult, error) { - type storageResult struct { - Key string `json:"key"` - Value *hexutil.Big `json:"value"` - Proof []string `json:"proof"` - } - - type accountResult struct { - Address common.Address `json:"address"` - AccountProof []string `json:"accountProof"` - Balance *hexutil.Big `json:"balance"` - CodeHash common.Hash `json:"codeHash"` - Nonce hexutil.Uint64 `json:"nonce"` - StorageHash common.Hash `json:"storageHash"` - StorageProof []storageResult `json:"storageProof"` - } - - // Avoid keys being 'null'. - if keys == nil { - keys = []string{} - } - - var res accountResult - err := ec.c.CallContext(ctx, &res, "eth_getProof", account, keys, ethclient.ToBlockNumArg(blockNumber)) - // Turn hexutils back to normal datatypes - storageResults := make([]StorageResult, 0, len(res.StorageProof)) - for _, st := range res.StorageProof { - storageResults = append(storageResults, StorageResult{ - Key: st.Key, - Value: st.Value.ToInt(), - Proof: st.Proof, - }) - } - result := AccountResult{ - Address: res.Address, - AccountProof: res.AccountProof, - Balance: res.Balance.ToInt(), - Nonce: uint64(res.Nonce), - CodeHash: res.CodeHash, - StorageHash: res.StorageHash, - StorageProof: storageResults, - } - return &result, err -} - -// OverrideAccount specifies the state of an account to be overridden. -type OverrideAccount struct { - Nonce uint64 `json:"nonce"` - Code []byte `json:"code"` - Balance *big.Int `json:"balance"` - State map[common.Hash]common.Hash `json:"state"` - StateDiff map[common.Hash]common.Hash `json:"stateDiff"` -} - -// CallContract executes a message call transaction, which is directly executed in the VM -// of the node, but never mined into the blockchain. -// -// blockNumber selects the block height at which the call runs. It can be nil, in which -// case the code is taken from the latest known block. Note that state from very old -// blocks might not be available. -// -// overrides specifies a map of contract states that should be overwritten before executing -// the message call. -// Please use ethclient.CallContract instead if you don't need the override functionality. -func (ec *Client) CallContract(ctx context.Context, msg interfaces.CallMsg, blockNumber *big.Int, overrides *map[common.Address]OverrideAccount) ([]byte, error) { - var hex hexutil.Bytes - err := ec.c.CallContext( - ctx, &hex, "eth_call", toCallArg(msg), - ethclient.ToBlockNumArg(blockNumber), toOverrideMap(overrides), - ) - return hex, err -} - -// GCStats retrieves the current garbage collection stats from a geth node. -func (ec *Client) GCStats(ctx context.Context) (*debug.GCStats, error) { - var result debug.GCStats - err := ec.c.CallContext(ctx, &result, "debug_gcStats") - return &result, err -} - -// MemStats retrieves the current memory stats from a geth node. -func (ec *Client) MemStats(ctx context.Context) (*runtime.MemStats, error) { - var result runtime.MemStats - err := ec.c.CallContext(ctx, &result, "debug_memStats") - return &result, err -} - -// SubscribePendingTransactions subscribes to new pending transactions. -func (ec *Client) SubscribePendingTransactions(ctx context.Context, ch chan<- common.Hash) (*rpc.ClientSubscription, error) { - return ec.c.EthSubscribe(ctx, ch, "newPendingTransactions") -} - -func toCallArg(msg interfaces.CallMsg) interface{} { - arg := map[string]interface{}{ - "from": msg.From, - "to": msg.To, - } - if len(msg.Data) > 0 { - arg["input"] = hexutil.Bytes(msg.Data) - } - if msg.Value != nil { - arg["value"] = (*hexutil.Big)(msg.Value) - } - if msg.Gas != 0 { - arg["gas"] = hexutil.Uint64(msg.Gas) - } - if msg.GasPrice != nil { - arg["gasPrice"] = (*hexutil.Big)(msg.GasPrice) - } - if msg.AccessList != nil { - arg["accessList"] = msg.AccessList - } - if msg.BlobGasFeeCap != nil { - arg["maxFeePerBlobGas"] = (*hexutil.Big)(msg.BlobGasFeeCap) - } - if msg.BlobHashes != nil { - arg["blobVersionedHashes"] = msg.BlobHashes - } - return arg -} - -func toOverrideMap(overrides *map[common.Address]OverrideAccount) interface{} { - if overrides == nil { - return nil - } - type overrideAccount struct { - Nonce hexutil.Uint64 `json:"nonce"` - Code hexutil.Bytes `json:"code"` - Balance *hexutil.Big `json:"balance"` - State map[common.Hash]common.Hash `json:"state"` - StateDiff map[common.Hash]common.Hash `json:"stateDiff"` - } - result := make(map[common.Address]overrideAccount) - for addr, override := range *overrides { - result[addr] = overrideAccount{ - Nonce: hexutil.Uint64(override.Nonce), - Code: override.Code, - Balance: (*hexutil.Big)(override.Balance), - State: override.State, - StateDiff: override.StateDiff, - } - } - return &result -} diff --git a/go.mod b/go.mod index 8bc5596d73..7c0883f5fc 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,7 @@ go 1.21.12 require ( github.com/VictoriaMetrics/fastcache v1.12.1 - github.com/antithesishq/antithesis-sdk-go v0.3.8 - github.com/ava-labs/avalanchego v1.11.11-0.20240819192939-df91c2f4ab99 + github.com/ava-labs/avalanchego v1.11.11-0.20240814145500-1ac532af76df github.com/cespare/cp v0.1.0 github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 github.com/davecgh/go-spew v1.1.1 @@ -16,7 +15,6 @@ require ( github.com/fsnotify/fsnotify v1.6.0 github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 - github.com/go-cmd/cmd v1.4.1 github.com/google/uuid v1.6.0 github.com/gorilla/rpc v1.2.0 github.com/gorilla/websocket v1.4.2 @@ -29,7 +27,6 @@ require ( github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-isatty v0.0.17 github.com/olekukonko/tablewriter v0.0.5 - github.com/onsi/ginkgo/v2 v2.13.1 github.com/prometheus/client_golang v1.16.0 github.com/prometheus/client_model v0.3.0 github.com/shirou/gopsutil v3.21.11+incompatible @@ -54,8 +51,6 @@ require ( require ( github.com/DataDog/zstd v1.5.2 // indirect - github.com/NYTimes/gziphandler v1.1.1 // indirect - github.com/ava-labs/coreth v0.13.8-fixed-genesis-upgrade.0.20240813194342-7635a96aa180 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.10.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect @@ -67,61 +62,46 @@ require ( github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 // indirect github.com/cockroachdb/redact v1.1.3 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect - github.com/compose-spec/compose-go v1.20.2 // indirect github.com/consensys/bavard v0.1.13 // indirect github.com/consensys/gnark-crypto v0.12.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect - github.com/distribution/reference v0.5.0 // indirect github.com/dlclark/regexp2 v1.7.0 // indirect - github.com/docker/go-connections v0.4.0 // indirect - github.com/docker/go-units v0.5.0 // indirect github.com/ethereum/c-kzg-4844 v0.4.0 // indirect github.com/getsentry/sentry-go v0.18.0 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect - github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect - github.com/google/btree v1.1.2 // indirect github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect github.com/google/renameio/v2 v2.0.0 // indirect - github.com/gorilla/mux v1.8.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/huin/goupnp v1.3.0 // indirect - github.com/jackpal/gateway v1.0.6 // indirect - github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/klauspost/compress v1.15.15 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/magiconair/properties v1.8.6 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect - github.com/mattn/go-shellwords v1.0.12 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/pointerstructure v1.2.0 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml/v2 v2.0.5 // indirect - github.com/pires/go-proxyproto v0.6.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.10.1 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.10.0 // indirect - github.com/rs/cors v1.7.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/sirupsen/logrus v1.9.0 // indirect github.com/spf13/afero v1.8.2 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.3.0 // indirect @@ -143,7 +123,6 @@ require ( go.uber.org/zap v1.26.0 // indirect golang.org/x/net v0.23.0 // indirect golang.org/x/term v0.18.0 // indirect - golang.org/x/tools v0.17.0 // indirect gonum.org/v1/gonum v0.11.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect diff --git a/go.sum b/go.sum index 31b6d72788..73f1c7615e 100644 --- a/go.sum +++ b/go.sum @@ -46,8 +46,6 @@ github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMd github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= -github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= -github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= @@ -55,13 +53,9 @@ github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBA github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= -github.com/antithesishq/antithesis-sdk-go v0.3.8 h1:OvGoHxIcOXFJLyn9IJQ5DzByZ3YVAWNBc394ObzDRb8= -github.com/antithesishq/antithesis-sdk-go v0.3.8/go.mod h1:IUpT2DPAKh6i/YhSbt6Gl3v2yvUZjmKncl7U91fup7E= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/ava-labs/avalanchego v1.11.11-0.20240819192939-df91c2f4ab99 h1:fPWpINk7O1W4w5thqa8CGMRJ/kvycHvehBEiWwMjezI= -github.com/ava-labs/avalanchego v1.11.11-0.20240819192939-df91c2f4ab99/go.mod h1:UkyrRDXK2E15Lq2abyae2Pt+JsWvgsg1pe0/AtoMyAM= -github.com/ava-labs/coreth v0.13.8-fixed-genesis-upgrade.0.20240813194342-7635a96aa180 h1:6aIHp7wbyGVYdhHVQUbG7BEcbCMEQ5SYopPPJyipyvk= -github.com/ava-labs/coreth v0.13.8-fixed-genesis-upgrade.0.20240813194342-7635a96aa180/go.mod h1:/wNBVq7J7wlC2Kbov7kk6LV5xZvau7VF9zwTVOeyAjY= +github.com/ava-labs/avalanchego v1.11.11-0.20240814145500-1ac532af76df h1:Yp9rCHpgEsPFzpx2MXxpb/T+/NbP2NpS1EDwFquffLQ= +github.com/ava-labs/avalanchego v1.11.11-0.20240814145500-1ac532af76df/go.mod h1:Kw2GKwTaCkLwq2z3zSVH4V2eiAmq2FohHmN3AIDWjvY= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= @@ -123,8 +117,6 @@ github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZ github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= -github.com/compose-spec/compose-go v1.20.2 h1:u/yfZHn4EaHGdidrZycWpxXgFffjYULlTbRfJ51ykjQ= -github.com/compose-spec/compose-go v1.20.2/go.mod h1:+MdqXV4RA7wdFsahh/Kb8U0pAJqkg7mr4PM9tFKU8RM= github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= @@ -154,15 +146,9 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2U github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127 h1:qwcF+vdFrvPSEUDSX5RVoRccG8a5DhOdWdQ4zN62zzo= github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4= @@ -204,8 +190,6 @@ github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnR github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= -github.com/go-cmd/cmd v1.4.1 h1:JUcEIE84v8DSy02XTZpUDeGKExk2oW3DA10hTjbQwmc= -github.com/go-cmd/cmd v1.4.1/go.mod h1:tbBenttXtZU4c5djS1o7PWL5pd2xAr5sIqH1kGdNiRc= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= @@ -223,10 +207,6 @@ github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiU github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= -github.com/go-test/deep v1.0.7 h1:/VSMRlnY/JSyqxQUzQLKVMAskpY/NZKFA5j2P+0pP2M= -github.com/go-test/deep v1.0.7/go.mod h1:QV8Hv/iy04NyLBxAdO9njL0iVPN1S4d/A3NVv1V36o8= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= @@ -276,8 +256,6 @@ github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= -github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -319,8 +297,6 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk= github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= @@ -346,8 +322,6 @@ github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iU github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= -github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -359,10 +333,6 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/ github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= -github.com/jackpal/gateway v1.0.6 h1:/MJORKvJEwNVldtGVJC2p2cwCnsSoLn3hl3zxmZT7tk= -github.com/jackpal/gateway v1.0.6/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA= -github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= -github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= @@ -420,8 +390,6 @@ github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= -github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= @@ -464,18 +432,13 @@ github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vv github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.13.1 h1:LNGfMbR2OVGBfXjvRZIZ2YCTQdGKtPLvuI1rMCCj3OU= -github.com/onsi/ginkgo/v2 v2.13.1/go.mod h1:XStQ8QcGwLyF4HdfcZB8SFOS/MWCgDuXMSBe6zrvLgM= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.29.0 h1:KIA/t2t5UBzoirT4H9tsML45GEbo3ouUnBHsCfD2tVg= -github.com/onsi/gomega v1.29.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= @@ -483,8 +446,6 @@ github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwb github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= -github.com/pires/go-proxyproto v0.6.2 h1:KAZ7UteSOt6urjme6ZldyFm4wDe/z0ZUP0Yv0Dos0d8= -github.com/pires/go-proxyproto v0.6.2/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -509,8 +470,6 @@ github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4 github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= -github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -522,8 +481,6 @@ github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAm github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= @@ -550,7 +507,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= @@ -813,7 +769,6 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -899,8 +854,6 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= -golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1040,8 +993,6 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= -gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/cmdtest/test_cmd.go b/internal/cmdtest/test_cmd.go deleted file mode 100644 index cf209889d2..0000000000 --- a/internal/cmdtest/test_cmd.go +++ /dev/null @@ -1,310 +0,0 @@ -// (c) 2023, Ava Labs, Inc. -// -// This file is a derived work, based on the go-ethereum library whose original -// notices appear below. -// -// It is distributed under a license compatible with the licensing terms of the -// original code from which it is derived. -// -// Much love to the original authors for their work. -// ********** -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package cmdtest - -import ( - "bufio" - "bytes" - "fmt" - "io" - "os" - "os/exec" - "regexp" - "strings" - "sync" - "sync/atomic" - "syscall" - "testing" - "text/template" - "time" - - "github.com/ava-labs/subnet-evm/internal/reexec" -) - -func NewTestCmd(t *testing.T, data interface{}) *TestCmd { - return &TestCmd{T: t, Data: data} -} - -type TestCmd struct { - // For total convenience, all testing methods are available. - *testing.T - - Func template.FuncMap - Data interface{} - Cleanup func() - - cmd *exec.Cmd - stdout *bufio.Reader - stdin io.WriteCloser - stderr *testlogger - // Err will contain the process exit error or interrupt signal error - Err error -} - -var id atomic.Int32 - -// Run exec's the current binary using name as argv[0] which will trigger the -// reexec init function for that name (e.g. "geth-test" in cmd/geth/run_test.go) -func (tt *TestCmd) Run(name string, args ...string) { - id.Add(1) - tt.stderr = &testlogger{t: tt.T, name: fmt.Sprintf("%d", id.Load())} - tt.cmd = &exec.Cmd{ - Path: reexec.Self(), - Args: append([]string{name}, args...), - Stderr: tt.stderr, - } - stdout, err := tt.cmd.StdoutPipe() - if err != nil { - tt.Fatal(err) - } - tt.stdout = bufio.NewReader(stdout) - if tt.stdin, err = tt.cmd.StdinPipe(); err != nil { - tt.Fatal(err) - } - if err := tt.cmd.Start(); err != nil { - tt.Fatal(err) - } -} - -// InputLine writes the given text to the child's stdin. -// This method can also be called from an expect template, e.g.: -// -// geth.expect(`Passphrase: {{.InputLine "password"}}`) -func (tt *TestCmd) InputLine(s string) string { - io.WriteString(tt.stdin, s+"\n") - return "" -} - -func (tt *TestCmd) SetTemplateFunc(name string, fn interface{}) { - if tt.Func == nil { - tt.Func = make(map[string]interface{}) - } - tt.Func[name] = fn -} - -// Expect runs its argument as a template, then expects the -// child process to output the result of the template within 5s. -// -// If the template starts with a newline, the newline is removed -// before matching. -func (tt *TestCmd) Expect(tplsource string) { - // Generate the expected output by running the template. - tpl := template.Must(template.New("").Funcs(tt.Func).Parse(tplsource)) - wantbuf := new(bytes.Buffer) - if err := tpl.Execute(wantbuf, tt.Data); err != nil { - panic(err) - } - // Trim exactly one newline at the beginning. This makes tests look - // much nicer because all expect strings are at column 0. - want := bytes.TrimPrefix(wantbuf.Bytes(), []byte("\n")) - if err := tt.matchExactOutput(want); err != nil { - tt.Fatal(err) - } - tt.Logf("Matched stdout text:\n%s", want) -} - -// Output reads all output from stdout, and returns the data. -func (tt *TestCmd) Output() []byte { - var buf []byte - tt.withKillTimeout(func() { buf, _ = io.ReadAll(tt.stdout) }) - return buf -} - -func (tt *TestCmd) matchExactOutput(want []byte) error { - buf := make([]byte, len(want)) - n := 0 - tt.withKillTimeout(func() { n, _ = io.ReadFull(tt.stdout, buf) }) - buf = buf[:n] - if n < len(want) || !bytes.Equal(buf, want) { - // Grab any additional buffered output in case of mismatch - // because it might help with debugging. - buf = append(buf, make([]byte, tt.stdout.Buffered())...) - tt.stdout.Read(buf[n:]) - // Find the mismatch position. - for i := 0; i < n; i++ { - if want[i] != buf[i] { - return fmt.Errorf("output mismatch at ◊:\n---------------- (stdout text)\n%s◊%s\n---------------- (expected text)\n%s", - buf[:i], buf[i:n], want) - } - } - if n < len(want) { - return fmt.Errorf("not enough output, got until ◊:\n---------------- (stdout text)\n%s\n---------------- (expected text)\n%s◊%s", - buf, want[:n], want[n:]) - } - } - return nil -} - -// ExpectRegexp expects the child process to output text matching the -// given regular expression within 5s. -// -// Note that an arbitrary amount of output may be consumed by the -// regular expression. This usually means that expect cannot be used -// after ExpectRegexp. -func (tt *TestCmd) ExpectRegexp(regex string) (*regexp.Regexp, []string) { - regex = strings.TrimPrefix(regex, "\n") - var ( - re = regexp.MustCompile(regex) - rtee = &runeTee{in: tt.stdout} - matches []int - ) - tt.withKillTimeout(func() { matches = re.FindReaderSubmatchIndex(rtee) }) - output := rtee.buf.Bytes() - if matches == nil { - tt.Fatalf("Output did not match:\n---------------- (stdout text)\n%s\n---------------- (regular expression)\n%s", - output, regex) - return re, nil - } - tt.Logf("Matched stdout text:\n%s", output) - var submatches []string - for i := 0; i < len(matches); i += 2 { - submatch := string(output[matches[i]:matches[i+1]]) - submatches = append(submatches, submatch) - } - return re, submatches -} - -// ExpectExit expects the child process to exit within 5s without -// printing any additional text on stdout. -func (tt *TestCmd) ExpectExit() { - var output []byte - tt.withKillTimeout(func() { - output, _ = io.ReadAll(tt.stdout) - }) - tt.WaitExit() - if tt.Cleanup != nil { - tt.Cleanup() - } - if len(output) > 0 { - tt.Errorf("Unmatched stdout text:\n%s", output) - } -} - -func (tt *TestCmd) WaitExit() { - tt.Err = tt.cmd.Wait() -} - -func (tt *TestCmd) Interrupt() { - tt.Err = tt.cmd.Process.Signal(os.Interrupt) -} - -// ExitStatus exposes the process' OS exit code -// It will only return a valid value after the process has finished. -func (tt *TestCmd) ExitStatus() int { - if tt.Err != nil { - exitErr := tt.Err.(*exec.ExitError) - if exitErr != nil { - if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { - return status.ExitStatus() - } - } - } - return 0 -} - -// StderrText returns any stderr output written so far. -// The returned text holds all log lines after ExpectExit has -// returned. -func (tt *TestCmd) StderrText() string { - tt.stderr.mu.Lock() - defer tt.stderr.mu.Unlock() - return tt.stderr.buf.String() -} - -func (tt *TestCmd) CloseStdin() { - tt.stdin.Close() -} - -func (tt *TestCmd) Kill() { - tt.cmd.Process.Kill() - if tt.Cleanup != nil { - tt.Cleanup() - } -} - -func (tt *TestCmd) withKillTimeout(fn func()) { - timeout := time.AfterFunc(30*time.Second, func() { - tt.Log("killing the child process (timeout)") - tt.Kill() - }) - defer timeout.Stop() - fn() -} - -// testlogger logs all written lines via t.Log and also -// collects them for later inspection. -type testlogger struct { - t *testing.T - mu sync.Mutex - buf bytes.Buffer - name string -} - -func (tl *testlogger) Write(b []byte) (n int, err error) { - lines := bytes.Split(b, []byte("\n")) - for _, line := range lines { - if len(line) > 0 { - tl.t.Logf("(stderr:%v) %s", tl.name, line) - } - } - tl.mu.Lock() - tl.buf.Write(b) - tl.mu.Unlock() - return len(b), err -} - -// runeTee collects text read through it into buf. -type runeTee struct { - in interface { - io.Reader - io.ByteReader - io.RuneReader - } - buf bytes.Buffer -} - -func (rtee *runeTee) Read(b []byte) (n int, err error) { - n, err = rtee.in.Read(b) - rtee.buf.Write(b[:n]) - return n, err -} - -func (rtee *runeTee) ReadRune() (r rune, size int, err error) { - r, size, err = rtee.in.ReadRune() - if err == nil { - rtee.buf.WriteRune(r) - } - return r, size, err -} - -func (rtee *runeTee) ReadByte() (b byte, err error) { - b, err = rtee.in.ReadByte() - if err == nil { - rtee.buf.WriteByte(b) - } - return b, err -} diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 00ad49e908..a340930206 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -35,6 +35,7 @@ import ( "strings" "time" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/subnet-evm/accounts" "github.com/ava-labs/subnet-evm/accounts/keystore" "github.com/ava-labs/subnet-evm/accounts/scwallet" @@ -138,7 +139,7 @@ func (s *EthereumAPI) FeeHistory(ctx context.Context, blockCount math.HexOrDecim // Syncing allows the caller to determine whether the chain is syncing or not. // In geth, the response is either a map representing an ethereum.SyncProgress // struct or "false" (indicating the chain is not syncing). -// In subnet-evm, avalanchego prevents API calls unless bootstrapping is complete, +// In coreth, avalanchego prevents API calls unless bootstrapping is complete, // so we always return false here for API compatibility. func (s *EthereumAPI) Syncing() (interface{}, error) { return false, nil @@ -635,6 +636,17 @@ func (s *BlockChainAPI) GetBalance(ctx context.Context, address common.Address, return (*hexutil.Big)(state.GetBalance(address)), state.Error() } +// GetAssetBalance returns the amount of [assetID] for the given address in the state of the +// given block number. The rpc.LatestBlockNumber, rpc.PendingBlockNumber, and +// rpc.AcceptedBlockNumber meta block numbers are also allowed. +func (s *BlockChainAPI) GetAssetBalance(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash, assetID ids.ID) (*hexutil.Big, error) { + state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) + if state == nil || err != nil { + return nil, err + } + return (*hexutil.Big)(state.GetBalanceMultiCoin(address, common.Hash(assetID))), state.Error() +} + // Result structs for GetProof type AccountResult struct { Address common.Address `json:"address"` @@ -769,7 +781,7 @@ func (s *BlockChainAPI) GetHeaderByNumber(ctx context.Context, number rpc.BlockN header, err := s.b.HeaderByNumber(ctx, number) if header != nil && err == nil { response := s.rpcMarshalHeader(ctx, header) - // subnet-evm has no notion of a pending block + // coreth has no notion of a pending block // if number == rpc.PendingBlockNumber { // // Pending header need to nil out a few fields // for _, field := range []string{"hash", "nonce", "miner"} { @@ -801,7 +813,7 @@ func (s *BlockChainAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNu block, err := s.b.BlockByNumber(ctx, number) if block != nil && err == nil { response, err := s.rpcMarshalBlock(ctx, block, true, fullTx) - // subnet-evm has no notion of a pending block + // coreth has no notion of a pending block // if err == nil && number == rpc.PendingBlockNumber { // // Pending blocks need to nil out a few fields // for _, field := range []string{"hash", "nonce", "miner"} { @@ -1229,10 +1241,14 @@ func RPCMarshalHeader(head *types.Header) map[string]interface{} { "timestamp": hexutil.Uint64(head.Time), "transactionsRoot": head.TxHash, "receiptsRoot": head.ReceiptHash, + "extDataHash": head.ExtDataHash, } if head.BaseFee != nil { result["baseFeePerGas"] = (*hexutil.Big)(head.BaseFee) } + if head.ExtDataGasUsed != nil { + result["extDataGasUsed"] = (*hexutil.Big)(head.ExtDataGasUsed) + } if head.BlockGasCost != nil { result["blockGasCost"] = (*hexutil.Big)(head.BlockGasCost) } @@ -1254,6 +1270,7 @@ func RPCMarshalHeader(head *types.Header) map[string]interface{} { func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool, config *params.ChainConfig) map[string]interface{} { fields := RPCMarshalHeader(block.Header()) fields["size"] = hexutil.Uint64(block.Size()) + fields["blockExtraData"] = hexutil.Bytes(block.ExtData()) if inclTx { formatTx := func(idx int, tx *types.Transaction) interface{} { @@ -1284,7 +1301,7 @@ func RPCMarshalBlock(block *types.Block, inclTx bool, fullTx bool, config *param // a `BlockchainAPI`. func (s *BlockChainAPI) rpcMarshalHeader(ctx context.Context, header *types.Header) map[string]interface{} { fields := RPCMarshalHeader(header) - // Note: Subnet-EVM enforces that the difficulty of a block is always 1, such that the total difficulty of a block + // Note: Coreth enforces that the difficulty of a block is always 1, such that the total difficulty of a block // will be equivalent to its height. fields["totalDifficulty"] = (*hexutil.Big)(header.Number) return fields @@ -1295,7 +1312,7 @@ func (s *BlockChainAPI) rpcMarshalHeader(ctx context.Context, header *types.Head func (s *BlockChainAPI) rpcMarshalBlock(ctx context.Context, b *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) { fields := RPCMarshalBlock(b, inclTx, fullTx, s.b.ChainConfig()) if inclTx { - // Note: Subnet-EVM enforces that the difficulty of a block is always 1, such that the total difficulty of a block + // Note: Coreth enforces that the difficulty of a block is always 1, such that the total difficulty of a block // will be equivalent to its height. fields["totalDifficulty"] = (*hexutil.Big)(b.Number()) } @@ -1418,7 +1435,7 @@ func effectiveGasPrice(tx *types.Transaction, baseFee *big.Int) *big.Int { // NewRPCTransaction returns a pending transaction that will serialize to the RPC representation // Note: in go-ethereum this function is called NewRPCPendingTransaction. -// In subnet-evm, we have renamed it to NewRPCTransaction as it is used for accepted transactions as well. +// In coreth, we have renamed it to NewRPCTransaction as it is used for accepted transactions as well. func NewRPCTransaction(tx *types.Transaction, current *types.Header, baseFee *big.Int, config *params.ChainConfig) *RPCTransaction { var ( blockNumber = uint64(0) diff --git a/internal/ethapi/api_extra.go b/internal/ethapi/api_extra.go index 5823ff3dc7..5aa1c185cd 100644 --- a/internal/ethapi/api_extra.go +++ b/internal/ethapi/api_extra.go @@ -6,11 +6,8 @@ package ethapi import ( "context" "fmt" - "math/big" - "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/core" - "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/rpc" "github.com/ethereum/go-ethereum/common" @@ -18,8 +15,9 @@ import ( "github.com/ethereum/go-ethereum/rlp" ) -func (s *BlockChainAPI) GetChainConfig(ctx context.Context) *params.ChainConfigWithUpgradesJSON { - return s.b.ChainConfig().ToWithUpgradesJSON() +// GetChainConfig returns the chain config. +func (api *BlockChainAPI) GetChainConfig(ctx context.Context) *params.ChainConfig { + return api.b.ChainConfig() } type DetailedExecutionResult struct { @@ -94,77 +92,3 @@ func (s *BlockChainAPI) GetBadBlocks(ctx context.Context) ([]*BadBlockArgs, erro } return results, nil } - -type FeeConfigResult struct { - FeeConfig commontype.FeeConfig `json:"feeConfig"` - LastChangedAt *big.Int `json:"lastChangedAt,omitempty"` -} - -func (s *BlockChainAPI) FeeConfig(ctx context.Context, blockNrOrHash *rpc.BlockNumberOrHash) (*FeeConfigResult, error) { - var ( - header *types.Header - err error - ) - if blockNrOrHash == nil { - header = s.b.CurrentHeader() - } else { - header, err = s.b.HeaderByNumberOrHash(ctx, *blockNrOrHash) - if err != nil { - return nil, err - } - } - - feeConfig, lastChangedAt, err := s.b.GetFeeConfigAt(header) - if err != nil { - return nil, err - } - return &FeeConfigResult{FeeConfig: feeConfig, LastChangedAt: lastChangedAt}, nil -} - -// GetActivePrecompilesAt returns the active precompile configs at the given block timestamp. -// DEPRECATED: Use GetActiveRulesAt instead. -func (s *BlockChainAPI) GetActivePrecompilesAt(ctx context.Context, blockTimestamp *uint64) params.Precompiles { - var timestamp uint64 - if blockTimestamp == nil { - timestamp = s.b.CurrentHeader().Time - } else { - timestamp = *blockTimestamp - } - - return s.b.ChainConfig().EnabledStatefulPrecompiles(timestamp) -} - -type ActivePrecompilesResult struct { - Timestamp uint64 `json:"timestamp"` -} - -type ActiveRulesResult struct { - EthRules params.EthRules `json:"ethRules"` - AvalancheRules params.AvalancheRules `json:"avalancheRules"` - ActivePrecompiles map[string]ActivePrecompilesResult `json:"precompiles"` -} - -// GetActiveRulesAt returns the active rules at the given block timestamp. -func (s *BlockChainAPI) GetActiveRulesAt(ctx context.Context, blockTimestamp *uint64) ActiveRulesResult { - var timestamp uint64 - if blockTimestamp == nil { - timestamp = s.b.CurrentHeader().Time - } else { - timestamp = *blockTimestamp - } - rules := s.b.ChainConfig().Rules(common.Big0, timestamp) - res := ActiveRulesResult{ - EthRules: rules.EthRules, - AvalancheRules: rules.AvalancheRules, - } - res.ActivePrecompiles = make(map[string]ActivePrecompilesResult) - for _, precompileConfig := range rules.ActivePrecompiles { - if precompileConfig.Timestamp() == nil { - continue - } - res.ActivePrecompiles[precompileConfig.Key()] = ActivePrecompilesResult{ - Timestamp: *precompileConfig.Timestamp(), - } - } - return res -} diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 1fd0b0da75..7123ccc626 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -39,9 +39,7 @@ import ( "testing" "time" - "github.com/ava-labs/avalanchego/upgrade" "github.com/ava-labs/subnet-evm/accounts" - "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/consensus" "github.com/ava-labs/subnet-evm/consensus/dummy" "github.com/ava-labs/subnet-evm/core" @@ -552,9 +550,6 @@ func (b testBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) even func (b testBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription { panic("implement me") } -func (b testBackend) GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error) { - panic("implement me") -} func (b testBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { panic("implement me") } @@ -699,7 +694,7 @@ func TestEstimateGas(t *testing.T) { call: TransactionArgs{ From: &accounts[0].addr, Input: hex2Bytes("6080604052348015600f57600080fd5b50483a1015601c57600080fd5b60003a111560315760004811603057600080fd5b5b603f80603e6000396000f3fe6080604052600080fdfea264697066735822122060729c2cee02b10748fae5200f1c9da4661963354973d9154c13a8e9ce9dee1564736f6c63430008130033"), - GasPrice: (*hexutil.Big)(big.NewInt(params.TestInitialBaseFee)), // Legacy as pricing + GasPrice: (*hexutil.Big)(big.NewInt(params.ApricotPhase3InitialBaseFee)), // Legacy as pricing }, expectErr: nil, want: 67617, @@ -709,7 +704,7 @@ func TestEstimateGas(t *testing.T) { call: TransactionArgs{ From: &accounts[0].addr, Input: hex2Bytes("6080604052348015600f57600080fd5b50483a1015601c57600080fd5b60003a111560315760004811603057600080fd5b5b603f80603e6000396000f3fe6080604052600080fdfea264697066735822122060729c2cee02b10748fae5200f1c9da4661963354973d9154c13a8e9ce9dee1564736f6c63430008130033"), - MaxFeePerGas: (*hexutil.Big)(big.NewInt(params.TestInitialBaseFee)), // 1559 gas pricing + MaxFeePerGas: (*hexutil.Big)(big.NewInt(params.ApricotPhase3InitialBaseFee)), // 1559 gas pricing }, expectErr: nil, want: 67617, @@ -984,11 +979,13 @@ func TestRPCMarshalBlock(t *testing.T) { inclTx: false, fullTx: false, want: `{ + "blockExtraData":"0x", "difficulty": "0x0", + "extDataHash":"0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "0x", "gasLimit": "0x0", "gasUsed": "0x0", - "hash": "0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee", + "hash": "0xed74541829e559a9256f4810c2358498c7fe41287cb57f4b8b8334ea81560757", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "miner": "0x0000000000000000000000000000000000000000", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", @@ -997,7 +994,7 @@ func TestRPCMarshalBlock(t *testing.T) { "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "size": "0x296", + "size": "0x2b9", "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", "timestamp": "0x0", "transactionsRoot": "0x661a9febcfa8f1890af549b874faf9fa274aede26ef489d9db0b25daa569450e", @@ -1009,11 +1006,13 @@ func TestRPCMarshalBlock(t *testing.T) { inclTx: true, fullTx: false, want: `{ + "blockExtraData":"0x", "difficulty": "0x0", + "extDataHash":"0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "0x", "gasLimit": "0x0", "gasUsed": "0x0", - "hash": "0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee", + "hash": "0xed74541829e559a9256f4810c2358498c7fe41287cb57f4b8b8334ea81560757", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "miner": "0x0000000000000000000000000000000000000000", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", @@ -1022,7 +1021,7 @@ func TestRPCMarshalBlock(t *testing.T) { "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "size": "0x296", + "size": "0x2b9", "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", "timestamp": "0x0", "transactions": [ @@ -1040,11 +1039,13 @@ func TestRPCMarshalBlock(t *testing.T) { inclTx: true, fullTx: true, want: `{ + "blockExtraData":"0x", "difficulty": "0x0", + "extDataHash":"0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "0x", "gasLimit": "0x0", "gasUsed": "0x0", - "hash": "0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee", + "hash": "0xed74541829e559a9256f4810c2358498c7fe41287cb57f4b8b8334ea81560757", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "miner": "0x0000000000000000000000000000000000000000", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", @@ -1053,12 +1054,12 @@ func TestRPCMarshalBlock(t *testing.T) { "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "size": "0x296", + "size": "0x2b9", "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000", "timestamp": "0x0", "transactions": [ { - "blockHash": "0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee", + "blockHash": "0xed74541829e559a9256f4810c2358498c7fe41287cb57f4b8b8334ea81560757", "blockNumber": "0x64", "from": "0x0000000000000000000000000000000000000000", "gas": "0x457", @@ -1078,7 +1079,7 @@ func TestRPCMarshalBlock(t *testing.T) { "yParity": "0x0" }, { - "blockHash": "0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee", + "blockHash": "0xed74541829e559a9256f4810c2358498c7fe41287cb57f4b8b8334ea81560757", "blockNumber": "0x64", "from": "0x0000000000000000000000000000000000000000", "gas": "0x457", @@ -1096,7 +1097,7 @@ func TestRPCMarshalBlock(t *testing.T) { "s": "0x0" }, { - "blockHash": "0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee", + "blockHash": "0xed74541829e559a9256f4810c2358498c7fe41287cb57f4b8b8334ea81560757", "blockNumber": "0x64", "from": "0x0000000000000000000000000000000000000000", "gas": "0x457", @@ -1116,7 +1117,7 @@ func TestRPCMarshalBlock(t *testing.T) { "yParity": "0x0" }, { - "blockHash": "0x9b73c83b25d0faf7eab854e3684c7e394336d6e135625aafa5c183f27baa8fee", + "blockHash": "0xed74541829e559a9256f4810c2358498c7fe41287cb57f4b8b8334ea81560757", "blockNumber": "0x64", "from": "0x0000000000000000000000000000000000000000", "gas": "0x457", @@ -1141,7 +1142,7 @@ func TestRPCMarshalBlock(t *testing.T) { } for i, tc := range testSuite { - resp := RPCMarshalBlock(block, tc.inclTx, tc.fullTx, params.TestSubnetEVMChainConfig) + resp := RPCMarshalBlock(block, tc.inclTx, tc.fullTx, params.TestChainConfig) out, err := json.Marshal(resp) if err != nil { t.Errorf("test %d: json marshal error: %v", i, err) @@ -1179,7 +1180,7 @@ func TestRPCGetBlockOrHeader(t *testing.T) { }) pending = types.NewBlock(&types.Header{Number: big.NewInt(11), Time: 42}, []*types.Transaction{tx}, nil, nil, blocktest.NewHasher()) ) - backend := newTestBackend(t, genBlocks, genesis, dummy.NewCoinbaseFaker(), func(i int, b *core.BlockGen) { + backend := newTestBackend(t, genBlocks, genesis, dummy.NewFaker(), func(i int, b *core.BlockGen) { // Transfer from account[0] to account[1] // value: 1000 wei // fee: 0 wei @@ -1400,6 +1401,8 @@ func TestRPCGetBlockOrHeader(t *testing.T) { func setupReceiptBackend(t *testing.T, genBlocks int) (*testBackend, []common.Hash) { config := *params.TestChainConfig + // config.ShanghaiTime = new(uint64) + config.CancunTime = new(uint64) var ( acc1Key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") acc2Key, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") @@ -1410,7 +1413,6 @@ func setupReceiptBackend(t *testing.T, genBlocks int) (*testBackend, []common.Ha Config: &config, ExcessBlobGas: new(uint64), BlobGasUsed: new(uint64), - Timestamp: uint64(upgrade.InitiallyActiveTime.Unix()), Alloc: core.GenesisAlloc{ acc1Addr: {Balance: big.NewInt(params.Ether)}, acc2Addr: {Balance: big.NewInt(params.Ether)}, @@ -1434,7 +1436,8 @@ func setupReceiptBackend(t *testing.T, genBlocks int) (*testBackend, []common.Ha // Set the terminal total difficulty in the config // genesis.Config.TerminalTotalDifficulty = big.NewInt(0) // genesis.Config.TerminalTotalDifficultyPassed = true - backend := newTestBackend(t, genBlocks, genesis, dummy.NewCoinbaseFaker(), func(i int, b *core.BlockGen) { + // FullFaker used to skip header verification that enforces no blobs. + backend := newTestBackend(t, genBlocks, genesis, dummy.NewFullFaker(), func(i int, b *core.BlockGen) { var ( tx *types.Transaction err error @@ -1487,6 +1490,7 @@ func setupReceiptBackend(t *testing.T, genBlocks int) (*testBackend, []common.Ha b.AddTx(tx) txHashes[i] = tx.Hash() } + // b.SetPoS() }) return backend, txHashes } diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index e80e4cad51..60ab5a45c0 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -33,7 +33,6 @@ import ( "time" "github.com/ava-labs/subnet-evm/accounts" - "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/consensus" "github.com/ava-labs/subnet-evm/core" "github.com/ava-labs/subnet-evm/core/bloombits" @@ -80,7 +79,6 @@ type Backend interface { SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription - GetFeeConfigAt(parent *types.Header) (commontype.FeeConfig, *big.Int, error) BadBlocks() ([]*types.Block, []*core.BadBlockReason) // Transaction pool API diff --git a/internal/ethapi/testdata/eth_getBlockByHash-hash-1.json b/internal/ethapi/testdata/eth_getBlockByHash-hash-1.json index a21af855b8..9c7b28fb93 100644 --- a/internal/ethapi/testdata/eth_getBlockByHash-hash-1.json +++ b/internal/ethapi/testdata/eth_getBlockByHash-hash-1.json @@ -1,26 +1,29 @@ { - "baseFeePerGas": "0x5d21dba00", + "baseFeePerGas": "0x34630b8a00", + "blockExtraData": "0x", "blockGasCost": "0x0", "difficulty": "0x1", + "extDataGasUsed": "0x0", + "extDataHash": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "gasLimit": "0x7a1200", + "gasLimit": "0xe4e1c0", "gasUsed": "0x5208", - "hash": "0x0f67ad1fc8052afad4c24551748600c164091cf37e068adef76315025d3c78e7", + "hash": "0x4729ded876444349cc71f0062017ccf35068c2831d27f75c1d35bc4d3eb0c3ba", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "miner": "0x0000000000000000000000000000000000000000", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "nonce": "0x0000000000000000", "number": "0x1", - "parentHash": "0x3ead7923676a44500c46ad2192a0fc084aa42063b1703e6866f138a47fb1a9ca", + "parentHash": "0x1509a989ede83d85f51c9a489ef5f9a1ef15e08ec50f9d569ad41b56ecc4dffd", "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "size": "0x2bb", - "stateRoot": "0x6b830601767ac4968163193facbe20123435180e325910b2c50efa21f778c697", + "size": "0x2df", + "stateRoot": "0xe6a34faf8c7bd056de278fa20a0bf3236c8c157f6bbf40bced8e5961c51f3691", "timestamp": "0xa", "totalDifficulty": "0x1", "transactions": [ - "0xdf92bc7c4c0341ecbdcd2a3ca7011fe9e21df4b8553bf0c8caabe6cb4a1aee26" + "0x09220a8629fd020cbb341ab146e6acb4dc4811ab5fdf021bec3d3219c5a29ab3" ], - "transactionsRoot": "0x87c65a3f1a98dafe282ace11eaf88b8f31bf41fe6794d401d2f986c1af84bcd5", + "transactionsRoot": "0x272d13afea9f2f2c9b9ab3d8bbdb492ce5f7b215c493adaac5d98abc9ad62352", "uncles": [] } \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockByHash-hash-genesis.json b/internal/ethapi/testdata/eth_getBlockByHash-hash-genesis.json index 446f5db6ab..b69afea8ff 100644 --- a/internal/ethapi/testdata/eth_getBlockByHash-hash-genesis.json +++ b/internal/ethapi/testdata/eth_getBlockByHash-hash-genesis.json @@ -1,10 +1,12 @@ { - "baseFeePerGas": "0x5d21dba00", + "baseFeePerGas": "0x34630b8a00", + "blockExtraData": "0x", "difficulty": "0x20000", + "extDataHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "0x", "gasLimit": "0x47e7c4", "gasUsed": "0x0", - "hash": "0x3ead7923676a44500c46ad2192a0fc084aa42063b1703e6866f138a47fb1a9ca", + "hash": "0x1509a989ede83d85f51c9a489ef5f9a1ef15e08ec50f9d569ad41b56ecc4dffd", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "miner": "0x0000000000000000000000000000000000000000", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", @@ -13,8 +15,8 @@ "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "size": "0x201", - "stateRoot": "0xfe168c5e9584a85927212e5bea5304bb7d0d8a893453b4b2c52176a72f585ae2", + "size": "0x224", + "stateRoot": "0xb5a65ee2c90afcbaccd940a768b2a719394755b7275bce8a4c0c742991e17131", "timestamp": "0x0", "totalDifficulty": "0x0", "transactions": [], diff --git a/internal/ethapi/testdata/eth_getBlockByHash-hash-latest-1-fullTx.json b/internal/ethapi/testdata/eth_getBlockByHash-hash-latest-1-fullTx.json index 4d54e3f10b..f073a2b21b 100644 --- a/internal/ethapi/testdata/eth_getBlockByHash-hash-latest-1-fullTx.json +++ b/internal/ethapi/testdata/eth_getBlockByHash-hash-latest-1-fullTx.json @@ -1,42 +1,45 @@ { - "baseFeePerGas": "0x5d21dba00", + "baseFeePerGas": "0x29d101e35b", + "blockExtraData": "0x", "blockGasCost": "0x0", "difficulty": "0x1", + "extDataGasUsed": "0x0", + "extDataHash": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "gasLimit": "0x7a1200", + "gasLimit": "0xe4e1c0", "gasUsed": "0x5208", - "hash": "0x0583a9d630632001771b4ecc7d62574aec3825aff47e2a680b0ea4ddb79e7365", + "hash": "0x3556a043faadc4f03ba18453a084ba91d49c6baec50d7792dab1f3f552d2c973", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "miner": "0x0000000000000000000000000000000000000000", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "nonce": "0x0000000000000000", "number": "0x9", - "parentHash": "0x2fab5c6892c66668842683ced6b384c2ee83bfd6096a58f451290cabaf57a63e", + "parentHash": "0x2006bc7d2d25edaef78eb0dd7392262bd865a7a0ab4ec70c169ab02eeb3a53f9", "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "size": "0x2bb", - "stateRoot": "0x3703d70c6443e809ce035c2a8212dbf9813f6b7d1b0f597766e9023867a852f5", + "size": "0x2df", + "stateRoot": "0x559db971f1bdcc4a39b612c81c26c3d91ca805d61eb614132352196f03d38192", "timestamp": "0x5a", "totalDifficulty": "0x9", "transactions": [ { - "blockHash": "0x0583a9d630632001771b4ecc7d62574aec3825aff47e2a680b0ea4ddb79e7365", + "blockHash": "0x3556a043faadc4f03ba18453a084ba91d49c6baec50d7792dab1f3f552d2c973", "blockNumber": "0x9", "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", "gas": "0x5208", - "gasPrice": "0x5d21dba00", - "hash": "0x237f95840187a93f8aaf8d6f1515f8a8ac9d9359fcb0c220cdb3d642d6b9a19a", + "gasPrice": "0x29d101e35b", + "hash": "0xc187ad4e657c1a75234a6456f52bb6d8fe3a234729cec11afa46bea7ffbce0d7", "input": "0x", "nonce": "0x8", "to": "0x0d3ab14bbad3d99f4203bd7a11acb94882050e7e", "transactionIndex": "0x0", "value": "0x3e8", "type": "0x0", - "v": "0x1c", - "r": "0xd7cdc527490b7ba29c515aae3bbe80c67729cda7f736e6515652cfc40e9da68f", - "s": "0x4d0a4a59bef165b16f910bdadd41efaaad1b73549bacc35eaf6d073eb1fb92b7" + "v": "0x1b", + "r": "0xc3590d4884299ac2e6d6db2de1aa36caf8ce3630bd41a5dd862b7aa5820a8501", + "s": "0x72325946a27ab5b142405a4db54691fe00b2249eed6dd6667fe9d36f1412bd1" } ], - "transactionsRoot": "0xe16929d9c7efab0f962c1ed8c1295ddff42d3026779ed1318ea079ca580ee4cb", + "transactionsRoot": "0x636f1c9b3e00b425fbab75c79989315b6f30c48f5a55ee636b1fc3805538c5f7", "uncles": [] } \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockByHash-hash-latest.json b/internal/ethapi/testdata/eth_getBlockByHash-hash-latest.json index 7917a2acc7..f87d96ee4b 100644 --- a/internal/ethapi/testdata/eth_getBlockByHash-hash-latest.json +++ b/internal/ethapi/testdata/eth_getBlockByHash-hash-latest.json @@ -1,26 +1,29 @@ { - "baseFeePerGas": "0x5d21dba00", + "baseFeePerGas": "0x28a7a56427", + "blockExtraData": "0x", "blockGasCost": "0x0", "difficulty": "0x1", + "extDataGasUsed": "0x0", + "extDataHash": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "gasLimit": "0x7a1200", + "gasLimit": "0xe4e1c0", "gasUsed": "0x5208", - "hash": "0x1ec39e7ec46f8df1fb31cfca53fbf71a01869af8bd8f9a1bccbffc16ffa1461d", + "hash": "0x9e839b3ad2ecd76f842bb6891144e073f015c785f5aad0001968222334131d02", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "miner": "0x0000000000000000000000000000000000000000", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "nonce": "0x0000000000000000", "number": "0xa", - "parentHash": "0x0583a9d630632001771b4ecc7d62574aec3825aff47e2a680b0ea4ddb79e7365", + "parentHash": "0x3556a043faadc4f03ba18453a084ba91d49c6baec50d7792dab1f3f552d2c973", "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "size": "0x2bb", - "stateRoot": "0x7e06187d15d50badf60930290fb292ebe43e79553ad8b7d8f1b614316631def7", + "size": "0x2df", + "stateRoot": "0xdded7f848268f119f12c77fdf32520ac3f98d710164997b6adf038e76c5007fe", "timestamp": "0x64", "totalDifficulty": "0xa", "transactions": [ - "0x71be223424ab6e3457513a760b196d43b094414c32a70ff929b2b720a16b832d" + "0x2947d62ddb16c5312dc40e5d9b29d75447bc011e1393c5f1544144bc764e16f8" ], - "transactionsRoot": "0x69ff8003291e1cd08f75d174f070618f7291e4540b2e33f60b3375743e3fda01", + "transactionsRoot": "0xf578b0855e1c4509f5248387fcc5d3144552ade53be98825df0884f36fbc3ab9", "uncles": [] } \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockByNumber-number-0.json b/internal/ethapi/testdata/eth_getBlockByNumber-number-0.json index 446f5db6ab..b69afea8ff 100644 --- a/internal/ethapi/testdata/eth_getBlockByNumber-number-0.json +++ b/internal/ethapi/testdata/eth_getBlockByNumber-number-0.json @@ -1,10 +1,12 @@ { - "baseFeePerGas": "0x5d21dba00", + "baseFeePerGas": "0x34630b8a00", + "blockExtraData": "0x", "difficulty": "0x20000", + "extDataHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "0x", "gasLimit": "0x47e7c4", "gasUsed": "0x0", - "hash": "0x3ead7923676a44500c46ad2192a0fc084aa42063b1703e6866f138a47fb1a9ca", + "hash": "0x1509a989ede83d85f51c9a489ef5f9a1ef15e08ec50f9d569ad41b56ecc4dffd", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "miner": "0x0000000000000000000000000000000000000000", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", @@ -13,8 +15,8 @@ "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "size": "0x201", - "stateRoot": "0xfe168c5e9584a85927212e5bea5304bb7d0d8a893453b4b2c52176a72f585ae2", + "size": "0x224", + "stateRoot": "0xb5a65ee2c90afcbaccd940a768b2a719394755b7275bce8a4c0c742991e17131", "timestamp": "0x0", "totalDifficulty": "0x0", "transactions": [], diff --git a/internal/ethapi/testdata/eth_getBlockByNumber-number-1.json b/internal/ethapi/testdata/eth_getBlockByNumber-number-1.json index a21af855b8..9c7b28fb93 100644 --- a/internal/ethapi/testdata/eth_getBlockByNumber-number-1.json +++ b/internal/ethapi/testdata/eth_getBlockByNumber-number-1.json @@ -1,26 +1,29 @@ { - "baseFeePerGas": "0x5d21dba00", + "baseFeePerGas": "0x34630b8a00", + "blockExtraData": "0x", "blockGasCost": "0x0", "difficulty": "0x1", + "extDataGasUsed": "0x0", + "extDataHash": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "gasLimit": "0x7a1200", + "gasLimit": "0xe4e1c0", "gasUsed": "0x5208", - "hash": "0x0f67ad1fc8052afad4c24551748600c164091cf37e068adef76315025d3c78e7", + "hash": "0x4729ded876444349cc71f0062017ccf35068c2831d27f75c1d35bc4d3eb0c3ba", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "miner": "0x0000000000000000000000000000000000000000", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "nonce": "0x0000000000000000", "number": "0x1", - "parentHash": "0x3ead7923676a44500c46ad2192a0fc084aa42063b1703e6866f138a47fb1a9ca", + "parentHash": "0x1509a989ede83d85f51c9a489ef5f9a1ef15e08ec50f9d569ad41b56ecc4dffd", "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "size": "0x2bb", - "stateRoot": "0x6b830601767ac4968163193facbe20123435180e325910b2c50efa21f778c697", + "size": "0x2df", + "stateRoot": "0xe6a34faf8c7bd056de278fa20a0bf3236c8c157f6bbf40bced8e5961c51f3691", "timestamp": "0xa", "totalDifficulty": "0x1", "transactions": [ - "0xdf92bc7c4c0341ecbdcd2a3ca7011fe9e21df4b8553bf0c8caabe6cb4a1aee26" + "0x09220a8629fd020cbb341ab146e6acb4dc4811ab5fdf021bec3d3219c5a29ab3" ], - "transactionsRoot": "0x87c65a3f1a98dafe282ace11eaf88b8f31bf41fe6794d401d2f986c1af84bcd5", + "transactionsRoot": "0x272d13afea9f2f2c9b9ab3d8bbdb492ce5f7b215c493adaac5d98abc9ad62352", "uncles": [] } \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockByNumber-number-latest-1.json b/internal/ethapi/testdata/eth_getBlockByNumber-number-latest-1.json index 4d54e3f10b..f073a2b21b 100644 --- a/internal/ethapi/testdata/eth_getBlockByNumber-number-latest-1.json +++ b/internal/ethapi/testdata/eth_getBlockByNumber-number-latest-1.json @@ -1,42 +1,45 @@ { - "baseFeePerGas": "0x5d21dba00", + "baseFeePerGas": "0x29d101e35b", + "blockExtraData": "0x", "blockGasCost": "0x0", "difficulty": "0x1", + "extDataGasUsed": "0x0", + "extDataHash": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "gasLimit": "0x7a1200", + "gasLimit": "0xe4e1c0", "gasUsed": "0x5208", - "hash": "0x0583a9d630632001771b4ecc7d62574aec3825aff47e2a680b0ea4ddb79e7365", + "hash": "0x3556a043faadc4f03ba18453a084ba91d49c6baec50d7792dab1f3f552d2c973", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "miner": "0x0000000000000000000000000000000000000000", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "nonce": "0x0000000000000000", "number": "0x9", - "parentHash": "0x2fab5c6892c66668842683ced6b384c2ee83bfd6096a58f451290cabaf57a63e", + "parentHash": "0x2006bc7d2d25edaef78eb0dd7392262bd865a7a0ab4ec70c169ab02eeb3a53f9", "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "size": "0x2bb", - "stateRoot": "0x3703d70c6443e809ce035c2a8212dbf9813f6b7d1b0f597766e9023867a852f5", + "size": "0x2df", + "stateRoot": "0x559db971f1bdcc4a39b612c81c26c3d91ca805d61eb614132352196f03d38192", "timestamp": "0x5a", "totalDifficulty": "0x9", "transactions": [ { - "blockHash": "0x0583a9d630632001771b4ecc7d62574aec3825aff47e2a680b0ea4ddb79e7365", + "blockHash": "0x3556a043faadc4f03ba18453a084ba91d49c6baec50d7792dab1f3f552d2c973", "blockNumber": "0x9", "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", "gas": "0x5208", - "gasPrice": "0x5d21dba00", - "hash": "0x237f95840187a93f8aaf8d6f1515f8a8ac9d9359fcb0c220cdb3d642d6b9a19a", + "gasPrice": "0x29d101e35b", + "hash": "0xc187ad4e657c1a75234a6456f52bb6d8fe3a234729cec11afa46bea7ffbce0d7", "input": "0x", "nonce": "0x8", "to": "0x0d3ab14bbad3d99f4203bd7a11acb94882050e7e", "transactionIndex": "0x0", "value": "0x3e8", "type": "0x0", - "v": "0x1c", - "r": "0xd7cdc527490b7ba29c515aae3bbe80c67729cda7f736e6515652cfc40e9da68f", - "s": "0x4d0a4a59bef165b16f910bdadd41efaaad1b73549bacc35eaf6d073eb1fb92b7" + "v": "0x1b", + "r": "0xc3590d4884299ac2e6d6db2de1aa36caf8ce3630bd41a5dd862b7aa5820a8501", + "s": "0x72325946a27ab5b142405a4db54691fe00b2249eed6dd6667fe9d36f1412bd1" } ], - "transactionsRoot": "0xe16929d9c7efab0f962c1ed8c1295ddff42d3026779ed1318ea079ca580ee4cb", + "transactionsRoot": "0x636f1c9b3e00b425fbab75c79989315b6f30c48f5a55ee636b1fc3805538c5f7", "uncles": [] } \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockByNumber-tag-latest.json b/internal/ethapi/testdata/eth_getBlockByNumber-tag-latest.json index 7917a2acc7..f87d96ee4b 100644 --- a/internal/ethapi/testdata/eth_getBlockByNumber-tag-latest.json +++ b/internal/ethapi/testdata/eth_getBlockByNumber-tag-latest.json @@ -1,26 +1,29 @@ { - "baseFeePerGas": "0x5d21dba00", + "baseFeePerGas": "0x28a7a56427", + "blockExtraData": "0x", "blockGasCost": "0x0", "difficulty": "0x1", + "extDataGasUsed": "0x0", + "extDataHash": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "gasLimit": "0x7a1200", + "gasLimit": "0xe4e1c0", "gasUsed": "0x5208", - "hash": "0x1ec39e7ec46f8df1fb31cfca53fbf71a01869af8bd8f9a1bccbffc16ffa1461d", + "hash": "0x9e839b3ad2ecd76f842bb6891144e073f015c785f5aad0001968222334131d02", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "miner": "0x0000000000000000000000000000000000000000", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "nonce": "0x0000000000000000", "number": "0xa", - "parentHash": "0x0583a9d630632001771b4ecc7d62574aec3825aff47e2a680b0ea4ddb79e7365", + "parentHash": "0x3556a043faadc4f03ba18453a084ba91d49c6baec50d7792dab1f3f552d2c973", "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "size": "0x2bb", - "stateRoot": "0x7e06187d15d50badf60930290fb292ebe43e79553ad8b7d8f1b614316631def7", + "size": "0x2df", + "stateRoot": "0xdded7f848268f119f12c77fdf32520ac3f98d710164997b6adf038e76c5007fe", "timestamp": "0x64", "totalDifficulty": "0xa", "transactions": [ - "0x71be223424ab6e3457513a760b196d43b094414c32a70ff929b2b720a16b832d" + "0x2947d62ddb16c5312dc40e5d9b29d75447bc011e1393c5f1544144bc764e16f8" ], - "transactionsRoot": "0x69ff8003291e1cd08f75d174f070618f7291e4540b2e33f60b3375743e3fda01", + "transactionsRoot": "0xf578b0855e1c4509f5248387fcc5d3144552ade53be98825df0884f36fbc3ab9", "uncles": [] } \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockReceipts-block-with-blob-tx.json b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-blob-tx.json index 5d888be080..4662ebc06c 100644 --- a/internal/ethapi/testdata/eth_getBlockReceipts-block-with-blob-tx.json +++ b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-blob-tx.json @@ -2,19 +2,19 @@ { "blobGasPrice": "0x1", "blobGasUsed": "0x20000", - "blockHash": "0x264b5f62e2900dd39a6c68af3ba656cffa3fe209614ca857af1f5702c6e2ba7e", + "blockHash": "0x1aa2bdf0b0357f6932543b62d8a4528e525bba89e9f250f6856a446b0f28ac8b", "blockNumber": "0x6", "contractAddress": null, "cumulativeGasUsed": "0x5208", - "effectiveGasPrice": "0x5d21dba01", + "effectiveGasPrice": "0x2d810ba348", "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", "gasUsed": "0x5208", "logs": [], "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "status": "0x1", "to": "0x0d3ab14bbad3d99f4203bd7a11acb94882050e7e", - "transactionHash": "0x7e71344129674f4bbfdaa86313d005a96581993d93ae3a30d81b13fa25579eb2", + "transactionHash": "0xbd076c8bafdc7cf57e7b76aa11c41852383db7584d9ab4b6f1f99b93bdf415e5", "transactionIndex": "0x0", "type": "0x3" } -] +] \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockReceipts-block-with-contract-create-tx.json b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-contract-create-tx.json index 1ef955ab92..43b480eb89 100644 --- a/internal/ethapi/testdata/eth_getBlockReceipts-block-with-contract-create-tx.json +++ b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-contract-create-tx.json @@ -1,18 +1,18 @@ [ { - "blockHash": "0x0524420ca4d3974c72883de6ccdeca2e8be7eafeac88ff03d144ed16fe78063a", + "blockHash": "0xe94440d64ef293387fd5245d15240c1b72789db4005502c62f03c5e29d92ed60", "blockNumber": "0x2", "contractAddress": "0xae9bea628c4ce503dcfd7e305cab4e29e7476592", "cumulativeGasUsed": "0xcf50", - "effectiveGasPrice": "0x5d21dba00", + "effectiveGasPrice": "0x32ee841b80", "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", "gasUsed": "0xcf50", "logs": [], "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "status": "0x1", "to": null, - "transactionHash": "0x22aa617165f83a9f8c191c2b7724ae43eeb1249bee06c98c03c7624c21d27dc8", + "transactionHash": "0x517f3174bd4501d55f0f93589ef0102152ab808f51bf595f2779461f04871a32", "transactionIndex": "0x0", "type": "0x0" } -] +] \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockReceipts-block-with-dynamic-fee-tx.json b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-dynamic-fee-tx.json index 96ca04b629..8dc9346333 100644 --- a/internal/ethapi/testdata/eth_getBlockReceipts-block-with-dynamic-fee-tx.json +++ b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-dynamic-fee-tx.json @@ -1,18 +1,18 @@ [ { - "blockHash": "0x0b7437b9229f72a563918ee8c73e9a8f5e294f9d0e17db6bf8408cdf6fbc84b7", + "blockHash": "0x1834358f0a607f09a35cf1448b968a9e0255fcd661f4f1f8d5b12016ba9a1069", "blockNumber": "0x4", "contractAddress": null, "cumulativeGasUsed": "0x538d", - "effectiveGasPrice": "0x5d21dbbf4", + "effectiveGasPrice": "0x302436f3a8", "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", "gasUsed": "0x538d", "logs": [], "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "status": "0x0", "to": "0x0000000000000000000000000000000000031ec7", - "transactionHash": "0x4e1e9194ca6f9d4e1736e9e441f66104f273548ed6d91b236a5f9c2ea10fa06d", + "transactionHash": "0xcdd1122456f8ea113309e2ba5ecc8f389bbdc2e6bcced8eb103c6fdef201bf1a", "transactionIndex": "0x0", "type": "0x2" } -] +] \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockReceipts-block-with-legacy-contract-call-tx.json b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-legacy-contract-call-tx.json index f63bffdf0a..90fa760aa6 100644 --- a/internal/ethapi/testdata/eth_getBlockReceipts-block-with-legacy-contract-call-tx.json +++ b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-legacy-contract-call-tx.json @@ -1,10 +1,10 @@ [ { - "blockHash": "0x587918ec6a2187a54418dc5334d02a53b61c39578d095f4845be8d4f25dea5b5", + "blockHash": "0x69086f9c85b303fc69154df16f34513f093f4937c6e3f6d8cbcf27a7617f8ba4", "blockNumber": "0x3", "contractAddress": null, "cumulativeGasUsed": "0x5e28", - "effectiveGasPrice": "0x5d21dba00", + "effectiveGasPrice": "0x318455c568", "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", "gasUsed": "0x5e28", "logs": [ @@ -17,9 +17,9 @@ ], "data": "0x000000000000000000000000000000000000000000000000000000000000000d", "blockNumber": "0x3", - "transactionHash": "0x7366a7738f47e32f5b6d292ca064b6b66f295d3931533a3745975be1191fccdf", + "transactionHash": "0x0e9c460065fee166157eaadf702a01fb6ac1ce27b651e32850a8b09f71f93937", "transactionIndex": "0x0", - "blockHash": "0x587918ec6a2187a54418dc5334d02a53b61c39578d095f4845be8d4f25dea5b5", + "blockHash": "0x69086f9c85b303fc69154df16f34513f093f4937c6e3f6d8cbcf27a7617f8ba4", "logIndex": "0x0", "removed": false } @@ -27,8 +27,8 @@ "logsBloom": "0x00000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000800000000000000008000000000000000000000000000000000020000000080000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000400000000002000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000", "status": "0x1", "to": "0x0000000000000000000000000000000000031ec7", - "transactionHash": "0x7366a7738f47e32f5b6d292ca064b6b66f295d3931533a3745975be1191fccdf", + "transactionHash": "0x0e9c460065fee166157eaadf702a01fb6ac1ce27b651e32850a8b09f71f93937", "transactionIndex": "0x0", "type": "0x0" } -] +] \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockReceipts-block-with-legacy-transfer-tx.json b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-legacy-transfer-tx.json index 6a7af4ce3c..39c5e5727e 100644 --- a/internal/ethapi/testdata/eth_getBlockReceipts-block-with-legacy-transfer-tx.json +++ b/internal/ethapi/testdata/eth_getBlockReceipts-block-with-legacy-transfer-tx.json @@ -1,18 +1,18 @@ [ { - "blockHash": "0x2918b59e4b455614c1b83c0281e2b8462af47ca9726fff31789cb168793015d7", + "blockHash": "0xb3839c504dc43f5cf0839a1a26f01c1a0e5da1a6497d3f280467931690274467", "blockNumber": "0x1", "contractAddress": null, "cumulativeGasUsed": "0x5208", - "effectiveGasPrice": "0x5d21dba00", + "effectiveGasPrice": "0x34630b8a00", "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", "gasUsed": "0x5208", "logs": [], "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "status": "0x1", "to": "0x0d3ab14bbad3d99f4203bd7a11acb94882050e7e", - "transactionHash": "0xdf92bc7c4c0341ecbdcd2a3ca7011fe9e21df4b8553bf0c8caabe6cb4a1aee26", + "transactionHash": "0x09220a8629fd020cbb341ab146e6acb4dc4811ab5fdf021bec3d3219c5a29ab3", "transactionIndex": "0x0", "type": "0x0" } -] +] \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getBlockReceipts-tag-latest.json b/internal/ethapi/testdata/eth_getBlockReceipts-tag-latest.json index 5d888be080..4662ebc06c 100644 --- a/internal/ethapi/testdata/eth_getBlockReceipts-tag-latest.json +++ b/internal/ethapi/testdata/eth_getBlockReceipts-tag-latest.json @@ -2,19 +2,19 @@ { "blobGasPrice": "0x1", "blobGasUsed": "0x20000", - "blockHash": "0x264b5f62e2900dd39a6c68af3ba656cffa3fe209614ca857af1f5702c6e2ba7e", + "blockHash": "0x1aa2bdf0b0357f6932543b62d8a4528e525bba89e9f250f6856a446b0f28ac8b", "blockNumber": "0x6", "contractAddress": null, "cumulativeGasUsed": "0x5208", - "effectiveGasPrice": "0x5d21dba01", + "effectiveGasPrice": "0x2d810ba348", "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", "gasUsed": "0x5208", "logs": [], "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "status": "0x1", "to": "0x0d3ab14bbad3d99f4203bd7a11acb94882050e7e", - "transactionHash": "0x7e71344129674f4bbfdaa86313d005a96581993d93ae3a30d81b13fa25579eb2", + "transactionHash": "0xbd076c8bafdc7cf57e7b76aa11c41852383db7584d9ab4b6f1f99b93bdf415e5", "transactionIndex": "0x0", "type": "0x3" } -] +] \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getHeaderByHash-hash-0.json b/internal/ethapi/testdata/eth_getHeaderByHash-hash-0.json index eced05ccfc..272f5135e6 100644 --- a/internal/ethapi/testdata/eth_getHeaderByHash-hash-0.json +++ b/internal/ethapi/testdata/eth_getHeaderByHash-hash-0.json @@ -1,10 +1,11 @@ { - "baseFeePerGas": "0x5d21dba00", + "baseFeePerGas": "0x34630b8a00", "difficulty": "0x20000", + "extDataHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "0x", "gasLimit": "0x47e7c4", "gasUsed": "0x0", - "hash": "0x3ead7923676a44500c46ad2192a0fc084aa42063b1703e6866f138a47fb1a9ca", + "hash": "0x1509a989ede83d85f51c9a489ef5f9a1ef15e08ec50f9d569ad41b56ecc4dffd", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "miner": "0x0000000000000000000000000000000000000000", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", @@ -13,7 +14,7 @@ "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "stateRoot": "0xfe168c5e9584a85927212e5bea5304bb7d0d8a893453b4b2c52176a72f585ae2", + "stateRoot": "0xb5a65ee2c90afcbaccd940a768b2a719394755b7275bce8a4c0c742991e17131", "timestamp": "0x0", "totalDifficulty": "0x0", "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" diff --git a/internal/ethapi/testdata/eth_getHeaderByHash-hash-1.json b/internal/ethapi/testdata/eth_getHeaderByHash-hash-1.json index d14b952ff6..ca2df164d2 100644 --- a/internal/ethapi/testdata/eth_getHeaderByHash-hash-1.json +++ b/internal/ethapi/testdata/eth_getHeaderByHash-hash-1.json @@ -1,21 +1,23 @@ { - "baseFeePerGas": "0x5d21dba00", + "baseFeePerGas": "0x34630b8a00", "blockGasCost": "0x0", "difficulty": "0x1", + "extDataGasUsed": "0x0", + "extDataHash": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "gasLimit": "0x7a1200", + "gasLimit": "0xe4e1c0", "gasUsed": "0x5208", - "hash": "0x0f67ad1fc8052afad4c24551748600c164091cf37e068adef76315025d3c78e7", + "hash": "0x4729ded876444349cc71f0062017ccf35068c2831d27f75c1d35bc4d3eb0c3ba", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "miner": "0x0000000000000000000000000000000000000000", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "nonce": "0x0000000000000000", "number": "0x1", - "parentHash": "0x3ead7923676a44500c46ad2192a0fc084aa42063b1703e6866f138a47fb1a9ca", + "parentHash": "0x1509a989ede83d85f51c9a489ef5f9a1ef15e08ec50f9d569ad41b56ecc4dffd", "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "stateRoot": "0x6b830601767ac4968163193facbe20123435180e325910b2c50efa21f778c697", + "stateRoot": "0xe6a34faf8c7bd056de278fa20a0bf3236c8c157f6bbf40bced8e5961c51f3691", "timestamp": "0xa", "totalDifficulty": "0x1", - "transactionsRoot": "0x87c65a3f1a98dafe282ace11eaf88b8f31bf41fe6794d401d2f986c1af84bcd5" + "transactionsRoot": "0x272d13afea9f2f2c9b9ab3d8bbdb492ce5f7b215c493adaac5d98abc9ad62352" } \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getHeaderByHash-hash-latest-1.json b/internal/ethapi/testdata/eth_getHeaderByHash-hash-latest-1.json index c90f186501..8f6f248043 100644 --- a/internal/ethapi/testdata/eth_getHeaderByHash-hash-latest-1.json +++ b/internal/ethapi/testdata/eth_getHeaderByHash-hash-latest-1.json @@ -1,21 +1,23 @@ { - "baseFeePerGas": "0x5d21dba00", + "baseFeePerGas": "0x29d101e35b", "blockGasCost": "0x0", "difficulty": "0x1", + "extDataGasUsed": "0x0", + "extDataHash": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "gasLimit": "0x7a1200", + "gasLimit": "0xe4e1c0", "gasUsed": "0x5208", - "hash": "0x0583a9d630632001771b4ecc7d62574aec3825aff47e2a680b0ea4ddb79e7365", + "hash": "0x3556a043faadc4f03ba18453a084ba91d49c6baec50d7792dab1f3f552d2c973", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "miner": "0x0000000000000000000000000000000000000000", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "nonce": "0x0000000000000000", "number": "0x9", - "parentHash": "0x2fab5c6892c66668842683ced6b384c2ee83bfd6096a58f451290cabaf57a63e", + "parentHash": "0x2006bc7d2d25edaef78eb0dd7392262bd865a7a0ab4ec70c169ab02eeb3a53f9", "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "stateRoot": "0x3703d70c6443e809ce035c2a8212dbf9813f6b7d1b0f597766e9023867a852f5", + "stateRoot": "0x559db971f1bdcc4a39b612c81c26c3d91ca805d61eb614132352196f03d38192", "timestamp": "0x5a", "totalDifficulty": "0x9", - "transactionsRoot": "0xe16929d9c7efab0f962c1ed8c1295ddff42d3026779ed1318ea079ca580ee4cb" + "transactionsRoot": "0x636f1c9b3e00b425fbab75c79989315b6f30c48f5a55ee636b1fc3805538c5f7" } \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getHeaderByHash-hash-latest.json b/internal/ethapi/testdata/eth_getHeaderByHash-hash-latest.json index ce691fa435..a7da76acf3 100644 --- a/internal/ethapi/testdata/eth_getHeaderByHash-hash-latest.json +++ b/internal/ethapi/testdata/eth_getHeaderByHash-hash-latest.json @@ -1,21 +1,23 @@ { - "baseFeePerGas": "0x5d21dba00", + "baseFeePerGas": "0x28a7a56427", "blockGasCost": "0x0", "difficulty": "0x1", + "extDataGasUsed": "0x0", + "extDataHash": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "gasLimit": "0x7a1200", + "gasLimit": "0xe4e1c0", "gasUsed": "0x5208", - "hash": "0x1ec39e7ec46f8df1fb31cfca53fbf71a01869af8bd8f9a1bccbffc16ffa1461d", + "hash": "0x9e839b3ad2ecd76f842bb6891144e073f015c785f5aad0001968222334131d02", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "miner": "0x0000000000000000000000000000000000000000", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "nonce": "0x0000000000000000", "number": "0xa", - "parentHash": "0x0583a9d630632001771b4ecc7d62574aec3825aff47e2a680b0ea4ddb79e7365", + "parentHash": "0x3556a043faadc4f03ba18453a084ba91d49c6baec50d7792dab1f3f552d2c973", "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "stateRoot": "0x7e06187d15d50badf60930290fb292ebe43e79553ad8b7d8f1b614316631def7", + "stateRoot": "0xdded7f848268f119f12c77fdf32520ac3f98d710164997b6adf038e76c5007fe", "timestamp": "0x64", "totalDifficulty": "0xa", - "transactionsRoot": "0x69ff8003291e1cd08f75d174f070618f7291e4540b2e33f60b3375743e3fda01" + "transactionsRoot": "0xf578b0855e1c4509f5248387fcc5d3144552ade53be98825df0884f36fbc3ab9" } \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getHeaderByNumber-number-0.json b/internal/ethapi/testdata/eth_getHeaderByNumber-number-0.json index eced05ccfc..272f5135e6 100644 --- a/internal/ethapi/testdata/eth_getHeaderByNumber-number-0.json +++ b/internal/ethapi/testdata/eth_getHeaderByNumber-number-0.json @@ -1,10 +1,11 @@ { - "baseFeePerGas": "0x5d21dba00", + "baseFeePerGas": "0x34630b8a00", "difficulty": "0x20000", + "extDataHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "0x", "gasLimit": "0x47e7c4", "gasUsed": "0x0", - "hash": "0x3ead7923676a44500c46ad2192a0fc084aa42063b1703e6866f138a47fb1a9ca", + "hash": "0x1509a989ede83d85f51c9a489ef5f9a1ef15e08ec50f9d569ad41b56ecc4dffd", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "miner": "0x0000000000000000000000000000000000000000", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", @@ -13,7 +14,7 @@ "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "stateRoot": "0xfe168c5e9584a85927212e5bea5304bb7d0d8a893453b4b2c52176a72f585ae2", + "stateRoot": "0xb5a65ee2c90afcbaccd940a768b2a719394755b7275bce8a4c0c742991e17131", "timestamp": "0x0", "totalDifficulty": "0x0", "transactionsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" diff --git a/internal/ethapi/testdata/eth_getHeaderByNumber-number-1.json b/internal/ethapi/testdata/eth_getHeaderByNumber-number-1.json index d14b952ff6..ca2df164d2 100644 --- a/internal/ethapi/testdata/eth_getHeaderByNumber-number-1.json +++ b/internal/ethapi/testdata/eth_getHeaderByNumber-number-1.json @@ -1,21 +1,23 @@ { - "baseFeePerGas": "0x5d21dba00", + "baseFeePerGas": "0x34630b8a00", "blockGasCost": "0x0", "difficulty": "0x1", + "extDataGasUsed": "0x0", + "extDataHash": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "gasLimit": "0x7a1200", + "gasLimit": "0xe4e1c0", "gasUsed": "0x5208", - "hash": "0x0f67ad1fc8052afad4c24551748600c164091cf37e068adef76315025d3c78e7", + "hash": "0x4729ded876444349cc71f0062017ccf35068c2831d27f75c1d35bc4d3eb0c3ba", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "miner": "0x0000000000000000000000000000000000000000", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "nonce": "0x0000000000000000", "number": "0x1", - "parentHash": "0x3ead7923676a44500c46ad2192a0fc084aa42063b1703e6866f138a47fb1a9ca", + "parentHash": "0x1509a989ede83d85f51c9a489ef5f9a1ef15e08ec50f9d569ad41b56ecc4dffd", "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "stateRoot": "0x6b830601767ac4968163193facbe20123435180e325910b2c50efa21f778c697", + "stateRoot": "0xe6a34faf8c7bd056de278fa20a0bf3236c8c157f6bbf40bced8e5961c51f3691", "timestamp": "0xa", "totalDifficulty": "0x1", - "transactionsRoot": "0x87c65a3f1a98dafe282ace11eaf88b8f31bf41fe6794d401d2f986c1af84bcd5" + "transactionsRoot": "0x272d13afea9f2f2c9b9ab3d8bbdb492ce5f7b215c493adaac5d98abc9ad62352" } \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getHeaderByNumber-number-latest-1.json b/internal/ethapi/testdata/eth_getHeaderByNumber-number-latest-1.json index c90f186501..8f6f248043 100644 --- a/internal/ethapi/testdata/eth_getHeaderByNumber-number-latest-1.json +++ b/internal/ethapi/testdata/eth_getHeaderByNumber-number-latest-1.json @@ -1,21 +1,23 @@ { - "baseFeePerGas": "0x5d21dba00", + "baseFeePerGas": "0x29d101e35b", "blockGasCost": "0x0", "difficulty": "0x1", + "extDataGasUsed": "0x0", + "extDataHash": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "gasLimit": "0x7a1200", + "gasLimit": "0xe4e1c0", "gasUsed": "0x5208", - "hash": "0x0583a9d630632001771b4ecc7d62574aec3825aff47e2a680b0ea4ddb79e7365", + "hash": "0x3556a043faadc4f03ba18453a084ba91d49c6baec50d7792dab1f3f552d2c973", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "miner": "0x0000000000000000000000000000000000000000", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "nonce": "0x0000000000000000", "number": "0x9", - "parentHash": "0x2fab5c6892c66668842683ced6b384c2ee83bfd6096a58f451290cabaf57a63e", + "parentHash": "0x2006bc7d2d25edaef78eb0dd7392262bd865a7a0ab4ec70c169ab02eeb3a53f9", "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "stateRoot": "0x3703d70c6443e809ce035c2a8212dbf9813f6b7d1b0f597766e9023867a852f5", + "stateRoot": "0x559db971f1bdcc4a39b612c81c26c3d91ca805d61eb614132352196f03d38192", "timestamp": "0x5a", "totalDifficulty": "0x9", - "transactionsRoot": "0xe16929d9c7efab0f962c1ed8c1295ddff42d3026779ed1318ea079ca580ee4cb" + "transactionsRoot": "0x636f1c9b3e00b425fbab75c79989315b6f30c48f5a55ee636b1fc3805538c5f7" } \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getHeaderByNumber-tag-latest.json b/internal/ethapi/testdata/eth_getHeaderByNumber-tag-latest.json index ce691fa435..a7da76acf3 100644 --- a/internal/ethapi/testdata/eth_getHeaderByNumber-tag-latest.json +++ b/internal/ethapi/testdata/eth_getHeaderByNumber-tag-latest.json @@ -1,21 +1,23 @@ { - "baseFeePerGas": "0x5d21dba00", + "baseFeePerGas": "0x28a7a56427", "blockGasCost": "0x0", "difficulty": "0x1", + "extDataGasUsed": "0x0", + "extDataHash": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "gasLimit": "0x7a1200", + "gasLimit": "0xe4e1c0", "gasUsed": "0x5208", - "hash": "0x1ec39e7ec46f8df1fb31cfca53fbf71a01869af8bd8f9a1bccbffc16ffa1461d", + "hash": "0x9e839b3ad2ecd76f842bb6891144e073f015c785f5aad0001968222334131d02", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "miner": "0x0000000000000000000000000000000000000000", "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "nonce": "0x0000000000000000", "number": "0xa", - "parentHash": "0x0583a9d630632001771b4ecc7d62574aec3825aff47e2a680b0ea4ddb79e7365", + "parentHash": "0x3556a043faadc4f03ba18453a084ba91d49c6baec50d7792dab1f3f552d2c973", "receiptsRoot": "0x056b23fbba480696b65fe5a59b8f2148a1299103c4f57df839233af2cf4ca2d2", "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", - "stateRoot": "0x7e06187d15d50badf60930290fb292ebe43e79553ad8b7d8f1b614316631def7", + "stateRoot": "0xdded7f848268f119f12c77fdf32520ac3f98d710164997b6adf038e76c5007fe", "timestamp": "0x64", "totalDifficulty": "0xa", - "transactionsRoot": "0x69ff8003291e1cd08f75d174f070618f7291e4540b2e33f60b3375743e3fda01" + "transactionsRoot": "0xf578b0855e1c4509f5248387fcc5d3144552ade53be98825df0884f36fbc3ab9" } \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getTransactionReceipt-blob-tx.json b/internal/ethapi/testdata/eth_getTransactionReceipt-blob-tx.json index e0db84ad00..2a0bbf49aa 100644 --- a/internal/ethapi/testdata/eth_getTransactionReceipt-blob-tx.json +++ b/internal/ethapi/testdata/eth_getTransactionReceipt-blob-tx.json @@ -1,18 +1,18 @@ { "blobGasPrice": "0x1", "blobGasUsed": "0x20000", - "blockHash": "0x264b5f62e2900dd39a6c68af3ba656cffa3fe209614ca857af1f5702c6e2ba7e", + "blockHash": "0x1aa2bdf0b0357f6932543b62d8a4528e525bba89e9f250f6856a446b0f28ac8b", "blockNumber": "0x6", "contractAddress": null, "cumulativeGasUsed": "0x5208", - "effectiveGasPrice": "0x5d21dba01", + "effectiveGasPrice": "0x2d810ba348", "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", "gasUsed": "0x5208", "logs": [], "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "status": "0x1", "to": "0x0d3ab14bbad3d99f4203bd7a11acb94882050e7e", - "transactionHash": "0x7e71344129674f4bbfdaa86313d005a96581993d93ae3a30d81b13fa25579eb2", + "transactionHash": "0xbd076c8bafdc7cf57e7b76aa11c41852383db7584d9ab4b6f1f99b93bdf415e5", "transactionIndex": "0x0", "type": "0x3" -} +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getTransactionReceipt-create-contract-tx.json b/internal/ethapi/testdata/eth_getTransactionReceipt-create-contract-tx.json index 41206a6c7a..7d8c23adc3 100644 --- a/internal/ethapi/testdata/eth_getTransactionReceipt-create-contract-tx.json +++ b/internal/ethapi/testdata/eth_getTransactionReceipt-create-contract-tx.json @@ -1,16 +1,16 @@ { - "blockHash": "0x0524420ca4d3974c72883de6ccdeca2e8be7eafeac88ff03d144ed16fe78063a", + "blockHash": "0xe94440d64ef293387fd5245d15240c1b72789db4005502c62f03c5e29d92ed60", "blockNumber": "0x2", "contractAddress": "0xae9bea628c4ce503dcfd7e305cab4e29e7476592", "cumulativeGasUsed": "0xcf50", - "effectiveGasPrice": "0x5d21dba00", + "effectiveGasPrice": "0x32ee841b80", "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", "gasUsed": "0xcf50", "logs": [], "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "status": "0x1", "to": null, - "transactionHash": "0x22aa617165f83a9f8c191c2b7724ae43eeb1249bee06c98c03c7624c21d27dc8", + "transactionHash": "0x517f3174bd4501d55f0f93589ef0102152ab808f51bf595f2779461f04871a32", "transactionIndex": "0x0", "type": "0x0" -} +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getTransactionReceipt-create-contract-with-access-list.json b/internal/ethapi/testdata/eth_getTransactionReceipt-create-contract-with-access-list.json index 0fb927479e..b536c819a3 100644 --- a/internal/ethapi/testdata/eth_getTransactionReceipt-create-contract-with-access-list.json +++ b/internal/ethapi/testdata/eth_getTransactionReceipt-create-contract-with-access-list.json @@ -1,16 +1,16 @@ { - "blockHash": "0x2bf952c71ce24c68f887c12ecba6f1a36f97f528259fe0574890969e1113187a", + "blockHash": "0xd8c23617a84076fe296ee742a3abdadc3d6e93c496bf95751b687ffeb54d09c1", "blockNumber": "0x5", "contractAddress": "0xfdaa97661a584d977b4d3abb5370766ff5b86a18", "cumulativeGasUsed": "0xe01c", - "effectiveGasPrice": "0x5d21dba00", + "effectiveGasPrice": "0x2ecde015a8", "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", "gasUsed": "0xe01c", "logs": [], "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "status": "0x1", "to": null, - "transactionHash": "0x8afe030574f663fe5096371d6f58a6287bfb3e0c73a5050220f5775a08e7abc9", + "transactionHash": "0xa9616380994fd7502d0350ee57882bb6e95d6678fa6c4782f179c9f5f3529c48", "transactionIndex": "0x0", "type": "0x1" -} +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getTransactionReceipt-dynamic-tx-with-logs.json b/internal/ethapi/testdata/eth_getTransactionReceipt-dynamic-tx-with-logs.json index c322944f2b..00d14795b6 100644 --- a/internal/ethapi/testdata/eth_getTransactionReceipt-dynamic-tx-with-logs.json +++ b/internal/ethapi/testdata/eth_getTransactionReceipt-dynamic-tx-with-logs.json @@ -1,16 +1,16 @@ { - "blockHash": "0x0b7437b9229f72a563918ee8c73e9a8f5e294f9d0e17db6bf8408cdf6fbc84b7", + "blockHash": "0x1834358f0a607f09a35cf1448b968a9e0255fcd661f4f1f8d5b12016ba9a1069", "blockNumber": "0x4", "contractAddress": null, "cumulativeGasUsed": "0x538d", - "effectiveGasPrice": "0x5d21dbbf4", + "effectiveGasPrice": "0x302436f3a8", "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", "gasUsed": "0x538d", "logs": [], "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "status": "0x0", "to": "0x0000000000000000000000000000000000031ec7", - "transactionHash": "0x4e1e9194ca6f9d4e1736e9e441f66104f273548ed6d91b236a5f9c2ea10fa06d", + "transactionHash": "0xcdd1122456f8ea113309e2ba5ecc8f389bbdc2e6bcced8eb103c6fdef201bf1a", "transactionIndex": "0x0", "type": "0x2" -} +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getTransactionReceipt-normal-transfer-tx.json b/internal/ethapi/testdata/eth_getTransactionReceipt-normal-transfer-tx.json index be72a02737..1f676d4280 100644 --- a/internal/ethapi/testdata/eth_getTransactionReceipt-normal-transfer-tx.json +++ b/internal/ethapi/testdata/eth_getTransactionReceipt-normal-transfer-tx.json @@ -1,16 +1,16 @@ { - "blockHash": "0x2918b59e4b455614c1b83c0281e2b8462af47ca9726fff31789cb168793015d7", + "blockHash": "0xb3839c504dc43f5cf0839a1a26f01c1a0e5da1a6497d3f280467931690274467", "blockNumber": "0x1", "contractAddress": null, "cumulativeGasUsed": "0x5208", - "effectiveGasPrice": "0x5d21dba00", + "effectiveGasPrice": "0x34630b8a00", "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", "gasUsed": "0x5208", "logs": [], "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "status": "0x1", "to": "0x0d3ab14bbad3d99f4203bd7a11acb94882050e7e", - "transactionHash": "0xdf92bc7c4c0341ecbdcd2a3ca7011fe9e21df4b8553bf0c8caabe6cb4a1aee26", + "transactionHash": "0x09220a8629fd020cbb341ab146e6acb4dc4811ab5fdf021bec3d3219c5a29ab3", "transactionIndex": "0x0", "type": "0x0" -} +} \ No newline at end of file diff --git a/internal/ethapi/testdata/eth_getTransactionReceipt-with-logs.json b/internal/ethapi/testdata/eth_getTransactionReceipt-with-logs.json index 7ced85c2a2..d3a6b70f46 100644 --- a/internal/ethapi/testdata/eth_getTransactionReceipt-with-logs.json +++ b/internal/ethapi/testdata/eth_getTransactionReceipt-with-logs.json @@ -1,9 +1,9 @@ { - "blockHash": "0x587918ec6a2187a54418dc5334d02a53b61c39578d095f4845be8d4f25dea5b5", + "blockHash": "0x69086f9c85b303fc69154df16f34513f093f4937c6e3f6d8cbcf27a7617f8ba4", "blockNumber": "0x3", "contractAddress": null, "cumulativeGasUsed": "0x5e28", - "effectiveGasPrice": "0x5d21dba00", + "effectiveGasPrice": "0x318455c568", "from": "0x703c4b2bd70c169f5717101caee543299fc946c7", "gasUsed": "0x5e28", "logs": [ @@ -16,9 +16,9 @@ ], "data": "0x000000000000000000000000000000000000000000000000000000000000000d", "blockNumber": "0x3", - "transactionHash": "0x7366a7738f47e32f5b6d292ca064b6b66f295d3931533a3745975be1191fccdf", + "transactionHash": "0x0e9c460065fee166157eaadf702a01fb6ac1ce27b651e32850a8b09f71f93937", "transactionIndex": "0x0", - "blockHash": "0x587918ec6a2187a54418dc5334d02a53b61c39578d095f4845be8d4f25dea5b5", + "blockHash": "0x69086f9c85b303fc69154df16f34513f093f4937c6e3f6d8cbcf27a7617f8ba4", "logIndex": "0x0", "removed": false } @@ -26,7 +26,7 @@ "logsBloom": "0x00000000000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000800000000000000008000000000000000000000000000000000020000000080000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000400000000002000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000", "status": "0x1", "to": "0x0000000000000000000000000000000000031ec7", - "transactionHash": "0x7366a7738f47e32f5b6d292ca064b6b66f295d3931533a3745975be1191fccdf", + "transactionHash": "0x0e9c460065fee166157eaadf702a01fb6ac1ce27b651e32850a8b09f71f93937", "transactionIndex": "0x0", "type": "0x0" -} +} \ No newline at end of file diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index 9dd9ede18d..1066695077 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -172,7 +172,7 @@ func (args *TransactionArgs) setFeeDefaults(ctx context.Context, b feeBackend) e } // Sanity check the non-EIP-1559 fee parameters. head := b.CurrentHeader() - isLondon := b.ChainConfig().IsSubnetEVM(head.Time) + isLondon := b.ChainConfig().IsApricotPhase3(head.Time) if args.GasPrice != nil && !eip1559ParamsSet { // Zero gas-price is not allowed after London fork if args.GasPrice.ToInt().Sign() == 0 && isLondon { @@ -184,25 +184,26 @@ func (args *TransactionArgs) setFeeDefaults(ctx context.Context, b feeBackend) e // Now attempt to fill in default value depending on whether London is active or not. if isLondon { // London is active, set maxPriorityFeePerGas and maxFeePerGas. - if err := args.setSubnetEVMFeeDefault(ctx, head, b); err != nil { + if err := args.setApricotPhase3FeeDefault(ctx, head, b); err != nil { return err } } else { if args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil { return errors.New("maxFeePerGas and maxPriorityFeePerGas are not valid before London is active") } - // London not active, set gas price. - price, err := b.SuggestGasTipCap(ctx) - if err != nil { - return err + if args.GasPrice == nil { + price, err := b.SuggestGasTipCap(ctx) + if err != nil { + return err + } + args.GasPrice = (*hexutil.Big)(price) } - args.GasPrice = (*hexutil.Big)(price) } return nil } -// setSubnetEVMFeeDefault fills in reasonable default fee values for unspecified fields. -func (args *TransactionArgs) setSubnetEVMFeeDefault(ctx context.Context, head *types.Header, b feeBackend) error { +// setApricotPhase3FeeDefault fills in reasonable default fee values for unspecified fields. +func (args *TransactionArgs) setApricotPhase3FeeDefault(ctx context.Context, head *types.Header, b feeBackend) error { // Set maxPriorityFeePerGas if it is missing. if args.MaxPriorityFeePerGas == nil { tip, err := b.SuggestGasTipCap(ctx) @@ -216,11 +217,11 @@ func (args *TransactionArgs) setSubnetEVMFeeDefault(ctx context.Context, head *t // Set the max fee to be 2 times larger than the previous block's base fee. // The additional slack allows the tx to not become invalidated if the base // fee is rising. - val := new(big.Int).Add( - args.MaxPriorityFeePerGas.ToInt(), + gasFeeCap := new(big.Int).Add( + (*big.Int)(args.MaxPriorityFeePerGas), new(big.Int).Mul(head.BaseFee, big.NewInt(2)), ) - args.MaxFeePerGas = (*hexutil.Big)(val) + args.MaxFeePerGas = (*hexutil.Big)(gasFeeCap) } // Both EIP-1559 fee parameters are now set; sanity check them. if args.MaxFeePerGas.ToInt().Cmp(args.MaxPriorityFeePerGas.ToInt()) < 0 { diff --git a/internal/ethapi/transaction_args_test.go b/internal/ethapi/transaction_args_test.go index 98bdde2edf..968a507059 100644 --- a/internal/ethapi/transaction_args_test.go +++ b/internal/ethapi/transaction_args_test.go @@ -240,6 +240,8 @@ func newBackendMock() *backendMock { config := ¶ms.ChainConfig{ ChainID: big.NewInt(42), HomesteadBlock: big.NewInt(0), + DAOForkBlock: nil, + DAOForkSupport: true, EIP150Block: big.NewInt(0), EIP155Block: big.NewInt(0), EIP158Block: big.NewInt(0), @@ -249,7 +251,7 @@ func newBackendMock() *backendMock { IstanbulBlock: big.NewInt(0), MuirGlacierBlock: big.NewInt(0), NetworkUpgrades: params.NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(1000), + ApricotPhase3BlockTimestamp: utils.NewUint64(1000), }, } return &backendMock{ diff --git a/internal/flags/flags.go b/internal/flags/flags.go deleted file mode 100644 index ed3a14bcf4..0000000000 --- a/internal/flags/flags.go +++ /dev/null @@ -1,387 +0,0 @@ -// (c) 2023, Ava Labs, Inc. -// -// This file is a derived work, based on the go-ethereum library whose original -// notices appear below. -// -// It is distributed under a license compatible with the licensing terms of the -// original code from which it is derived. -// -// Much love to the original authors for their work. -// ********** -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package flags - -import ( - "encoding" - "errors" - "flag" - "fmt" - "math/big" - "os" - "os/user" - "path/filepath" - "strings" - "syscall" - - "github.com/ethereum/go-ethereum/common/math" - "github.com/urfave/cli/v2" -) - -// DirectoryString is custom type which is registered in the flags library which cli uses for -// argument parsing. This allows us to expand Value to an absolute path when -// the argument is parsed -type DirectoryString string - -func (s *DirectoryString) String() string { - return string(*s) -} - -func (s *DirectoryString) Set(value string) error { - *s = DirectoryString(expandPath(value)) - return nil -} - -var ( - _ cli.Flag = (*DirectoryFlag)(nil) - _ cli.RequiredFlag = (*DirectoryFlag)(nil) - _ cli.VisibleFlag = (*DirectoryFlag)(nil) - _ cli.DocGenerationFlag = (*DirectoryFlag)(nil) - _ cli.CategorizableFlag = (*DirectoryFlag)(nil) -) - -// DirectoryFlag is custom cli.Flag type which expand the received string to an absolute path. -// e.g. ~/.ethereum -> /home/username/.ethereum -type DirectoryFlag struct { - Name string - - Category string - DefaultText string - Usage string - - Required bool - Hidden bool - HasBeenSet bool - - Value DirectoryString - - Aliases []string - EnvVars []string -} - -// For cli.Flag: - -func (f *DirectoryFlag) Names() []string { return append([]string{f.Name}, f.Aliases...) } -func (f *DirectoryFlag) IsSet() bool { return f.HasBeenSet } -func (f *DirectoryFlag) String() string { return cli.FlagStringer(f) } - -// Apply called by cli library, grabs variable from environment (if in env) -// and adds variable to flag set for parsing. -func (f *DirectoryFlag) Apply(set *flag.FlagSet) error { - for _, envVar := range f.EnvVars { - envVar = strings.TrimSpace(envVar) - if value, found := syscall.Getenv(envVar); found { - f.Value.Set(value) - f.HasBeenSet = true - break - } - } - eachName(f, func(name string) { - set.Var(&f.Value, f.Name, f.Usage) - }) - return nil -} - -// For cli.RequiredFlag: - -func (f *DirectoryFlag) IsRequired() bool { return f.Required } - -// For cli.VisibleFlag: - -func (f *DirectoryFlag) IsVisible() bool { return !f.Hidden } - -// For cli.CategorizableFlag: - -func (f *DirectoryFlag) GetCategory() string { return f.Category } - -// For cli.DocGenerationFlag: - -func (f *DirectoryFlag) TakesValue() bool { return true } -func (f *DirectoryFlag) GetUsage() string { return f.Usage } -func (f *DirectoryFlag) GetValue() string { return f.Value.String() } -func (f *DirectoryFlag) GetEnvVars() []string { return f.EnvVars } - -func (f *DirectoryFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - -type TextMarshaler interface { - encoding.TextMarshaler - encoding.TextUnmarshaler -} - -// textMarshalerVal turns a TextMarshaler into a flag.Value -type textMarshalerVal struct { - v TextMarshaler -} - -func (v textMarshalerVal) String() string { - if v.v == nil { - return "" - } - text, _ := v.v.MarshalText() - return string(text) -} - -func (v textMarshalerVal) Set(s string) error { - return v.v.UnmarshalText([]byte(s)) -} - -var ( - _ cli.Flag = (*TextMarshalerFlag)(nil) - _ cli.RequiredFlag = (*TextMarshalerFlag)(nil) - _ cli.VisibleFlag = (*TextMarshalerFlag)(nil) - _ cli.DocGenerationFlag = (*TextMarshalerFlag)(nil) - _ cli.CategorizableFlag = (*TextMarshalerFlag)(nil) -) - -// TextMarshalerFlag wraps a TextMarshaler value. -type TextMarshalerFlag struct { - Name string - - Category string - DefaultText string - Usage string - - Required bool - Hidden bool - HasBeenSet bool - - Value TextMarshaler - - Aliases []string - EnvVars []string -} - -// For cli.Flag: - -func (f *TextMarshalerFlag) Names() []string { return append([]string{f.Name}, f.Aliases...) } -func (f *TextMarshalerFlag) IsSet() bool { return f.HasBeenSet } -func (f *TextMarshalerFlag) String() string { return cli.FlagStringer(f) } - -func (f *TextMarshalerFlag) Apply(set *flag.FlagSet) error { - for _, envVar := range f.EnvVars { - envVar = strings.TrimSpace(envVar) - if value, found := syscall.Getenv(envVar); found { - if err := f.Value.UnmarshalText([]byte(value)); err != nil { - return fmt.Errorf("could not parse %q from environment variable %q for flag %s: %s", value, envVar, f.Name, err) - } - f.HasBeenSet = true - break - } - } - eachName(f, func(name string) { - set.Var(textMarshalerVal{f.Value}, f.Name, f.Usage) - }) - return nil -} - -// For cli.RequiredFlag: - -func (f *TextMarshalerFlag) IsRequired() bool { return f.Required } - -// For cli.VisibleFlag: - -func (f *TextMarshalerFlag) IsVisible() bool { return !f.Hidden } - -// For cli.CategorizableFlag: - -func (f *TextMarshalerFlag) GetCategory() string { return f.Category } - -// For cli.DocGenerationFlag: - -func (f *TextMarshalerFlag) TakesValue() bool { return true } -func (f *TextMarshalerFlag) GetUsage() string { return f.Usage } -func (f *TextMarshalerFlag) GetEnvVars() []string { return f.EnvVars } - -func (f *TextMarshalerFlag) GetValue() string { - t, err := f.Value.MarshalText() - if err != nil { - return "(ERR: " + err.Error() + ")" - } - return string(t) -} - -func (f *TextMarshalerFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - -// GlobalTextMarshaler returns the value of a TextMarshalerFlag from the global flag set. -func GlobalTextMarshaler(ctx *cli.Context, name string) TextMarshaler { - val := ctx.Generic(name) - if val == nil { - return nil - } - return val.(textMarshalerVal).v -} - -var ( - _ cli.Flag = (*BigFlag)(nil) - _ cli.RequiredFlag = (*BigFlag)(nil) - _ cli.VisibleFlag = (*BigFlag)(nil) - _ cli.DocGenerationFlag = (*BigFlag)(nil) - _ cli.CategorizableFlag = (*BigFlag)(nil) -) - -// BigFlag is a command line flag that accepts 256 bit big integers in decimal or -// hexadecimal syntax. -type BigFlag struct { - Name string - - Category string - DefaultText string - Usage string - - Required bool - Hidden bool - HasBeenSet bool - - Value *big.Int - - Aliases []string - EnvVars []string -} - -// For cli.Flag: - -func (f *BigFlag) Names() []string { return append([]string{f.Name}, f.Aliases...) } -func (f *BigFlag) IsSet() bool { return f.HasBeenSet } -func (f *BigFlag) String() string { return cli.FlagStringer(f) } - -func (f *BigFlag) Apply(set *flag.FlagSet) error { - for _, envVar := range f.EnvVars { - envVar = strings.TrimSpace(envVar) - if value, found := syscall.Getenv(envVar); found { - if _, ok := f.Value.SetString(value, 10); !ok { - return fmt.Errorf("could not parse %q from environment variable %q for flag %s", value, envVar, f.Name) - } - f.HasBeenSet = true - break - } - } - eachName(f, func(name string) { - f.Value = new(big.Int) - set.Var((*bigValue)(f.Value), f.Name, f.Usage) - }) - - return nil -} - -// For cli.RequiredFlag: - -func (f *BigFlag) IsRequired() bool { return f.Required } - -// For cli.VisibleFlag: - -func (f *BigFlag) IsVisible() bool { return !f.Hidden } - -// For cli.CategorizableFlag: - -func (f *BigFlag) GetCategory() string { return f.Category } - -// For cli.DocGenerationFlag: - -func (f *BigFlag) TakesValue() bool { return true } -func (f *BigFlag) GetUsage() string { return f.Usage } -func (f *BigFlag) GetValue() string { return f.Value.String() } -func (f *BigFlag) GetEnvVars() []string { return f.EnvVars } - -func (f *BigFlag) GetDefaultText() string { - if f.DefaultText != "" { - return f.DefaultText - } - return f.GetValue() -} - -// bigValue turns *big.Int into a flag.Value -type bigValue big.Int - -func (b *bigValue) String() string { - if b == nil { - return "" - } - return (*big.Int)(b).String() -} - -func (b *bigValue) Set(s string) error { - intVal, ok := math.ParseBig256(s) - if !ok { - return errors.New("invalid integer syntax") - } - *b = (bigValue)(*intVal) - return nil -} - -// GlobalBig returns the value of a BigFlag from the global flag set. -func GlobalBig(ctx *cli.Context, name string) *big.Int { - val := ctx.Generic(name) - if val == nil { - return nil - } - return (*big.Int)(val.(*bigValue)) -} - -// Expands a file path -// 1. replace tilde with users home dir -// 2. expands embedded environment variables -// 3. cleans the path, e.g. /a/b/../c -> /a/c -// Note, it has limitations, e.g. ~someuser/tmp will not be expanded -func expandPath(p string) string { - // Named pipes are not file paths on windows, ignore - if strings.HasPrefix(p, `\\.\pipe`) { - return p - } - if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") { - if home := HomeDir(); home != "" { - p = home + p[1:] - } - } - return filepath.Clean(os.ExpandEnv(p)) -} - -func HomeDir() string { - if home := os.Getenv("HOME"); home != "" { - return home - } - if usr, err := user.Current(); err == nil { - return usr.HomeDir - } - return "" -} - -func eachName(f cli.Flag, fn func(string)) { - for _, name := range f.Names() { - name = strings.Trim(name, " ") - fn(name) - } -} diff --git a/internal/flags/flags_test.go b/internal/flags/flags_test.go deleted file mode 100644 index ce78870dcd..0000000000 --- a/internal/flags/flags_test.go +++ /dev/null @@ -1,71 +0,0 @@ -// (c) 2023, Ava Labs, Inc. -// -// This file is a derived work, based on the go-ethereum library whose original -// notices appear below. -// -// It is distributed under a license compatible with the licensing terms of the -// original code from which it is derived. -// -// Much love to the original authors for their work. -// ********** -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package flags - -import ( - "os" - "os/user" - "runtime" - "testing" -) - -func TestPathExpansion(t *testing.T) { - user, _ := user.Current() - var tests map[string]string - - if runtime.GOOS == "windows" { - tests = map[string]string{ - `/home/someuser/tmp`: `\home\someuser\tmp`, - `~/tmp`: user.HomeDir + `\tmp`, - `~thisOtherUser/b/`: `~thisOtherUser\b`, - `$DDDXXX/a/b`: `\tmp\a\b`, - `/a/b/`: `\a\b`, - `C:\Documents\Newsletters\`: `C:\Documents\Newsletters`, - `C:\`: `C:\`, - `\\.\pipe\\pipe\geth621383`: `\\.\pipe\\pipe\geth621383`, - } - } else { - tests = map[string]string{ - `/home/someuser/tmp`: `/home/someuser/tmp`, - `~/tmp`: user.HomeDir + `/tmp`, - `~thisOtherUser/b/`: `~thisOtherUser/b`, - `$DDDXXX/a/b`: `/tmp/a/b`, - `/a/b/`: `/a/b`, - `C:\Documents\Newsletters\`: `C:\Documents\Newsletters\`, - `C:\`: `C:\`, - `\\.\pipe\\pipe\geth621383`: `\\.\pipe\\pipe\geth621383`, - } - } - - os.Setenv(`DDDXXX`, `/tmp`) - for test, expected := range tests { - got := expandPath(test) - if got != expected { - t.Errorf(`test %s, got %s, expected %s\n`, test, got, expected) - } - } -} diff --git a/internal/flags/helpers.go b/internal/flags/helpers.go index 0724e4ccee..f38e8d63b4 100644 --- a/internal/flags/helpers.go +++ b/internal/flags/helpers.go @@ -30,12 +30,10 @@ import ( "fmt" "os" "regexp" - "sort" "strings" "github.com/ava-labs/subnet-evm/internal/version" "github.com/ava-labs/subnet-evm/params" - "github.com/ethereum/go-ethereum/log" "github.com/mattn/go-isatty" "github.com/urfave/cli/v2" ) @@ -234,87 +232,3 @@ func wordWrap(s string, width int) string { return output.String() } - -// AutoEnvVars extends all the specific CLI flags with automatically generated -// env vars by capitalizing the flag, replacing . with _ and prefixing it with -// the specified string. -// -// Note, the prefix should *not* contain the separator underscore, that will be -// added automatically. -func AutoEnvVars(flags []cli.Flag, prefix string) { - for _, flag := range flags { - envvar := strings.ToUpper(prefix + "_" + strings.ReplaceAll(strings.ReplaceAll(flag.Names()[0], ".", "_"), "-", "_")) - - switch flag := flag.(type) { - case *cli.StringFlag: - flag.EnvVars = append(flag.EnvVars, envvar) - - case *cli.StringSliceFlag: - flag.EnvVars = append(flag.EnvVars, envvar) - - case *cli.BoolFlag: - flag.EnvVars = append(flag.EnvVars, envvar) - - case *cli.IntFlag: - flag.EnvVars = append(flag.EnvVars, envvar) - - case *cli.Int64Flag: - flag.EnvVars = append(flag.EnvVars, envvar) - - case *cli.Uint64Flag: - flag.EnvVars = append(flag.EnvVars, envvar) - - case *cli.Float64Flag: - flag.EnvVars = append(flag.EnvVars, envvar) - - case *cli.DurationFlag: - flag.EnvVars = append(flag.EnvVars, envvar) - - case *cli.PathFlag: - flag.EnvVars = append(flag.EnvVars, envvar) - - case *BigFlag: - flag.EnvVars = append(flag.EnvVars, envvar) - - case *TextMarshalerFlag: - flag.EnvVars = append(flag.EnvVars, envvar) - - case *DirectoryFlag: - flag.EnvVars = append(flag.EnvVars, envvar) - } - } -} - -// CheckEnvVars iterates over all the environment variables and checks if any of -// them look like a CLI flag but is not consumed. This can be used to detect old -// or mistyped names. -func CheckEnvVars(ctx *cli.Context, flags []cli.Flag, prefix string) { - known := make(map[string]string) - for _, flag := range flags { - docflag, ok := flag.(cli.DocGenerationFlag) - if !ok { - continue - } - for _, envvar := range docflag.GetEnvVars() { - known[envvar] = flag.Names()[0] - } - } - keyvals := os.Environ() - sort.Strings(keyvals) - - for _, keyval := range keyvals { - key := strings.Split(keyval, "=")[0] - if !strings.HasPrefix(key, prefix) { - continue - } - if flag, ok := known[key]; ok { - if ctx.Count(flag) > 0 { - log.Info("Config environment variable found", "envvar", key, "shadowedby", "--"+flag) - } else { - log.Info("Config environment variable found", "envvar", key) - } - } else { - log.Warn("Unknown config environment variable", "envvar", key) - } - } -} diff --git a/miner/README.md b/miner/README.md index 59df974e7c..4cb9ca9b04 100644 --- a/miner/README.md +++ b/miner/README.md @@ -1,14 +1,14 @@ # Miner -The miner is a package inherited from go-ethereum with a large amount of functionality stripped out since it is not needed in subnet-evm. +The miner is a package inherited from go-ethereum with a large amount of functionality stripped out since it is not needed in coreth. -In go-ethereum, the miner needs to perform PoW in order to try and produce the next block. Since Avalanche does not rely on PoW in any way, the miner within Subnet-EVM is only used to produce blocks on demand. +In go-ethereum, the miner needs to perform PoW in order to try and produce the next block. Since Avalanche does not rely on PoW in any way, the miner within Coreth is only used to produce blocks on demand. All of the async functionality has been stripped out in favor of a much lighter weight miner implementation which takes a backend that supplies the blockchain and transaction pool and exposes the functionality to produce a new block with the contents of the transaction pool. ## FinalizeAndAssemble -One nuance of the miner, is that it makes use of the call `FinalizeAndAssemble` from the subnet-evm [consensus engine](../consensus/dummy/README.md). This callback, as hinted at in the name, performs the same work as `Finalize` in addition to assembling the block. +One nuance of the miner, is that it makes use of the call `FinalizeAndAssemble` from the coreth [consensus engine](../consensus/dummy/README.md). This callback, as hinted at in the name, performs the same work as `Finalize` in addition to assembling the block. This means that whenever a verification or processing operation is added in `Finalize` it must be added in `FinalizeAndAssemble` as well to ensure that a block produced by the `miner` is processed in the same way by a node receiving that block, which did not produce it. diff --git a/miner/worker.go b/miner/worker.go index 7c2f7d2e23..4a585e07db 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -55,7 +55,9 @@ import ( ) const ( - targetTxsSize = 1800 * units.KiB + // Leaves 256 KBs for other sections of the block (limit is 2MB). + // This should suffice for atomic txs, proposervm header, and serialization overhead. + targetTxsSize = 1792 * units.KiB ) // environment is the worker's current environment and holds all of the current state information. @@ -144,19 +146,14 @@ func (w *worker) commitNewWork(predicateContext *precompileconfig.PredicateConte } var gasLimit uint64 - // The fee manager relies on the state of the parent block to set the fee config - // because the fee config may be changed by the current block. - feeConfig, _, err := w.chain.GetFeeConfigAt(parent) - if err != nil { - return nil, err - } - configuredGasLimit := feeConfig.GasLimit.Uint64() - if w.chainConfig.IsSubnetEVM(timestamp) { - gasLimit = configuredGasLimit + if w.chainConfig.IsCortina(timestamp) { + gasLimit = params.CortinaGasLimit + } else if w.chainConfig.IsApricotPhase1(timestamp) { + gasLimit = params.ApricotPhase1GasLimit } else { - // The gas limit is set in SubnetEVMGasLimit because the ceiling and floor were set to the same value + // The gas limit is set in phase1 to ApricotPhase1GasLimit because the ceiling and floor were set to the same value // such that the gas limit converged to it. Since this is hardbaked now, we remove the ability to configure it. - gasLimit = core.CalcGasLimit(parent.GasUsed, parent.GasLimit, configuredGasLimit, configuredGasLimit) + gasLimit = core.CalcGasLimit(parent.GasUsed, parent.GasLimit, params.ApricotPhase1GasLimit, params.ApricotPhase1GasLimit) } header := &types.Header{ ParentHash: parent.Hash(), @@ -166,9 +163,10 @@ func (w *worker) commitNewWork(predicateContext *precompileconfig.PredicateConte Time: timestamp, } - if w.chainConfig.IsSubnetEVM(timestamp) { + // Set BaseFee and Extra data field if we are post ApricotPhase3 + if w.chainConfig.IsApricotPhase3(timestamp) { var err error - header.Extra, header.BaseFee, err = dummy.CalcBaseFee(w.chainConfig, feeConfig, parent, timestamp) + header.Extra, header.BaseFee, err = dummy.CalcBaseFee(w.chainConfig, parent, timestamp) if err != nil { return nil, fmt.Errorf("failed to calculate new base fee: %w", err) } @@ -191,20 +189,6 @@ func (w *worker) commitNewWork(predicateContext *precompileconfig.PredicateConte return nil, errors.New("cannot mine without etherbase") } header.Coinbase = w.coinbase - - configuredCoinbase, isAllowFeeRecipient, err := w.chain.GetCoinbaseAt(parent) - if err != nil { - return nil, fmt.Errorf("failed to get configured coinbase: %w", err) - } - - // if fee recipients are not allowed, then the coinbase is the configured coinbase - // don't set w.coinbase directly to the configured coinbase because that would override the - // coinbase set by the user - if !isAllowFeeRecipient && w.coinbase != configuredCoinbase { - log.Info("fee recipients are not allowed, using required coinbase for the mining", "currentminer", w.coinbase, "required", configuredCoinbase) - header.Coinbase = configuredCoinbase - } - if err := w.engine.Prepare(w.chain, header); err != nil { return nil, fmt.Errorf("failed to prepare header for mining: %w", err) } @@ -468,11 +452,8 @@ func (w *worker) handleResult(env *environment, block *types.Block, createdAt ti } logs = append(logs, receipt.Logs...) } - - feesInEther, err := core.TotalFeesFloat(block, receipts) - if err != nil { - log.Error("TotalFeesFloat error: %s", err) - } + fees := totalFees(block, receipts) + feesInEther := new(big.Float).Quo(new(big.Float).SetInt(fees), big.NewFloat(params.Ether)) log.Info("Commit new mining work", "number", block.Number(), "hash", hash, "uncles", 0, "txs", env.tcount, "gas", block.GasUsed(), "fees", feesInEther, @@ -493,3 +474,20 @@ func copyReceipts(receipts []*types.Receipt) []*types.Receipt { } return result } + +// totalFees computes total consumed miner fees in Wei. Block transactions and receipts have to have the same order. +func totalFees(block *types.Block, receipts []*types.Receipt) *big.Int { + feesWei := new(big.Int) + for i, tx := range block.Transactions() { + var minerFee *big.Int + if baseFee := block.BaseFee(); baseFee != nil { + // Note in coreth the coinbase payment is (baseFee + effectiveGasTip) * gasUsed + minerFee = new(big.Int).Add(baseFee, tx.EffectiveGasTipValue(baseFee)) + } else { + // Prior to activation of EIP-1559, the coinbase payment was gasPrice * gasUsed + minerFee = tx.GasPrice() + } + feesWei.Add(feesWei, new(big.Int).Mul(new(big.Int).SetUint64(receipts[i].GasUsed), minerFee)) + } + return feesWei +} diff --git a/node/api.go b/node/api.go index 10fb8e00e4..d22001bcbf 100644 --- a/node/api.go +++ b/node/api.go @@ -56,7 +56,7 @@ type web3API struct { // ClientVersion returns the node name func (s *web3API) ClientVersion() string { - return s.stack.config.SubnetEVMVersion + return s.stack.config.CorethVersion } // Sha3 applies the ethereum sha3 implementation on the input. diff --git a/node/config.go b/node/config.go index bf67d774ff..05b7922851 100644 --- a/node/config.go +++ b/node/config.go @@ -66,7 +66,7 @@ type Config struct { // BatchResponseMaxSize is the maximum number of bytes returned from a batched rpc call. BatchResponseMaxSize int `toml:",omitempty"` - SubnetEVMVersion string + CorethVersion string } // ExtRPCEnabled returns the indicator whether node enables the external @@ -101,7 +101,7 @@ func (c *Config) GetKeyStoreDir() (string, bool, error) { isEphemeral := false if keydir == "" { // There is no datadir. - keydir, err = os.MkdirTemp("", "subnet-evm-keystore") + keydir, err = os.MkdirTemp("", "coreth-keystore") isEphemeral = true } diff --git a/params/config.go b/params/config.go index 70cc2159ba..018649cc23 100644 --- a/params/config.go +++ b/params/config.go @@ -28,27 +28,35 @@ package params import ( "encoding/json" + "errors" "fmt" "math/big" - "github.com/ava-labs/avalanchego/upgrade" - "github.com/ava-labs/avalanchego/utils/constants" - "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/precompile/modules" "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" ) +// Avalanche ChainIDs var ( - // SubnetEVMDefaultConfig is the default configuration - // without any network upgrades. - SubnetEVMDefaultChainConfig = &ChainConfig{ - ChainID: DefaultChainID, - FeeConfig: DefaultFeeConfig, - AllowFeeRecipients: false, + // AvalancheMainnetChainID ... + AvalancheMainnetChainID = big.NewInt(43114) + // AvalancheFujiChainID ... + AvalancheFujiChainID = big.NewInt(43113) + // AvalancheLocalChainID ... + AvalancheLocalChainID = big.NewInt(43112) + + errNonGenesisForkByHeight = errors.New("coreth only supports forking by height at the genesis block") +) +var ( + TestChainConfig = &ChainConfig{ + AvalancheContext: AvalancheContext{utils.TestSnowContext()}, + ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), + DAOForkBlock: nil, + DAOForkSupport: false, EIP150Block: big.NewInt(0), EIP155Block: big.NewInt(0), EIP158Block: big.NewInt(0), @@ -57,16 +65,88 @@ var ( PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(0), MuirGlacierBlock: big.NewInt(0), - NetworkUpgrades: getDefaultNetworkUpgrades(upgrade.GetConfig(constants.MainnetID)), // This can be changed to correct network (local, test) via VM. - GenesisPrecompiles: Precompiles{}, + NetworkUpgrades: NetworkUpgrades{ + ApricotPhase1BlockTimestamp: utils.NewUint64(0), + ApricotPhase2BlockTimestamp: utils.NewUint64(0), + ApricotPhase3BlockTimestamp: utils.NewUint64(0), + ApricotPhase4BlockTimestamp: utils.NewUint64(0), + ApricotPhase5BlockTimestamp: utils.NewUint64(0), + ApricotPhasePre6BlockTimestamp: utils.NewUint64(0), + ApricotPhase6BlockTimestamp: utils.NewUint64(0), + ApricotPhasePost6BlockTimestamp: utils.NewUint64(0), + BanffBlockTimestamp: utils.NewUint64(0), + CortinaBlockTimestamp: utils.NewUint64(0), + DurangoBlockTimestamp: utils.NewUint64(0), + EtnaTimestamp: utils.NewUint64(0), + }, } - TestChainConfig = &ChainConfig{ + TestLaunchConfig = &ChainConfig{ + AvalancheContext: AvalancheContext{utils.TestSnowContext()}, + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + DAOForkBlock: nil, + DAOForkSupport: false, + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + NetworkUpgrades: NetworkUpgrades{ + ApricotPhase1BlockTimestamp: nil, + ApricotPhase2BlockTimestamp: nil, + ApricotPhase3BlockTimestamp: nil, + ApricotPhase4BlockTimestamp: nil, + ApricotPhase5BlockTimestamp: nil, + ApricotPhasePre6BlockTimestamp: nil, + ApricotPhase6BlockTimestamp: nil, + ApricotPhasePost6BlockTimestamp: nil, + BanffBlockTimestamp: nil, + CortinaBlockTimestamp: nil, + DurangoBlockTimestamp: nil, + EtnaTimestamp: nil, + }, + } + + TestApricotPhase1Config = &ChainConfig{ + AvalancheContext: AvalancheContext{utils.TestSnowContext()}, + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + DAOForkBlock: nil, + DAOForkSupport: false, + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + NetworkUpgrades: NetworkUpgrades{ + ApricotPhase1BlockTimestamp: utils.NewUint64(0), + ApricotPhase2BlockTimestamp: nil, + ApricotPhase3BlockTimestamp: nil, + ApricotPhase4BlockTimestamp: nil, + ApricotPhase5BlockTimestamp: nil, + ApricotPhasePre6BlockTimestamp: nil, + ApricotPhase6BlockTimestamp: nil, + ApricotPhasePost6BlockTimestamp: nil, + BanffBlockTimestamp: nil, + CortinaBlockTimestamp: nil, + DurangoBlockTimestamp: nil, + EtnaTimestamp: nil, + }, + } + + TestApricotPhase2Config = &ChainConfig{ AvalancheContext: AvalancheContext{utils.TestSnowContext()}, ChainID: big.NewInt(1), - FeeConfig: DefaultFeeConfig, - AllowFeeRecipients: false, HomesteadBlock: big.NewInt(0), + DAOForkBlock: nil, + DAOForkSupport: false, EIP150Block: big.NewInt(0), EIP155Block: big.NewInt(0), EIP158Block: big.NewInt(0), @@ -75,18 +155,28 @@ var ( PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(0), MuirGlacierBlock: big.NewInt(0), - CancunTime: utils.TimeToNewUint64(upgrade.GetConfig(constants.UnitTestID).EtnaTime), - NetworkUpgrades: getDefaultNetworkUpgrades(upgrade.GetConfig(constants.UnitTestID)), // This can be changed to correct network (local, test) via VM. - GenesisPrecompiles: Precompiles{}, - UpgradeConfig: UpgradeConfig{}, + NetworkUpgrades: NetworkUpgrades{ + ApricotPhase1BlockTimestamp: utils.NewUint64(0), + ApricotPhase2BlockTimestamp: utils.NewUint64(0), + ApricotPhase3BlockTimestamp: nil, + ApricotPhase4BlockTimestamp: nil, + ApricotPhase5BlockTimestamp: nil, + ApricotPhasePre6BlockTimestamp: nil, + ApricotPhase6BlockTimestamp: nil, + ApricotPhasePost6BlockTimestamp: nil, + BanffBlockTimestamp: nil, + CortinaBlockTimestamp: nil, + DurangoBlockTimestamp: nil, + EtnaTimestamp: nil, + }, } - TestPreSubnetEVMChainConfig = &ChainConfig{ + TestApricotPhase3Config = &ChainConfig{ AvalancheContext: AvalancheContext{utils.TestSnowContext()}, ChainID: big.NewInt(1), - FeeConfig: DefaultFeeConfig, - AllowFeeRecipients: false, HomesteadBlock: big.NewInt(0), + DAOForkBlock: nil, + DAOForkSupport: false, EIP150Block: big.NewInt(0), EIP155Block: big.NewInt(0), EIP158Block: big.NewInt(0), @@ -96,20 +186,27 @@ var ( IstanbulBlock: big.NewInt(0), MuirGlacierBlock: big.NewInt(0), NetworkUpgrades: NetworkUpgrades{ - SubnetEVMTimestamp: utils.TimeToNewUint64(upgrade.UnscheduledActivationTime), - DurangoTimestamp: utils.TimeToNewUint64(upgrade.UnscheduledActivationTime), - EtnaTimestamp: utils.TimeToNewUint64(upgrade.UnscheduledActivationTime), + ApricotPhase1BlockTimestamp: utils.NewUint64(0), + ApricotPhase2BlockTimestamp: utils.NewUint64(0), + ApricotPhase3BlockTimestamp: utils.NewUint64(0), + ApricotPhase4BlockTimestamp: nil, + ApricotPhase5BlockTimestamp: nil, + ApricotPhasePre6BlockTimestamp: nil, + ApricotPhase6BlockTimestamp: nil, + ApricotPhasePost6BlockTimestamp: nil, + BanffBlockTimestamp: nil, + CortinaBlockTimestamp: nil, + DurangoBlockTimestamp: nil, + EtnaTimestamp: nil, }, - GenesisPrecompiles: Precompiles{}, - UpgradeConfig: UpgradeConfig{}, } - TestSubnetEVMChainConfig = &ChainConfig{ + TestApricotPhase4Config = &ChainConfig{ AvalancheContext: AvalancheContext{utils.TestSnowContext()}, ChainID: big.NewInt(1), - FeeConfig: DefaultFeeConfig, - AllowFeeRecipients: false, HomesteadBlock: big.NewInt(0), + DAOForkBlock: nil, + DAOForkSupport: false, EIP150Block: big.NewInt(0), EIP155Block: big.NewInt(0), EIP158Block: big.NewInt(0), @@ -119,20 +216,207 @@ var ( IstanbulBlock: big.NewInt(0), MuirGlacierBlock: big.NewInt(0), NetworkUpgrades: NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(0), - DurangoTimestamp: utils.TimeToNewUint64(upgrade.UnscheduledActivationTime), - EtnaTimestamp: utils.TimeToNewUint64(upgrade.UnscheduledActivationTime), + ApricotPhase1BlockTimestamp: utils.NewUint64(0), + ApricotPhase2BlockTimestamp: utils.NewUint64(0), + ApricotPhase3BlockTimestamp: utils.NewUint64(0), + ApricotPhase4BlockTimestamp: utils.NewUint64(0), + ApricotPhase5BlockTimestamp: nil, + ApricotPhasePre6BlockTimestamp: nil, + ApricotPhase6BlockTimestamp: nil, + ApricotPhasePost6BlockTimestamp: nil, + BanffBlockTimestamp: nil, + CortinaBlockTimestamp: nil, + DurangoBlockTimestamp: nil, + EtnaTimestamp: nil, + }, + } + + TestApricotPhase5Config = &ChainConfig{ + AvalancheContext: AvalancheContext{utils.TestSnowContext()}, + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + DAOForkBlock: nil, + DAOForkSupport: false, + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + NetworkUpgrades: NetworkUpgrades{ + ApricotPhase1BlockTimestamp: utils.NewUint64(0), + ApricotPhase2BlockTimestamp: utils.NewUint64(0), + ApricotPhase3BlockTimestamp: utils.NewUint64(0), + ApricotPhase4BlockTimestamp: utils.NewUint64(0), + ApricotPhase5BlockTimestamp: utils.NewUint64(0), + ApricotPhasePre6BlockTimestamp: nil, + ApricotPhase6BlockTimestamp: nil, + ApricotPhasePost6BlockTimestamp: nil, + BanffBlockTimestamp: nil, + CortinaBlockTimestamp: nil, + DurangoBlockTimestamp: nil, + EtnaTimestamp: nil, + }, + } + + TestApricotPhasePre6Config = &ChainConfig{ + AvalancheContext: AvalancheContext{utils.TestSnowContext()}, + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + DAOForkBlock: nil, + DAOForkSupport: false, + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + NetworkUpgrades: NetworkUpgrades{ + ApricotPhase1BlockTimestamp: utils.NewUint64(0), + ApricotPhase2BlockTimestamp: utils.NewUint64(0), + ApricotPhase3BlockTimestamp: utils.NewUint64(0), + ApricotPhase4BlockTimestamp: utils.NewUint64(0), + ApricotPhase5BlockTimestamp: utils.NewUint64(0), + ApricotPhasePre6BlockTimestamp: utils.NewUint64(0), + ApricotPhase6BlockTimestamp: nil, + ApricotPhasePost6BlockTimestamp: nil, + BanffBlockTimestamp: nil, + CortinaBlockTimestamp: nil, + DurangoBlockTimestamp: nil, + EtnaTimestamp: nil, + }, + } + + TestApricotPhase6Config = &ChainConfig{ + AvalancheContext: AvalancheContext{utils.TestSnowContext()}, + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + DAOForkBlock: nil, + DAOForkSupport: false, + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + NetworkUpgrades: NetworkUpgrades{ + ApricotPhase1BlockTimestamp: utils.NewUint64(0), + ApricotPhase2BlockTimestamp: utils.NewUint64(0), + ApricotPhase3BlockTimestamp: utils.NewUint64(0), + ApricotPhase4BlockTimestamp: utils.NewUint64(0), + ApricotPhase5BlockTimestamp: utils.NewUint64(0), + ApricotPhasePre6BlockTimestamp: utils.NewUint64(0), + ApricotPhase6BlockTimestamp: utils.NewUint64(0), + ApricotPhasePost6BlockTimestamp: nil, + BanffBlockTimestamp: nil, + CortinaBlockTimestamp: nil, + DurangoBlockTimestamp: nil, + EtnaTimestamp: nil, + }, + } + + TestApricotPhasePost6Config = &ChainConfig{ + AvalancheContext: AvalancheContext{utils.TestSnowContext()}, + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + DAOForkBlock: nil, + DAOForkSupport: false, + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + NetworkUpgrades: NetworkUpgrades{ + ApricotPhase1BlockTimestamp: utils.NewUint64(0), + ApricotPhase2BlockTimestamp: utils.NewUint64(0), + ApricotPhase3BlockTimestamp: utils.NewUint64(0), + ApricotPhase4BlockTimestamp: utils.NewUint64(0), + ApricotPhase5BlockTimestamp: utils.NewUint64(0), + ApricotPhasePre6BlockTimestamp: utils.NewUint64(0), + ApricotPhase6BlockTimestamp: utils.NewUint64(0), + ApricotPhasePost6BlockTimestamp: utils.NewUint64(0), + BanffBlockTimestamp: nil, + CortinaBlockTimestamp: nil, + DurangoBlockTimestamp: nil, + EtnaTimestamp: nil, + }, + } + + TestBanffChainConfig = &ChainConfig{ + AvalancheContext: AvalancheContext{utils.TestSnowContext()}, + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + DAOForkBlock: nil, + DAOForkSupport: false, + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + NetworkUpgrades: NetworkUpgrades{ + ApricotPhase1BlockTimestamp: utils.NewUint64(0), + ApricotPhase2BlockTimestamp: utils.NewUint64(0), + ApricotPhase3BlockTimestamp: utils.NewUint64(0), + ApricotPhase4BlockTimestamp: utils.NewUint64(0), + ApricotPhase5BlockTimestamp: utils.NewUint64(0), + ApricotPhasePre6BlockTimestamp: utils.NewUint64(0), + ApricotPhase6BlockTimestamp: utils.NewUint64(0), + ApricotPhasePost6BlockTimestamp: utils.NewUint64(0), + BanffBlockTimestamp: utils.NewUint64(0), + CortinaBlockTimestamp: nil, + DurangoBlockTimestamp: nil, + EtnaTimestamp: nil, + }, + } + + TestCortinaChainConfig = &ChainConfig{ + AvalancheContext: AvalancheContext{utils.TestSnowContext()}, + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + DAOForkBlock: nil, + DAOForkSupport: false, + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + NetworkUpgrades: NetworkUpgrades{ + ApricotPhase1BlockTimestamp: utils.NewUint64(0), + ApricotPhase2BlockTimestamp: utils.NewUint64(0), + ApricotPhase3BlockTimestamp: utils.NewUint64(0), + ApricotPhase4BlockTimestamp: utils.NewUint64(0), + ApricotPhase5BlockTimestamp: utils.NewUint64(0), + ApricotPhasePre6BlockTimestamp: utils.NewUint64(0), + ApricotPhase6BlockTimestamp: utils.NewUint64(0), + ApricotPhasePost6BlockTimestamp: utils.NewUint64(0), + BanffBlockTimestamp: utils.NewUint64(0), + CortinaBlockTimestamp: utils.NewUint64(0), + DurangoBlockTimestamp: nil, + EtnaTimestamp: nil, }, - GenesisPrecompiles: Precompiles{}, - UpgradeConfig: UpgradeConfig{}, } TestDurangoChainConfig = &ChainConfig{ AvalancheContext: AvalancheContext{utils.TestSnowContext()}, ChainID: big.NewInt(1), - FeeConfig: DefaultFeeConfig, - AllowFeeRecipients: false, HomesteadBlock: big.NewInt(0), + DAOForkBlock: nil, + DAOForkSupport: false, EIP150Block: big.NewInt(0), EIP155Block: big.NewInt(0), EIP158Block: big.NewInt(0), @@ -142,20 +426,27 @@ var ( IstanbulBlock: big.NewInt(0), MuirGlacierBlock: big.NewInt(0), NetworkUpgrades: NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(0), - DurangoTimestamp: utils.NewUint64(0), - EtnaTimestamp: utils.TimeToNewUint64(upgrade.UnscheduledActivationTime), + ApricotPhase1BlockTimestamp: utils.NewUint64(0), + ApricotPhase2BlockTimestamp: utils.NewUint64(0), + ApricotPhase3BlockTimestamp: utils.NewUint64(0), + ApricotPhase4BlockTimestamp: utils.NewUint64(0), + ApricotPhase5BlockTimestamp: utils.NewUint64(0), + ApricotPhasePre6BlockTimestamp: utils.NewUint64(0), + ApricotPhase6BlockTimestamp: utils.NewUint64(0), + ApricotPhasePost6BlockTimestamp: utils.NewUint64(0), + BanffBlockTimestamp: utils.NewUint64(0), + CortinaBlockTimestamp: utils.NewUint64(0), + DurangoBlockTimestamp: utils.NewUint64(0), + EtnaTimestamp: nil, }, - GenesisPrecompiles: Precompiles{}, - UpgradeConfig: UpgradeConfig{}, } TestEtnaChainConfig = &ChainConfig{ AvalancheContext: AvalancheContext{utils.TestSnowContext()}, ChainID: big.NewInt(1), - FeeConfig: DefaultFeeConfig, - AllowFeeRecipients: false, HomesteadBlock: big.NewInt(0), + DAOForkBlock: nil, + DAOForkSupport: false, EIP150Block: big.NewInt(0), EIP155Block: big.NewInt(0), EIP158Block: big.NewInt(0), @@ -165,13 +456,21 @@ var ( IstanbulBlock: big.NewInt(0), MuirGlacierBlock: big.NewInt(0), NetworkUpgrades: NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(0), - DurangoTimestamp: utils.NewUint64(0), - EtnaTimestamp: utils.NewUint64(0), + ApricotPhase1BlockTimestamp: utils.NewUint64(0), + ApricotPhase2BlockTimestamp: utils.NewUint64(0), + ApricotPhase3BlockTimestamp: utils.NewUint64(0), + ApricotPhase4BlockTimestamp: utils.NewUint64(0), + ApricotPhase5BlockTimestamp: utils.NewUint64(0), + ApricotPhasePre6BlockTimestamp: utils.NewUint64(0), + ApricotPhase6BlockTimestamp: utils.NewUint64(0), + ApricotPhasePost6BlockTimestamp: utils.NewUint64(0), + BanffBlockTimestamp: utils.NewUint64(0), + CortinaBlockTimestamp: utils.NewUint64(0), + DurangoBlockTimestamp: utils.NewUint64(0), + EtnaTimestamp: utils.NewUint64(0), }, - GenesisPrecompiles: Precompiles{}, - UpgradeConfig: UpgradeConfig{}, } + TestRules = TestChainConfig.Rules(new(big.Int), 0) ) @@ -185,6 +484,9 @@ type ChainConfig struct { HomesteadBlock *big.Int `json:"homesteadBlock,omitempty"` // Homestead switch block (nil = no fork, 0 = already homestead) + DAOForkBlock *big.Int `json:"daoForkBlock,omitempty"` // TheDAO hard-fork switch block (nil = no fork) + DAOForkSupport bool `json:"daoForkSupport,omitempty"` // Whether the nodes supports or opposes the DAO hard-fork + // EIP150 implements the Gas price changes (https://github.com/ethereum/EIPs/issues/150) EIP150Block *big.Int `json:"eip150Block,omitempty"` // EIP150 HF block (nil = no fork) EIP155Block *big.Int `json:"eip155Block,omitempty"` // EIP155 HF block @@ -200,16 +502,12 @@ type ChainConfig struct { CancunTime *uint64 `json:"cancunTime,omitempty"` // Cancun switch time (nil = no fork, 0 = already activated) VerkleTime *uint64 `json:"verkleTime,omitempty"` // Verkle switch time (nil = no fork, 0 = already on verkle) - - NetworkUpgrades // Config for timestamps that enable network upgrades. Skip encoding/decoding directly into ChainConfig. + // Avalanche Network Upgrades + NetworkUpgrades AvalancheContext `json:"-"` // Avalanche specific context set during VM initialization. Not serialized. - FeeConfig commontype.FeeConfig `json:"feeConfig"` // Set the configuration for the dynamic fee algorithm - AllowFeeRecipients bool `json:"allowFeeRecipients,omitempty"` // Allows fees to be collected by block builders. - - GenesisPrecompiles Precompiles `json:"-"` // Config for enabling precompiles from genesis. JSON encode/decode will be handled by the custom marshaler/unmarshaler. - UpgradeConfig `json:"-"` // Config specified in upgradeBytes (avalanche network upgrades or enable/disabling precompiles). Skip encoding/decoding directly into ChainConfig. + UpgradeConfig `json:"-"` // Config specified in upgradeBytes (avalanche network upgrades or enable/disabling precompiles). Skip encoding/decoding directly into ChainConfig. } // Description returns a human-readable description of ChainConfig. @@ -224,6 +522,9 @@ func (c *ChainConfig) Description() string { // the output for testnets and private networks. banner += "Hard Forks (block based):\n" banner += fmt.Sprintf(" - Homestead: #%-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/homestead.md)\n", c.HomesteadBlock) + if c.DAOForkBlock != nil { + banner += fmt.Sprintf(" - DAO Fork: #%-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/dao-fork.md)\n", c.DAOForkBlock) + } banner += fmt.Sprintf(" - Tangerine Whistle (EIP 150): #%-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/tangerine-whistle.md)\n", c.EIP150Block) banner += fmt.Sprintf(" - Spurious Dragon/1 (EIP 155): #%-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/spurious-dragon.md)\n", c.EIP155Block) banner += fmt.Sprintf(" - Spurious Dragon/2 (EIP 158): #%-8v (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/spurious-dragon.md)\n", c.EIP155Block) @@ -237,34 +538,18 @@ func (c *ChainConfig) Description() string { banner += "Hard forks (timestamp based):\n" banner += fmt.Sprintf(" - Cancun Timestamp: @%-10v (https://github.com/ava-labs/avalanchego/releases/tag/v1.12.0)\n", ptrToString(c.CancunTime)) + banner += "\n" banner += "Avalanche Upgrades (timestamp based):\n" banner += c.NetworkUpgrades.Description() banner += "\n" - precompileUpgradeBytes, err := json.Marshal(c.GenesisPrecompiles) - if err != nil { - precompileUpgradeBytes = []byte("cannot marshal PrecompileUpgrade") - } - banner += fmt.Sprintf("Precompile Upgrades: %s", string(precompileUpgradeBytes)) - banner += "\n" - upgradeConfigBytes, err := json.Marshal(c.UpgradeConfig) if err != nil { upgradeConfigBytes = []byte("cannot marshal UpgradeConfig") } banner += fmt.Sprintf("Upgrade Config: %s", string(upgradeConfigBytes)) banner += "\n" - - feeBytes, err := json.Marshal(c.FeeConfig) - if err != nil { - feeBytes = []byte("cannot marshal FeeConfig") - } - banner += fmt.Sprintf("Fee Config: %s", string(feeBytes)) - banner += "\n" - - banner += fmt.Sprintf("Allow Fee Recipients: %v", c.AllowFeeRecipients) - banner += "\n" return banner } @@ -273,6 +558,11 @@ func (c *ChainConfig) IsHomestead(num *big.Int) bool { return isBlockForked(c.HomesteadBlock, num) } +// IsDAOFork returns whether num is either equal to the DAO fork block or greater. +func (c *ChainConfig) IsDAOFork(num *big.Int) bool { + return isBlockForked(c.DAOForkBlock, num) +} + // IsEIP150 returns whether num is either equal to the EIP150 fork block or greater. func (c *ChainConfig) IsEIP150(num *big.Int) bool { return isBlockForked(c.EIP150Block, num) @@ -362,6 +652,7 @@ type fork struct { func (c *ChainConfig) CheckConfigForkOrder() error { ethForks := []fork{ {name: "homesteadBlock", block: c.HomesteadBlock}, + {name: "daoForkBlock", block: c.DAOForkBlock, optional: true}, {name: "eip150Block", block: c.EIP150Block}, {name: "eip155Block", block: c.EIP155Block}, {name: "eip158Block", block: c.EIP158Block}, @@ -411,7 +702,7 @@ func checkForks(forks []fork, blockFork bool) error { lastFork.name, cur.name, cur.block) } else { return fmt.Errorf("unsupported fork ordering: %v not enabled, but %v enabled at timestamp %v", - lastFork.name, cur.name, *cur.timestamp) + lastFork.name, cur.name, cur.timestamp) } // Fork (whether defined by block or timestamp) must follow the fork definition sequence @@ -421,7 +712,7 @@ func checkForks(forks []fork, blockFork bool) error { lastFork.name, lastFork.block, cur.name, cur.block) } else if lastFork.timestamp != nil && *lastFork.timestamp > *cur.timestamp { return fmt.Errorf("unsupported fork ordering: %v enabled at timestamp %v, but %v enabled at timestamp %v", - lastFork.name, *lastFork.timestamp, cur.name, *cur.timestamp) + lastFork.name, lastFork.timestamp, cur.name, cur.timestamp) } // Timestamp based forks can follow block based ones, but not the other way around @@ -446,6 +737,12 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, headNumber *big.Int, if isForkBlockIncompatible(c.HomesteadBlock, newcfg.HomesteadBlock, headNumber) { return newBlockCompatError("Homestead fork block", c.HomesteadBlock, newcfg.HomesteadBlock) } + if isForkBlockIncompatible(c.DAOForkBlock, newcfg.DAOForkBlock, headNumber) { + return newBlockCompatError("DAO fork block", c.DAOForkBlock, newcfg.DAOForkBlock) + } + if c.IsDAOFork(headNumber) && c.DAOForkSupport != newcfg.DAOForkSupport { + return newBlockCompatError("DAO fork support flag", c.DAOForkBlock, newcfg.DAOForkBlock) + } if isForkBlockIncompatible(c.EIP150Block, newcfg.EIP150Block, headNumber) { return newBlockCompatError("EIP150 fork block", c.EIP150Block, newcfg.EIP150Block) } @@ -455,7 +752,7 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, headNumber *big.Int, if isForkBlockIncompatible(c.EIP158Block, newcfg.EIP158Block, headNumber) { return newBlockCompatError("EIP158 fork block", c.EIP158Block, newcfg.EIP158Block) } - if c.IsEIP158(headNumber) && !utils.BigNumEqual(c.ChainID, newcfg.ChainID) { + if c.IsEIP158(headNumber) && !configBlockEqual(c.ChainID, newcfg.ChainID) { return newBlockCompatError("EIP158 chain ID", c.EIP158Block, newcfg.EIP158Block) } if isForkBlockIncompatible(c.ByzantiumBlock, newcfg.ByzantiumBlock, headNumber) { @@ -488,18 +785,6 @@ func (c *ChainConfig) checkCompatible(newcfg *ChainConfig, headNumber *big.Int, if err := c.CheckNetworkUpgradesCompatible(&newcfg.NetworkUpgrades, headTimestamp); err != nil { return err } - - // Check that the precompiles on the new config are compatible with the existing precompile config. - if err := c.CheckPrecompilesCompatible(newcfg.PrecompileUpgrades, headTimestamp); err != nil { - return err - } - - // Check that the state upgrades on the new config are compatible with the existing state upgrade config. - if err := c.CheckStateUpgradesCompatible(newcfg.StateUpgrades, headTimestamp); err != nil { - return err - } - - // TODO verify that the fee config is fully compatible between [c] and [newcfg]. return nil } diff --git a/params/config_extra.go b/params/config_extra.go index 882161bf9f..decc15f027 100644 --- a/params/config_extra.go +++ b/params/config_extra.go @@ -10,7 +10,7 @@ import ( "math/big" "github.com/ava-labs/avalanchego/snow" - "github.com/ava-labs/subnet-evm/commontype" + "github.com/ava-labs/avalanchego/upgrade" "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" ) @@ -18,45 +18,12 @@ import ( const ( maxJSONLen = 64 * 1024 * 1024 // 64MB - // Consensus Params - RollupWindow uint64 = 10 - DynamicFeeExtraDataSize = 80 - - // For legacy tests - MinGasPrice int64 = 225_000_000_000 - TestInitialBaseFee int64 = 225_000_000_000 - TestMaxBaseFee int64 = 225_000_000_000 -) - -var ( - errNonGenesisForkByHeight = errors.New("subnet-evm only supports forking by height at the genesis block") - - DefaultChainID = big.NewInt(43214) - - DefaultFeeConfig = commontype.FeeConfig{ - GasLimit: big.NewInt(8_000_000), - TargetBlockRate: 2, // in seconds - - MinBaseFee: big.NewInt(25_000_000_000), - TargetGas: big.NewInt(15_000_000), - BaseFeeChangeDenominator: big.NewInt(36), - - MinBlockGasCost: big.NewInt(0), - MaxBlockGasCost: big.NewInt(1_000_000), - BlockGasCostStep: big.NewInt(200_000), - } ) // UpgradeConfig includes the following configs that may be specified in upgradeBytes: // - Timestamps that enable avalanche network upgrades, // - Enabling or disabling precompiles as network upgrades. type UpgradeConfig struct { - // Config for timestamps that enable network upgrades. - NetworkUpgradeOverrides *NetworkUpgrades `json:"networkUpgradeOverrides,omitempty"` - - // Config for modifying state as a network upgrade. - StateUpgrades []StateUpgrade `json:"stateUpgrades,omitempty"` - // Config for enabling and disabling precompiles as network upgrades. PrecompileUpgrades []PrecompileUpgrade `json:"precompileUpgrades,omitempty"` } @@ -66,10 +33,15 @@ type AvalancheContext struct { SnowCtx *snow.Context } -// SetEthUpgrades sets the mapped upgrades Avalanche > EVM upgrades) for the chain config. -func (c *ChainConfig) SetEthUpgrades(avalancheUpgrades NetworkUpgrades) { - if avalancheUpgrades.EtnaTimestamp != nil { - c.CancunTime = utils.NewUint64(*avalancheUpgrades.EtnaTimestamp) +// SetEthUpgrades enables Etheruem network upgrades using the same time as +// the Avalanche network upgrade that enables them. +// +// TODO: Prior to Cancun, Avalanche upgrades are referenced inline in the +// code in place of their Ethereum counterparts. The original Ethereum names +// should be restored for maintainability. +func (c *ChainConfig) SetEthUpgrades() { + if c.EtnaTimestamp != nil { + c.CancunTime = utils.NewUint64(*c.EtnaTimestamp) } } @@ -89,8 +61,7 @@ func (c *ChainConfig) UnmarshalJSON(data []byte) error { // At this point we have populated all fields except PrecompileUpgrade *c = ChainConfig(tmp) - // Unmarshal inlined PrecompileUpgrade - return json.Unmarshal(data, &c.GenesisPrecompiles) + return nil } // MarshalJSON returns the JSON encoding of c. @@ -98,27 +69,7 @@ func (c *ChainConfig) UnmarshalJSON(data []byte) error { func (c ChainConfig) MarshalJSON() ([]byte, error) { // Alias ChainConfig to avoid recursion type _ChainConfig ChainConfig - tmp, err := json.Marshal(_ChainConfig(c)) - if err != nil { - return nil, err - } - - // To include PrecompileUpgrades, we unmarshal the json representing c - // then directly add the corresponding keys to the json. - raw := make(map[string]json.RawMessage) - if err := json.Unmarshal(tmp, &raw); err != nil { - return nil, err - } - - for key, value := range c.GenesisPrecompiles { - conf, err := json.Marshal(value) - if err != nil { - return nil, err - } - raw[key] = conf - } - - return json.Marshal(raw) + return json.Marshal(_ChainConfig(c)) } type ChainConfigWithUpgradesJSON struct { @@ -182,25 +133,11 @@ func (cu *ChainConfigWithUpgradesJSON) UnmarshalJSON(input []byte) error { // Verify verifies chain config and returns error func (c *ChainConfig) Verify() error { - if err := c.FeeConfig.Verify(); err != nil { - return err - } - // Verify the precompile upgrades are internally consistent given the existing chainConfig. if err := c.verifyPrecompileUpgrades(); err != nil { return fmt.Errorf("invalid precompile upgrades: %w", err) } - // Verify the state upgrades are internally consistent given the existing chainConfig. - if err := c.verifyStateUpgrades(); err != nil { - return fmt.Errorf("invalid state upgrades: %w", err) - } - - // Verify the network upgrades are internally consistent given the existing chainConfig. - if err := c.verifyNetworkUpgrades(c.SnowCtx.NetworkUpgrades); err != nil { - return fmt.Errorf("invalid network upgrades: %w", err) - } - return nil } @@ -210,18 +147,6 @@ func (c *ChainConfig) IsPrecompileEnabled(address common.Address, timestamp uint return config != nil && !config.IsDisabled() } -// GetFeeConfig returns the original FeeConfig contained in the genesis ChainConfig. -// Implements precompile.ChainConfig interface. -func (c *ChainConfig) GetFeeConfig() commontype.FeeConfig { - return c.FeeConfig -} - -// AllowedFeeRecipients returns the original AllowedFeeRecipients parameter contained in the genesis ChainConfig. -// Implements precompile.ChainConfig interface. -func (c *ChainConfig) AllowedFeeRecipients() bool { - return c.AllowFeeRecipients -} - // ToWithUpgradesJSON converts the ChainConfig to ChainConfigWithUpgradesJSON with upgrades explicitly displayed. // ChainConfig does not include upgrades in its JSON output. // This is a workaround for showing upgrades in the JSON output. @@ -232,36 +157,22 @@ func (c *ChainConfig) ToWithUpgradesJSON() *ChainConfigWithUpgradesJSON { } } -func (c *ChainConfig) SetNetworkUpgradeDefaults() { - if c.HomesteadBlock == nil { - c.HomesteadBlock = big.NewInt(0) - } - if c.EIP150Block == nil { - c.EIP150Block = big.NewInt(0) - } - if c.EIP155Block == nil { - c.EIP155Block = big.NewInt(0) - } - if c.EIP158Block == nil { - c.EIP158Block = big.NewInt(0) - } - if c.ByzantiumBlock == nil { - c.ByzantiumBlock = big.NewInt(0) +func GetChainConfig(agoUpgrade upgrade.Config, chainID *big.Int) *ChainConfig { + return &ChainConfig{ + ChainID: chainID, + HomesteadBlock: big.NewInt(0), + DAOForkBlock: big.NewInt(0), + DAOForkSupport: true, + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + NetworkUpgrades: getNetworkUpgrades(agoUpgrade), } - if c.ConstantinopleBlock == nil { - c.ConstantinopleBlock = big.NewInt(0) - } - if c.PetersburgBlock == nil { - c.PetersburgBlock = big.NewInt(0) - } - if c.IstanbulBlock == nil { - c.IstanbulBlock = big.NewInt(0) - } - if c.MuirGlacierBlock == nil { - c.MuirGlacierBlock = big.NewInt(0) - } - - c.NetworkUpgrades.setDefaults(c.SnowCtx.NetworkUpgrades) } func (r *Rules) PredicatersExist() bool { diff --git a/params/config_test.go b/params/config_test.go index 707cf89f9d..6d408c7278 100644 --- a/params/config_test.go +++ b/params/config_test.go @@ -27,19 +27,13 @@ package params import ( - "encoding/json" "math" "math/big" "reflect" "testing" "time" - "github.com/ava-labs/subnet-evm/precompile/contracts/nativeminter" - "github.com/ava-labs/subnet-evm/precompile/contracts/rewardmanager" - "github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist" "github.com/ava-labs/subnet-evm/utils" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" ) func TestCheckCompatible(t *testing.T) { @@ -117,25 +111,25 @@ func TestCheckCompatible(t *testing.T) { }, { stored: TestChainConfig, - new: TestPreSubnetEVMChainConfig, + new: TestApricotPhase4Config, headBlock: 0, headTimestamp: 0, wantErr: &ConfigCompatError{ - What: "SubnetEVM fork block timestamp", + What: "ApricotPhase5 fork block timestamp", StoredTime: utils.NewUint64(0), - NewTime: TestPreSubnetEVMChainConfig.NetworkUpgrades.SubnetEVMTimestamp, + NewTime: nil, RewindToTime: 0, }, }, { stored: TestChainConfig, - new: TestPreSubnetEVMChainConfig, + new: TestApricotPhase4Config, headBlock: 10, headTimestamp: 100, wantErr: &ConfigCompatError{ - What: "SubnetEVM fork block timestamp", + What: "ApricotPhase5 fork block timestamp", StoredTime: utils.NewUint64(0), - NewTime: TestPreSubnetEVMChainConfig.NetworkUpgrades.SubnetEVMTimestamp, + NewTime: nil, RewindToTime: 0, }, }, @@ -152,179 +146,19 @@ func TestCheckCompatible(t *testing.T) { func TestConfigRules(t *testing.T) { c := &ChainConfig{ NetworkUpgrades: NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(500), + CortinaBlockTimestamp: utils.NewUint64(500), }, } - var stamp uint64 - if r := c.Rules(big.NewInt(0), stamp); r.IsSubnetEVM { - t.Errorf("expected %v to not be subnet-evm", stamp) + if r := c.Rules(big.NewInt(0), stamp); r.IsCortina { + t.Errorf("expected %v to not be cortina", stamp) } stamp = 500 - if r := c.Rules(big.NewInt(0), stamp); !r.IsSubnetEVM { - t.Errorf("expected %v to be subnet-evm", stamp) + if r := c.Rules(big.NewInt(0), stamp); !r.IsCortina { + t.Errorf("expected %v to be cortina", stamp) } stamp = math.MaxInt64 - if r := c.Rules(big.NewInt(0), stamp); !r.IsSubnetEVM { - t.Errorf("expected %v to be subnet-evm", stamp) - } -} - -func TestConfigUnmarshalJSON(t *testing.T) { - require := require.New(t) - - testRewardManagerConfig := rewardmanager.NewConfig( - utils.NewUint64(1671542573), - []common.Address{common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC")}, - nil, - nil, - &rewardmanager.InitialRewardConfig{ - AllowFeeRecipients: true, - }) - - testContractNativeMinterConfig := nativeminter.NewConfig( - utils.NewUint64(0), - []common.Address{common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC")}, - nil, - nil, - nil, - ) - - config := []byte(` - { - "chainId": 43214, - "allowFeeRecipients": true, - "rewardManagerConfig": { - "blockTimestamp": 1671542573, - "adminAddresses": [ - "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" - ], - "initialRewardConfig": { - "allowFeeRecipients": true - } - }, - "contractNativeMinterConfig": { - "blockTimestamp": 0, - "adminAddresses": [ - "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" - ] - } - } - `) - c := ChainConfig{} - err := json.Unmarshal(config, &c) - require.NoError(err) - - require.Equal(c.ChainID, big.NewInt(43214)) - require.Equal(c.AllowFeeRecipients, true) - - rewardManagerConfig, ok := c.GenesisPrecompiles[rewardmanager.ConfigKey] - require.True(ok) - require.Equal(rewardManagerConfig.Key(), rewardmanager.ConfigKey) - require.True(rewardManagerConfig.Equal(testRewardManagerConfig)) - - nativeMinterConfig := c.GenesisPrecompiles[nativeminter.ConfigKey] - require.Equal(nativeMinterConfig.Key(), nativeminter.ConfigKey) - require.True(nativeMinterConfig.Equal(testContractNativeMinterConfig)) - - // Marshal and unmarshal again and check that the result is the same - marshaled, err := json.Marshal(c) - require.NoError(err) - c2 := ChainConfig{} - err = json.Unmarshal(marshaled, &c2) - require.NoError(err) - require.Equal(c, c2) -} - -func TestActivePrecompiles(t *testing.T) { - config := ChainConfig{ - UpgradeConfig: UpgradeConfig{ - PrecompileUpgrades: []PrecompileUpgrade{ - { - nativeminter.NewConfig(utils.NewUint64(0), nil, nil, nil, nil), // enable at genesis - }, - { - nativeminter.NewDisableConfig(utils.NewUint64(1)), // disable at timestamp 1 - }, - }, - }, + if r := c.Rules(big.NewInt(0), stamp); !r.IsCortina { + t.Errorf("expected %v to be cortina", stamp) } - - rules0 := config.Rules(common.Big0, 0) - require.True(t, rules0.IsPrecompileEnabled(nativeminter.Module.Address)) - - rules1 := config.Rules(common.Big0, 1) - require.False(t, rules1.IsPrecompileEnabled(nativeminter.Module.Address)) -} - -func TestChainConfigMarshalWithUpgrades(t *testing.T) { - config := ChainConfigWithUpgradesJSON{ - ChainConfig: ChainConfig{ - ChainID: big.NewInt(1), - FeeConfig: DefaultFeeConfig, - AllowFeeRecipients: false, - HomesteadBlock: big.NewInt(0), - EIP150Block: big.NewInt(0), - EIP155Block: big.NewInt(0), - EIP158Block: big.NewInt(0), - ByzantiumBlock: big.NewInt(0), - ConstantinopleBlock: big.NewInt(0), - PetersburgBlock: big.NewInt(0), - IstanbulBlock: big.NewInt(0), - MuirGlacierBlock: big.NewInt(0), - NetworkUpgrades: NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(0), - DurangoTimestamp: utils.NewUint64(0), - }, - GenesisPrecompiles: Precompiles{}, - }, - UpgradeConfig: UpgradeConfig{ - PrecompileUpgrades: []PrecompileUpgrade{ - { - Config: txallowlist.NewConfig(utils.NewUint64(100), nil, nil, nil), - }, - }, - }, - } - result, err := json.Marshal(&config) - require.NoError(t, err) - expectedJSON := `{ - "chainId": 1, - "feeConfig": { - "gasLimit": 8000000, - "targetBlockRate": 2, - "minBaseFee": 25000000000, - "targetGas": 15000000, - "baseFeeChangeDenominator": 36, - "minBlockGasCost": 0, - "maxBlockGasCost": 1000000, - "blockGasCostStep": 200000 - }, - "homesteadBlock": 0, - "eip150Block": 0, - "eip155Block": 0, - "eip158Block": 0, - "byzantiumBlock": 0, - "constantinopleBlock": 0, - "petersburgBlock": 0, - "istanbulBlock": 0, - "muirGlacierBlock": 0, - "subnetEVMTimestamp": 0, - "durangoTimestamp": 0, - "upgrades": { - "precompileUpgrades": [ - { - "txAllowListConfig": { - "blockTimestamp": 100 - } - } - ] - } - }` - require.JSONEq(t, expectedJSON, string(result)) - - var unmarshalled ChainConfigWithUpgradesJSON - err = json.Unmarshal(result, &unmarshalled) - require.NoError(t, err) - require.Equal(t, config, unmarshalled) } diff --git a/params/network_upgrades.go b/params/network_upgrades.go index 9b475c19d2..52fecdf7bc 100644 --- a/params/network_upgrades.go +++ b/params/network_upgrades.go @@ -11,20 +11,35 @@ import ( "github.com/ava-labs/subnet-evm/utils" ) -var ( - errCannotBeNil = fmt.Errorf("timestamp cannot be nil") -) - -// NetworkUpgrades contains timestamps that enable network upgrades. -// Avalanche specific network upgrades are also included here. type NetworkUpgrades struct { - // SubnetEVMTimestamp is a placeholder that activates Avalanche Upgrades prior to ApricotPhase6 - SubnetEVMTimestamp *uint64 `json:"subnetEVMTimestamp,omitempty"` + ApricotPhase1BlockTimestamp *uint64 `json:"apricotPhase1BlockTimestamp,omitempty"` // Apricot Phase 1 Block Timestamp (nil = no fork, 0 = already activated) + // Apricot Phase 2 Block Timestamp (nil = no fork, 0 = already activated) + // Apricot Phase 2 includes a modified version of the Berlin Hard Fork from Ethereum + ApricotPhase2BlockTimestamp *uint64 `json:"apricotPhase2BlockTimestamp,omitempty"` + // Apricot Phase 3 introduces dynamic fees and a modified version of the London Hard Fork from Ethereum (nil = no fork, 0 = already activated) + ApricotPhase3BlockTimestamp *uint64 `json:"apricotPhase3BlockTimestamp,omitempty"` + // Apricot Phase 4 introduces the notion of a block fee to the dynamic fee algorithm (nil = no fork, 0 = already activated) + ApricotPhase4BlockTimestamp *uint64 `json:"apricotPhase4BlockTimestamp,omitempty"` + // Apricot Phase 5 introduces a batch of atomic transactions with a maximum atomic gas limit per block. (nil = no fork, 0 = already activated) + ApricotPhase5BlockTimestamp *uint64 `json:"apricotPhase5BlockTimestamp,omitempty"` + // Apricot Phase Pre-6 deprecates the NativeAssetCall precompile (soft). (nil = no fork, 0 = already activated) + ApricotPhasePre6BlockTimestamp *uint64 `json:"apricotPhasePre6BlockTimestamp,omitempty"` + // Apricot Phase 6 deprecates the NativeAssetBalance and NativeAssetCall precompiles. (nil = no fork, 0 = already activated) + ApricotPhase6BlockTimestamp *uint64 `json:"apricotPhase6BlockTimestamp,omitempty"` + // Apricot Phase Post-6 deprecates the NativeAssetCall precompile (soft). (nil = no fork, 0 = already activated) + ApricotPhasePost6BlockTimestamp *uint64 `json:"apricotPhasePost6BlockTimestamp,omitempty"` + // Banff restricts import/export transactions to AVAX. (nil = no fork, 0 = already activated) + BanffBlockTimestamp *uint64 `json:"banffBlockTimestamp,omitempty"` + // Cortina increases the block gas limit to 15M. (nil = no fork, 0 = already activated) + CortinaBlockTimestamp *uint64 `json:"cortinaBlockTimestamp,omitempty"` // Durango activates the Shanghai Execution Spec Upgrade from Ethereum (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/shanghai.md#included-eips) - // and Avalanche Warp Messaging. + // and Avalanche Warp Messaging. (nil = no fork, 0 = already activated) // Note: EIP-4895 is excluded since withdrawals are not relevant to the Avalanche C-Chain or Subnets running the EVM. - DurangoTimestamp *uint64 `json:"durangoTimestamp,omitempty"` - // Placeholder for EtnaTimestamp + DurangoBlockTimestamp *uint64 `json:"durangoBlockTimestamp,omitempty"` + // Etna activates Cancun from Ethereum (https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/cancun.md#included-eips) + // and reduces the min base fee. (nil = no fork, 0 = already activated) + // Note: EIP-4844 BlobTxs are not enabled in the mempool and blocks are not + // allowed to contain them. For details see https://github.com/avalanche-foundation/ACPs/pull/131 EtnaTimestamp *uint64 `json:"etnaTimestamp,omitempty"` } @@ -33,83 +48,127 @@ func (n *NetworkUpgrades) Equal(other *NetworkUpgrades) bool { } func (n *NetworkUpgrades) CheckNetworkUpgradesCompatible(newcfg *NetworkUpgrades, time uint64) *ConfigCompatError { - if isForkTimestampIncompatible(n.SubnetEVMTimestamp, newcfg.SubnetEVMTimestamp, time) { - return newTimestampCompatError("SubnetEVM fork block timestamp", n.SubnetEVMTimestamp, newcfg.SubnetEVMTimestamp) + if isForkTimestampIncompatible(n.ApricotPhase1BlockTimestamp, newcfg.ApricotPhase1BlockTimestamp, time) { + return newTimestampCompatError("ApricotPhase1 fork block timestamp", n.ApricotPhase1BlockTimestamp, newcfg.ApricotPhase1BlockTimestamp) + } + if isForkTimestampIncompatible(n.ApricotPhase2BlockTimestamp, newcfg.ApricotPhase2BlockTimestamp, time) { + return newTimestampCompatError("ApricotPhase2 fork block timestamp", n.ApricotPhase2BlockTimestamp, newcfg.ApricotPhase2BlockTimestamp) + } + if isForkTimestampIncompatible(n.ApricotPhase3BlockTimestamp, newcfg.ApricotPhase3BlockTimestamp, time) { + return newTimestampCompatError("ApricotPhase3 fork block timestamp", n.ApricotPhase3BlockTimestamp, newcfg.ApricotPhase3BlockTimestamp) + } + if isForkTimestampIncompatible(n.ApricotPhase4BlockTimestamp, newcfg.ApricotPhase4BlockTimestamp, time) { + return newTimestampCompatError("ApricotPhase4 fork block timestamp", n.ApricotPhase4BlockTimestamp, newcfg.ApricotPhase4BlockTimestamp) + } + if isForkTimestampIncompatible(n.ApricotPhase5BlockTimestamp, newcfg.ApricotPhase5BlockTimestamp, time) { + return newTimestampCompatError("ApricotPhase5 fork block timestamp", n.ApricotPhase5BlockTimestamp, newcfg.ApricotPhase5BlockTimestamp) } - if isForkTimestampIncompatible(n.DurangoTimestamp, newcfg.DurangoTimestamp, time) { - return newTimestampCompatError("Durango fork block timestamp", n.DurangoTimestamp, newcfg.DurangoTimestamp) + if isForkTimestampIncompatible(n.ApricotPhasePre6BlockTimestamp, newcfg.ApricotPhasePre6BlockTimestamp, time) { + return newTimestampCompatError("ApricotPhasePre6 fork block timestamp", n.ApricotPhasePre6BlockTimestamp, newcfg.ApricotPhasePre6BlockTimestamp) + } + if isForkTimestampIncompatible(n.ApricotPhase6BlockTimestamp, newcfg.ApricotPhase6BlockTimestamp, time) { + return newTimestampCompatError("ApricotPhase6 fork block timestamp", n.ApricotPhase6BlockTimestamp, newcfg.ApricotPhase6BlockTimestamp) + } + if isForkTimestampIncompatible(n.ApricotPhasePost6BlockTimestamp, newcfg.ApricotPhasePost6BlockTimestamp, time) { + return newTimestampCompatError("ApricotPhasePost6 fork block timestamp", n.ApricotPhasePost6BlockTimestamp, newcfg.ApricotPhasePost6BlockTimestamp) + } + if isForkTimestampIncompatible(n.BanffBlockTimestamp, newcfg.BanffBlockTimestamp, time) { + return newTimestampCompatError("Banff fork block timestamp", n.BanffBlockTimestamp, newcfg.BanffBlockTimestamp) + } + if isForkTimestampIncompatible(n.CortinaBlockTimestamp, newcfg.CortinaBlockTimestamp, time) { + return newTimestampCompatError("Cortina fork block timestamp", n.CortinaBlockTimestamp, newcfg.CortinaBlockTimestamp) + } + if isForkTimestampIncompatible(n.DurangoBlockTimestamp, newcfg.DurangoBlockTimestamp, time) { + return newTimestampCompatError("Durango fork block timestamp", n.DurangoBlockTimestamp, newcfg.DurangoBlockTimestamp) } if isForkTimestampIncompatible(n.EtnaTimestamp, newcfg.EtnaTimestamp, time) { return newTimestampCompatError("Etna fork block timestamp", n.EtnaTimestamp, newcfg.EtnaTimestamp) } + return nil } func (n *NetworkUpgrades) forkOrder() []fork { return []fork{ - {name: "subnetEVMTimestamp", timestamp: n.SubnetEVMTimestamp}, - {name: "durangoTimestamp", timestamp: n.DurangoTimestamp}, + {name: "apricotPhase1BlockTimestamp", timestamp: n.ApricotPhase1BlockTimestamp}, + {name: "apricotPhase2BlockTimestamp", timestamp: n.ApricotPhase2BlockTimestamp}, + {name: "apricotPhase3BlockTimestamp", timestamp: n.ApricotPhase3BlockTimestamp}, + {name: "apricotPhase4BlockTimestamp", timestamp: n.ApricotPhase4BlockTimestamp}, + {name: "apricotPhase5BlockTimestamp", timestamp: n.ApricotPhase5BlockTimestamp}, + {name: "apricotPhasePre6BlockTimestamp", timestamp: n.ApricotPhasePre6BlockTimestamp}, + {name: "apricotPhase6BlockTimestamp", timestamp: n.ApricotPhase6BlockTimestamp}, + {name: "apricotPhasePost6BlockTimestamp", timestamp: n.ApricotPhasePost6BlockTimestamp}, + {name: "banffBlockTimestamp", timestamp: n.BanffBlockTimestamp}, + {name: "cortinaBlockTimestamp", timestamp: n.CortinaBlockTimestamp}, + {name: "durangoBlockTimestamp", timestamp: n.DurangoBlockTimestamp}, {name: "etnaTimestamp", timestamp: n.EtnaTimestamp}, } } -// setDefaults sets the default values for the network upgrades. -// This overrides deactivating the network upgrade by providing a timestamp of nil value. -func (n *NetworkUpgrades) setDefaults(agoUpgrades upgrade.Config) { - defaults := getDefaultNetworkUpgrades(agoUpgrades) - // If the network upgrade is not set, set it to the default value. - // If the network upgrade is set to 0, we also treat it as nil and set it default. - // This is because in prior versions, upgrades were not modifiable and were directly set to their default values. - // Most of the tools and configurations just provide these as 0, so it is safer to treat 0 as nil and set to default - // to prevent premature activations of the network upgrades for live networks. - if n.SubnetEVMTimestamp == nil || *n.SubnetEVMTimestamp == 0 { - n.SubnetEVMTimestamp = defaults.SubnetEVMTimestamp - } - if n.DurangoTimestamp == nil || *n.DurangoTimestamp == 0 { - n.DurangoTimestamp = defaults.DurangoTimestamp - } - if n.EtnaTimestamp == nil || *n.EtnaTimestamp == 0 { - n.EtnaTimestamp = defaults.EtnaTimestamp - } +// IsApricotPhase1 returns whether [time] represents a block +// with a timestamp after the Apricot Phase 1 upgrade time. +func (n *NetworkUpgrades) IsApricotPhase1(time uint64) bool { + return isTimestampForked(n.ApricotPhase1BlockTimestamp, time) } -// verifyNetworkUpgrades checks that the network upgrades are well formed. -func (n *NetworkUpgrades) verifyNetworkUpgrades(agoUpgrades upgrade.Config) error { - defaults := getDefaultNetworkUpgrades(agoUpgrades) - if err := verifyWithDefault(n.SubnetEVMTimestamp, defaults.SubnetEVMTimestamp); err != nil { - return fmt.Errorf("SubnetEVM fork block timestamp is invalid: %w", err) - } - if err := verifyWithDefault(n.DurangoTimestamp, defaults.DurangoTimestamp); err != nil { - return fmt.Errorf("Durango fork block timestamp is invalid: %w", err) - } - if err := verifyWithDefault(n.EtnaTimestamp, defaults.EtnaTimestamp); err != nil { - return fmt.Errorf("Etna fork block timestamp is invalid: %w", err) - } - return nil +// IsApricotPhase2 returns whether [time] represents a block +// with a timestamp after the Apricot Phase 2 upgrade time. +func (n *NetworkUpgrades) IsApricotPhase2(time uint64) bool { + return isTimestampForked(n.ApricotPhase2BlockTimestamp, time) } -func (n *NetworkUpgrades) Override(o *NetworkUpgrades) { - if o.SubnetEVMTimestamp != nil { - n.SubnetEVMTimestamp = o.SubnetEVMTimestamp - } - if o.DurangoTimestamp != nil { - n.DurangoTimestamp = o.DurangoTimestamp - } - if o.EtnaTimestamp != nil { - n.EtnaTimestamp = o.EtnaTimestamp - } +// IsApricotPhase3 returns whether [time] represents a block +// with a timestamp after the Apricot Phase 3 upgrade time. +func (n *NetworkUpgrades) IsApricotPhase3(time uint64) bool { + return isTimestampForked(n.ApricotPhase3BlockTimestamp, time) +} + +// IsApricotPhase4 returns whether [time] represents a block +// with a timestamp after the Apricot Phase 4 upgrade time. +func (n *NetworkUpgrades) IsApricotPhase4(time uint64) bool { + return isTimestampForked(n.ApricotPhase4BlockTimestamp, time) +} + +// IsApricotPhase5 returns whether [time] represents a block +// with a timestamp after the Apricot Phase 5 upgrade time. +func (n *NetworkUpgrades) IsApricotPhase5(time uint64) bool { + return isTimestampForked(n.ApricotPhase5BlockTimestamp, time) +} + +// IsApricotPhasePre6 returns whether [time] represents a block +// with a timestamp after the Apricot Phase Pre 6 upgrade time. +func (n *NetworkUpgrades) IsApricotPhasePre6(time uint64) bool { + return isTimestampForked(n.ApricotPhasePre6BlockTimestamp, time) +} + +// IsApricotPhase6 returns whether [time] represents a block +// with a timestamp after the Apricot Phase 6 upgrade time. +func (n *NetworkUpgrades) IsApricotPhase6(time uint64) bool { + return isTimestampForked(n.ApricotPhase6BlockTimestamp, time) +} + +// IsApricotPhasePost6 returns whether [time] represents a block +// with a timestamp after the Apricot Phase 6 Post upgrade time. +func (n *NetworkUpgrades) IsApricotPhasePost6(time uint64) bool { + return isTimestampForked(n.ApricotPhasePost6BlockTimestamp, time) } -// IsSubnetEVM returns whether [time] represents a block -// with a timestamp after the SubnetEVM upgrade time. -func (n *NetworkUpgrades) IsSubnetEVM(time uint64) bool { - return isTimestampForked(n.SubnetEVMTimestamp, time) +// IsBanff returns whether [time] represents a block +// with a timestamp after the Banff upgrade time. +func (n *NetworkUpgrades) IsBanff(time uint64) bool { + return isTimestampForked(n.BanffBlockTimestamp, time) +} + +// IsCortina returns whether [time] represents a block +// with a timestamp after the Cortina upgrade time. +func (n *NetworkUpgrades) IsCortina(time uint64) bool { + return isTimestampForked(n.CortinaBlockTimestamp, time) } // IsDurango returns whether [time] represents a block // with a timestamp after the Durango upgrade time. func (n *NetworkUpgrades) IsDurango(time uint64) bool { - return isTimestampForked(n.DurangoTimestamp, time) + return isTimestampForked(n.DurangoBlockTimestamp, time) } // IsEtna returns whether [time] represents a block @@ -120,44 +179,60 @@ func (n *NetworkUpgrades) IsEtna(time uint64) bool { func (n *NetworkUpgrades) Description() string { var banner string - banner += fmt.Sprintf(" - SubnetEVM Timestamp: @%-10v (https://github.com/ava-labs/avalanchego/releases/tag/v1.10.0)\n", ptrToString(n.SubnetEVMTimestamp)) - banner += fmt.Sprintf(" - Durango Timestamp: @%-10v (https://github.com/ava-labs/avalanchego/releases/tag/v1.11.0)\n", ptrToString(n.DurangoTimestamp)) - banner += fmt.Sprintf(" - Etna Timestamp: @%-10v (https://github.com/ava-labs/avalanchego/releases/tag/v1.12.0)\n", ptrToString(n.EtnaTimestamp)) + banner += fmt.Sprintf(" - Apricot Phase 1 Timestamp: @%-10v (https://github.com/ava-labs/avalanchego/releases/tag/v1.3.0)\n", ptrToString(n.ApricotPhase1BlockTimestamp)) + banner += fmt.Sprintf(" - Apricot Phase 2 Timestamp: @%-10v (https://github.com/ava-labs/avalanchego/releases/tag/v1.4.0)\n", ptrToString(n.ApricotPhase2BlockTimestamp)) + banner += fmt.Sprintf(" - Apricot Phase 3 Timestamp: @%-10v (https://github.com/ava-labs/avalanchego/releases/tag/v1.5.0)\n", ptrToString(n.ApricotPhase3BlockTimestamp)) + banner += fmt.Sprintf(" - Apricot Phase 4 Timestamp: @%-10v (https://github.com/ava-labs/avalanchego/releases/tag/v1.6.0)\n", ptrToString(n.ApricotPhase4BlockTimestamp)) + banner += fmt.Sprintf(" - Apricot Phase 5 Timestamp: @%-10v (https://github.com/ava-labs/avalanchego/releases/tag/v1.7.0)\n", ptrToString(n.ApricotPhase5BlockTimestamp)) + banner += fmt.Sprintf(" - Apricot Phase P6 Timestamp @%-10v (https://github.com/ava-labs/avalanchego/releases/tag/v1.8.0)\n", ptrToString(n.ApricotPhasePre6BlockTimestamp)) + banner += fmt.Sprintf(" - Apricot Phase 6 Timestamp: @%-10v (https://github.com/ava-labs/avalanchego/releases/tag/v1.8.0)\n", ptrToString(n.ApricotPhase6BlockTimestamp)) + banner += fmt.Sprintf(" - Apricot Phase Post-6 Timestamp: @%-10v (https://github.com/ava-labs/avalanchego/releases/tag/v1.8.0\n", ptrToString(n.ApricotPhasePost6BlockTimestamp)) + banner += fmt.Sprintf(" - Banff Timestamp: @%-10v (https://github.com/ava-labs/avalanchego/releases/tag/v1.9.0)\n", ptrToString(n.BanffBlockTimestamp)) + banner += fmt.Sprintf(" - Cortina Timestamp: @%-10v (https://github.com/ava-labs/avalanchego/releases/tag/v1.10.0)\n", ptrToString(n.CortinaBlockTimestamp)) + banner += fmt.Sprintf(" - Durango Timestamp: @%-10v (https://github.com/ava-labs/avalanchego/releases/tag/v1.11.0)\n", ptrToString(n.DurangoBlockTimestamp)) + banner += fmt.Sprintf(" - Etna Timestamp: @%-10v (https://github.com/ava-labs/avalanchego/releases/tag/v1.12.0)\n", ptrToString(n.EtnaTimestamp)) return banner } -type AvalancheRules struct { - IsSubnetEVM bool - IsDurango bool - IsEtna bool -} - -func (n *NetworkUpgrades) GetAvalancheRules(time uint64) AvalancheRules { - return AvalancheRules{ - IsSubnetEVM: n.IsSubnetEVM(time), - IsDurango: n.IsDurango(time), - IsEtna: n.IsEtna(time), - } -} - -// getDefaultNetworkUpgrades returns the network upgrades for the specified avalanchego upgrades. -// These should not return nil values. -func getDefaultNetworkUpgrades(agoUpgrade upgrade.Config) NetworkUpgrades { +func getNetworkUpgrades(agoUpgrade upgrade.Config) NetworkUpgrades { return NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(0), - DurangoTimestamp: utils.TimeToNewUint64(agoUpgrade.DurangoTime), - EtnaTimestamp: utils.TimeToNewUint64(agoUpgrade.EtnaTime), + ApricotPhase1BlockTimestamp: utils.TimeToNewUint64(agoUpgrade.ApricotPhase1Time), + ApricotPhase2BlockTimestamp: utils.TimeToNewUint64(agoUpgrade.ApricotPhase2Time), + ApricotPhase3BlockTimestamp: utils.TimeToNewUint64(agoUpgrade.ApricotPhase3Time), + ApricotPhase4BlockTimestamp: utils.TimeToNewUint64(agoUpgrade.ApricotPhase4Time), + ApricotPhase5BlockTimestamp: utils.TimeToNewUint64(agoUpgrade.ApricotPhase5Time), + ApricotPhasePre6BlockTimestamp: utils.TimeToNewUint64(agoUpgrade.ApricotPhasePre6Time), + ApricotPhase6BlockTimestamp: utils.TimeToNewUint64(agoUpgrade.ApricotPhase6Time), + ApricotPhasePost6BlockTimestamp: utils.TimeToNewUint64(agoUpgrade.ApricotPhasePost6Time), + BanffBlockTimestamp: utils.TimeToNewUint64(agoUpgrade.BanffTime), + CortinaBlockTimestamp: utils.TimeToNewUint64(agoUpgrade.CortinaTime), + DurangoBlockTimestamp: utils.TimeToNewUint64(agoUpgrade.DurangoTime), + EtnaTimestamp: utils.TimeToNewUint64(agoUpgrade.EtnaTime), } } -// verifyWithDefault checks that the provided timestamp is greater than or equal to the default timestamp. -func verifyWithDefault(configTimestamp *uint64, defaultTimestamp *uint64) error { - if configTimestamp == nil { - return errCannotBeNil - } +type AvalancheRules struct { + IsApricotPhase1, IsApricotPhase2, IsApricotPhase3, IsApricotPhase4, IsApricotPhase5 bool + IsApricotPhasePre6, IsApricotPhase6, IsApricotPhasePost6 bool + IsBanff bool + IsCortina bool + IsDurango bool + IsEtna bool +} - if *configTimestamp < *defaultTimestamp { - return fmt.Errorf("provided timestamp (%d) must be greater than or equal to the default timestamp (%d)", *configTimestamp, *defaultTimestamp) +func (n *NetworkUpgrades) GetAvalancheRules(timestamp uint64) AvalancheRules { + return AvalancheRules{ + IsApricotPhase1: n.IsApricotPhase1(timestamp), + IsApricotPhase2: n.IsApricotPhase2(timestamp), + IsApricotPhase3: n.IsApricotPhase3(timestamp), + IsApricotPhase4: n.IsApricotPhase4(timestamp), + IsApricotPhase5: n.IsApricotPhase5(timestamp), + IsApricotPhasePre6: n.IsApricotPhasePre6(timestamp), + IsApricotPhase6: n.IsApricotPhase6(timestamp), + IsApricotPhasePost6: n.IsApricotPhasePost6(timestamp), + IsBanff: n.IsBanff(timestamp), + IsCortina: n.IsCortina(timestamp), + IsDurango: n.IsDurango(timestamp), + IsEtna: n.IsEtna(timestamp), } - return nil } diff --git a/params/network_upgrades_test.go b/params/network_upgrades_test.go deleted file mode 100644 index 1a93a2587e..0000000000 --- a/params/network_upgrades_test.go +++ /dev/null @@ -1,302 +0,0 @@ -// (c) 2024, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package params - -import ( - "testing" - - "github.com/ava-labs/avalanchego/upgrade" - "github.com/ava-labs/avalanchego/utils/constants" - "github.com/ava-labs/subnet-evm/utils" - "github.com/stretchr/testify/require" -) - -func TestNetworkUpgradesEqual(t *testing.T) { - testcases := []struct { - name string - upgrades1 *NetworkUpgrades - upgrades2 *NetworkUpgrades - expected bool - }{ - { - name: "EqualNetworkUpgrades", - upgrades1: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(1), - DurangoTimestamp: utils.NewUint64(2), - }, - upgrades2: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(1), - DurangoTimestamp: utils.NewUint64(2), - }, - expected: true, - }, - { - name: "NotEqualNetworkUpgrades", - upgrades1: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(1), - DurangoTimestamp: utils.NewUint64(2), - }, - upgrades2: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(1), - DurangoTimestamp: utils.NewUint64(3), - }, - expected: false, - }, - { - name: "NilNetworkUpgrades", - upgrades1: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(1), - DurangoTimestamp: utils.NewUint64(2), - }, - upgrades2: nil, - expected: false, - }, - { - name: "NilNetworkUpgrade", - upgrades1: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(1), - DurangoTimestamp: utils.NewUint64(2), - }, - upgrades2: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(1), - DurangoTimestamp: nil, - }, - expected: false, - }, - } - for _, test := range testcases { - t.Run(test.name, func(t *testing.T) { - require.Equal(t, test.expected, test.upgrades1.Equal(test.upgrades2)) - }) - } -} - -func TestCheckNetworkUpgradesCompatible(t *testing.T) { - testcases := []struct { - name string - upgrades1 *NetworkUpgrades - upgrades2 *NetworkUpgrades - time uint64 - expected bool - }{ - { - name: "Compatible same NetworkUpgrades", - upgrades1: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(1), - DurangoTimestamp: utils.NewUint64(2), - }, - upgrades2: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(1), - DurangoTimestamp: utils.NewUint64(2), - }, - time: 1, - expected: true, - }, - { - name: "Compatible different NetworkUpgrades", - upgrades1: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(1), - DurangoTimestamp: utils.NewUint64(2), - }, - upgrades2: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(1), - DurangoTimestamp: utils.NewUint64(3), - }, - time: 1, - expected: true, - }, - { - name: "Compatible nil NetworkUpgrades", - upgrades1: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(1), - DurangoTimestamp: utils.NewUint64(2), - }, - upgrades2: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(1), - DurangoTimestamp: nil, - }, - time: 1, - expected: true, - }, - { - name: "Incompatible rewinded NetworkUpgrades", - upgrades1: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(1), - DurangoTimestamp: utils.NewUint64(2), - }, - upgrades2: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(1), - DurangoTimestamp: utils.NewUint64(1), - }, - time: 1, - expected: false, - }, - { - name: "Incompatible fastforward NetworkUpgrades", - upgrades1: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(1), - DurangoTimestamp: utils.NewUint64(2), - }, - upgrades2: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(1), - DurangoTimestamp: utils.NewUint64(3), - }, - time: 4, - expected: false, - }, - { - name: "Incompatible nil NetworkUpgrades", - upgrades1: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(1), - DurangoTimestamp: utils.NewUint64(2), - }, - upgrades2: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(1), - DurangoTimestamp: nil, - }, - time: 2, - expected: false, - }, - } - for _, test := range testcases { - t.Run(test.name, func(t *testing.T) { - err := test.upgrades1.CheckNetworkUpgradesCompatible(test.upgrades2, test.time) - if test.expected { - require.Nil(t, err) - } else { - require.NotNil(t, err) - } - }) - } -} - -func TestVerifyNetworkUpgrades(t *testing.T) { - testcases := []struct { - name string - upgrades *NetworkUpgrades - avagoUpgrades upgrade.Config - expected bool - }{ - { - name: "ValidNetworkUpgrades for custom network", - upgrades: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(0), - DurangoTimestamp: utils.NewUint64(1607144400), - EtnaTimestamp: utils.NewUint64(1607144400), - }, - avagoUpgrades: upgrade.GetConfig(1111), - expected: true, - }, - { - name: "Invalid Durango nil upgrade", - upgrades: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(1), - DurangoTimestamp: nil, - }, - avagoUpgrades: upgrade.GetConfig(constants.MainnetID), - expected: false, - }, - { - name: "Invalid Subnet-EVM non-zero", - upgrades: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(1), - DurangoTimestamp: utils.NewUint64(2), - }, - avagoUpgrades: upgrade.GetConfig(constants.MainnetID), - expected: false, - }, - { - name: "Invalid Durango before default upgrade", - upgrades: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(0), - DurangoTimestamp: utils.NewUint64(1), - }, - avagoUpgrades: upgrade.GetConfig(constants.MainnetID), - expected: false, - }, - { - name: "Invalid Mainnet Durango reconfigured to Fuji", - upgrades: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(0), - DurangoTimestamp: utils.TimeToNewUint64(upgrade.GetConfig(constants.FujiID).DurangoTime), - }, - avagoUpgrades: upgrade.GetConfig(constants.MainnetID), - expected: false, - }, - { - name: "Valid Fuji Durango reconfigured to Mainnet", - upgrades: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(0), - DurangoTimestamp: utils.TimeToNewUint64(upgrade.GetConfig(constants.MainnetID).DurangoTime), - }, - avagoUpgrades: upgrade.GetConfig(constants.FujiID), - expected: false, - }, - { - name: "Invalid Etna nil", - upgrades: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(0), - DurangoTimestamp: utils.NewUint64(2), - EtnaTimestamp: nil, - }, - avagoUpgrades: upgrade.GetConfig(constants.MainnetID), - expected: false, - }, - { - name: "Invalid Etna before Durango", - upgrades: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(0), - DurangoTimestamp: utils.NewUint64(2), - EtnaTimestamp: utils.NewUint64(1), - }, - avagoUpgrades: upgrade.GetConfig(constants.MainnetID), - expected: false, - }, - } - for _, test := range testcases { - t.Run(test.name, func(t *testing.T) { - err := test.upgrades.verifyNetworkUpgrades(test.avagoUpgrades) - if test.expected { - require.Nil(t, err) - } else { - require.NotNil(t, err) - } - }) - } -} - -func TestForkOrder(t *testing.T) { - testcases := []struct { - name string - upgrades *NetworkUpgrades - expectedErr bool - }{ - { - name: "ValidNetworkUpgrades", - upgrades: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(0), - DurangoTimestamp: utils.NewUint64(2), - }, - expectedErr: false, - }, - { - name: "Invalid order", - upgrades: &NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(1), - DurangoTimestamp: utils.NewUint64(0), - }, - expectedErr: true, - }, - } - for _, test := range testcases { - t.Run(test.name, func(t *testing.T) { - err := checkForks(test.upgrades.forkOrder(), false) - if test.expectedErr { - require.NotNil(t, err) - } else { - require.Nil(t, err) - } - }) - } -} diff --git a/params/precompile_config_test.go b/params/precompile_config_test.go deleted file mode 100644 index 4e2c287241..0000000000 --- a/params/precompile_config_test.go +++ /dev/null @@ -1,343 +0,0 @@ -// (c) 2022 Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package params - -import ( - "encoding/json" - "math/big" - "testing" - - "github.com/ava-labs/subnet-evm/commontype" - "github.com/ava-labs/subnet-evm/precompile/contracts/deployerallowlist" - "github.com/ava-labs/subnet-evm/precompile/contracts/feemanager" - "github.com/ava-labs/subnet-evm/precompile/contracts/nativeminter" - "github.com/ava-labs/subnet-evm/precompile/contracts/rewardmanager" - "github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist" - "github.com/ava-labs/subnet-evm/utils" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" -) - -func TestVerifyWithChainConfig(t *testing.T) { - admins := []common.Address{{1}} - baseConfig := *TestChainConfig - config := &baseConfig - config.GenesisPrecompiles = Precompiles{ - txallowlist.ConfigKey: txallowlist.NewConfig(utils.NewUint64(2), nil, nil, nil), - } - config.PrecompileUpgrades = []PrecompileUpgrade{ - { - // disable TxAllowList at timestamp 4 - txallowlist.NewDisableConfig(utils.NewUint64(4)), - }, - { - // re-enable TxAllowList at timestamp 5 - txallowlist.NewConfig(utils.NewUint64(5), admins, nil, nil), - }, - } - - // check this config is valid - err := config.Verify() - require.NoError(t, err) - - // same precompile cannot be configured twice for the same timestamp - badConfig := *config - badConfig.PrecompileUpgrades = append( - badConfig.PrecompileUpgrades, - PrecompileUpgrade{ - Config: txallowlist.NewDisableConfig(utils.NewUint64(5)), - }, - ) - err = badConfig.Verify() - require.ErrorContains(t, err, "config block timestamp (5) <= previous timestamp (5) of same key") - - // cannot enable a precompile without disabling it first. - badConfig = *config - badConfig.PrecompileUpgrades = append( - badConfig.PrecompileUpgrades, - PrecompileUpgrade{ - Config: txallowlist.NewConfig(utils.NewUint64(5), admins, nil, nil), - }, - ) - err = badConfig.Verify() - require.ErrorContains(t, err, "disable should be [true]") -} - -func TestVerifyWithChainConfigAtNilTimestamp(t *testing.T) { - admins := []common.Address{{0}} - baseConfig := *TestChainConfig - config := &baseConfig - config.PrecompileUpgrades = []PrecompileUpgrade{ - // this does NOT enable the precompile, so it should be upgradeable. - {Config: txallowlist.NewConfig(nil, nil, nil, nil)}, - } - require.False(t, config.IsPrecompileEnabled(txallowlist.ContractAddress, 0)) // check the precompile is not enabled. - config.PrecompileUpgrades = []PrecompileUpgrade{ - { - // enable TxAllowList at timestamp 5 - Config: txallowlist.NewConfig(utils.NewUint64(5), admins, nil, nil), - }, - } - - // check this config is valid - require.NoError(t, config.Verify()) -} - -func TestVerifyPrecompileUpgrades(t *testing.T) { - admins := []common.Address{{1}} - tests := []struct { - name string - upgrades []PrecompileUpgrade - expectedError string - }{ - { - name: "enable and disable tx allow list", - upgrades: []PrecompileUpgrade{ - { - Config: txallowlist.NewConfig(utils.NewUint64(1), admins, nil, nil), - }, - { - Config: txallowlist.NewDisableConfig(utils.NewUint64(2)), - }, - }, - expectedError: "", - }, - { - name: "invalid allow list config in tx allowlist", - upgrades: []PrecompileUpgrade{ - { - Config: txallowlist.NewConfig(utils.NewUint64(1), admins, nil, nil), - }, - { - Config: txallowlist.NewDisableConfig(utils.NewUint64(2)), - }, - { - Config: txallowlist.NewConfig(utils.NewUint64(3), admins, admins, admins), - }, - }, - expectedError: "cannot set address", - }, - { - name: "invalid initial fee manager config", - upgrades: []PrecompileUpgrade{ - { - Config: feemanager.NewConfig(utils.NewUint64(3), admins, nil, nil, - func() *commontype.FeeConfig { - feeConfig := DefaultFeeConfig - feeConfig.GasLimit = big.NewInt(-1) - return &feeConfig - }()), - }, - }, - expectedError: "gasLimit = -1 cannot be less than or equal to 0", - }, - { - name: "invalid initial fee manager config gas limit 0", - upgrades: []PrecompileUpgrade{ - { - Config: feemanager.NewConfig(utils.NewUint64(3), admins, nil, nil, - func() *commontype.FeeConfig { - feeConfig := DefaultFeeConfig - feeConfig.GasLimit = common.Big0 - return &feeConfig - }()), - }, - }, - expectedError: "gasLimit = 0 cannot be less than or equal to 0", - }, - { - name: "different upgrades are allowed to configure same timestamp for different precompiles", - upgrades: []PrecompileUpgrade{ - { - Config: txallowlist.NewConfig(utils.NewUint64(1), admins, nil, nil), - }, - { - Config: feemanager.NewConfig(utils.NewUint64(1), admins, nil, nil, nil), - }, - }, - expectedError: "", - }, - { - name: "different upgrades must be monotonically increasing", - upgrades: []PrecompileUpgrade{ - { - Config: txallowlist.NewConfig(utils.NewUint64(2), admins, nil, nil), - }, - { - Config: feemanager.NewConfig(utils.NewUint64(1), admins, nil, nil, nil), - }, - }, - expectedError: "config block timestamp (1) < previous timestamp (2)", - }, - { - name: "upgrades with same keys are not allowed to configure same timestamp for same precompiles", - upgrades: []PrecompileUpgrade{ - { - Config: txallowlist.NewConfig(utils.NewUint64(1), admins, nil, nil), - }, - { - Config: txallowlist.NewDisableConfig(utils.NewUint64(1)), - }, - }, - expectedError: "config block timestamp (1) <= previous timestamp (1) of same key", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require := require.New(t) - baseConfig := *TestChainConfig - config := &baseConfig - config.PrecompileUpgrades = tt.upgrades - - err := config.Verify() - if tt.expectedError == "" { - require.NoError(err) - } else { - require.ErrorContains(err, tt.expectedError) - } - }) - } -} - -func TestVerifyPrecompiles(t *testing.T) { - admins := []common.Address{{1}} - tests := []struct { - name string - precompiles Precompiles - expectedError string - }{ - { - name: "invalid allow list config in tx allowlist", - precompiles: Precompiles{ - txallowlist.ConfigKey: txallowlist.NewConfig(utils.NewUint64(3), admins, admins, admins), - }, - expectedError: "cannot set address", - }, - { - name: "invalid initial fee manager config", - precompiles: Precompiles{ - feemanager.ConfigKey: feemanager.NewConfig(utils.NewUint64(3), admins, nil, nil, - func() *commontype.FeeConfig { - feeConfig := DefaultFeeConfig - feeConfig.GasLimit = big.NewInt(-1) - return &feeConfig - }()), - }, - expectedError: "gasLimit = -1 cannot be less than or equal to 0", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require := require.New(t) - baseConfig := *TestChainConfig - config := &baseConfig - config.GenesisPrecompiles = tt.precompiles - - err := config.Verify() - if tt.expectedError == "" { - require.NoError(err) - } else { - require.ErrorContains(err, tt.expectedError) - } - }) - } -} - -func TestVerifyRequiresSortedTimestamps(t *testing.T) { - admins := []common.Address{{1}} - baseConfig := *TestChainConfig - config := &baseConfig - config.PrecompileUpgrades = []PrecompileUpgrade{ - { - Config: txallowlist.NewConfig(utils.NewUint64(2), admins, nil, nil), - }, - { - Config: deployerallowlist.NewConfig(utils.NewUint64(1), admins, nil, nil), - }, - } - - // block timestamps must be monotonically increasing, so this config is invalid - err := config.Verify() - require.ErrorContains(t, err, "config block timestamp (1) < previous timestamp (2)") -} - -func TestGetPrecompileConfig(t *testing.T) { - require := require.New(t) - baseConfig := *TestChainConfig - config := &baseConfig - config.GenesisPrecompiles = Precompiles{ - deployerallowlist.ConfigKey: deployerallowlist.NewConfig(utils.NewUint64(10), nil, nil, nil), - } - - deployerConfig := config.getActivePrecompileConfig(deployerallowlist.ContractAddress, 0) - require.Nil(deployerConfig) - - deployerConfig = config.getActivePrecompileConfig(deployerallowlist.ContractAddress, 10) - require.NotNil(deployerConfig) - - deployerConfig = config.getActivePrecompileConfig(deployerallowlist.ContractAddress, 11) - require.NotNil(deployerConfig) - - txAllowListConfig := config.getActivePrecompileConfig(txallowlist.ContractAddress, 0) - require.Nil(txAllowListConfig) -} - -func TestPrecompileUpgradeUnmarshalJSON(t *testing.T) { - require := require.New(t) - - upgradeBytes := []byte(` - { - "precompileUpgrades": [ - { - "rewardManagerConfig": { - "blockTimestamp": 1671542573, - "adminAddresses": [ - "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" - ], - "initialRewardConfig": { - "allowFeeRecipients": true - } - } - }, - { - "contractNativeMinterConfig": { - "blockTimestamp": 1671543172, - "disable": false - } - } - ] - } - `) - - var upgradeConfig UpgradeConfig - err := json.Unmarshal(upgradeBytes, &upgradeConfig) - require.NoError(err) - - require.Len(upgradeConfig.PrecompileUpgrades, 2) - - rewardManagerConf := upgradeConfig.PrecompileUpgrades[0] - require.Equal(rewardManagerConf.Key(), rewardmanager.ConfigKey) - testRewardManagerConfig := rewardmanager.NewConfig( - utils.NewUint64(1671542573), - []common.Address{common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC")}, - nil, - nil, - &rewardmanager.InitialRewardConfig{ - AllowFeeRecipients: true, - }) - require.True(rewardManagerConf.Equal(testRewardManagerConfig)) - - nativeMinterConfig := upgradeConfig.PrecompileUpgrades[1] - require.Equal(nativeMinterConfig.Key(), nativeminter.ConfigKey) - expectedNativeMinterConfig := nativeminter.NewConfig(utils.NewUint64(1671543172), nil, nil, nil, nil) - require.True(nativeMinterConfig.Equal(expectedNativeMinterConfig)) - - // Marshal and unmarshal again and check that the result is the same - upgradeBytes2, err := json.Marshal(upgradeConfig) - require.NoError(err) - var upgradeConfig2 UpgradeConfig - err = json.Unmarshal(upgradeBytes2, &upgradeConfig2) - require.NoError(err) - require.Equal(upgradeConfig, upgradeConfig2) -} diff --git a/params/precompile_upgrade.go b/params/precompile_upgrade.go index f4985dc9ec..3488dbfd0f 100644 --- a/params/precompile_upgrade.go +++ b/params/precompile_upgrade.go @@ -77,22 +77,6 @@ func (c *ChainConfig) verifyPrecompileUpgrades() error { lastPrecompileUpgrades := make(map[string]lastUpgradeData) - // verify genesis precompiles - for key, config := range c.GenesisPrecompiles { - if err := config.Verify(c); err != nil { - return err - } - // if the precompile is disabled at genesis, skip it. - if config.Timestamp() == nil { - continue - } - // check the genesis chain config for any enabled upgrade - lastPrecompileUpgrades[key] = lastUpgradeData{ - disabled: false, - blockTimestamp: *config.Timestamp(), - } - } - // next range over upgrades to verify correct use of disabled and blockTimestamps. // previousUpgradeTimestamp is used to verify monotonically increasing timestamps. var previousUpgradeTimestamp *uint64 @@ -167,13 +151,6 @@ func (c *ChainConfig) GetActivatingPrecompileConfigs(address common.Address, fro } configs := make([]precompileconfig.Config, 0) key := module.ConfigKey - // First check the embedded [upgrade] for precompiles configured - // in the genesis chain config. - if config, ok := c.GenesisPrecompiles[key]; ok { - if IsForkTransition(config.Timestamp(), from, to) { - configs = append(configs, config) - } - } // Loop over all upgrades checking for the requested precompile config. for _, upgrade := range upgrades { if upgrade.Key() == key { diff --git a/params/precompile_upgrade_test.go b/params/precompile_upgrade_test.go deleted file mode 100644 index 8384ef4279..0000000000 --- a/params/precompile_upgrade_test.go +++ /dev/null @@ -1,300 +0,0 @@ -// (c) 2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package params - -import ( - "testing" - - "github.com/ava-labs/subnet-evm/precompile/contracts/deployerallowlist" - "github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist" - "github.com/ava-labs/subnet-evm/utils" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" -) - -func TestVerifyUpgradeConfig(t *testing.T) { - admins := []common.Address{{1}} - chainConfig := *TestChainConfig - chainConfig.GenesisPrecompiles = Precompiles{ - txallowlist.ConfigKey: txallowlist.NewConfig(utils.NewUint64(1), admins, nil, nil), - } - - type test struct { - upgrades []PrecompileUpgrade - expectedErrorString string - } - - tests := map[string]test{ - "upgrade bytes conflicts with genesis (re-enable without disable)": { - expectedErrorString: "disable should be [true]", - upgrades: []PrecompileUpgrade{ - { - Config: txallowlist.NewConfig(utils.NewUint64(2), admins, nil, nil), - }, - }, - }, - "upgrade bytes conflicts with genesis (disable before enable)": { - expectedErrorString: "config block timestamp (0) <= previous timestamp (1) of same key", - upgrades: []PrecompileUpgrade{ - { - Config: txallowlist.NewDisableConfig(utils.NewUint64(0)), - }, - }, - }, - "upgrade bytes conflicts with genesis (disable same time as enable)": { - expectedErrorString: "config block timestamp (1) <= previous timestamp (1) of same key", - upgrades: []PrecompileUpgrade{ - { - Config: txallowlist.NewDisableConfig(utils.NewUint64(1)), - }, - }, - }, - } - - for name, tt := range tests { - t.Run(name, func(t *testing.T) { - // make a local copy of the chainConfig - chainConfig := chainConfig - - // verify with the upgrades from the test - chainConfig.UpgradeConfig.PrecompileUpgrades = tt.upgrades - err := chainConfig.Verify() - - if tt.expectedErrorString != "" { - require.ErrorContains(t, err, tt.expectedErrorString) - } else { - require.NoError(t, err) - } - }) - } -} - -func TestCheckCompatibleUpgradeConfigs(t *testing.T) { - admins := []common.Address{{1}} - chainConfig := *TestChainConfig - chainConfig.GenesisPrecompiles = Precompiles{ - txallowlist.ConfigKey: txallowlist.NewConfig(utils.NewUint64(1), admins, nil, nil), - deployerallowlist.ConfigKey: deployerallowlist.NewConfig(utils.NewUint64(10), admins, nil, nil), - } - - tests := map[string]upgradeCompatibilityTest{ - "disable and re-enable": { - startTimestamps: []uint64{5}, - configs: []*UpgradeConfig{ - { - PrecompileUpgrades: []PrecompileUpgrade{ - { - Config: txallowlist.NewDisableConfig(utils.NewUint64(6)), - }, - { - Config: txallowlist.NewConfig(utils.NewUint64(7), admins, nil, nil), - }, - }, - }, - }, - }, - "disable and re-enable, reschedule upgrade before it happens": { - startTimestamps: []uint64{5, 6}, - configs: []*UpgradeConfig{ - { - PrecompileUpgrades: []PrecompileUpgrade{ - { - Config: txallowlist.NewDisableConfig(utils.NewUint64(6)), - }, - { - Config: txallowlist.NewConfig(utils.NewUint64(7), admins, nil, nil), - }, - }, - }, - { - PrecompileUpgrades: []PrecompileUpgrade{ - { - Config: txallowlist.NewDisableConfig(utils.NewUint64(6)), - }, - { - Config: txallowlist.NewConfig(utils.NewUint64(8), admins, nil, nil), - }, - }, - }, - }, - }, - "disable and re-enable, reschedule upgrade after it happens": { - expectedErrorString: "mismatching PrecompileUpgrade", - startTimestamps: []uint64{5, 8}, - configs: []*UpgradeConfig{ - { - PrecompileUpgrades: []PrecompileUpgrade{ - { - Config: txallowlist.NewDisableConfig(utils.NewUint64(6)), - }, - { - Config: txallowlist.NewConfig(utils.NewUint64(7), admins, nil, nil), - }, - }, - }, - { - PrecompileUpgrades: []PrecompileUpgrade{ - { - Config: txallowlist.NewDisableConfig(utils.NewUint64(6)), - }, - { - Config: txallowlist.NewConfig(utils.NewUint64(8), admins, nil, nil), - }, - }, - }, - }, - }, - "disable and re-enable, cancel upgrade before it happens": { - startTimestamps: []uint64{5, 6}, - configs: []*UpgradeConfig{ - { - PrecompileUpgrades: []PrecompileUpgrade{ - { - Config: txallowlist.NewDisableConfig(utils.NewUint64(6)), - }, - { - Config: txallowlist.NewConfig(utils.NewUint64(7), admins, nil, nil), - }, - }, - }, - { - PrecompileUpgrades: []PrecompileUpgrade{ - { - Config: txallowlist.NewDisableConfig(utils.NewUint64(6)), - }, - }, - }, - }, - }, - "disable and re-enable, cancel upgrade after it happens": { - expectedErrorString: "mismatching missing PrecompileUpgrade", - startTimestamps: []uint64{5, 8}, - configs: []*UpgradeConfig{ - { - PrecompileUpgrades: []PrecompileUpgrade{ - { - Config: txallowlist.NewDisableConfig(utils.NewUint64(6)), - }, - { - Config: txallowlist.NewConfig(utils.NewUint64(7), admins, nil, nil), - }, - }, - }, - { - PrecompileUpgrades: []PrecompileUpgrade{ - { - Config: txallowlist.NewDisableConfig(utils.NewUint64(6)), - }, - }, - }, - }, - }, - "disable and re-enable, change upgrade config after upgrade not allowed": { - expectedErrorString: "mismatching PrecompileUpgrade", - startTimestamps: []uint64{5, 8}, - configs: []*UpgradeConfig{ - { - PrecompileUpgrades: []PrecompileUpgrade{ - { - Config: txallowlist.NewDisableConfig(utils.NewUint64(6)), - }, - { - Config: txallowlist.NewConfig(utils.NewUint64(7), admins, nil, nil), - }, - }, - }, - { - PrecompileUpgrades: []PrecompileUpgrade{ - { - Config: txallowlist.NewDisableConfig(utils.NewUint64(6)), - }, - { - // uses a different (empty) admin list, not allowed - Config: txallowlist.NewConfig(utils.NewUint64(7), []common.Address{}, nil, nil), - }, - }, - }, - }, - }, - "disable and re-enable, identical upgrade config should be accepted": { - startTimestamps: []uint64{5, 8}, - configs: []*UpgradeConfig{ - { - PrecompileUpgrades: []PrecompileUpgrade{ - { - Config: txallowlist.NewDisableConfig(utils.NewUint64(6)), - }, - { - Config: txallowlist.NewConfig(utils.NewUint64(7), admins, nil, nil), - }, - }, - }, - { - PrecompileUpgrades: []PrecompileUpgrade{ - { - Config: txallowlist.NewDisableConfig(utils.NewUint64(6)), - }, - { - Config: txallowlist.NewConfig(utils.NewUint64(7), admins, nil, nil), - }, - }, - }, - }, - }, - "retroactively enabling upgrades is not allowed": { - expectedErrorString: "cannot retroactively enable PrecompileUpgrade[1] in database (have timestamp nil, want timestamp 5, rewindto timestamp 4)", - startTimestamps: []uint64{6}, - configs: []*UpgradeConfig{ - { - PrecompileUpgrades: []PrecompileUpgrade{ - { - Config: txallowlist.NewDisableConfig(utils.NewUint64(5)), - }, - { - Config: txallowlist.NewConfig(utils.NewUint64(6), admins, nil, nil), - }, - }, - }, - }, - }, - } - - for name, tt := range tests { - t.Run(name, func(t *testing.T) { - tt.run(t, chainConfig) - }) - } -} - -type upgradeCompatibilityTest struct { - configs []*UpgradeConfig - startTimestamps []uint64 - expectedErrorString string -} - -func (tt *upgradeCompatibilityTest) run(t *testing.T, chainConfig ChainConfig) { - // apply all the upgrade bytes specified in order - for i, upgrade := range tt.configs { - newCfg := chainConfig - newCfg.UpgradeConfig = *upgrade - - err := chainConfig.checkCompatible(&newCfg, nil, tt.startTimestamps[i]) - - // if this is not the final upgradeBytes, continue applying - // the next upgradeBytes. (only check the result on the last apply) - if i != len(tt.configs)-1 { - if err != nil { - t.Fatalf("expecting checkCompatible call %d to return nil, got %s", i+1, err) - } - chainConfig = newCfg - continue - } - - if tt.expectedErrorString != "" { - require.ErrorContains(t, err, tt.expectedErrorString) - } else { - require.Nil(t, err) - } - } -} diff --git a/params/protocol_params.go b/params/protocol_params.go index 6e4b96f8e7..4876b573e1 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -38,6 +38,8 @@ const ( MaxGasLimit uint64 = 0x7fffffffffffffff // Maximum the gas limit (2^63-1). GenesisGasLimit uint64 = 4712388 // Gas limit of the Genesis block. + // Note: MaximumExtraDataSize has been reduced to 32 in Geth, but is kept the same in Coreth for + // backwards compatibility. MaximumExtraDataSize uint64 = 64 // Maximum size extra data may be after Genesis. ExpByteGas uint64 = 10 // Times ceil(log256(exponent)) for the EXP instruction. SloadGas uint64 = 50 // Multiplied by the number of 32-byte words that are copied (round up) for any *COPY operation and added. @@ -182,6 +184,17 @@ const ( MaxBlobGasPerBlock = 6 * BlobTxBlobGasPerBlob // Maximum consumable blob gas for data blobs per block ) +const ( + // Avalanche Stateful Precompile Params + // Gas price for native asset balance lookup. Based on the cost of an SLOAD operation since native + // asset balances are kept in state storage. + AssetBalanceApricot uint64 = 2100 + // Gas price for native asset call. This gas price reflects the additional work done for the native + // asset transfer itself, which is a write to state storage. The cost of creating a new account and + // normal value transfer is assessed separately from this cost. + AssetCallApricot uint64 = 20000 +) + // Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations var Bls12381MultiExpDiscountTable = [128]uint64{1200, 888, 764, 641, 594, 547, 500, 453, 438, 423, 408, 394, 379, 364, 349, 334, 330, 326, 322, 318, 314, 310, 306, 302, 298, 294, 289, 285, 281, 277, 273, 269, 268, 266, 265, 263, 262, 260, 259, 257, 256, 254, 253, 251, 250, 248, 247, 245, 244, 242, 241, 239, 238, 236, 235, 233, 232, 231, 229, 228, 226, 225, 223, 222, 221, 220, 219, 219, 218, 217, 216, 216, 215, 214, 213, 213, 212, 211, 211, 210, 209, 208, 208, 207, 206, 205, 205, 204, 203, 202, 202, 201, 200, 199, 199, 198, 197, 196, 196, 195, 194, 193, 193, 192, 191, 191, 190, 189, 188, 188, 187, 186, 185, 185, 184, 183, 182, 182, 181, 180, 179, 179, 178, 177, 176, 176, 175, 174} diff --git a/params/state_upgrade.go b/params/state_upgrade.go deleted file mode 100644 index 2b529a3f11..0000000000 --- a/params/state_upgrade.go +++ /dev/null @@ -1,107 +0,0 @@ -// (c) 2023 Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package params - -import ( - "fmt" - "reflect" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/common/math" -) - -// StateUpgrade describes the modifications to be made to the state during -// a state upgrade. -type StateUpgrade struct { - BlockTimestamp *uint64 `json:"blockTimestamp,omitempty"` - - // map from account address to the modification to be made to the account. - StateUpgradeAccounts map[common.Address]StateUpgradeAccount `json:"accounts"` -} - -// StateUpgradeAccount describes the modifications to be made to an account during -// a state upgrade. -type StateUpgradeAccount struct { - Code hexutil.Bytes `json:"code,omitempty"` - Storage map[common.Hash]common.Hash `json:"storage,omitempty"` - BalanceChange *math.HexOrDecimal256 `json:"balanceChange,omitempty"` -} - -func (s *StateUpgrade) Equal(other *StateUpgrade) bool { - return reflect.DeepEqual(s, other) -} - -// verifyStateUpgrades checks [c.StateUpgrades] is well formed: -// - the specified blockTimestamps must monotonically increase -func (c *ChainConfig) verifyStateUpgrades() error { - var previousUpgradeTimestamp *uint64 - for i, upgrade := range c.StateUpgrades { - upgradeTimestamp := upgrade.BlockTimestamp - if upgradeTimestamp == nil { - return fmt.Errorf("StateUpgrade[%d]: config block timestamp cannot be nil ", i) - } - // Verify the upgrade's timestamp is equal 0 (to avoid confusion with genesis). - if *upgradeTimestamp == 0 { - return fmt.Errorf("StateUpgrade[%d]: config block timestamp (%v) must be greater than 0", i, *upgradeTimestamp) - } - - // Verify specified timestamps are strictly monotonically increasing. - if previousUpgradeTimestamp != nil && *upgradeTimestamp <= *previousUpgradeTimestamp { - return fmt.Errorf("StateUpgrade[%d]: config block timestamp (%v) <= previous timestamp (%v)", i, *upgradeTimestamp, *previousUpgradeTimestamp) - } - previousUpgradeTimestamp = upgradeTimestamp - } - return nil -} - -// GetActivatingStateUpgrades returns all state upgrades configured to activate during the -// state transition from a block with timestamp [from] to a block with timestamp [to]. -func (c *ChainConfig) GetActivatingStateUpgrades(from *uint64, to uint64, upgrades []StateUpgrade) []StateUpgrade { - activating := make([]StateUpgrade, 0) - for _, upgrade := range upgrades { - if IsForkTransition(upgrade.BlockTimestamp, from, to) { - activating = append(activating, upgrade) - } - } - return activating -} - -// CheckStateUpgradesCompatible checks if [stateUpgrades] are compatible with [c] at [headTimestamp]. -func (c *ChainConfig) CheckStateUpgradesCompatible(stateUpgrades []StateUpgrade, lastTimestamp uint64) *ConfigCompatError { - // All active upgrades (from nil to [lastTimestamp]) must match. - activeUpgrades := c.GetActivatingStateUpgrades(nil, lastTimestamp, c.StateUpgrades) - newUpgrades := c.GetActivatingStateUpgrades(nil, lastTimestamp, stateUpgrades) - - // Check activated upgrades are still present. - for i, upgrade := range activeUpgrades { - if len(newUpgrades) <= i { - // missing upgrade - return newTimestampCompatError( - fmt.Sprintf("missing StateUpgrade[%d]", i), - upgrade.BlockTimestamp, - nil, - ) - } - // All upgrades that have activated must be identical. - if !upgrade.Equal(&newUpgrades[i]) { - return newTimestampCompatError( - fmt.Sprintf("StateUpgrade[%d]", i), - upgrade.BlockTimestamp, - newUpgrades[i].BlockTimestamp, - ) - } - } - // then, make sure newUpgrades does not have additional upgrades - // that are already activated. (cannot perform retroactive upgrade) - if len(newUpgrades) > len(activeUpgrades) { - return newTimestampCompatError( - fmt.Sprintf("cannot retroactively enable StateUpgrade[%d]", len(activeUpgrades)), - nil, - newUpgrades[len(activeUpgrades)].BlockTimestamp, // this indexes to the first element in newUpgrades after the end of activeUpgrades - ) - } - - return nil -} diff --git a/params/state_upgrade_test.go b/params/state_upgrade_test.go deleted file mode 100644 index 6ee4094fc0..0000000000 --- a/params/state_upgrade_test.go +++ /dev/null @@ -1,187 +0,0 @@ -// (c) 2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package params - -import ( - "encoding/json" - "math/big" - "testing" - - "github.com/ava-labs/subnet-evm/utils" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/math" - "github.com/stretchr/testify/require" -) - -func TestVerifyStateUpgrades(t *testing.T) { - modifiedAccounts := map[common.Address]StateUpgradeAccount{ - {1}: { - BalanceChange: (*math.HexOrDecimal256)(common.Big1), - }, - } - tests := []struct { - name string - upgrades []StateUpgrade - expectedError string - }{ - { - name: "valid upgrade", - upgrades: []StateUpgrade{ - {BlockTimestamp: utils.NewUint64(1), StateUpgradeAccounts: modifiedAccounts}, - {BlockTimestamp: utils.NewUint64(2), StateUpgradeAccounts: modifiedAccounts}, - }, - }, - { - name: "upgrade block timestamp is not strictly increasing", - upgrades: []StateUpgrade{ - {BlockTimestamp: utils.NewUint64(1), StateUpgradeAccounts: modifiedAccounts}, - {BlockTimestamp: utils.NewUint64(1), StateUpgradeAccounts: modifiedAccounts}, - }, - expectedError: "config block timestamp (1) <= previous timestamp (1)", - }, - { - name: "upgrade block timestamp decreases", - upgrades: []StateUpgrade{ - {BlockTimestamp: utils.NewUint64(2), StateUpgradeAccounts: modifiedAccounts}, - {BlockTimestamp: utils.NewUint64(1), StateUpgradeAccounts: modifiedAccounts}, - }, - expectedError: "config block timestamp (1) <= previous timestamp (2)", - }, - { - name: "upgrade block timestamp is zero", - upgrades: []StateUpgrade{ - {BlockTimestamp: utils.NewUint64(0), StateUpgradeAccounts: modifiedAccounts}, - }, - expectedError: "config block timestamp (0) must be greater than 0", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require := require.New(t) - baseConfig := *TestChainConfig - config := &baseConfig - config.StateUpgrades = tt.upgrades - - err := config.Verify() - if tt.expectedError == "" { - require.NoError(err) - } else { - require.ErrorContains(err, tt.expectedError) - } - }) - } -} - -func TestCheckCompatibleStateUpgrades(t *testing.T) { - chainConfig := *TestChainConfig - stateUpgrade := map[common.Address]StateUpgradeAccount{ - {1}: {BalanceChange: (*math.HexOrDecimal256)(common.Big1)}, - } - differentStateUpgrade := map[common.Address]StateUpgradeAccount{ - {2}: {BalanceChange: (*math.HexOrDecimal256)(common.Big1)}, - } - - tests := map[string]upgradeCompatibilityTest{ - "reschedule upgrade before it happens": { - startTimestamps: []uint64{5, 6}, - configs: []*UpgradeConfig{ - { - StateUpgrades: []StateUpgrade{ - {BlockTimestamp: utils.NewUint64(6), StateUpgradeAccounts: stateUpgrade}, - }, - }, - { - StateUpgrades: []StateUpgrade{ - {BlockTimestamp: utils.NewUint64(6), StateUpgradeAccounts: stateUpgrade}, - }, - }, - }, - }, - "modify upgrade after it happens not allowed": { - expectedErrorString: "mismatching StateUpgrade", - startTimestamps: []uint64{5, 8}, - configs: []*UpgradeConfig{ - { - StateUpgrades: []StateUpgrade{ - {BlockTimestamp: utils.NewUint64(6), StateUpgradeAccounts: stateUpgrade}, - {BlockTimestamp: utils.NewUint64(7), StateUpgradeAccounts: stateUpgrade}, - }, - }, - { - StateUpgrades: []StateUpgrade{ - {BlockTimestamp: utils.NewUint64(6), StateUpgradeAccounts: stateUpgrade}, - {BlockTimestamp: utils.NewUint64(7), StateUpgradeAccounts: differentStateUpgrade}, - }, - }, - }, - }, - "cancel upgrade before it happens": { - startTimestamps: []uint64{5, 6}, - configs: []*UpgradeConfig{ - { - StateUpgrades: []StateUpgrade{ - {BlockTimestamp: utils.NewUint64(6), StateUpgradeAccounts: stateUpgrade}, - {BlockTimestamp: utils.NewUint64(7), StateUpgradeAccounts: stateUpgrade}, - }, - }, - { - StateUpgrades: []StateUpgrade{ - {BlockTimestamp: utils.NewUint64(6), StateUpgradeAccounts: stateUpgrade}, - }, - }, - }, - }, - "retroactively enabling upgrades is not allowed": { - expectedErrorString: "cannot retroactively enable StateUpgrade[0] in database (have timestamp nil, want timestamp 5, rewindto timestamp 4)", - startTimestamps: []uint64{6}, - configs: []*UpgradeConfig{ - { - StateUpgrades: []StateUpgrade{ - {BlockTimestamp: utils.NewUint64(5), StateUpgradeAccounts: stateUpgrade}, - }, - }, - }, - }, - } - - for name, tt := range tests { - t.Run(name, func(t *testing.T) { - tt.run(t, chainConfig) - }) - } -} - -func TestUnmarshalStateUpgradeJSON(t *testing.T) { - jsonBytes := []byte( - `{ - "stateUpgrades": [ - { - "blockTimestamp": 1677608400, - "accounts": { - "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC": { - "balanceChange": "100" - } - } - } - ] - }`, - ) - - upgradeConfig := UpgradeConfig{ - StateUpgrades: []StateUpgrade{ - { - BlockTimestamp: utils.NewUint64(1677608400), - StateUpgradeAccounts: map[common.Address]StateUpgradeAccount{ - common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"): { - BalanceChange: (*math.HexOrDecimal256)(big.NewInt(100)), - }, - }, - }, - }, - } - var unmarshaledConfig UpgradeConfig - err := json.Unmarshal(jsonBytes, &unmarshaledConfig) - require.NoError(t, err) - require.Equal(t, upgradeConfig, unmarshaledConfig) -} diff --git a/peer/network_test.go b/peer/network_test.go index d93439b22b..8e297785af 100644 --- a/peer/network_test.go +++ b/peer/network_test.go @@ -737,6 +737,12 @@ type testGossipHandler struct { nodeID ids.NodeID } +func (t *testGossipHandler) HandleAtomicTx(nodeID ids.NodeID, msg message.AtomicTxGossip) error { + t.received = true + t.nodeID = nodeID + return nil +} + func (t *testGossipHandler) HandleEthTxs(nodeID ids.NodeID, msg message.EthTxsGossip) error { t.received = true t.nodeID = nodeID diff --git a/plugin/evm/README.md b/plugin/evm/README.md index cb180bafc7..44bbe3640c 100644 --- a/plugin/evm/README.md +++ b/plugin/evm/README.md @@ -8,7 +8,7 @@ The VM creates the Ethereum backend and provides basic block building, parsing, ## APIs -The VM creates APIs for the node through the function `CreateHandlers()`. CreateHandlers returns the `Service` struct to serve Subnet-EVM specific APIs. Additionally, the Ethereum backend APIs are also returned at the `/rpc` extension. +The VM creates APIs for the node through the function `CreateHandlers()`. CreateHandlers returns the `Service` struct to serve Coreth specific APIs. Additionally, the Ethereum backend APIs are also returned at the `/rpc` extension. ## Block Handling @@ -21,3 +21,11 @@ To do this, the VM uses a modified version of the Ethereum RLP block type [here] The Block type implements the AvalancheGo ChainVM Block interface. The key functions for this interface are `Verify()`, `Accept()`, `Reject()`, and `Status()`. The Block type wraps the stateless block type [here](../../core/types/block.go) and implements these functions to allow the consensus engine to verify blocks as valid, perform consensus, and mark them as accepted or rejected. See the documentation in AvalancheGo for the more detailed VM invariants that are maintained here. + +## Atomic Transactions + +Atomic transactions utilize Shared Memory (documented [here](https://github.com/ava-labs/avalanchego/blob/master/chains/atomic/README.md)) to send assets to the P-Chain and X-Chain. + +Operations on shared memory cannot be reverted, so atomic transactions must separate their verification and processing into two stages: verifying the transaction as valid to be performed within its block and actually performing the operation. For example, once an export transaction is accepted, there is no way for the C-Chain to take that asset back and it can be imported immediately by the recipient chain. + +The C-Chain uses the account model for its own state, but atomic transactions must be compatible with the P-Chain and X-Chain, such that C-Chain atomic transactions must transform between the account model and the UTXO model. diff --git a/plugin/evm/block.go b/plugin/evm/block.go index 9d3d238d02..a639f92a29 100644 --- a/plugin/evm/block.go +++ b/plugin/evm/block.go @@ -10,6 +10,7 @@ import ( "fmt" "time" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" @@ -30,20 +31,106 @@ var ( _ block.WithVerifyContext = (*Block)(nil) ) +var ( + errMissingUTXOs = errors.New("missing UTXOs") +) + +// readMainnetBonusBlocks returns maps of bonus block numbers to block IDs. +// Note bonus blocks are indexed in the atomic trie. +func readMainnetBonusBlocks() (map[uint64]ids.ID, error) { + mainnetBonusBlocks := map[uint64]string{ + 102972: "Njm9TcLUXRojZk8YhEM6ksvfiPdC1TME4zJvGaDXgzMCyB6oB", + 103105: "BYqLB6xpqy7HsAgP2XNfGE8Ubg1uEzse5mBPTSJH9z5s8pvMa", + 103143: "AfWvJH3rB2fdHuPWQp6qYNCFVT29MooQPRigD88rKKwUDEDhq", + 103183: "2KPW9G5tiNF14tZNfG4SqHuQrtUYVZyxuof37aZ7AnTKrQdsHn", + 103197: "pE93VXY3N5QKfwsEFcM9i59UpPFgeZ8nxpJNaGaDQyDgsscNf", + 103203: "2czmtnBS44VCWNRFUM89h4Fe9m3ZeZVYyh7Pe3FhNqjRNgPXhZ", + 103208: "esx5J962LtYm2aSrskpLai5e4CMMsaS1dsu9iuLGJ3KWgSu2M", + 103209: "DK9NqAJGry1wAo767uuYc1dYXAjUhzwka6vi8d9tNheqzGUTd", + 103259: "i1HoerJ1axognkUKKL58FvF9aLrbZKtv7TdKLkT5kgzoeU1vB", + 103261: "2DpCuBaH94zKKFNY2XTs4GeJcwsEv6qT2DHc59S8tdg97GZpcJ", + 103266: "2ez4CA7w4HHr8SSobHQUAwFgj2giRNjNFUZK9JvrZFa1AuRj6X", + 103287: "2QBNMMFJmhVHaGF45GAPszKyj1gK6ToBERRxYvXtM7yfrdUGPK", + 103339: "2pSjfo7rkFCfZ2CqAxqfw8vqM2CU2nVLHrFZe3rwxz43gkVuGo", + 103346: "2SiSziHHqPjb1qkw7CdGYupokiYpd2b7mMqRiyszurctcA5AKr", + 103350: "2F5tSQbdTfhZxvkxZqdFp7KR3FrJPKEsDLQK7KtPhNXj1EZAh4", + 103358: "2tCe88ur6MLQcVgwE5XxoaHiTGtSrthwKN3SdbHE4kWiQ7MSTV", + 103437: "21o2fVTnzzmtgXqkV1yuQeze7YEQhR5JB31jVVD9oVUnaaV8qm", + 103472: "2nG4exd9eUoAGzELfksmBR8XDCKhohY1uDKRFzEXJG4M8p3qA7", + 103478: "63YLdYXfXc5tY3mwWLaDsbXzQHYmwWVxMP7HKbRh4Du3C2iM1", + 103493: "soPweZ8DGaoUMjrnzjH3V2bypa7ZvvfqBan4UCsMUxMP759gw", + 103514: "2dNkpQF4mooveyUDfBYQTBfsGDV4wkncQPpEw4kHKfSTSTo5x", + 103536: "PJTkRrHvKZ1m4AQdPND1MBpUXpCrGN4DDmXmJQAiUrsxPoLQX", + 103545: "22ck2Z7cC38hmBfX2v3jMWxun8eD8psNaicfYeokS67DxwmPTx", + 103547: "pTf7gfk1ksj7bqMrLyMCij8FBKth1uRqQrtfykMFeXhx5xnrL", + 103554: "9oZh4qyBCcVwSGyDoUzRAuausvPJN3xH6nopKS6bwYzMfLoQ2", + 103555: "MjExz2z1qhwugc1tAyiGxRsCq4GvJwKfyyS29nr4tRVB8ooic", + 103559: "cwJusfmn98TW3DjAbfLRN9utYR24KAQ82qpAXmVSvjHyJZuM2", + 103561: "2YgxGHns7Z2hMMHJsPCgVXuJaL7x1b3gnHbmSCfCdyAcYGr6mx", + 103563: "2AXxT3PSEnaYHNtBTnYrVTf24TtKDWjky9sqoFEhydrGXE9iKH", + 103564: "Ry2sfjFfGEnJxRkUGFSyZNn7GR3m4aKAf1scDW2uXSNQB568Y", + 103569: "21Jys8UNURmtckKSV89S2hntEWymJszrLQbdLaNcbXcxDAsQSa", + 103570: "sg6wAwFBsPQiS5Yfyh41cVkCRQbrrXsxXmeNyQ1xkunf2sdyv", + 103575: "z3BgePPpCXq1mRBRvUi28rYYxnEtJizkUEHnDBrcZeVA7MFVk", + 103577: "uK5Ff9iBfDtREpVv9NgCQ1STD1nzLJG3yrfibHG4mGvmybw6f", + 103578: "Qv5v5Ru8ArfnWKB1w6s4G5EYPh7TybHJtF6UsVwAkfvZFoqmj", + 103582: "7KCZKBpxovtX9opb7rMRie9WmW5YbZ8A4HwBBokJ9eSHpZPqx", + 103587: "2AfTQ2FXNj9bkSUQnud9pFXULx6EbF7cbbw6i3ayvc2QNhgxfF", + 103590: "2gTygYckZgFZfN5QQWPaPBD3nabqjidV55mwy1x1Nd4JmJAwaM", + 103591: "2cUPPHy1hspr2nAKpQrrAEisLKkaWSS9iF2wjNFyFRs8vnSkKK", + 103594: "5MptSdP6dBMPSwk9GJjeVe39deZJTRh9i82cgNibjeDffrrTf", + 103597: "2J8z7HNv4nwh82wqRGyEHqQeuw4wJ6mCDCSvUgusBu35asnshK", + 103598: "2i2FP6nJyvhX9FR15qN2D9AVoK5XKgBD2i2AQ7FoSpfowxvQDX", + 103603: "2v3smb35s4GLACsK4Zkd2RcLBLdWA4huqrvq8Y3VP4CVe8kfTM", + 103604: "b7XfDDLgwB12DfL7UTWZoxwBpkLPL5mdHtXngD94Y2RoeWXSh", + 103607: "PgaRk1UAoUvRybhnXsrLq5t6imWhEa6ksNjbN6hWgs4qPrSzm", + 103612: "2oueNTj4dUE2FFtGyPpawnmCCsy6EUQeVHVLZy8NHeQmkAciP4", + 103614: "2YHZ1KymFjiBhpXzgt6HXJhLSt5SV9UQ4tJuUNjfN1nQQdm5zz", + 103617: "amgH2C1s9H3Av7vSW4y7n7TXb9tKyKHENvrDXutgNN6nsejgc", + 103618: "fV8k1U8oQDmfVwK66kAwN73aSsWiWhm8quNpVnKmSznBycV2W", + 103621: "Nzs93kFTvcXanFUp9Y8VQkKYnzmH8xykxVNFJTkdyAEeuxWbP", + 103623: "2rAsBj3emqQa13CV8r5fTtHogs4sXnjvbbXVzcKPi3WmzhpK9D", + 103624: "2JbuExUGKW5mYz5KfXATwq1ibRDimgks9wEdYGNSC6Ttey1R4U", + 103627: "tLLijh7oKfvWT1yk9zRv4FQvuQ5DAiuvb5kHCNN9zh4mqkFMG", + 103628: "dWBsRYRwFrcyi3DPdLoHsL67QkZ5h86hwtVfP94ZBaY18EkmF", + 103629: "XMoEsew2DhSgQaydcJFJUQAQYP8BTNTYbEJZvtbrV2QsX7iE3", + 103630: "2db2wMbVAoCc5EUJrsBYWvNZDekqyY8uNpaaVapdBAQZ5oRaou", + 103633: "2QiHZwLhQ3xLuyyfcdo5yCUfoSqWDvRZox5ECU19HiswfroCGp", + } + + bonusBlockMainnetHeights := make(map[uint64]ids.ID) + for height, blkIDStr := range mainnetBonusBlocks { + blkID, err := ids.FromString(blkIDStr) + if err != nil { + return nil, err + } + bonusBlockMainnetHeights[height] = blkID + } + return bonusBlockMainnetHeights, nil +} + // Block implements the snowman.Block interface type Block struct { - id ids.ID - ethBlock *types.Block - vm *VM + id ids.ID + ethBlock *types.Block + vm *VM + atomicTxs []*Tx } // newBlock returns a new Block wrapping the ethBlock type and implementing the snowman.Block interface -func (vm *VM) newBlock(ethBlock *types.Block) *Block { - return &Block{ - id: ids.ID(ethBlock.Hash()), - ethBlock: ethBlock, - vm: vm, +func (vm *VM) newBlock(ethBlock *types.Block) (*Block, error) { + isApricotPhase5 := vm.chainConfig.IsApricotPhase5(ethBlock.Time()) + atomicTxs, err := ExtractAtomicTxs(ethBlock.ExtData(), isApricotPhase5, vm.codec) + if err != nil { + return nil, err } + + return &Block{ + id: ids.ID(ethBlock.Hash()), + ethBlock: ethBlock, + vm: vm, + atomicTxs: atomicTxs, + }, nil } // ID implements the snowman.Block interface @@ -77,6 +164,18 @@ func (b *Block) Accept(context.Context) error { return fmt.Errorf("failed to put %s as the last accepted block: %w", b.ID(), err) } + for _, tx := range b.atomicTxs { + // Remove the accepted transaction from the mempool + vm.mempool.RemoveTx(tx) + } + + // Update VM state for atomic txs in this block. This includes updating the + // atomic tx repo, atomic trie, and shared memory. + atomicState, err := b.vm.atomicBackend.GetVerifiedAtomicState(common.Hash(b.ID())) + if err != nil { + // should never occur since [b] must be verified before calling Accept + return err + } // Get pending operations on the vm's versionDB so we can apply them atomically // with the shared memory requests. vdbBatch, err := b.vm.db.CommitBatch() @@ -87,7 +186,7 @@ func (b *Block) Accept(context.Context) error { // Apply any shared memory requests that accumulated from processing the logs // of the accepted block (generated by precompiles) atomically with other pending // changes to the vm's versionDB. - return vm.ctx.SharedMemory.Apply(sharedMemoryWriter.requests, vdbBatch) + return atomicState.Accept(vdbBatch, sharedMemoryWriter.requests) } // handlePrecompileAccept calls Accept on any logs generated with an active precompile address that implements @@ -128,8 +227,23 @@ func (b *Block) handlePrecompileAccept(rules params.Rules, sharedMemoryWriter *s } // Reject implements the snowman.Block interface +// If [b] contains an atomic transaction, attempt to re-issue it func (b *Block) Reject(context.Context) error { log.Debug(fmt.Sprintf("Rejecting block %s (%s) at height %d", b.ID().Hex(), b.ID(), b.Height())) + for _, tx := range b.atomicTxs { + b.vm.mempool.RemoveTx(tx) + if err := b.vm.mempool.AddTx(tx); err != nil { + log.Debug("Failed to re-issue transaction in rejected block", "txID", tx.ID(), "err", err) + } + } + atomicState, err := b.vm.atomicBackend.GetVerifiedAtomicState(common.Hash(b.ID())) + if err != nil { + // should never occur since [b] must be verified before calling Reject + return err + } + if err := atomicState.Reject(); err != nil { + return err + } return b.vm.blockChain.Reject(b.ethBlock) } @@ -211,6 +325,11 @@ func (b *Block) verify(predicateContext *precompileconfig.PredicateContext, writ return fmt.Errorf("syntactic block verification failed: %w", err) } + // verify UTXOs named in import txs are present in shared memory. + if err := b.verifyUTXOsPresent(); err != nil { + return err + } + // Only enforce predicates if the chain has already bootstrapped. // If the chain is still bootstrapping, we can assume that all blocks we are verifying have // been accepted by the network (so the predicate was validated by the network when the @@ -230,7 +349,16 @@ func (b *Block) verify(predicateContext *precompileconfig.PredicateContext, writ return nil } - return b.vm.blockChain.InsertBlockManual(b.ethBlock, writes) + err := b.vm.blockChain.InsertBlockManual(b.ethBlock, writes) + if err != nil || !writes { + // if an error occurred inserting the block into the chain + // or if we are not pinning to memory, unpin the atomic trie + // changes from memory (if they were pinned). + if atomicState, err := b.vm.atomicBackend.GetVerifiedAtomicState(b.ethBlock.Hash()); err == nil { + _ = atomicState.Reject() // ignore this error so we can return the original error instead. + } + } + return err } // verifyPredicates verifies the predicates in the block are valid according to predicateContext. @@ -268,6 +396,33 @@ func (b *Block) verifyPredicates(predicateContext *precompileconfig.PredicateCon return nil } +// verifyUTXOsPresent returns an error if any of the atomic transactions name UTXOs that +// are not present in shared memory. +func (b *Block) verifyUTXOsPresent() error { + blockHash := common.Hash(b.ID()) + if b.vm.atomicBackend.IsBonus(b.Height(), blockHash) { + log.Info("skipping atomic tx verification on bonus block", "block", blockHash) + return nil + } + + if !b.vm.bootstrapped { + return nil + } + + // verify UTXOs named in import txs are present in shared memory. + for _, atomicTx := range b.atomicTxs { + utx := atomicTx.UnsignedAtomicTx + chainID, requests, err := utx.AtomicOps() + if err != nil { + return err + } + if _, err := b.vm.ctx.SharedMemory.Get(chainID, requests.RemoveRequests); err != nil { + return fmt.Errorf("%w: %s", errMissingUTXOs, err) + } + } + return nil +} + // Bytes implements the snowman.Block interface func (b *Block) Bytes() []byte { res, err := rlp.EncodeToBytes(b.ethBlock) diff --git a/plugin/evm/block_builder.go b/plugin/evm/block_builder.go index cfeb2385ec..06f931dd68 100644 --- a/plugin/evm/block_builder.go +++ b/plugin/evm/block_builder.go @@ -27,7 +27,8 @@ type blockBuilder struct { ctx *snow.Context chainConfig *params.ChainConfig - txPool *txpool.TxPool + txPool *txpool.TxPool + mempool *Mempool shutdownChan <-chan struct{} shutdownWg *sync.WaitGroup @@ -55,6 +56,7 @@ func (vm *VM) NewBlockBuilder(notifyBuildBlockChan chan<- commonEng.Message) *bl ctx: vm.ctx, chainConfig: vm.chainConfig, txPool: vm.txPool, + mempool: vm.mempool, shutdownChan: vm.shutdownChan, shutdownWg: &vm.shutdownWg, notifyBuildBlockChan: notifyBuildBlockChan, @@ -99,7 +101,7 @@ func (b *blockBuilder) handleGenerateBlock() { // into a block. func (b *blockBuilder) needToBuild() bool { size := b.txPool.PendingSize(true) - return size > 0 + return size > 0 || b.mempool.Len() > 0 } // markBuilding adds a PendingTxs message to the toEngine channel. @@ -153,6 +155,9 @@ func (b *blockBuilder) awaitSubmittedTxs() { case <-txSubmitChan: log.Trace("New tx detected, trying to generate a block") b.signalTxsReady() + case <-b.mempool.Pending: + log.Trace("New atomic Tx detected, trying to generate a block") + b.signalTxsReady() case <-b.shutdownChan: b.buildBlockTimer.Stop() return diff --git a/plugin/evm/block_test.go b/plugin/evm/block_test.go deleted file mode 100644 index f30cc4ceae..0000000000 --- a/plugin/evm/block_test.go +++ /dev/null @@ -1,97 +0,0 @@ -// (c) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package evm - -import ( - "math/big" - "testing" - - "github.com/ava-labs/subnet-evm/core/rawdb" - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - "github.com/ava-labs/subnet-evm/trie" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -func TestHandlePrecompileAccept(t *testing.T) { - require := require.New(t) - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - db := rawdb.NewMemoryDatabase() - vm := &VM{ - chaindb: db, - chainConfig: params.TestChainConfig, - } - - precompileAddr := common.Address{0x05} - otherAddr := common.Address{0x06} - - // Prepare a receipt with 3 logs, two of which are from the precompile - receipt := &types.Receipt{ - Logs: []*types.Log{ - { - Address: precompileAddr, - Topics: []common.Hash{{0x01}, {0x02}, {0x03}}, - Data: []byte("log1"), - }, - { - Address: otherAddr, - Topics: []common.Hash{{0x01}, {0x02}, {0x04}}, - Data: []byte("log2"), - }, - { - Address: precompileAddr, - Topics: []common.Hash{{0x01}, {0x02}, {0x05}}, - Data: []byte("log3"), - }, - }, - } - ethBlock := types.NewBlock( - &types.Header{Number: big.NewInt(1)}, - []*types.Transaction{types.NewTx(&types.LegacyTx{})}, - nil, - []*types.Receipt{receipt}, - trie.NewStackTrie(nil), - ) - // Write the block to the db - rawdb.WriteBlock(db, ethBlock) - rawdb.WriteReceipts(db, ethBlock.Hash(), ethBlock.NumberU64(), []*types.Receipt{receipt}) - - // Set up the mock with the expected calls to Accept - txIndex := 0 - mockAccepter := precompileconfig.NewMockAccepter(ctrl) - gomock.InOrder( - mockAccepter.EXPECT().Accept( - gomock.Not(gomock.Nil()), // acceptCtx - ethBlock.Hash(), // blockHash - ethBlock.NumberU64(), // blockNumber - ethBlock.Transactions()[txIndex].Hash(), // txHash - 0, // logIndex - receipt.Logs[0].Topics, // topics - receipt.Logs[0].Data, // logData - ), - mockAccepter.EXPECT().Accept( - gomock.Not(gomock.Nil()), // acceptCtx - ethBlock.Hash(), // blockHash - ethBlock.NumberU64(), // blockNumber - ethBlock.Transactions()[txIndex].Hash(), // txHash - 2, // logIndex - receipt.Logs[2].Topics, // topics - receipt.Logs[2].Data, // logData - ), - ) - - // Call handlePrecompileAccept - blk := vm.newBlock(ethBlock) - rules := params.Rules{ - AccepterPrecompiles: map[common.Address]precompileconfig.Accepter{ - precompileAddr: mockAccepter, - }, - } - require.NoError(blk.handlePrecompileAccept(rules, nil)) -} diff --git a/plugin/evm/block_verification.go b/plugin/evm/block_verification.go index 753fa19b30..ce6387517d 100644 --- a/plugin/evm/block_verification.go +++ b/plugin/evm/block_verification.go @@ -10,41 +10,86 @@ import ( "github.com/ethereum/go-ethereum/common" + safemath "github.com/ava-labs/avalanchego/utils/math" + + "github.com/ava-labs/subnet-evm/constants" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/trie" ) -var legacyMinGasPrice = big.NewInt(params.MinGasPrice) +var ( + apricotPhase0MinGasPrice = big.NewInt(params.LaunchMinGasPrice) + apricotPhase1MinGasPrice = big.NewInt(params.ApricotPhase1MinGasPrice) +) type BlockValidator interface { SyntacticVerify(b *Block, rules params.Rules) error } -type blockValidator struct{} +type blockValidator struct { + extDataHashes map[common.Hash]common.Hash +} -func NewBlockValidator() BlockValidator { - return &blockValidator{} +func NewBlockValidator(extDataHashes map[common.Hash]common.Hash) BlockValidator { + return &blockValidator{ + extDataHashes: extDataHashes, + } } func (v blockValidator) SyntacticVerify(b *Block, rules params.Rules) error { if b == nil || b.ethBlock == nil { return errInvalidBlock } + ethHeader := b.ethBlock.Header() blockHash := b.ethBlock.Hash() + if !rules.IsApricotPhase1 { + if v.extDataHashes != nil { + extData := b.ethBlock.ExtData() + extDataHash := types.CalcExtDataHash(extData) + // If there is no extra data, check that there is no extra data in the hash map either to ensure we do not + // have a block that is unexpectedly missing extra data. + expectedExtDataHash, ok := v.extDataHashes[blockHash] + if len(extData) == 0 { + if ok { + return fmt.Errorf("found block with unexpected missing extra data (%s, %d), expected extra data hash: %s", blockHash, b.Height(), expectedExtDataHash) + } + } else { + // If there is extra data, check to make sure that the extra data hash matches the expected extra data hash for this + // block + if extDataHash != expectedExtDataHash { + return fmt.Errorf("extra data hash in block (%s, %d): %s, did not match the expected extra data hash: %s", blockHash, b.Height(), extDataHash, expectedExtDataHash) + } + } + } + } + // Skip verification of the genesis block since it should already be marked as accepted. if blockHash == b.vm.genesisHash { return nil } + // Verify the ExtDataHash field + if rules.IsApricotPhase1 { + if hash := types.CalcExtDataHash(b.ethBlock.ExtData()); ethHeader.ExtDataHash != hash { + return fmt.Errorf("extra data hash mismatch: have %x, want %x", ethHeader.ExtDataHash, hash) + } + } else { + if ethHeader.ExtDataHash != (common.Hash{}) { + return fmt.Errorf( + "expected ExtDataHash to be empty but got %x", + ethHeader.ExtDataHash, + ) + } + } + // Perform block and header sanity checks - if ethHeader.Number == nil || !ethHeader.Number.IsUint64() { - return errInvalidBlock + if !ethHeader.Number.IsUint64() { + return fmt.Errorf("invalid block number: %v", ethHeader.Number) } - if ethHeader.Difficulty == nil || !ethHeader.Difficulty.IsUint64() || - ethHeader.Difficulty.Uint64() != 1 { + if !ethHeader.Difficulty.IsUint64() || ethHeader.Difficulty.Cmp(common.Big1) != 0 { return fmt.Errorf("invalid difficulty: %d", ethHeader.Difficulty) } if ethHeader.Nonce.Uint64() != 0 { @@ -58,6 +103,23 @@ func (v blockValidator) SyntacticVerify(b *Block, rules params.Rules) error { return fmt.Errorf("invalid mix digest: %v", ethHeader.MixDigest) } + // Enforce static gas limit after ApricotPhase1 (prior to ApricotPhase1 it's handled in processing). + if rules.IsCortina { + if ethHeader.GasLimit != params.CortinaGasLimit { + return fmt.Errorf( + "expected gas limit to be %d after cortina but got %d", + params.CortinaGasLimit, ethHeader.GasLimit, + ) + } + } else if rules.IsApricotPhase1 { + if ethHeader.GasLimit != params.ApricotPhase1GasLimit { + return fmt.Errorf( + "expected gas limit to be %d after apricot phase 1 but got %d", + params.ApricotPhase1GasLimit, ethHeader.GasLimit, + ) + } + } + // Check that the size of the header's Extra data field is correct for [rules]. headerExtraDataSize := len(ethHeader.Extra) switch { @@ -68,13 +130,20 @@ func (v blockValidator) SyntacticVerify(b *Block, rules params.Rules) error { params.DynamicFeeExtraDataSize, len(ethHeader.Extra), ) } - case rules.IsSubnetEVM: + case rules.IsApricotPhase3: if headerExtraDataSize != params.DynamicFeeExtraDataSize { return fmt.Errorf( "expected header ExtraData to be len %d but got %d", params.DynamicFeeExtraDataSize, headerExtraDataSize, ) } + case rules.IsApricotPhase1: + if headerExtraDataSize != 0 { + return fmt.Errorf( + "expected header ExtraData to be 0 but got %d", + headerExtraDataSize, + ) + } default: if uint64(headerExtraDataSize) > params.MaximumExtraDataSize { return fmt.Errorf( @@ -84,13 +153,8 @@ func (v blockValidator) SyntacticVerify(b *Block, rules params.Rules) error { } } - if rules.IsSubnetEVM { - if ethHeader.BaseFee == nil { - return errNilBaseFeeSubnetEVM - } - if bfLen := ethHeader.BaseFee.BitLen(); bfLen > 256 { - return fmt.Errorf("too large base fee: bitlen %d", bfLen) - } + if b.ethBlock.Version() != 0 { + return fmt.Errorf("invalid version: %d", b.ethBlock.Version()) } // Check that the tx hash in the header matches the body @@ -103,7 +167,10 @@ func (v blockValidator) SyntacticVerify(b *Block, rules params.Rules) error { if uncleHash != ethHeader.UncleHash { return fmt.Errorf("invalid uncle hash %v does not match calculated uncle hash %v", ethHeader.UncleHash, uncleHash) } - + // Coinbase must match the BlackholeAddr on C-Chain + if ethHeader.Coinbase != constants.BlackholeAddr { + return fmt.Errorf("invalid coinbase %v does not match required blackhole address %v", ethHeader.Coinbase, constants.BlackholeAddr) + } // Block must not have any uncles if len(b.ethBlock.Uncles()) > 0 { return errUnclesUnsupported @@ -111,31 +178,84 @@ func (v blockValidator) SyntacticVerify(b *Block, rules params.Rules) error { // Block must not be empty txs := b.ethBlock.Transactions() - if len(txs) == 0 { + if len(txs) == 0 && len(b.atomicTxs) == 0 { return errEmptyBlock } - if !rules.IsSubnetEVM { - // Make sure that all the txs have the correct fee set. - for _, tx := range txs { - if tx.GasPrice().Cmp(legacyMinGasPrice) < 0 { - return fmt.Errorf("block contains tx %s with gas price too low (%d < %d)", tx.Hash(), tx.GasPrice(), legacyMinGasPrice) + // Enforce minimum gas prices here prior to dynamic fees going into effect. + switch { + case !rules.IsApricotPhase1: + // If we are in ApricotPhase0, enforce each transaction has a minimum gas price of at least the LaunchMinGasPrice + for _, tx := range b.ethBlock.Transactions() { + if tx.GasPrice().Cmp(apricotPhase0MinGasPrice) < 0 { + return fmt.Errorf("block contains tx %s with gas price too low (%d < %d)", tx.Hash(), tx.GasPrice(), params.LaunchMinGasPrice) + } + } + case !rules.IsApricotPhase3: + // If we are prior to ApricotPhase3, enforce each transaction has a minimum gas price of at least the ApricotPhase1MinGasPrice + for _, tx := range b.ethBlock.Transactions() { + if tx.GasPrice().Cmp(apricotPhase1MinGasPrice) < 0 { + return fmt.Errorf("block contains tx %s with gas price too low (%d < %d)", tx.Hash(), tx.GasPrice(), params.ApricotPhase1MinGasPrice) } } } // Make sure the block isn't too far in the future + // TODO: move this to only be part of semantic verification. blockTimestamp := b.ethBlock.Time() if maxBlockTime := uint64(b.vm.clock.Time().Add(maxFutureBlockTime).Unix()); blockTimestamp > maxBlockTime { return fmt.Errorf("block timestamp is too far in the future: %d > allowed %d", blockTimestamp, maxBlockTime) } - if rules.IsSubnetEVM { + // Ensure BaseFee is non-nil as of ApricotPhase3. + if rules.IsApricotPhase3 { + if ethHeader.BaseFee == nil { + return errNilBaseFeeApricotPhase3 + } + // TODO: this should be removed as 256 is the maximum possible bit length of a big int + if bfLen := ethHeader.BaseFee.BitLen(); bfLen > 256 { + return fmt.Errorf("too large base fee: bitlen %d", bfLen) + } + } + + // If we are in ApricotPhase4, ensure that ExtDataGasUsed is populated correctly. + if rules.IsApricotPhase4 { + // Make sure ExtDataGasUsed is not nil and correct + if ethHeader.ExtDataGasUsed == nil { + return errNilExtDataGasUsedApricotPhase4 + } + if rules.IsApricotPhase5 { + if ethHeader.ExtDataGasUsed.Cmp(params.AtomicGasLimit) == 1 { + return fmt.Errorf("too large extDataGasUsed: %d", ethHeader.ExtDataGasUsed) + } + } else { + if !ethHeader.ExtDataGasUsed.IsUint64() { + return fmt.Errorf("too large extDataGasUsed: %d", ethHeader.ExtDataGasUsed) + } + } + var totalGasUsed uint64 + for _, atomicTx := range b.atomicTxs { + // We perform this check manually here to avoid the overhead of having to + // reparse the atomicTx in `CalcExtDataGasUsed`. + fixedFee := rules.IsApricotPhase5 // Charge the atomic tx fixed fee as of ApricotPhase5 + gasUsed, err := atomicTx.GasUsed(fixedFee) + if err != nil { + return err + } + totalGasUsed, err = safemath.Add64(totalGasUsed, gasUsed) + if err != nil { + return err + } + } + switch { + case ethHeader.ExtDataGasUsed.Cmp(new(big.Int).SetUint64(totalGasUsed)) != 0: + return fmt.Errorf("invalid extDataGasUsed: have %d, want %d", ethHeader.ExtDataGasUsed, totalGasUsed) + // Make sure BlockGasCost is not nil // NOTE: ethHeader.BlockGasCost correctness is checked in header verification case ethHeader.BlockGasCost == nil: - return errNilBlockGasCostSubnetEVM + return errNilBlockGasCostApricotPhase4 case !ethHeader.BlockGasCost.IsUint64(): return fmt.Errorf("too large blockGasCost: %d", ethHeader.BlockGasCost) } @@ -167,6 +287,11 @@ func (v blockValidator) SyntacticVerify(b *Block, rules params.Rules) error { case *ethHeader.ParentBeaconRoot != (common.Hash{}): return fmt.Errorf("invalid parentBeaconRoot: have %x, expected empty hash", ethHeader.ParentBeaconRoot) } + if ethHeader.BlobGasUsed == nil { + return fmt.Errorf("blob gas used must not be nil in Cancun") + } else if *ethHeader.BlobGasUsed > 0 { + return fmt.Errorf("blobs not enabled on avalanche networks: used %d blob gas, expected 0", *ethHeader.BlobGasUsed) + } } return nil } diff --git a/plugin/evm/client.go b/plugin/evm/client.go index b91618fc4c..4701c22b9c 100644 --- a/plugin/evm/client.go +++ b/plugin/evm/client.go @@ -7,9 +7,15 @@ import ( "context" "fmt" + "github.com/ethereum/go-ethereum/common" "golang.org/x/exp/slog" "github.com/ava-labs/avalanchego/api" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" + "github.com/ava-labs/avalanchego/utils/formatting" + "github.com/ava-labs/avalanchego/utils/formatting/address" + "github.com/ava-labs/avalanchego/utils/json" "github.com/ava-labs/avalanchego/utils/rpc" ) @@ -18,6 +24,15 @@ var _ Client = (*client)(nil) // Client interface for interacting with EVM [chain] type Client interface { + IssueTx(ctx context.Context, txBytes []byte, options ...rpc.Option) (ids.ID, error) + GetAtomicTxStatus(ctx context.Context, txID ids.ID, options ...rpc.Option) (Status, error) + GetAtomicTx(ctx context.Context, txID ids.ID, options ...rpc.Option) ([]byte, error) + GetAtomicUTXOs(ctx context.Context, addrs []ids.ShortID, sourceChain string, limit uint32, startAddress ids.ShortID, startUTXOID ids.ID, options ...rpc.Option) ([][]byte, ids.ShortID, ids.ID, error) + ExportKey(ctx context.Context, userPass api.UserPass, addr common.Address, options ...rpc.Option) (*secp256k1.PrivateKey, string, error) + ImportKey(ctx context.Context, userPass api.UserPass, privateKey *secp256k1.PrivateKey, options ...rpc.Option) (common.Address, error) + Import(ctx context.Context, userPass api.UserPass, to common.Address, sourceChain string, options ...rpc.Option) (ids.ID, error) + ExportAVAX(ctx context.Context, userPass api.UserPass, amount uint64, to ids.ShortID, targetChain string, options ...rpc.Option) (ids.ID, error) + Export(ctx context.Context, userPass api.UserPass, amount uint64, to ids.ShortID, targetChain string, assetID string, options ...rpc.Option) (ids.ID, error) StartCPUProfiler(ctx context.Context, options ...rpc.Option) error StopCPUProfiler(ctx context.Context, options ...rpc.Option) error MemoryProfile(ctx context.Context, options ...rpc.Option) error @@ -28,22 +43,168 @@ type Client interface { // Client implementation for interacting with EVM [chain] type client struct { + requester rpc.EndpointRequester adminRequester rpc.EndpointRequester } // NewClient returns a Client for interacting with EVM [chain] func NewClient(uri, chain string) Client { return &client{ + requester: rpc.NewEndpointRequester(fmt.Sprintf("%s/ext/bc/%s/avax", uri, chain)), adminRequester: rpc.NewEndpointRequester(fmt.Sprintf("%s/ext/bc/%s/admin", uri, chain)), } } // NewCChainClient returns a Client for interacting with the C Chain func NewCChainClient(uri string) Client { - // TODO: Update for Subnet-EVM compatibility return NewClient(uri, "C") } +// IssueTx issues a transaction to a node and returns the TxID +func (c *client) IssueTx(ctx context.Context, txBytes []byte, options ...rpc.Option) (ids.ID, error) { + res := &api.JSONTxID{} + txStr, err := formatting.Encode(formatting.Hex, txBytes) + if err != nil { + return res.TxID, fmt.Errorf("problem hex encoding bytes: %w", err) + } + err = c.requester.SendRequest(ctx, "avax.issueTx", &api.FormattedTx{ + Tx: txStr, + Encoding: formatting.Hex, + }, res, options...) + return res.TxID, err +} + +// GetAtomicTxStatus returns the status of [txID] +func (c *client) GetAtomicTxStatus(ctx context.Context, txID ids.ID, options ...rpc.Option) (Status, error) { + res := &GetAtomicTxStatusReply{} + err := c.requester.SendRequest(ctx, "avax.getAtomicTxStatus", &api.JSONTxID{ + TxID: txID, + }, res, options...) + return res.Status, err +} + +// GetAtomicTx returns the byte representation of [txID] +func (c *client) GetAtomicTx(ctx context.Context, txID ids.ID, options ...rpc.Option) ([]byte, error) { + res := &api.FormattedTx{} + err := c.requester.SendRequest(ctx, "avax.getAtomicTx", &api.GetTxArgs{ + TxID: txID, + Encoding: formatting.Hex, + }, res, options...) + if err != nil { + return nil, err + } + + return formatting.Decode(formatting.Hex, res.Tx) +} + +// GetAtomicUTXOs returns the byte representation of the atomic UTXOs controlled by [addresses] +// from [sourceChain] +func (c *client) GetAtomicUTXOs(ctx context.Context, addrs []ids.ShortID, sourceChain string, limit uint32, startAddress ids.ShortID, startUTXOID ids.ID, options ...rpc.Option) ([][]byte, ids.ShortID, ids.ID, error) { + res := &api.GetUTXOsReply{} + err := c.requester.SendRequest(ctx, "avax.getUTXOs", &api.GetUTXOsArgs{ + Addresses: ids.ShortIDsToStrings(addrs), + SourceChain: sourceChain, + Limit: json.Uint32(limit), + StartIndex: api.Index{ + Address: startAddress.String(), + UTXO: startUTXOID.String(), + }, + Encoding: formatting.Hex, + }, res, options...) + if err != nil { + return nil, ids.ShortID{}, ids.Empty, err + } + + utxos := make([][]byte, len(res.UTXOs)) + for i, utxo := range res.UTXOs { + utxoBytes, err := formatting.Decode(res.Encoding, utxo) + if err != nil { + return nil, ids.ShortID{}, ids.Empty, err + } + utxos[i] = utxoBytes + } + endAddr, err := address.ParseToID(res.EndIndex.Address) + if err != nil { + return nil, ids.ShortID{}, ids.Empty, err + } + endUTXOID, err := ids.FromString(res.EndIndex.UTXO) + return utxos, endAddr, endUTXOID, err +} + +// ExportKey returns the private key corresponding to [addr] controlled by [user] +// in both Avalanche standard format and hex format +func (c *client) ExportKey(ctx context.Context, user api.UserPass, addr common.Address, options ...rpc.Option) (*secp256k1.PrivateKey, string, error) { + res := &ExportKeyReply{} + err := c.requester.SendRequest(ctx, "avax.exportKey", &ExportKeyArgs{ + UserPass: user, + Address: addr.Hex(), + }, res, options...) + return res.PrivateKey, res.PrivateKeyHex, err +} + +// ImportKey imports [privateKey] to [user] +func (c *client) ImportKey(ctx context.Context, user api.UserPass, privateKey *secp256k1.PrivateKey, options ...rpc.Option) (common.Address, error) { + res := &api.JSONAddress{} + err := c.requester.SendRequest(ctx, "avax.importKey", &ImportKeyArgs{ + UserPass: user, + PrivateKey: privateKey, + }, res, options...) + if err != nil { + return common.Address{}, err + } + return ParseEthAddress(res.Address) +} + +// Import sends an import transaction to import funds from [sourceChain] and +// returns the ID of the newly created transaction +func (c *client) Import(ctx context.Context, user api.UserPass, to common.Address, sourceChain string, options ...rpc.Option) (ids.ID, error) { + res := &api.JSONTxID{} + err := c.requester.SendRequest(ctx, "avax.import", &ImportArgs{ + UserPass: user, + To: to, + SourceChain: sourceChain, + }, res, options...) + return res.TxID, err +} + +// ExportAVAX sends AVAX from this chain to the address specified by [to]. +// Returns the ID of the newly created atomic transaction +func (c *client) ExportAVAX( + ctx context.Context, + user api.UserPass, + amount uint64, + to ids.ShortID, + targetChain string, + options ...rpc.Option, +) (ids.ID, error) { + return c.Export(ctx, user, amount, to, targetChain, "AVAX", options...) +} + +// Export sends an asset from this chain to the P/C-Chain. +// After this tx is accepted, the AVAX must be imported to the P/C-chain with an importTx. +// Returns the ID of the newly created atomic transaction +func (c *client) Export( + ctx context.Context, + user api.UserPass, + amount uint64, + to ids.ShortID, + targetChain string, + assetID string, + options ...rpc.Option, +) (ids.ID, error) { + res := &api.JSONTxID{} + err := c.requester.SendRequest(ctx, "avax.export", &ExportArgs{ + ExportAVAXArgs: ExportAVAXArgs{ + UserPass: user, + Amount: json.Uint64(amount), + TargetChain: targetChain, + To: to.String(), + }, + AssetID: assetID, + }, res, options...) + return res.TxID, err +} + func (c *client) StartCPUProfiler(ctx context.Context, options ...rpc.Option) error { return c.adminRequester.SendRequest(ctx, "admin.startCPUProfiler", struct{}{}, &api.EmptyReply{}, options...) } diff --git a/plugin/evm/config.go b/plugin/evm/config.go index 47b342537c..1b583c433a 100644 --- a/plugin/evm/config.go +++ b/plugin/evm/config.go @@ -42,7 +42,7 @@ const ( defaultPushRegossipNumPeers = 0 defaultPushGossipFrequency = 100 * time.Millisecond defaultPullGossipFrequency = 1 * time.Second - defaultRegossipFrequency = 30 * time.Second + defaultTxRegossipFrequency = 30 * time.Second defaultOfflinePruningBloomFilterSize uint64 = 512 // Default size (MB) for the offline pruner to use defaultLogLevel = "info" defaultLogJSONFormat = false @@ -83,14 +83,13 @@ type Duration struct { // Config ... type Config struct { - // Airdrop - AirdropFile string `json:"airdrop"` - - // Subnet EVM APIs - SnowmanAPIEnabled bool `json:"snowman-api-enabled"` - AdminAPIEnabled bool `json:"admin-api-enabled"` - AdminAPIDir string `json:"admin-api-dir"` - WarpAPIEnabled bool `json:"warp-api-enabled"` + // Coreth APIs + SnowmanAPIEnabled bool `json:"snowman-api-enabled"` + AdminAPIEnabled bool `json:"admin-api-enabled"` + AdminAPIDir string `json:"admin-api-dir"` + CorethAdminAPIEnabled bool `json:"coreth-admin-api-enabled"` // Deprecated: use AdminAPIEnabled instead + CorethAdminAPIDir string `json:"coreth-admin-api-dir"` // Deprecated: use AdminAPIDir instead + WarpAPIEnabled bool `json:"warp-api-enabled"` // EnabledEthAPIs is a list of Ethereum services that should be enabled // If none is specified, then we use the default list [defaultEnabledAPIs] @@ -154,23 +153,20 @@ type Config struct { KeystoreInsecureUnlockAllowed bool `json:"keystore-insecure-unlock-allowed"` // Gossip Settings - PushGossipPercentStake float64 `json:"push-gossip-percent-stake"` - PushGossipNumValidators int `json:"push-gossip-num-validators"` - PushGossipNumPeers int `json:"push-gossip-num-peers"` - PushRegossipNumValidators int `json:"push-regossip-num-validators"` - PushRegossipNumPeers int `json:"push-regossip-num-peers"` - PushGossipFrequency Duration `json:"push-gossip-frequency"` - PullGossipFrequency Duration `json:"pull-gossip-frequency"` - RegossipFrequency Duration `json:"regossip-frequency"` - PriorityRegossipAddresses []common.Address `json:"priority-regossip-addresses"` + PushGossipPercentStake float64 `json:"push-gossip-percent-stake"` + PushGossipNumValidators int `json:"push-gossip-num-validators"` + PushGossipNumPeers int `json:"push-gossip-num-peers"` + PushRegossipNumValidators int `json:"push-regossip-num-validators"` + PushRegossipNumPeers int `json:"push-regossip-num-peers"` + PushGossipFrequency Duration `json:"push-gossip-frequency"` + PullGossipFrequency Duration `json:"pull-gossip-frequency"` + RegossipFrequency Duration `json:"regossip-frequency"` + TxRegossipFrequency Duration `json:"tx-regossip-frequency"` // Deprecated: use RegossipFrequency instead // Log LogLevel string `json:"log-level"` LogJSONFormat bool `json:"log-json-format"` - // Address for Tx Fees (must be empty if not supported by blockchain) - FeeRecipient string `json:"feeRecipient"` - // Offline Pruning Settings OfflinePruning bool `json:"offline-pruning-enabled"` OfflinePruningBloomFilterSize uint64 `json:"offline-pruning-bloom-filter-size"` @@ -180,7 +176,7 @@ type Config struct { MaxOutboundActiveRequests int64 `json:"max-outbound-active-requests"` // Sync settings - StateSyncEnabled bool `json:"state-sync-enabled"` + StateSyncEnabled *bool `json:"state-sync-enabled"` // Pointer distinguishes false (no state sync) and not set (state sync only at genesis). StateSyncSkipResume bool `json:"state-sync-skip-resume"` // Forces state sync to use the highest available summary block StateSyncServerTrieCache int `json:"state-sync-server-trie-cache"` StateSyncIDs string `json:"state-sync-ids"` @@ -269,7 +265,7 @@ func (c *Config) SetDefaults() { c.PushRegossipNumPeers = defaultPushRegossipNumPeers c.PushGossipFrequency.Duration = defaultPushGossipFrequency c.PullGossipFrequency.Duration = defaultPullGossipFrequency - c.RegossipFrequency.Duration = defaultRegossipFrequency + c.RegossipFrequency.Duration = defaultTxRegossipFrequency c.OfflinePruningBloomFilterSize = defaultOfflinePruningBloomFilterSize c.LogLevel = defaultLogLevel c.LogJSONFormat = defaultLogJSONFormat @@ -328,6 +324,18 @@ func (c *Config) Validate() error { func (c *Config) Deprecate() string { msg := "" // Deprecate the old config options and set the new ones. + if c.CorethAdminAPIEnabled { + msg += "coreth-admin-api-enabled is deprecated, use admin-api-enabled instead. " + c.AdminAPIEnabled = c.CorethAdminAPIEnabled + } + if c.CorethAdminAPIDir != "" { + msg += "coreth-admin-api-dir is deprecated, use admin-api-dir instead. " + c.AdminAPIDir = c.CorethAdminAPIDir + } + if c.TxRegossipFrequency != (Duration{}) { + msg += "tx-regossip-frequency is deprecated, use regossip-frequency instead. " + c.RegossipFrequency = c.TxRegossipFrequency + } if c.TxLookupLimit != 0 { msg += "tx-lookup-limit is deprecated, use transaction-history instead. " c.TransactionHistory = c.TxLookupLimit diff --git a/plugin/evm/config_test.go b/plugin/evm/config_test.go index 2a69ea68d0..e6f17283b4 100644 --- a/plugin/evm/config_test.go +++ b/plugin/evm/config_test.go @@ -13,6 +13,12 @@ import ( "github.com/stretchr/testify/assert" ) +// newTrue returns a pointer to a bool that is true +func newTrue() *bool { + b := true + return &b +} + func TestUnmarshalConfig(t *testing.T) { tests := []struct { name string @@ -62,7 +68,7 @@ func TestUnmarshalConfig(t *testing.T) { { "state sync enabled", []byte(`{"state-sync-enabled":true}`), - Config{StateSyncEnabled: true}, + Config{StateSyncEnabled: newTrue()}, false, }, { diff --git a/plugin/evm/factory.go b/plugin/evm/factory.go index 1b307df562..a08fbc2a40 100644 --- a/plugin/evm/factory.go +++ b/plugin/evm/factory.go @@ -11,8 +11,7 @@ import ( var ( // ID this VM should be referenced by - IDStr = "subnetevm" - ID = ids.ID{'s', 'u', 'b', 'n', 'e', 't', 'e', 'v', 'm'} + ID = ids.ID{'e', 'v', 'm'} _ vms.Factory = &Factory{} ) diff --git a/plugin/evm/gossip.go b/plugin/evm/gossip.go index 05270f7a65..22d0e8cc35 100644 --- a/plugin/evm/gossip.go +++ b/plugin/evm/gossip.go @@ -31,9 +31,11 @@ const pendingTxsBuffer = 10 var ( _ p2p.Handler = (*txGossipHandler)(nil) - _ gossip.Gossipable = (*GossipEthTx)(nil) - _ gossip.Marshaller[*GossipEthTx] = (*GossipEthTxMarshaller)(nil) - _ gossip.Set[*GossipEthTx] = (*GossipEthTxPool)(nil) + _ gossip.Gossipable = (*GossipEthTx)(nil) + _ gossip.Gossipable = (*GossipAtomicTx)(nil) + _ gossip.Marshaller[*GossipAtomicTx] = (*GossipAtomicTxMarshaller)(nil) + _ gossip.Marshaller[*GossipEthTx] = (*GossipEthTxMarshaller)(nil) + _ gossip.Set[*GossipEthTx] = (*GossipEthTxPool)(nil) _ eth.PushGossiper = (*EthPushGossiper)(nil) ) @@ -88,6 +90,27 @@ func (t txGossipHandler) AppRequest(ctx context.Context, nodeID ids.NodeID, dead return t.appRequestHandler.AppRequest(ctx, nodeID, deadline, requestBytes) } +type GossipAtomicTxMarshaller struct{} + +func (g GossipAtomicTxMarshaller) MarshalGossip(tx *GossipAtomicTx) ([]byte, error) { + return tx.Tx.SignedBytes(), nil +} + +func (g GossipAtomicTxMarshaller) UnmarshalGossip(bytes []byte) (*GossipAtomicTx, error) { + tx, err := ExtractAtomicTx(bytes, Codec) + return &GossipAtomicTx{ + Tx: tx, + }, err +} + +type GossipAtomicTx struct { + Tx *Tx +} + +func (tx *GossipAtomicTx) GossipID() ids.ID { + return tx.Tx.ID() +} + func NewGossipEthTxPool(mempool *txpool.TxPool, registerer prometheus.Registerer) (*GossipEthTxPool, error) { bloom, err := gossip.NewBloomFilter(registerer, "eth_tx_bloom_filter", txGossipBloomMinTargetElements, txGossipBloomTargetFalsePositiveRate, txGossipBloomResetFalsePositiveRate) if err != nil { diff --git a/plugin/evm/gossip_stats.go b/plugin/evm/gossip_stats.go index 3a6f552fcc..5aca018ee8 100644 --- a/plugin/evm/gossip_stats.go +++ b/plugin/evm/gossip_stats.go @@ -9,9 +9,14 @@ var _ GossipStats = &gossipStats{} // GossipStats contains methods for updating incoming and outgoing gossip stats. type GossipStats interface { + IncAtomicGossipReceived() IncEthTxsGossipReceived() // new vs. known txs received + IncAtomicGossipReceivedDropped() + IncAtomicGossipReceivedError() + IncAtomicGossipReceivedKnown() + IncAtomicGossipReceivedNew() IncEthTxsGossipReceivedError() IncEthTxsGossipReceivedKnown() IncEthTxsGossipReceivedNew() @@ -20,27 +25,43 @@ type GossipStats interface { // gossipStats implements stats for incoming and outgoing gossip stats. type gossipStats struct { // messages + atomicGossipReceived metrics.Counter ethTxsGossipReceived metrics.Counter // new vs. known txs received - ethTxsGossipReceivedError metrics.Counter - ethTxsGossipReceivedKnown metrics.Counter - ethTxsGossipReceivedNew metrics.Counter + atomicGossipReceivedDropped metrics.Counter + atomicGossipReceivedError metrics.Counter + atomicGossipReceivedKnown metrics.Counter + atomicGossipReceivedNew metrics.Counter + ethTxsGossipReceivedError metrics.Counter + ethTxsGossipReceivedKnown metrics.Counter + ethTxsGossipReceivedNew metrics.Counter } func NewGossipStats() GossipStats { return &gossipStats{ - ethTxsGossipReceived: metrics.GetOrRegisterCounter("gossip_eth_txs_received", nil), - ethTxsGossipReceivedError: metrics.GetOrRegisterCounter("gossip_eth_txs_received_error", nil), - ethTxsGossipReceivedKnown: metrics.GetOrRegisterCounter("gossip_eth_txs_received_known", nil), - ethTxsGossipReceivedNew: metrics.GetOrRegisterCounter("gossip_eth_txs_received_new", nil), + atomicGossipReceived: metrics.GetOrRegisterCounter("gossip_atomic_received", nil), + ethTxsGossipReceived: metrics.GetOrRegisterCounter("gossip_eth_txs_received", nil), + + atomicGossipReceivedDropped: metrics.GetOrRegisterCounter("gossip_atomic_received_dropped", nil), + atomicGossipReceivedError: metrics.GetOrRegisterCounter("gossip_atomic_received_error", nil), + atomicGossipReceivedKnown: metrics.GetOrRegisterCounter("gossip_atomic_received_known", nil), + atomicGossipReceivedNew: metrics.GetOrRegisterCounter("gossip_atomic_received_new", nil), + ethTxsGossipReceivedError: metrics.GetOrRegisterCounter("gossip_eth_txs_received_error", nil), + ethTxsGossipReceivedKnown: metrics.GetOrRegisterCounter("gossip_eth_txs_received_known", nil), + ethTxsGossipReceivedNew: metrics.GetOrRegisterCounter("gossip_eth_txs_received_new", nil), } } // incoming messages +func (g *gossipStats) IncAtomicGossipReceived() { g.atomicGossipReceived.Inc(1) } func (g *gossipStats) IncEthTxsGossipReceived() { g.ethTxsGossipReceived.Inc(1) } // new vs. known txs received -func (g *gossipStats) IncEthTxsGossipReceivedError() { g.ethTxsGossipReceivedError.Inc(1) } -func (g *gossipStats) IncEthTxsGossipReceivedKnown() { g.ethTxsGossipReceivedKnown.Inc(1) } -func (g *gossipStats) IncEthTxsGossipReceivedNew() { g.ethTxsGossipReceivedNew.Inc(1) } +func (g *gossipStats) IncAtomicGossipReceivedDropped() { g.atomicGossipReceivedDropped.Inc(1) } +func (g *gossipStats) IncAtomicGossipReceivedError() { g.atomicGossipReceivedError.Inc(1) } +func (g *gossipStats) IncAtomicGossipReceivedKnown() { g.atomicGossipReceivedKnown.Inc(1) } +func (g *gossipStats) IncAtomicGossipReceivedNew() { g.atomicGossipReceivedNew.Inc(1) } +func (g *gossipStats) IncEthTxsGossipReceivedError() { g.ethTxsGossipReceivedError.Inc(1) } +func (g *gossipStats) IncEthTxsGossipReceivedKnown() { g.ethTxsGossipReceivedKnown.Inc(1) } +func (g *gossipStats) IncEthTxsGossipReceivedNew() { g.ethTxsGossipReceivedNew.Inc(1) } diff --git a/plugin/evm/gossip_test.go b/plugin/evm/gossip_test.go index 61772063d4..86ec687082 100644 --- a/plugin/evm/gossip_test.go +++ b/plugin/evm/gossip_test.go @@ -9,7 +9,11 @@ import ( "testing" "time" + "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/network/p2p/gossip" + "github.com/ava-labs/avalanchego/snow" + "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" + "github.com/ava-labs/avalanchego/vms/components/verify" "github.com/ava-labs/subnet-evm/consensus/dummy" "github.com/ava-labs/subnet-evm/core" "github.com/ava-labs/subnet-evm/core/rawdb" @@ -25,6 +29,110 @@ import ( "github.com/stretchr/testify/require" ) +func TestGossipAtomicTxMarshaller(t *testing.T) { + require := require.New(t) + + want := &GossipAtomicTx{ + Tx: &Tx{ + UnsignedAtomicTx: &UnsignedImportTx{}, + Creds: []verify.Verifiable{}, + }, + } + marshaller := GossipAtomicTxMarshaller{} + + key0 := testKeys[0] + require.NoError(want.Tx.Sign(Codec, [][]*secp256k1.PrivateKey{{key0}})) + + bytes, err := marshaller.MarshalGossip(want) + require.NoError(err) + + got, err := marshaller.UnmarshalGossip(bytes) + require.NoError(err) + require.Equal(want.GossipID(), got.GossipID()) +} + +func TestAtomicMempoolIterate(t *testing.T) { + txs := []*GossipAtomicTx{ + { + Tx: &Tx{ + UnsignedAtomicTx: &TestUnsignedTx{ + IDV: ids.GenerateTestID(), + }, + }, + }, + { + Tx: &Tx{ + UnsignedAtomicTx: &TestUnsignedTx{ + IDV: ids.GenerateTestID(), + }, + }, + }, + } + + tests := []struct { + name string + add []*GossipAtomicTx + f func(tx *GossipAtomicTx) bool + possibleValues []*GossipAtomicTx + expectedLen int + }{ + { + name: "func matches nothing", + add: txs, + f: func(*GossipAtomicTx) bool { + return false + }, + possibleValues: nil, + }, + { + name: "func matches all", + add: txs, + f: func(*GossipAtomicTx) bool { + return true + }, + possibleValues: txs, + expectedLen: 2, + }, + { + name: "func matches subset", + add: txs, + f: func(tx *GossipAtomicTx) bool { + return tx.Tx == txs[0].Tx + }, + possibleValues: txs, + expectedLen: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + m, err := NewMempool(&snow.Context{}, prometheus.NewRegistry(), 10, nil) + require.NoError(err) + + for _, add := range tt.add { + require.NoError(m.Add(add)) + } + + matches := make([]*GossipAtomicTx, 0) + f := func(tx *GossipAtomicTx) bool { + match := tt.f(tx) + + if match { + matches = append(matches, tx) + } + + return match + } + + m.Iterate(f) + + require.Len(matches, tt.expectedLen) + require.Subset(tt.possibleValues, matches) + }) + } +} + func TestGossipEthTxMarshaller(t *testing.T) { require := require.New(t) @@ -72,7 +180,7 @@ func TestGossipSubscribe(t *testing.T) { // Notify mempool about txs errs := txPool.AddRemotesSync(ethTxs) for _, err := range errs { - require.NoError(err, "failed adding subnet-evm tx to remote mempool") + require.NoError(err, "failed adding tx to remote mempool") } require.EventuallyWithTf( diff --git a/plugin/evm/gossiper_eth_gossiping_test.go b/plugin/evm/gossiper_eth_gossiping_test.go index d7052a6d7d..853d793fc1 100644 --- a/plugin/evm/gossiper_eth_gossiping_test.go +++ b/plugin/evm/gossiper_eth_gossiping_test.go @@ -33,7 +33,7 @@ func fundAddressByGenesis(addrs []common.Address) (string, error) { balance := big.NewInt(0xffffffffffffff) genesis := &core.Genesis{ Difficulty: common.Big0, - GasLimit: params.TestChainConfig.FeeConfig.GasLimit.Uint64(), + GasLimit: uint64(5000000), } funds := make(map[common.Address]core.GenesisAccount) for _, addr := range addrs { @@ -87,7 +87,7 @@ func TestMempoolEthTxsAppGossipHandling(t *testing.T) { genesisJSON, err := fundAddressByGenesis([]common.Address{addr}) assert.NoError(err) - _, vm, _, sender := GenesisVM(t, true, genesisJSON, "", "") + _, vm, _, _, sender := GenesisVM(t, true, genesisJSON, "", "") defer func() { err := vm.Shutdown(context.Background()) assert.NoError(err) @@ -115,7 +115,7 @@ func TestMempoolEthTxsAppGossipHandling(t *testing.T) { // Txs must be submitted over the API to be included in push gossip. // (i.e., txs received via p2p are not included in push gossip) - vm.eth.APIBackend.SendTx(context.Background(), tx) + err = vm.eth.APIBackend.SendTx(context.Background(), tx) assert.NoError(err) assert.False(txRequested, "tx should not be requested") diff --git a/plugin/evm/handler.go b/plugin/evm/handler.go index f01db79b04..4a0c7bb202 100644 --- a/plugin/evm/handler.go +++ b/plugin/evm/handler.go @@ -16,19 +16,82 @@ import ( // GossipHandler handles incoming gossip messages type GossipHandler struct { - vm *VM - txPool *txpool.TxPool - stats GossipStats + vm *VM + atomicMempool *Mempool + txPool *txpool.TxPool + stats GossipStats } func NewGossipHandler(vm *VM, stats GossipStats) *GossipHandler { return &GossipHandler{ - vm: vm, - txPool: vm.txPool, - stats: stats, + vm: vm, + atomicMempool: vm.mempool, + txPool: vm.txPool, + stats: stats, } } +func (h *GossipHandler) HandleAtomicTx(nodeID ids.NodeID, msg message.AtomicTxGossip) error { + log.Trace( + "AppGossip called with AtomicTxGossip", + "peerID", nodeID, + ) + + if len(msg.Tx) == 0 { + log.Trace( + "AppGossip received empty AtomicTxGossip Message", + "peerID", nodeID, + ) + return nil + } + + // In the case that the gossip message contains a transaction, + // attempt to parse it and add it as a remote. + tx := Tx{} + if _, err := Codec.Unmarshal(msg.Tx, &tx); err != nil { + log.Trace( + "AppGossip provided invalid tx", + "err", err, + ) + return nil + } + unsignedBytes, err := Codec.Marshal(codecVersion, &tx.UnsignedAtomicTx) + if err != nil { + log.Trace( + "AppGossip failed to marshal unsigned tx", + "err", err, + ) + return nil + } + tx.Initialize(unsignedBytes, msg.Tx) + + txID := tx.ID() + h.stats.IncAtomicGossipReceived() + if _, dropped, found := h.atomicMempool.GetTx(txID); found { + h.stats.IncAtomicGossipReceivedKnown() + return nil + } else if dropped { + h.stats.IncAtomicGossipReceivedDropped() + return nil + } + + h.stats.IncAtomicGossipReceivedNew() + + h.vm.ctx.Lock.RLock() + defer h.vm.ctx.Lock.RUnlock() + + if err := h.vm.mempool.AddTx(&tx); err != nil { + log.Trace( + "AppGossip provided invalid transaction", + "peerID", nodeID, + "err", err, + ) + h.stats.IncAtomicGossipReceivedError() + } + + return nil +} + func (h *GossipHandler) HandleEthTxs(nodeID ids.NodeID, msg message.EthTxsGossip) error { log.Trace( "AppGossip called with EthTxsGossip", diff --git a/plugin/evm/log.go b/plugin/evm/log.go index 048367818d..9d0f4cecc2 100644 --- a/plugin/evm/log.go +++ b/plugin/evm/log.go @@ -15,7 +15,7 @@ import ( "golang.org/x/exp/slog" ) -type SubnetEVMLogger struct { +type CorethLogger struct { gethlog.Logger logLevel *slog.LevelVar @@ -23,7 +23,7 @@ type SubnetEVMLogger struct { // InitLogger initializes logger with alias and sets the log level and format with the original [os.StdErr] interface // along with the context logger. -func InitLogger(alias string, level string, jsonFormat bool, writer io.Writer) (SubnetEVMLogger, error) { +func InitLogger(alias string, level string, jsonFormat bool, writer io.Writer) (CorethLogger, error) { logLevel := &slog.LevelVar{} var handler slog.Handler @@ -46,40 +46,45 @@ func InitLogger(alias string, level string, jsonFormat bool, writer io.Writer) ( } // Create handler - c := SubnetEVMLogger{ + c := CorethLogger{ Logger: gethlog.NewLogger(handler), logLevel: logLevel, } if err := c.SetLogLevel(level); err != nil { - return SubnetEVMLogger{}, err + return CorethLogger{}, err } gethlog.SetDefault(c.Logger) return c, nil } // SetLogLevel sets the log level of initialized log handler. -func (s *SubnetEVMLogger) SetLogLevel(level string) error { +func (c *CorethLogger) SetLogLevel(level string) error { // Set log level logLevel, err := log.LvlFromString(level) if err != nil { return err } - s.logLevel.Set(logLevel) + c.logLevel.Set(logLevel) return nil } // locationTrims are trimmed for display to avoid unwieldy log lines. var locationTrims = []string{ - "subnet-evm/", + "coreth", } func trimPrefixes(s string) string { for _, prefix := range locationTrims { idx := strings.LastIndex(s, prefix) - if idx >= 0 { - s = s[idx+len(prefix):] + if idx < 0 { + continue } + slashIdx := strings.Index(s[idx:], "/") + if slashIdx < 0 || slashIdx+idx >= len(s)-1 { + continue + } + s = s[idx+slashIdx+1:] } return s } diff --git a/plugin/evm/message/codec.go b/plugin/evm/message/codec.go index 9ae2112b1a..de3603b9c2 100644 --- a/plugin/evm/message/codec.go +++ b/plugin/evm/message/codec.go @@ -26,6 +26,7 @@ func init() { errs := wrappers.Errs{} errs.Add( // Gossip types + c.RegisterType(AtomicTxGossip{}), c.RegisterType(EthTxsGossip{}), // Types for state sync frontier consensus diff --git a/plugin/evm/message/handler.go b/plugin/evm/message/handler.go index d1e83d7883..9b94828509 100644 --- a/plugin/evm/message/handler.go +++ b/plugin/evm/message/handler.go @@ -18,11 +18,17 @@ var ( // GossipHandler handles incoming gossip messages type GossipHandler interface { + HandleAtomicTx(nodeID ids.NodeID, msg AtomicTxGossip) error HandleEthTxs(nodeID ids.NodeID, msg EthTxsGossip) error } type NoopMempoolGossipHandler struct{} +func (NoopMempoolGossipHandler) HandleAtomicTx(nodeID ids.NodeID, msg AtomicTxGossip) error { + log.Debug("dropping unexpected AtomicTxGossip message", "peerID", nodeID) + return nil +} + func (NoopMempoolGossipHandler) HandleEthTxs(nodeID ids.NodeID, msg EthTxsGossip) error { log.Debug("dropping unexpected EthTxsGossip message", "peerID", nodeID) return nil @@ -35,6 +41,7 @@ func (NoopMempoolGossipHandler) HandleEthTxs(nodeID ids.NodeID, msg EthTxsGossip // Also see GossipHandler for implementation style. type RequestHandler interface { HandleStateTrieLeafsRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, leafsRequest LeafsRequest) ([]byte, error) + HandleAtomicTrieLeafsRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, leafsRequest LeafsRequest) ([]byte, error) HandleBlockRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, request BlockRequest) ([]byte, error) HandleCodeRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, codeRequest CodeRequest) ([]byte, error) HandleMessageSignatureRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, signatureRequest MessageSignatureRequest) ([]byte, error) @@ -56,6 +63,10 @@ func (NoopRequestHandler) HandleStateTrieLeafsRequest(ctx context.Context, nodeI return nil, nil } +func (NoopRequestHandler) HandleAtomicTrieLeafsRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, leafsRequest LeafsRequest) ([]byte, error) { + return nil, nil +} + func (NoopRequestHandler) HandleBlockRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, request BlockRequest) ([]byte, error) { return nil, nil } diff --git a/plugin/evm/message/handler_test.go b/plugin/evm/message/handler_test.go index 8b87135ff5..a27b1f9d4f 100644 --- a/plugin/evm/message/handler_test.go +++ b/plugin/evm/message/handler_test.go @@ -12,7 +12,12 @@ import ( ) type CounterHandler struct { - EthTxs int + AtomicTx, EthTxs int +} + +func (h *CounterHandler) HandleAtomicTx(ids.NodeID, AtomicTxGossip) error { + h.AtomicTx++ + return nil } func (h *CounterHandler) HandleEthTxs(ids.NodeID, EthTxsGossip) error { @@ -20,6 +25,18 @@ func (h *CounterHandler) HandleEthTxs(ids.NodeID, EthTxsGossip) error { return nil } +func TestHandleAtomicTx(t *testing.T) { + assert := assert.New(t) + + handler := CounterHandler{} + msg := AtomicTxGossip{} + + err := msg.Handle(&handler, ids.EmptyNodeID) + assert.NoError(err) + assert.Equal(1, handler.AtomicTx) + assert.Zero(handler.EthTxs) +} + func TestHandleEthTxs(t *testing.T) { assert := assert.New(t) @@ -28,6 +45,7 @@ func TestHandleEthTxs(t *testing.T) { err := msg.Handle(&handler, ids.EmptyNodeID) assert.NoError(err) + assert.Zero(handler.AtomicTx) assert.Equal(1, handler.EthTxs) } @@ -38,4 +56,7 @@ func TestNoopHandler(t *testing.T) { err := handler.HandleEthTxs(ids.EmptyNodeID, EthTxsGossip{}) assert.NoError(err) + + err = handler.HandleAtomicTx(ids.EmptyNodeID, AtomicTxGossip{}) + assert.NoError(err) } diff --git a/plugin/evm/message/leafs_request.go b/plugin/evm/message/leafs_request.go index 872dea2824..22629e62ef 100644 --- a/plugin/evm/message/leafs_request.go +++ b/plugin/evm/message/leafs_request.go @@ -9,31 +9,65 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" ) const MaxCodeHashesPerRequest = 5 var _ Request = LeafsRequest{} +// NodeType outlines the trie that a leaf node belongs to +// handlers.LeafsRequestHandler uses this information to determine +// which of the two tries (state/atomic) to fetch the information from +type NodeType uint8 + +const ( + // StateTrieNode represents a leaf node that belongs to the coreth State trie + StateTrieNode NodeType = iota + 1 + // AtomicTrieNode represents a leaf node that belongs to the coreth evm.AtomicTrie + AtomicTrieNode +) + +func (nt NodeType) String() string { + switch nt { + case StateTrieNode: + return "StateTrie" + case AtomicTrieNode: + return "AtomicTrie" + default: + return "Unknown" + } +} + // LeafsRequest is a request to receive trie leaves at specified Root within Start and End byte range // Limit outlines maximum number of leaves to returns starting at Start +// NodeType outlines which trie to read from state/atomic. type LeafsRequest struct { - Root common.Hash `serialize:"true"` - Account common.Hash `serialize:"true"` - Start []byte `serialize:"true"` - End []byte `serialize:"true"` - Limit uint16 `serialize:"true"` + Root common.Hash `serialize:"true"` + Account common.Hash `serialize:"true"` + Start []byte `serialize:"true"` + End []byte `serialize:"true"` + Limit uint16 `serialize:"true"` + NodeType NodeType `serialize:"true"` } func (l LeafsRequest) String() string { return fmt.Sprintf( - "LeafsRequest(Root=%s, Account=%s, Start=%s, End %s, Limit=%d)", - l.Root, l.Account, common.Bytes2Hex(l.Start), common.Bytes2Hex(l.End), l.Limit, + "LeafsRequest(Root=%s, Account=%s, Start=%s, End=%s, Limit=%d, NodeType=%s)", + l.Root, l.Account, common.Bytes2Hex(l.Start), common.Bytes2Hex(l.End), l.Limit, l.NodeType, ) } func (l LeafsRequest) Handle(ctx context.Context, nodeID ids.NodeID, requestID uint32, handler RequestHandler) ([]byte, error) { - return handler.HandleStateTrieLeafsRequest(ctx, nodeID, requestID, l) + switch l.NodeType { + case StateTrieNode: + return handler.HandleStateTrieLeafsRequest(ctx, nodeID, requestID, l) + case AtomicTrieNode: + return handler.HandleAtomicTrieLeafsRequest(ctx, nodeID, requestID, l) + } + + log.Debug("node type is not recognised, dropping request", "nodeID", nodeID, "requestID", requestID, "nodeType", l.NodeType) + return nil, nil } // LeafsResponse is a response to a LeafsRequest diff --git a/plugin/evm/message/leafs_request_test.go b/plugin/evm/message/leafs_request_test.go index 6a698c890c..ab6cab5124 100644 --- a/plugin/evm/message/leafs_request_test.go +++ b/plugin/evm/message/leafs_request_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/ava-labs/avalanchego/ids" + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" ) @@ -32,13 +33,14 @@ func TestMarshalLeafsRequest(t *testing.T) { assert.NoError(t, err) leafsRequest := LeafsRequest{ - Root: common.BytesToHash([]byte("im ROOTing for ya")), - Start: startBytes, - End: endBytes, - Limit: 1024, + Root: common.BytesToHash([]byte("im ROOTing for ya")), + Start: startBytes, + End: endBytes, + Limit: 1024, + NodeType: StateTrieNode, } - base64LeafsRequest := "AAAAAAAAAAAAAAAAAAAAAABpbSBST09UaW5nIGZvciB5YQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIFL9/AchgmVPFj9fD5piHXKVZsdNEAN8TXu7BAfR4sZJAAAAIIGFWthoHQ2G0ekeABZ5OctmlNLEIqzSCKAHKTlIf2mZBAA=" + base64LeafsRequest := "AAAAAAAAAAAAAAAAAAAAAABpbSBST09UaW5nIGZvciB5YQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIFL9/AchgmVPFj9fD5piHXKVZsdNEAN8TXu7BAfR4sZJAAAAIIGFWthoHQ2G0ekeABZ5OctmlNLEIqzSCKAHKTlIf2mZBAAB" leafsRequestBytes, err := Codec.Marshal(Version, leafsRequest) assert.NoError(t, err) @@ -51,6 +53,7 @@ func TestMarshalLeafsRequest(t *testing.T) { assert.Equal(t, leafsRequest.Start, l.Start) assert.Equal(t, leafsRequest.End, l.End) assert.Equal(t, leafsRequest.Limit, l.Limit) + assert.Equal(t, leafsRequest.NodeType, l.NodeType) } // TestMarshalLeafsResponse asserts that the structure or serialization logic hasn't changed, primarily to @@ -115,13 +118,45 @@ func TestLeafsRequestValidation(t *testing.T) { }{ "node type StateTrieNode": { request: LeafsRequest{ - Root: common.BytesToHash([]byte("some hash goes here")), - Start: bytes.Repeat([]byte{0x00}, common.HashLength), - End: bytes.Repeat([]byte{0xff}, common.HashLength), - Limit: 10, + Root: common.BytesToHash([]byte("some hash goes here")), + Start: bytes.Repeat([]byte{0x00}, common.HashLength), + End: bytes.Repeat([]byte{0xff}, common.HashLength), + Limit: 10, + NodeType: StateTrieNode, }, assertResponse: func(t *testing.T) { assert.True(t, mockRequestHandler.handleStateTrieCalled) + assert.False(t, mockRequestHandler.handleAtomicTrieCalled) + assert.False(t, mockRequestHandler.handleBlockRequestCalled) + assert.False(t, mockRequestHandler.handleCodeRequestCalled) + }, + }, + "node type AtomicTrieNode": { + request: LeafsRequest{ + Root: common.BytesToHash([]byte("some hash goes here")), + Start: bytes.Repeat([]byte{0x00}, common.HashLength), + End: bytes.Repeat([]byte{0xff}, common.HashLength), + Limit: 10, + NodeType: AtomicTrieNode, + }, + assertResponse: func(t *testing.T) { + assert.False(t, mockRequestHandler.handleStateTrieCalled) + assert.True(t, mockRequestHandler.handleAtomicTrieCalled) + assert.False(t, mockRequestHandler.handleBlockRequestCalled) + assert.False(t, mockRequestHandler.handleCodeRequestCalled) + }, + }, + "unknown node type": { + request: LeafsRequest{ + Root: common.BytesToHash([]byte("some hash goes here")), + Start: bytes.Repeat([]byte{0x00}, common.HashLength), + End: bytes.Repeat([]byte{0xff}, common.HashLength), + Limit: 10, + NodeType: NodeType(11), + }, + assertResponse: func(t *testing.T) { + assert.False(t, mockRequestHandler.handleStateTrieCalled) + assert.False(t, mockRequestHandler.handleAtomicTrieCalled) assert.False(t, mockRequestHandler.handleBlockRequestCalled) assert.False(t, mockRequestHandler.handleCodeRequestCalled) }, @@ -140,6 +175,7 @@ var _ RequestHandler = &mockHandler{} type mockHandler struct { handleStateTrieCalled, + handleAtomicTrieCalled, handleBlockRequestCalled, handleCodeRequestCalled, handleMessageSignatureCalled, @@ -151,6 +187,11 @@ func (m *mockHandler) HandleStateTrieLeafsRequest(context.Context, ids.NodeID, u return nil, nil } +func (m *mockHandler) HandleAtomicTrieLeafsRequest(context.Context, ids.NodeID, uint32, LeafsRequest) ([]byte, error) { + m.handleAtomicTrieCalled = true + return nil, nil +} + func (m *mockHandler) HandleBlockRequest(context.Context, ids.NodeID, uint32, BlockRequest) ([]byte, error) { m.handleBlockRequestCalled = true return nil, nil @@ -172,6 +213,7 @@ func (m *mockHandler) HandleBlockSignatureRequest(ctx context.Context, nodeID id func (m *mockHandler) reset() { m.handleStateTrieCalled = false + m.handleAtomicTrieCalled = false m.handleBlockRequestCalled = false m.handleCodeRequestCalled = false } diff --git a/plugin/evm/message/message.go b/plugin/evm/message/message.go index 35887911c9..c8c80a0343 100644 --- a/plugin/evm/message/message.go +++ b/plugin/evm/message/message.go @@ -22,6 +22,7 @@ const ( ) var ( + _ GossipMessage = AtomicTxGossip{} _ GossipMessage = EthTxsGossip{} errUnexpectedCodecVersion = errors.New("unexpected codec version") @@ -35,6 +36,18 @@ type GossipMessage interface { Handle(handler GossipHandler, nodeID ids.NodeID) error } +type AtomicTxGossip struct { + Tx []byte `serialize:"true"` +} + +func (msg AtomicTxGossip) Handle(handler GossipHandler, nodeID ids.NodeID) error { + return handler.HandleAtomicTx(nodeID, msg) +} + +func (msg AtomicTxGossip) String() string { + return fmt.Sprintf("AtomicTxGossip(Len=%d)", len(msg.Tx)) +} + type EthTxsGossip struct { Txs []byte `serialize:"true"` } diff --git a/plugin/evm/message/message_test.go b/plugin/evm/message/message_test.go index e1779d4b75..dbcdea2d75 100644 --- a/plugin/evm/message/message_test.go +++ b/plugin/evm/message/message_test.go @@ -13,12 +13,35 @@ import ( "github.com/stretchr/testify/assert" ) +// TestMarshalAtomicTx asserts that the structure or serialization logic hasn't changed, primarily to +// ensure compatibility with the network. +func TestMarshalAtomicTx(t *testing.T) { + assert := assert.New(t) + + base64AtomicTxGossip := "AAAAAAAAAAAABGJsYWg=" + msg := []byte("blah") + builtMsg := AtomicTxGossip{ + Tx: msg, + } + builtMsgBytes, err := BuildGossipMessage(Codec, builtMsg) + assert.NoError(err) + assert.Equal(base64AtomicTxGossip, base64.StdEncoding.EncodeToString(builtMsgBytes)) + + parsedMsgIntf, err := ParseGossipMessage(Codec, builtMsgBytes) + assert.NoError(err) + + parsedMsg, ok := parsedMsgIntf.(AtomicTxGossip) + assert.True(ok) + + assert.Equal(msg, parsedMsg.Tx) +} + // TestMarshalEthTxs asserts that the structure or serialization logic hasn't changed, primarily to // ensure compatibility with the network. func TestMarshalEthTxs(t *testing.T) { assert := assert.New(t) - base64EthTxGossip := "AAAAAAAAAAAABGJsYWg=" + base64EthTxGossip := "AAAAAAABAAAABGJsYWg=" msg := []byte("blah") builtMsg := EthTxsGossip{ Txs: msg, diff --git a/plugin/evm/message/syncable.go b/plugin/evm/message/syncable.go index f79a9f2dfa..c8631bbb5e 100644 --- a/plugin/evm/message/syncable.go +++ b/plugin/evm/message/syncable.go @@ -22,6 +22,7 @@ type SyncSummary struct { BlockNumber uint64 `serialize:"true"` BlockHash common.Hash `serialize:"true"` BlockRoot common.Hash `serialize:"true"` + AtomicRoot common.Hash `serialize:"true"` summaryID ids.ID bytes []byte @@ -46,11 +47,12 @@ func NewSyncSummaryFromBytes(summaryBytes []byte, acceptImpl func(SyncSummary) ( return summary, nil } -func NewSyncSummary(blockHash common.Hash, blockNumber uint64, blockRoot common.Hash) (SyncSummary, error) { +func NewSyncSummary(blockHash common.Hash, blockNumber uint64, blockRoot common.Hash, atomicRoot common.Hash) (SyncSummary, error) { summary := SyncSummary{ BlockNumber: blockNumber, BlockHash: blockHash, BlockRoot: blockRoot, + AtomicRoot: atomicRoot, } bytes, err := Codec.Marshal(Version, &summary) if err != nil { @@ -80,7 +82,7 @@ func (s SyncSummary) ID() ids.ID { } func (s SyncSummary) String() string { - return fmt.Sprintf("SyncSummary(BlockHash=%s, BlockNumber=%d, BlockRoot=%s)", s.BlockHash, s.BlockNumber, s.BlockRoot) + return fmt.Sprintf("SyncSummary(BlockHash=%s, BlockNumber=%d, BlockRoot=%s, AtomicRoot=%s)", s.BlockHash, s.BlockNumber, s.BlockRoot, s.AtomicRoot) } func (s SyncSummary) Accept(context.Context) (block.StateSyncMode, error) { diff --git a/plugin/evm/network_handler.go b/plugin/evm/network_handler.go index 2e46477cc0..e57be8626d 100644 --- a/plugin/evm/network_handler.go +++ b/plugin/evm/network_handler.go @@ -21,10 +21,11 @@ import ( var _ message.RequestHandler = &networkHandler{} type networkHandler struct { - stateTrieLeafsRequestHandler *syncHandlers.LeafsRequestHandler - blockRequestHandler *syncHandlers.BlockRequestHandler - codeRequestHandler *syncHandlers.CodeRequestHandler - signatureRequestHandler *warpHandlers.SignatureRequestHandler + stateTrieLeafsRequestHandler *syncHandlers.LeafsRequestHandler + atomicTrieLeafsRequestHandler *syncHandlers.LeafsRequestHandler + blockRequestHandler *syncHandlers.BlockRequestHandler + codeRequestHandler *syncHandlers.CodeRequestHandler + signatureRequestHandler *warpHandlers.SignatureRequestHandler } // newNetworkHandler constructs the handler for serving network requests. @@ -32,15 +33,17 @@ func newNetworkHandler( provider syncHandlers.SyncDataProvider, diskDB ethdb.KeyValueReader, evmTrieDB *trie.Database, + atomicTrieDB *trie.Database, warpBackend warp.Backend, networkCodec codec.Manager, ) message.RequestHandler { syncStats := syncStats.NewHandlerStats(metrics.Enabled) return &networkHandler{ - stateTrieLeafsRequestHandler: syncHandlers.NewLeafsRequestHandler(evmTrieDB, provider, networkCodec, syncStats), - blockRequestHandler: syncHandlers.NewBlockRequestHandler(provider, networkCodec, syncStats), - codeRequestHandler: syncHandlers.NewCodeRequestHandler(diskDB, networkCodec, syncStats), - signatureRequestHandler: warpHandlers.NewSignatureRequestHandler(warpBackend, networkCodec), + stateTrieLeafsRequestHandler: syncHandlers.NewLeafsRequestHandler(evmTrieDB, provider, networkCodec, syncStats), + atomicTrieLeafsRequestHandler: syncHandlers.NewLeafsRequestHandler(atomicTrieDB, nil, networkCodec, syncStats), + blockRequestHandler: syncHandlers.NewBlockRequestHandler(provider, networkCodec, syncStats), + codeRequestHandler: syncHandlers.NewCodeRequestHandler(diskDB, networkCodec, syncStats), + signatureRequestHandler: warpHandlers.NewSignatureRequestHandler(warpBackend, networkCodec), } } @@ -48,6 +51,10 @@ func (n networkHandler) HandleStateTrieLeafsRequest(ctx context.Context, nodeID return n.stateTrieLeafsRequestHandler.OnLeafsRequest(ctx, nodeID, requestID, leafsRequest) } +func (n networkHandler) HandleAtomicTrieLeafsRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, leafsRequest message.LeafsRequest) ([]byte, error) { + return n.atomicTrieLeafsRequestHandler.OnLeafsRequest(ctx, nodeID, requestID, leafsRequest) +} + func (n networkHandler) HandleBlockRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, blockRequest message.BlockRequest) ([]byte, error) { return n.blockRequestHandler.OnBlockRequest(ctx, nodeID, requestID, blockRequest) } diff --git a/plugin/evm/service.go b/plugin/evm/service.go index a8fe61cbc0..4f6ad6a543 100644 --- a/plugin/evm/service.go +++ b/plugin/evm/service.go @@ -5,12 +5,41 @@ package evm import ( "context" + "errors" + "fmt" "math/big" + "net/http" + "github.com/ava-labs/avalanchego/api" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" + "github.com/ava-labs/avalanchego/utils/formatting" + "github.com/ava-labs/avalanchego/utils/json" + "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/subnet-evm/params" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/log" ) +// test constants +const ( + GenesisTestAddr = "0x751a0b96e1042bee789452ecb20253fba40dbe85" + GenesisTestKey = "0xabd71b35d559563fea757f0f5edbde286fb8c043105b15abb7cd57189306d7d1" + + // Max number of addresses that can be passed in as argument to GetUTXOs + maxGetUTXOsAddrs = 1024 +) + +var ( + errNoAddresses = errors.New("no addresses provided") + errNoSourceChain = errors.New("no source chain provided") + errNilTxID = errors.New("nil transaction ID") + errMissingPrivateKey = errors.New("argument 'privateKey' not given") + + initialBaseFee = big.NewInt(params.ApricotPhase3InitialBaseFee) +) + // SnowmanAPI introduces snowman specific functionality to the evm type SnowmanAPI struct{ vm *VM } @@ -33,6 +62,474 @@ func (api *SnowmanAPI) GetAcceptedFront(ctx context.Context) (*GetAcceptedFrontR // IssueBlock to the chain func (api *SnowmanAPI) IssueBlock(ctx context.Context) error { log.Info("Issuing a new block") + api.vm.builder.signalTxsReady() return nil } + +// AvaxAPI offers Avalanche network related API methods +type AvaxAPI struct{ vm *VM } + +// parseAssetID parses an assetID string into an ID +func (service *AvaxAPI) parseAssetID(assetID string) (ids.ID, error) { + if assetID == "" { + return ids.ID{}, fmt.Errorf("assetID is required") + } else if assetID == "AVAX" { + return service.vm.ctx.AVAXAssetID, nil + } else { + return ids.FromString(assetID) + } +} + +type VersionReply struct { + Version string `json:"version"` +} + +// ClientVersion returns the version of the VM running +func (service *AvaxAPI) Version(r *http.Request, _ *struct{}, reply *VersionReply) error { + reply.Version = Version + return nil +} + +// ExportKeyArgs are arguments for ExportKey +type ExportKeyArgs struct { + api.UserPass + Address string `json:"address"` +} + +// ExportKeyReply is the response for ExportKey +type ExportKeyReply struct { + // The decrypted PrivateKey for the Address provided in the arguments + PrivateKey *secp256k1.PrivateKey `json:"privateKey"` + PrivateKeyHex string `json:"privateKeyHex"` +} + +// ExportKey returns a private key from the provided user +func (service *AvaxAPI) ExportKey(r *http.Request, args *ExportKeyArgs, reply *ExportKeyReply) error { + log.Info("EVM: ExportKey called") + + address, err := ParseEthAddress(args.Address) + if err != nil { + return fmt.Errorf("couldn't parse %s to address: %s", args.Address, err) + } + + service.vm.ctx.Lock.Lock() + defer service.vm.ctx.Lock.Unlock() + + db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password) + if err != nil { + return fmt.Errorf("problem retrieving user '%s': %w", args.Username, err) + } + defer db.Close() + + user := user{db: db} + reply.PrivateKey, err = user.getKey(address) + if err != nil { + return fmt.Errorf("problem retrieving private key: %w", err) + } + reply.PrivateKeyHex = hexutil.Encode(reply.PrivateKey.Bytes()) + return nil +} + +// ImportKeyArgs are arguments for ImportKey +type ImportKeyArgs struct { + api.UserPass + PrivateKey *secp256k1.PrivateKey `json:"privateKey"` +} + +// ImportKey adds a private key to the provided user +func (service *AvaxAPI) ImportKey(r *http.Request, args *ImportKeyArgs, reply *api.JSONAddress) error { + log.Info("EVM: ImportKey called", "username", args.Username) + + if args.PrivateKey == nil { + return errMissingPrivateKey + } + + reply.Address = GetEthAddress(args.PrivateKey).Hex() + + service.vm.ctx.Lock.Lock() + defer service.vm.ctx.Lock.Unlock() + + db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password) + if err != nil { + return fmt.Errorf("problem retrieving data: %w", err) + } + defer db.Close() + + user := user{db: db} + if err := user.putAddress(args.PrivateKey); err != nil { + return fmt.Errorf("problem saving key %w", err) + } + return nil +} + +// ImportArgs are arguments for passing into Import requests +type ImportArgs struct { + api.UserPass + + // Fee that should be used when creating the tx + BaseFee *hexutil.Big `json:"baseFee"` + + // Chain the funds are coming from + SourceChain string `json:"sourceChain"` + + // The address that will receive the imported funds + To common.Address `json:"to"` +} + +// ImportAVAX is a deprecated name for Import. +func (service *AvaxAPI) ImportAVAX(_ *http.Request, args *ImportArgs, response *api.JSONTxID) error { + return service.Import(nil, args, response) +} + +// Import issues a transaction to import AVAX from the X-chain. The AVAX +// must have already been exported from the X-Chain. +func (service *AvaxAPI) Import(_ *http.Request, args *ImportArgs, response *api.JSONTxID) error { + log.Info("EVM: ImportAVAX called") + + chainID, err := service.vm.ctx.BCLookup.Lookup(args.SourceChain) + if err != nil { + return fmt.Errorf("problem parsing chainID %q: %w", args.SourceChain, err) + } + + service.vm.ctx.Lock.Lock() + defer service.vm.ctx.Lock.Unlock() + + // Get the user's info + db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password) + if err != nil { + return fmt.Errorf("couldn't get user '%s': %w", args.Username, err) + } + defer db.Close() + + user := user{db: db} + privKeys, err := user.getKeys() + if err != nil { // Get keys + return fmt.Errorf("couldn't get keys controlled by the user: %w", err) + } + + var baseFee *big.Int + if args.BaseFee == nil { + // Get the base fee to use + baseFee, err = service.vm.estimateBaseFee(context.Background()) + if err != nil { + return err + } + } else { + baseFee = args.BaseFee.ToInt() + } + + tx, err := service.vm.newImportTx(chainID, args.To, baseFee, privKeys) + if err != nil { + return err + } + + response.TxID = tx.ID() + if err := service.vm.mempool.AddLocalTx(tx); err != nil { + return err + } + service.vm.atomicTxPushGossiper.Add(&GossipAtomicTx{tx}) + return nil +} + +// ExportAVAXArgs are the arguments to ExportAVAX +type ExportAVAXArgs struct { + api.UserPass + + // Fee that should be used when creating the tx + BaseFee *hexutil.Big `json:"baseFee"` + + // Amount of asset to send + Amount json.Uint64 `json:"amount"` + + // Chain the funds are going to. Optional. Used if To address does not + // include the chainID. + TargetChain string `json:"targetChain"` + + // ID of the address that will receive the AVAX. This address may include + // the chainID, which is used to determine what the destination chain is. + To string `json:"to"` +} + +// ExportAVAX exports AVAX from the C-Chain to the X-Chain +// It must be imported on the X-Chain to complete the transfer +func (service *AvaxAPI) ExportAVAX(_ *http.Request, args *ExportAVAXArgs, response *api.JSONTxID) error { + return service.Export(nil, &ExportArgs{ + ExportAVAXArgs: *args, + AssetID: service.vm.ctx.AVAXAssetID.String(), + }, response) +} + +// ExportArgs are the arguments to Export +type ExportArgs struct { + ExportAVAXArgs + // AssetID of the tokens + AssetID string `json:"assetID"` +} + +// Export exports an asset from the C-Chain to the X-Chain +// It must be imported on the X-Chain to complete the transfer +func (service *AvaxAPI) Export(_ *http.Request, args *ExportArgs, response *api.JSONTxID) error { + log.Info("EVM: Export called") + + assetID, err := service.parseAssetID(args.AssetID) + if err != nil { + return err + } + + if args.Amount == 0 { + return errors.New("argument 'amount' must be > 0") + } + + // Get the chainID and parse the to address + chainID, to, err := service.vm.ParseAddress(args.To) + if err != nil { + chainID, err = service.vm.ctx.BCLookup.Lookup(args.TargetChain) + if err != nil { + return err + } + to, err = ids.ShortFromString(args.To) + if err != nil { + return err + } + } + + service.vm.ctx.Lock.Lock() + defer service.vm.ctx.Lock.Unlock() + + // Get this user's data + db, err := service.vm.ctx.Keystore.GetDatabase(args.Username, args.Password) + if err != nil { + return fmt.Errorf("problem retrieving user '%s': %w", args.Username, err) + } + defer db.Close() + + user := user{db: db} + privKeys, err := user.getKeys() + if err != nil { + return fmt.Errorf("couldn't get addresses controlled by the user: %w", err) + } + + var baseFee *big.Int + if args.BaseFee == nil { + // Get the base fee to use + baseFee, err = service.vm.estimateBaseFee(context.Background()) + if err != nil { + return err + } + } else { + baseFee = args.BaseFee.ToInt() + } + + // Create the transaction + tx, err := service.vm.newExportTx( + assetID, // AssetID + uint64(args.Amount), // Amount + chainID, // ID of the chain to send the funds to + to, // Address + baseFee, + privKeys, // Private keys + ) + if err != nil { + return fmt.Errorf("couldn't create tx: %w", err) + } + + response.TxID = tx.ID() + if err := service.vm.mempool.AddLocalTx(tx); err != nil { + return err + } + service.vm.atomicTxPushGossiper.Add(&GossipAtomicTx{tx}) + return nil +} + +// GetUTXOs gets all utxos for passed in addresses +func (service *AvaxAPI) GetUTXOs(r *http.Request, args *api.GetUTXOsArgs, reply *api.GetUTXOsReply) error { + log.Info("EVM: GetUTXOs called", "Addresses", args.Addresses) + + if len(args.Addresses) == 0 { + return errNoAddresses + } + if len(args.Addresses) > maxGetUTXOsAddrs { + return fmt.Errorf("number of addresses given, %d, exceeds maximum, %d", len(args.Addresses), maxGetUTXOsAddrs) + } + + if args.SourceChain == "" { + return errNoSourceChain + } + + chainID, err := service.vm.ctx.BCLookup.Lookup(args.SourceChain) + if err != nil { + return fmt.Errorf("problem parsing source chainID %q: %w", args.SourceChain, err) + } + sourceChain := chainID + + addrSet := set.Set[ids.ShortID]{} + for _, addrStr := range args.Addresses { + addr, err := service.vm.ParseServiceAddress(addrStr) + if err != nil { + return fmt.Errorf("couldn't parse address %q: %w", addrStr, err) + } + addrSet.Add(addr) + } + + startAddr := ids.ShortEmpty + startUTXO := ids.Empty + if args.StartIndex.Address != "" || args.StartIndex.UTXO != "" { + startAddr, err = service.vm.ParseServiceAddress(args.StartIndex.Address) + if err != nil { + return fmt.Errorf("couldn't parse start index address %q: %w", args.StartIndex.Address, err) + } + startUTXO, err = ids.FromString(args.StartIndex.UTXO) + if err != nil { + return fmt.Errorf("couldn't parse start index utxo: %w", err) + } + } + + service.vm.ctx.Lock.Lock() + defer service.vm.ctx.Lock.Unlock() + + utxos, endAddr, endUTXOID, err := service.vm.GetAtomicUTXOs( + sourceChain, + addrSet, + startAddr, + startUTXO, + int(args.Limit), + ) + if err != nil { + return fmt.Errorf("problem retrieving UTXOs: %w", err) + } + + reply.UTXOs = make([]string, len(utxos)) + for i, utxo := range utxos { + b, err := service.vm.codec.Marshal(codecVersion, utxo) + if err != nil { + return fmt.Errorf("problem marshalling UTXO: %w", err) + } + str, err := formatting.Encode(args.Encoding, b) + if err != nil { + return fmt.Errorf("problem encoding utxo: %w", err) + } + reply.UTXOs[i] = str + } + + endAddress, err := service.vm.FormatLocalAddress(endAddr) + if err != nil { + return fmt.Errorf("problem formatting address: %w", err) + } + + reply.EndIndex.Address = endAddress + reply.EndIndex.UTXO = endUTXOID.String() + reply.NumFetched = json.Uint64(len(utxos)) + reply.Encoding = args.Encoding + return nil +} + +func (service *AvaxAPI) IssueTx(r *http.Request, args *api.FormattedTx, response *api.JSONTxID) error { + log.Info("EVM: IssueTx called") + + txBytes, err := formatting.Decode(args.Encoding, args.Tx) + if err != nil { + return fmt.Errorf("problem decoding transaction: %w", err) + } + + tx := &Tx{} + if _, err := service.vm.codec.Unmarshal(txBytes, tx); err != nil { + return fmt.Errorf("problem parsing transaction: %w", err) + } + if err := tx.Sign(service.vm.codec, nil); err != nil { + return fmt.Errorf("problem initializing transaction: %w", err) + } + + response.TxID = tx.ID() + + service.vm.ctx.Lock.Lock() + defer service.vm.ctx.Lock.Unlock() + + if err := service.vm.mempool.AddLocalTx(tx); err != nil { + return err + } + service.vm.atomicTxPushGossiper.Add(&GossipAtomicTx{tx}) + return nil +} + +// GetAtomicTxStatusReply defines the GetAtomicTxStatus replies returned from the API +type GetAtomicTxStatusReply struct { + Status Status `json:"status"` + BlockHeight *json.Uint64 `json:"blockHeight,omitempty"` +} + +// GetAtomicTxStatus returns the status of the specified transaction +func (service *AvaxAPI) GetAtomicTxStatus(r *http.Request, args *api.JSONTxID, reply *GetAtomicTxStatusReply) error { + log.Info("EVM: GetAtomicTxStatus called", "txID", args.TxID) + + if args.TxID == ids.Empty { + return errNilTxID + } + + service.vm.ctx.Lock.Lock() + defer service.vm.ctx.Lock.Unlock() + + _, status, height, _ := service.vm.getAtomicTx(args.TxID) + + reply.Status = status + if status == Accepted { + // Since chain state updates run asynchronously with VM block acceptance, + // avoid returning [Accepted] until the chain state reaches the block + // containing the atomic tx. + lastAccepted := service.vm.blockChain.LastAcceptedBlock() + if height > lastAccepted.NumberU64() { + reply.Status = Processing + return nil + } + + jsonHeight := json.Uint64(height) + reply.BlockHeight = &jsonHeight + } + return nil +} + +type FormattedTx struct { + api.FormattedTx + BlockHeight *json.Uint64 `json:"blockHeight,omitempty"` +} + +// GetAtomicTx returns the specified transaction +func (service *AvaxAPI) GetAtomicTx(r *http.Request, args *api.GetTxArgs, reply *FormattedTx) error { + log.Info("EVM: GetAtomicTx called", "txID", args.TxID) + + if args.TxID == ids.Empty { + return errNilTxID + } + + service.vm.ctx.Lock.Lock() + defer service.vm.ctx.Lock.Unlock() + + tx, status, height, err := service.vm.getAtomicTx(args.TxID) + if err != nil { + return err + } + + if status == Unknown { + return fmt.Errorf("could not find tx %s", args.TxID) + } + + txBytes, err := formatting.Encode(args.Encoding, tx.SignedBytes()) + if err != nil { + return err + } + reply.Tx = txBytes + reply.Encoding = args.Encoding + if status == Accepted { + // Since chain state updates run asynchronously with VM block acceptance, + // avoid returning [Accepted] until the chain state reaches the block + // containing the atomic tx. + lastAccepted := service.vm.blockChain.LastAcceptedBlock() + if height > lastAccepted.NumberU64() { + return nil + } + + jsonHeight := json.Uint64(height) + reply.BlockHeight = &jsonHeight + } + return nil +} diff --git a/plugin/evm/static_service.go b/plugin/evm/static_service.go index 66b39d4acb..13a8823229 100644 --- a/plugin/evm/static_service.go +++ b/plugin/evm/static_service.go @@ -4,93 +4,35 @@ package evm import ( - "errors" - "net/http" + "context" + "encoding/json" "github.com/ava-labs/avalanchego/utils/formatting" "github.com/ava-labs/subnet-evm/core" - "github.com/ethereum/go-ethereum/common" -) - -var ( - errNoGenesisData = errors.New("no genesis data provided") - errNoConfig = errors.New("no config provided in genesis") - errNoChainID = errors.New("no chain ID provided in genesis config") - errInvalidChainID = errors.New("chainID must be greater than 0") - errNoAlloc = errors.New("no alloc table provided in genesis") ) // StaticService defines the static API services exposed by the evm type StaticService struct{} -func CreateStaticService() *StaticService { - return &StaticService{} -} - -// BuildGenesisArgs are arguments for BuildGenesis -type BuildGenesisArgs struct { - GenesisData *core.Genesis `json:"genesisData"` - Encoding formatting.Encoding `json:"encoding"` -} - // BuildGenesisReply is the reply from BuildGenesis type BuildGenesisReply struct { - GenesisBytes string `json:"genesisBytes"` - Encoding formatting.Encoding `json:"encoding"` -} - -// BuildGenesis constructs a genesis file. -func (ss *StaticService) BuildGenesis(_ *http.Request, args *BuildGenesisArgs, reply *BuildGenesisReply) error { - // check for critical fields first - switch { - case args.GenesisData == nil: - return errNoGenesisData - case args.GenesisData.Config == nil: - return errNoConfig - case args.GenesisData.Config.ChainID == nil: - return errNoChainID - case args.GenesisData.Config.ChainID.Cmp(common.Big1) == -1: - return errInvalidChainID - case args.GenesisData.Alloc == nil: - return errNoAlloc - } - - bytes, err := args.GenesisData.MarshalJSON() - if err != nil { - return err - } - bytesStr, err := formatting.Encode(args.Encoding, bytes) - if err != nil { - return err - } - reply.GenesisBytes = bytesStr - reply.Encoding = args.Encoding - return nil -} - -// DecodeGenesisArgs are arguments for DecodeGenesis -type DecodeGenesisArgs struct { - GenesisBytes string `json:"genesisBytes"` - Encoding formatting.Encoding `json:"encoding"` -} - -// DecodeGenesisReply is the reply from DecodeGenesis -type DecodeGenesisReply struct { - Genesis *core.Genesis `json:"genesisData"` + Bytes string `json:"bytes"` Encoding formatting.Encoding `json:"encoding"` } -// BuildGenesis constructs a genesis file. -func (ss *StaticService) DecodeGenesis(_ *http.Request, args *DecodeGenesisArgs, reply *DecodeGenesisReply) error { - bytesStr, err := formatting.Decode(args.Encoding, args.GenesisBytes) +// BuildGenesis returns the UTXOs such that at least one address in [args.Addresses] is +// referenced in the UTXO. +func (*StaticService) BuildGenesis(_ context.Context, args *core.Genesis) (*BuildGenesisReply, error) { + bytes, err := json.Marshal(args) if err != nil { - return err + return nil, err } - genesis := core.Genesis{} - if err := genesis.UnmarshalJSON(bytesStr); err != nil { - return err + bytesStr, err := formatting.Encode(formatting.Hex, bytes) + if err != nil { + return nil, err } - reply.Genesis = &genesis - reply.Encoding = args.Encoding - return nil + return &BuildGenesisReply{ + Bytes: bytesStr, + Encoding: formatting.Hex, + }, nil } diff --git a/plugin/evm/static_service_test.go b/plugin/evm/static_service_test.go deleted file mode 100644 index 626f8f30d3..0000000000 --- a/plugin/evm/static_service_test.go +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (C) 2019-2021, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package evm - -import ( - "encoding/json" - "math/big" - "testing" - - "github.com/ava-labs/avalanchego/utils/formatting" - "github.com/ava-labs/subnet-evm/core" - "github.com/ava-labs/subnet-evm/params" - "github.com/stretchr/testify/assert" -) - -var testGenesisJSON = `{"config":{"chainId":43111,"homesteadBlock":0,"eip150Block":0,"eip155Block":0,"eip158Block":0,"byzantiumBlock":0,"constantinopleBlock":0,"petersburgBlock":0,"istanbulBlock":0,"muirGlacierBlock":0,"subnetEVMTimestamp":0},"nonce":"0x0","timestamp":"0x0","extraData":"0x00","gasLimit":"0x5f5e100","difficulty":"0x0","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","coinbase":"0x0000000000000000000000000000000000000000","alloc":{"0100000000000000000000000000000000000000":{"code":"0x7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631e010439146042578063b6510bb314606e575b600080fd5b605c60048036036020811015605657600080fd5b503560b1565b60408051918252519081900360200190f35b818015607957600080fd5b5060af60048036036080811015608e57600080fd5b506001600160a01b03813516906020810135906040810135906060013560b6565b005b30cd90565b836001600160a01b031681836108fc8690811502906040516000604051808303818888878c8acf9550505050505015801560f4573d6000803e3d6000fd5b505050505056fea26469706673582212201eebce970fe3f5cb96bf8ac6ba5f5c133fc2908ae3dcd51082cfee8f583429d064736f6c634300060a0033","balance":"0x0"}},"number":"0x0","gasUsed":"0x0","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000000"}` - -func TestBuildGenesis(t *testing.T) { - ss := CreateStaticService() - - genesis := &core.Genesis{} - if err := json.Unmarshal([]byte(testGenesisJSON), genesis); err != nil { - t.Fatalf("Problem unmarshaling genesis JSON: %s", err) - } - - // add test allocs - testAlloc := core.GenesisAlloc{ - testEthAddrs[0]: core.GenesisAccount{Balance: genesisBalance}, - testEthAddrs[1]: core.GenesisAccount{Balance: genesisBalance}, - } - genesis.Alloc = testAlloc - genesis.Config.FeeConfig = params.DefaultFeeConfig - testGasLimit := big.NewInt(999999) - genesis.Config.FeeConfig.GasLimit = testGasLimit - genesis.GasLimit = testGasLimit.Uint64() - - args := &BuildGenesisArgs{GenesisData: genesis} - reply := &BuildGenesisReply{} - err := ss.BuildGenesis(nil, args, reply) - if err != nil { - t.Fatalf("Failed to create test genesis") - } - // now decode - genesisBytes, err := formatting.Decode(reply.Encoding, reply.GenesisBytes) - if err != nil { - t.Fatalf("Failed to decode genesis bytes: %s", err) - } - // unmarshal it again - decodedGenesis := &core.Genesis{} - decodedGenesis.UnmarshalJSON(genesisBytes) - // test - assert.Equal(t, testGasLimit, decodedGenesis.Config.FeeConfig.GasLimit) - assert.Equal(t, testAlloc, decodedGenesis.Alloc) -} - -func TestDecodeGenesis(t *testing.T) { - ss := CreateStaticService() - - genesis := &core.Genesis{} - if err := json.Unmarshal([]byte(testGenesisJSON), genesis); err != nil { - t.Fatalf("Problem unmarshaling genesis JSON: %s", err) - } - - // add test allocs - testAlloc := core.GenesisAlloc{ - testEthAddrs[0]: core.GenesisAccount{Balance: genesisBalance}, - testEthAddrs[1]: core.GenesisAccount{Balance: genesisBalance}, - } - genesis.Alloc = testAlloc - genesis.Config.FeeConfig = params.DefaultFeeConfig - testGasLimit := big.NewInt(999999) - genesis.Config.FeeConfig.GasLimit = testGasLimit - genesis.GasLimit = testGasLimit.Uint64() - - args := &BuildGenesisArgs{GenesisData: genesis} - reply := &BuildGenesisReply{} - err := ss.BuildGenesis(nil, args, reply) - if err != nil { - t.Fatalf("Failed to create test genesis") - } - - // now decode - decArgs := &DecodeGenesisArgs{GenesisBytes: reply.GenesisBytes} - decReply := &DecodeGenesisReply{} - err = ss.DecodeGenesis(nil, decArgs, decReply) - if err != nil { - t.Fatalf("Failed to create test genesis") - } - decodedGenesis := decReply.Genesis - - // test - assert.Equal(t, testGasLimit, decodedGenesis.Config.FeeConfig.GasLimit) - assert.Equal(t, testAlloc, decodedGenesis.Alloc) -} diff --git a/plugin/evm/syncervm_client.go b/plugin/evm/syncervm_client.go index f18372995e..40e2da774f 100644 --- a/plugin/evm/syncervm_client.go +++ b/plugin/evm/syncervm_client.go @@ -52,6 +52,7 @@ type stateSyncClientConfig struct { metadataDB database.Database acceptedBlockDB database.Database db *versiondb.Database + atomicBackend AtomicBackend client syncclient.Client @@ -148,8 +149,13 @@ func (client *stateSyncerClient) stateSync(ctx context.Context) error { return err } - // Sync the EVM trie. - return client.syncStateTrie(ctx) + // Sync the EVM trie and then the atomic trie. These steps could be done + // in parallel or in the opposite order. Keeping them serial for simplicity for now. + if err := client.syncStateTrie(ctx); err != nil { + return err + } + + return client.syncAtomicTrie(ctx) } // acceptSyncSummary returns true if sync will be performed and launches the state sync process @@ -268,6 +274,20 @@ func (client *stateSyncerClient) syncBlocks(ctx context.Context, fromHash common return batch.Write() } +func (client *stateSyncerClient) syncAtomicTrie(ctx context.Context) error { + log.Info("atomic tx: sync starting", "root", client.syncSummary.AtomicRoot) + atomicSyncer, err := client.atomicBackend.Syncer(client.client, client.syncSummary.AtomicRoot, client.syncSummary.BlockNumber, client.stateSyncRequestSize) + if err != nil { + return err + } + if err := atomicSyncer.Start(ctx); err != nil { + return err + } + err = <-atomicSyncer.Done() + log.Info("atomic tx: sync finished", "root", client.syncSummary.AtomicRoot, "err", err) + return err +} + func (client *stateSyncerClient) syncStateTrie(ctx context.Context) error { log.Info("state sync: sync starting", "root", client.syncSummary.BlockRoot) evmSyncer, err := statesync.NewStateSyncer(&statesync.StateSyncerConfig{ @@ -344,7 +364,17 @@ func (client *stateSyncerClient) finishSync() error { return fmt.Errorf("error updating vm markers, height=%d, hash=%s, err=%w", block.NumberU64(), block.Hash(), err) } - return client.state.SetLastAcceptedBlock(evmBlock) + if err := client.state.SetLastAcceptedBlock(evmBlock); err != nil { + return err + } + + // the chain state is already restored, and from this point on + // the block synced to is the accepted block. the last operation + // is updating shared memory with the atomic trie. + // ApplyToSharedMemory does this, and even if the VM is stopped + // (gracefully or ungracefully), since MarkApplyToSharedMemoryCursor + // is called, VM will resume ApplyToSharedMemory on Initialize. + return client.atomicBackend.ApplyToSharedMemory(block.NumberU64()) } // updateVMMarkers updates the following markers in the VM's database @@ -354,6 +384,13 @@ func (client *stateSyncerClient) finishSync() error { // - updates lastAcceptedKey // - removes state sync progress markers func (client *stateSyncerClient) updateVMMarkers() error { + // Mark the previously last accepted block for the shared memory cursor, so that we will execute shared + // memory operations from the previously last accepted block to [vm.syncSummary] when ApplyToSharedMemory + // is called. + if err := client.atomicBackend.MarkApplyToSharedMemoryCursor(client.lastAcceptedHeight); err != nil { + return err + } + client.atomicBackend.SetLastAccepted(client.syncSummary.BlockHash) if err := client.acceptedBlockDB.Put(lastAcceptedKey, client.syncSummary.BlockHash[:]); err != nil { return err } diff --git a/plugin/evm/syncervm_server.go b/plugin/evm/syncervm_server.go index 0f3643f6c4..40c66cfc64 100644 --- a/plugin/evm/syncervm_server.go +++ b/plugin/evm/syncervm_server.go @@ -12,18 +12,21 @@ import ( "github.com/ava-labs/subnet-evm/core" "github.com/ava-labs/subnet-evm/plugin/evm/message" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" ) type stateSyncServerConfig struct { - Chain *core.BlockChain + Chain *core.BlockChain + AtomicTrie AtomicTrie // SyncableInterval is the interval at which blocks are eligible to provide syncable block summaries. SyncableInterval uint64 } type stateSyncServer struct { - chain *core.BlockChain + chain *core.BlockChain + atomicTrie AtomicTrie syncableInterval uint64 } @@ -36,12 +39,22 @@ type StateSyncServer interface { func NewStateSyncServer(config *stateSyncServerConfig) StateSyncServer { return &stateSyncServer{ chain: config.Chain, + atomicTrie: config.AtomicTrie, syncableInterval: config.SyncableInterval, } } // stateSummaryAtHeight returns the SyncSummary at [height] if valid and available. func (server *stateSyncServer) stateSummaryAtHeight(height uint64) (message.SyncSummary, error) { + atomicRoot, err := server.atomicTrie.Root(height) + if err != nil { + return message.SyncSummary{}, fmt.Errorf("error getting atomic trie root for height (%d): %w", height, err) + } + + if (atomicRoot == common.Hash{}) { + return message.SyncSummary{}, fmt.Errorf("atomic trie root not found for height (%d)", height) + } + blk := server.chain.GetBlockByNumber(height) if blk == nil { return message.SyncSummary{}, fmt.Errorf("block not found for height (%d)", height) @@ -51,7 +64,7 @@ func (server *stateSyncServer) stateSummaryAtHeight(height uint64) (message.Sync return message.SyncSummary{}, fmt.Errorf("block root does not exist for height (%d), root (%s)", height, blk.Root()) } - summary, err := message.NewSyncSummary(blk.Hash(), height, blk.Root()) + summary, err := message.NewSyncSummary(blk.Hash(), height, blk.Root(), atomicRoot) if err != nil { return message.SyncSummary{}, fmt.Errorf("failed to construct syncable block at height %d: %w", height, err) } diff --git a/plugin/evm/syncervm_test.go b/plugin/evm/syncervm_test.go index 533740666b..930d52ce3c 100644 --- a/plugin/evm/syncervm_test.go +++ b/plugin/evm/syncervm_test.go @@ -15,6 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/database/prefixdb" "github.com/ava-labs/avalanchego/ids" @@ -22,7 +23,9 @@ import ( commonEng "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/snow/engine/enginetest" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" + "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/subnet-evm/accounts/keystore" "github.com/ava-labs/subnet-evm/consensus/dummy" @@ -241,7 +244,6 @@ func TestStateSyncToggleEnabledToDisabled(t *testing.T) { } func TestVMShutdownWhileSyncing(t *testing.T) { - t.Skip("FLAKY") var ( lock sync.Mutex vmSetup *syncVMSetup @@ -274,27 +276,68 @@ func TestVMShutdownWhileSyncing(t *testing.T) { func createSyncServerAndClientVMs(t *testing.T, test syncTest, numBlocks int) *syncVMSetup { var ( - require = require.New(t) + require = require.New(t) + importAmount = 2000000 * units.Avax // 2M avax + alloc = map[ids.ShortID]uint64{ + testShortIDAddrs[0]: importAmount, + } + ) + _, serverVM, _, serverAtomicMemory, serverAppSender := GenesisVMWithUTXOs( + t, true, "", "", "", alloc, ) - // configure [serverVM] - _, serverVM, _, serverAppSender := GenesisVM(t, true, genesisJSONLatest, "", "") t.Cleanup(func() { log.Info("Shutting down server VM") require.NoError(serverVM.Shutdown(context.Background())) }) + var ( + importTx, exportTx *Tx + err error + ) generateAndAcceptBlocks(t, serverVM, numBlocks, func(i int, gen *core.BlockGen) { b, err := predicate.NewResults().Bytes() if err != nil { t.Fatal(err) } gen.AppendExtra(b) - - tx := types.NewTransaction(gen.TxNonce(testEthAddrs[0]), testEthAddrs[1], common.Big1, params.TxGas, big.NewInt(testMinGasPrice), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(serverVM.chainConfig.ChainID), testKeys[0]) - require.NoError(err) - gen.AddTx(signedTx) + switch i { + case 0: + // spend the UTXOs from shared memory + importTx, err = serverVM.newImportTx(serverVM.ctx.XChainID, testEthAddrs[0], initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) + require.NoError(err) + require.NoError(serverVM.mempool.AddLocalTx(importTx)) + case 1: + // export some of the imported UTXOs to test exportTx is properly synced + exportTx, err = serverVM.newExportTx( + serverVM.ctx.AVAXAssetID, + importAmount/2, + serverVM.ctx.XChainID, + testShortIDAddrs[0], + initialBaseFee, + []*secp256k1.PrivateKey{testKeys[0]}, + ) + require.NoError(err) + require.NoError(serverVM.mempool.AddLocalTx(exportTx)) + default: // Generate simple transfer transactions. + pk := testKeys[0].ToECDSA() + tx := types.NewTransaction(gen.TxNonce(testEthAddrs[0]), testEthAddrs[1], common.Big1, params.TxGas, initialBaseFee, nil) + signedTx, err := types.SignTx(tx, types.NewEIP155Signer(serverVM.chainID), pk) + require.NoError(err) + gen.AddTx(signedTx) + } }, nil) + // override serverAtomicTrie's commitInterval so the call to [serverAtomicTrie.Index] + // creates a commit at the height [syncableInterval]. This is necessary to support + // fetching a state summary. + serverAtomicTrie := serverVM.atomicTrie.(*atomicTrie) + serverAtomicTrie.commitInterval = test.syncableInterval + require.NoError(serverAtomicTrie.commit(test.syncableInterval, serverAtomicTrie.LastAcceptedRoot())) + require.NoError(serverVM.db.Commit()) + + serverSharedMemories := newSharedMemories(serverAtomicMemory, serverVM.ctx.ChainID, serverVM.ctx.XChainID) + serverSharedMemories.assertOpsApplied(t, importTx.mustAtomicOps()) + serverSharedMemories.assertOpsApplied(t, exportTx.mustAtomicOps()) + // make some accounts trieDB := trie.NewDatabase(serverVM.chaindb, nil) root, accounts := statesync.FillAccountsWithOverlappingStorage(t, trieDB, types.EmptyRootHash, 1000, 16) @@ -315,7 +358,9 @@ func createSyncServerAndClientVMs(t *testing.T, test syncTest, numBlocks int) *s // initialise [syncerVM] with blank genesis state stateSyncEnabledJSON := fmt.Sprintf(`{"state-sync-enabled":true, "state-sync-min-blocks": %d, "tx-lookup-limit": %d}`, test.stateSyncMinBlocks, 4) - syncerEngineChan, syncerVM, syncerDB, syncerAppSender := GenesisVM(t, false, genesisJSONLatest, stateSyncEnabledJSON, "") + syncerEngineChan, syncerVM, syncerDB, syncerAtomicMemory, syncerAppSender := GenesisVMWithUTXOs( + t, false, "", stateSyncEnabledJSON, "", alloc, + ) shutdownOnceSyncerVM := &shutdownOnceVM{VM: syncerVM} t.Cleanup(func() { require.NoError(shutdownOnceSyncerVM.Shutdown(context.Background())) @@ -325,6 +370,9 @@ func createSyncServerAndClientVMs(t *testing.T, test syncTest, numBlocks int) *s require.NoError(err) require.True(enabled) + // override [syncerVM]'s commit interval so the atomic trie works correctly. + syncerVM.atomicTrie.(*atomicTrie).commitInterval = test.syncableInterval + // override [serverVM]'s SendAppResponse function to trigger AppResponse on [syncerVM] serverAppSender.SendAppResponseF = func(ctx context.Context, nodeID ids.NodeID, requestID uint32, response []byte) error { if test.responseIntercept == nil { @@ -355,12 +403,17 @@ func createSyncServerAndClientVMs(t *testing.T, test syncTest, numBlocks int) *s } return &syncVMSetup{ - serverVM: serverVM, - serverAppSender: serverAppSender, + serverVM: serverVM, + serverAppSender: serverAppSender, + includedAtomicTxs: []*Tx{ + importTx, + exportTx, + }, fundedAccounts: accounts, syncerVM: syncerVM, syncerDB: syncerDB, syncerEngineChan: syncerEngineChan, + syncerAtomicMemory: syncerAtomicMemory, shutdownOnceSyncerVM: shutdownOnceSyncerVM, } } @@ -371,11 +424,13 @@ type syncVMSetup struct { serverVM *VM serverAppSender *enginetest.Sender - fundedAccounts map[*keystore.Key]*types.StateAccount + includedAtomicTxs []*Tx + fundedAccounts map[*keystore.Key]*types.StateAccount syncerVM *VM syncerDB database.Database syncerEngineChan <-chan commonEng.Message + syncerAtomicMemory *atomic.Memory shutdownOnceSyncerVM *shutdownOnceVM } @@ -402,11 +457,13 @@ type syncTest struct { func testSyncerVM(t *testing.T, vmSetup *syncVMSetup, test syncTest) { t.Helper() var ( - require = require.New(t) - serverVM = vmSetup.serverVM - fundedAccounts = vmSetup.fundedAccounts - syncerVM = vmSetup.syncerVM - syncerEngineChan = vmSetup.syncerEngineChan + require = require.New(t) + serverVM = vmSetup.serverVM + includedAtomicTxs = vmSetup.includedAtomicTxs + fundedAccounts = vmSetup.fundedAccounts + syncerVM = vmSetup.syncerVM + syncerEngineChan = vmSetup.syncerEngineChan + syncerAtomicMemory = vmSetup.syncerAtomicMemory ) // get last summary and test related methods summary, err := serverVM.GetLastStateSummary(context.Background()) @@ -455,7 +512,7 @@ func testSyncerVM(t *testing.T, vmSetup *syncVMSetup, test syncTest) { } // tail should be the last block synced - if syncerVM.ethConfig.TransactionHistory != 0 { + if syncerVM.ethConfig.TxLookupLimit != 0 { tail := lastSyncedBlock.NumberU64() core.CheckTxIndices(t, &tail, tail, syncerVM.chaindb, true) @@ -472,7 +529,7 @@ func testSyncerVM(t *testing.T, vmSetup *syncVMSetup, test syncTest) { gen.AppendExtra(b) i := 0 for k := range fundedAccounts { - tx := types.NewTransaction(gen.TxNonce(k.Address), toAddress, big.NewInt(1), 21000, big.NewInt(testMinGasPrice), nil) + tx := types.NewTransaction(gen.TxNonce(k.Address), toAddress, big.NewInt(1), 21000, initialBaseFee, nil) signedTx, err := types.SignTx(tx, types.NewEIP155Signer(serverVM.chainConfig.ChainID), k.PrivateKey) require.NoError(err) gen.AddTx(signedTx) @@ -483,8 +540,8 @@ func testSyncerVM(t *testing.T, vmSetup *syncVMSetup, test syncTest) { } }, func(block *types.Block) { - if syncerVM.ethConfig.TransactionHistory != 0 { - tail := block.NumberU64() - syncerVM.ethConfig.TransactionHistory + 1 + if syncerVM.ethConfig.TxLookupLimit != 0 { + tail := block.NumberU64() - syncerVM.ethConfig.TxLookupLimit + 1 // tail should be the minimum last synced block, since we skipped it to the last block if tail < lastSyncedBlock.NumberU64() { tail = lastSyncedBlock.NumberU64() @@ -498,14 +555,23 @@ func testSyncerVM(t *testing.T, vmSetup *syncVMSetup, test syncTest) { require.NoError(syncerVM.SetState(context.Background(), snow.NormalOp)) require.True(syncerVM.bootstrapped) + // check atomic memory was synced properly + syncerSharedMemories := newSharedMemories(syncerAtomicMemory, syncerVM.ctx.ChainID, syncerVM.ctx.XChainID) + + for _, tx := range includedAtomicTxs { + syncerSharedMemories.assertOpsApplied(t, tx.mustAtomicOps()) + } + // Generate blocks after we have entered normal consensus as well generateAndAcceptBlocks(t, syncerVM, blocksToBuild, func(_ int, gen *core.BlockGen) { b, err := predicate.NewResults().Bytes() - require.NoError(err) + if err != nil { + t.Fatal(err) + } gen.AppendExtra(b) i := 0 for k := range fundedAccounts { - tx := types.NewTransaction(gen.TxNonce(k.Address), toAddress, big.NewInt(1), 21000, big.NewInt(testMinGasPrice), nil) + tx := types.NewTransaction(gen.TxNonce(k.Address), toAddress, big.NewInt(1), 21000, initialBaseFee, nil) signedTx, err := types.SignTx(tx, types.NewEIP155Signer(serverVM.chainConfig.ChainID), k.PrivateKey) require.NoError(err) gen.AddTx(signedTx) @@ -516,8 +582,8 @@ func testSyncerVM(t *testing.T, vmSetup *syncVMSetup, test syncTest) { } }, func(block *types.Block) { - if syncerVM.ethConfig.TransactionHistory != 0 { - tail := block.NumberU64() - syncerVM.ethConfig.TransactionHistory + 1 + if syncerVM.ethConfig.TxLookupLimit != 0 { + tail := block.NumberU64() - syncerVM.ethConfig.TxLookupLimit + 1 // tail should be the minimum last synced block, since we skipped it to the last block if tail < lastSyncedBlock.NumberU64() { tail = lastSyncedBlock.NumberU64() @@ -537,8 +603,8 @@ func patchBlock(blk *types.Block, root common.Hash, db ethdb.Database) *types.Bl header := blk.Header() header.Root = root receipts := rawdb.ReadRawReceipts(db, blk.Hash(), blk.NumberU64()) - newBlk := types.NewBlock( - header, blk.Transactions(), blk.Uncles(), receipts, trie.NewStackTrie(nil), + newBlk := types.NewBlockWithExtData( + header, blk.Transactions(), blk.Uncles(), receipts, trie.NewStackTrie(nil), blk.ExtData(), true, ) rawdb.WriteBlock(db, newBlk) rawdb.WriteCanonicalHash(db, newBlk.Hash(), newBlk.NumberU64()) @@ -576,7 +642,7 @@ func generateAndAcceptBlocks(t *testing.T, vm *VM, numBlocks int, gen func(int, _, _, err := core.GenerateChain( vm.chainConfig, vm.blockChain.LastAcceptedBlock(), - dummy.NewETHFaker(), + dummy.NewFakerWithCallbacks(vm.createConsensusCallbacks()), vm.chaindb, numBlocks, 10, diff --git a/plugin/evm/tx_gossip_test.go b/plugin/evm/tx_gossip_test.go index 36c47ffde5..8c584bdb78 100644 --- a/plugin/evm/tx_gossip_test.go +++ b/plugin/evm/tx_gossip_test.go @@ -11,6 +11,7 @@ import ( "testing" "time" + "github.com/ava-labs/avalanchego/chains/atomic" "github.com/ava-labs/avalanchego/database/memdb" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/network/p2p" @@ -22,14 +23,19 @@ import ( "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/snow/validators/validatorstest" agoUtils "github.com/ava-labs/avalanchego/utils" + "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/set" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" + "github.com/ava-labs/avalanchego/vms/components/avax" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" + "google.golang.org/protobuf/proto" "github.com/ava-labs/subnet-evm/core/types" + "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/utils" ) @@ -40,18 +46,27 @@ func TestEthTxGossip(t *testing.T) { validatorState := &validatorstest.State{} snowCtx.ValidatorState = validatorState + pk, err := secp256k1.NewPrivateKey() + require.NoError(err) + address := GetEthAddress(pk) + genesis := newPrefundedGenesis(100_000_000_000_000_000, address) + genesisBytes, err := genesis.MarshalJSON() + require.NoError(err) + responseSender := &enginetest.SenderStub{ SentAppResponse: make(chan []byte, 1), } vm := &VM{ - p2pSender: responseSender, + p2pSender: responseSender, + atomicTxGossipHandler: &p2p.NoOpHandler{}, + atomicTxPullGossiper: &gossip.NoOpGossiper{}, } require.NoError(vm.Initialize( ctx, snowCtx, memdb.New(), - []byte(genesisJSONLatest), + genesisBytes, nil, nil, make(chan common.Message), @@ -71,7 +86,7 @@ func TestEthTxGossip(t *testing.T) { network, err := p2p.NewNetwork(logging.NoLog{}, peerSender, prometheus.NewRegistry(), "") require.NoError(err) - client := network.NewClient(p2p.TxGossipHandlerID) + client := network.NewClient(ethTxGossipProtocol) // we only accept gossip requests from validators requestingNodeID := ids.GenerateTestNodeID() @@ -116,10 +131,8 @@ func TestEthTxGossip(t *testing.T) { wg.Wait() // Issue a tx to the VM - address := testEthAddrs[0] - key := testKeys[0] - tx := types.NewTransaction(0, address, big.NewInt(10), 21000, big.NewInt(testMinGasPrice), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainConfig.ChainID), key) + tx := types.NewTransaction(0, address, big.NewInt(10), 100_000, big.NewInt(params.LaunchMinGasPrice), nil) + signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainID), pk.ToECDSA()) require.NoError(err) errs := vm.txPool.Add([]*types.Transaction{signedTx}, true, true) @@ -151,6 +164,146 @@ func TestEthTxGossip(t *testing.T) { wg.Wait() } +func TestAtomicTxGossip(t *testing.T) { + require := require.New(t) + ctx := context.Background() + snowCtx := utils.TestSnowContext() + snowCtx.AVAXAssetID = ids.GenerateTestID() + snowCtx.XChainID = ids.GenerateTestID() + validatorState := &validatorstest.State{ + GetSubnetIDF: func(context.Context, ids.ID) (ids.ID, error) { + return ids.Empty, nil + }, + } + snowCtx.ValidatorState = validatorState + memory := atomic.NewMemory(memdb.New()) + snowCtx.SharedMemory = memory.NewSharedMemory(ids.Empty) + + pk, err := secp256k1.NewPrivateKey() + require.NoError(err) + address := GetEthAddress(pk) + genesis := newPrefundedGenesis(100_000_000_000_000_000, address) + genesisBytes, err := genesis.MarshalJSON() + require.NoError(err) + + responseSender := &enginetest.SenderStub{ + SentAppResponse: make(chan []byte, 1), + } + vm := &VM{ + p2pSender: responseSender, + ethTxGossipHandler: &p2p.NoOpHandler{}, + ethTxPullGossiper: &gossip.NoOpGossiper{}, + } + + require.NoError(vm.Initialize( + ctx, + snowCtx, + memdb.New(), + genesisBytes, + nil, + nil, + make(chan common.Message), + nil, + &enginetest.Sender{}, + )) + require.NoError(vm.SetState(ctx, snow.NormalOp)) + + defer func() { + require.NoError(vm.Shutdown(ctx)) + }() + + // sender for the peer requesting gossip from [vm] + peerSender := &enginetest.SenderStub{ + SentAppRequest: make(chan []byte, 1), + } + network, err := p2p.NewNetwork(logging.NoLog{}, peerSender, prometheus.NewRegistry(), "") + require.NoError(err) + client := network.NewClient(atomicTxGossipProtocol) + + // we only accept gossip requests from validators + requestingNodeID := ids.GenerateTestNodeID() + require.NoError(vm.Network.Connected(ctx, requestingNodeID, nil)) + validatorState.GetCurrentHeightF = func(context.Context) (uint64, error) { + return 0, nil + } + validatorState.GetValidatorSetF = func(context.Context, uint64, ids.ID) (map[ids.NodeID]*validators.GetValidatorOutput, error) { + return map[ids.NodeID]*validators.GetValidatorOutput{ + requestingNodeID: { + NodeID: requestingNodeID, + Weight: 1, + }, + }, nil + } + + // Ask the VM for any new transactions. We should get nothing at first. + emptyBloomFilter, err := gossip.NewBloomFilter(prometheus.NewRegistry(), "", txGossipBloomMinTargetElements, txGossipBloomTargetFalsePositiveRate, txGossipBloomResetFalsePositiveRate) + require.NoError(err) + emptyBloomFilterBytes, _ := emptyBloomFilter.Marshal() + request := &sdk.PullGossipRequest{ + Filter: emptyBloomFilterBytes, + Salt: agoUtils.RandomBytes(32), + } + + requestBytes, err := proto.Marshal(request) + require.NoError(err) + + wg := &sync.WaitGroup{} + wg.Add(1) + onResponse := func(_ context.Context, nodeID ids.NodeID, responseBytes []byte, err error) { + require.NoError(err) + + response := &sdk.PullGossipResponse{} + require.NoError(proto.Unmarshal(responseBytes, response)) + require.Empty(response.Gossip) + wg.Done() + } + require.NoError(client.AppRequest(ctx, set.Of(vm.ctx.NodeID), requestBytes, onResponse)) + require.NoError(vm.AppRequest(ctx, requestingNodeID, 1, time.Time{}, <-peerSender.SentAppRequest)) + require.NoError(network.AppResponse(ctx, snowCtx.NodeID, 1, <-responseSender.SentAppResponse)) + wg.Wait() + + // Issue a tx to the VM + utxo, err := addUTXO( + memory, + snowCtx, + ids.GenerateTestID(), + 0, + snowCtx.AVAXAssetID, + 100_000_000_000, + pk.PublicKey().Address(), + ) + require.NoError(err) + tx, err := vm.newImportTxWithUTXOs(vm.ctx.XChainID, address, initialBaseFee, secp256k1fx.NewKeychain(pk), []*avax.UTXO{utxo}) + require.NoError(err) + require.NoError(vm.mempool.AddLocalTx(tx)) + + // wait so we aren't throttled by the vm + time.Sleep(5 * time.Second) + + // Ask the VM for new transactions. We should get the newly issued tx. + wg.Add(1) + + marshaller := GossipAtomicTxMarshaller{} + onResponse = func(_ context.Context, nodeID ids.NodeID, responseBytes []byte, err error) { + require.NoError(err) + + response := &sdk.PullGossipResponse{} + require.NoError(proto.Unmarshal(responseBytes, response)) + require.Len(response.Gossip, 1) + + gotTx, err := marshaller.UnmarshalGossip(response.Gossip[0]) + require.NoError(err) + require.Equal(tx.ID(), gotTx.GossipID()) + + wg.Done() + } + require.NoError(client.AppRequest(ctx, set.Of(vm.ctx.NodeID), requestBytes, onResponse)) + require.NoError(vm.AppRequest(ctx, requestingNodeID, 3, time.Time{}, <-peerSender.SentAppRequest)) + require.NoError(network.AppResponse(ctx, snowCtx.NodeID, 3, <-responseSender.SentAppResponse)) + wg.Wait() +} + +// Tests that a tx is gossiped when it is issued func TestEthTxPushGossipOutbound(t *testing.T) { require := require.New(t) ctx := context.Background() @@ -168,14 +321,23 @@ func TestEthTxPushGossipOutbound(t *testing.T) { } vm := &VM{ - ethTxPullGossiper: gossip.NoOpGossiper{}, + p2pSender: sender, + ethTxPullGossiper: gossip.NoOpGossiper{}, + atomicTxPullGossiper: gossip.NoOpGossiper{}, } + pk, err := secp256k1.NewPrivateKey() + require.NoError(err) + address := GetEthAddress(pk) + genesis := newPrefundedGenesis(100_000_000_000_000_000, address) + genesisBytes, err := genesis.MarshalJSON() + require.NoError(err) + require.NoError(vm.Initialize( ctx, snowCtx, memdb.New(), - []byte(genesisJSONLatest), + genesisBytes, nil, nil, make(chan common.Message), @@ -188,10 +350,8 @@ func TestEthTxPushGossipOutbound(t *testing.T) { require.NoError(vm.Shutdown(ctx)) }() - address := testEthAddrs[0] - key := testKeys[0] - tx := types.NewTransaction(0, address, big.NewInt(10), 21000, big.NewInt(testMinGasPrice), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainConfig.ChainID), key) + tx := types.NewTransaction(0, address, big.NewInt(10), 100_000, big.NewInt(params.LaunchMinGasPrice), nil) + signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainID), pk.ToECDSA()) require.NoError(err) // issue a tx @@ -203,7 +363,7 @@ func TestEthTxPushGossipOutbound(t *testing.T) { // we should get a message that has the protocol prefix and the gossip // message - require.Equal(byte(p2p.TxGossipHandlerID), sent[0]) + require.Equal(byte(ethTxGossipProtocol), sent[0]) require.NoError(proto.Unmarshal(sent[1:], got)) marshaller := GossipEthTxMarshaller{} @@ -221,14 +381,23 @@ func TestEthTxPushGossipInbound(t *testing.T) { sender := &enginetest.Sender{} vm := &VM{ - ethTxPullGossiper: gossip.NoOpGossiper{}, + p2pSender: sender, + ethTxPullGossiper: gossip.NoOpGossiper{}, + atomicTxPullGossiper: gossip.NoOpGossiper{}, } + pk, err := secp256k1.NewPrivateKey() + require.NoError(err) + address := GetEthAddress(pk) + genesis := newPrefundedGenesis(100_000_000_000_000_000, address) + genesisBytes, err := genesis.MarshalJSON() + require.NoError(err) + require.NoError(vm.Initialize( ctx, snowCtx, memdb.New(), - []byte(genesisJSONLatest), + genesisBytes, nil, nil, make(chan common.Message), @@ -241,10 +410,8 @@ func TestEthTxPushGossipInbound(t *testing.T) { require.NoError(vm.Shutdown(ctx)) }() - address := testEthAddrs[0] - key := testKeys[0] - tx := types.NewTransaction(0, address, big.NewInt(10), 21000, big.NewInt(testMinGasPrice), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainConfig.ChainID), key) + tx := types.NewTransaction(0, address, big.NewInt(10), 100_000, big.NewInt(params.LaunchMinGasPrice), nil) + signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainID), pk.ToECDSA()) require.NoError(err) marshaller := GossipEthTxMarshaller{} @@ -261,8 +428,167 @@ func TestEthTxPushGossipInbound(t *testing.T) { inboundGossipBytes, err := proto.Marshal(inboundGossip) require.NoError(err) - inboundGossipMsg := append(binary.AppendUvarint(nil, p2p.TxGossipHandlerID), inboundGossipBytes...) + inboundGossipMsg := append(binary.AppendUvarint(nil, ethTxGossipProtocol), inboundGossipBytes...) require.NoError(vm.AppGossip(ctx, ids.EmptyNodeID, inboundGossipMsg)) require.True(vm.txPool.Has(signedTx.Hash())) } + +// Tests that a tx is gossiped when it is issued +func TestAtomicTxPushGossipOutbound(t *testing.T) { + require := require.New(t) + ctx := context.Background() + snowCtx := utils.TestSnowContext() + snowCtx.AVAXAssetID = ids.GenerateTestID() + snowCtx.XChainID = ids.GenerateTestID() + validatorState := &validatorstest.State{ + GetSubnetIDF: func(context.Context, ids.ID) (ids.ID, error) { + return ids.Empty, nil + }, + } + snowCtx.ValidatorState = validatorState + memory := atomic.NewMemory(memdb.New()) + snowCtx.SharedMemory = memory.NewSharedMemory(ids.Empty) + + pk, err := secp256k1.NewPrivateKey() + require.NoError(err) + address := GetEthAddress(pk) + genesis := newPrefundedGenesis(100_000_000_000_000_000, address) + genesisBytes, err := genesis.MarshalJSON() + require.NoError(err) + + sender := &enginetest.SenderStub{ + SentAppGossip: make(chan []byte, 1), + } + vm := &VM{ + p2pSender: sender, + ethTxPullGossiper: gossip.NoOpGossiper{}, + atomicTxPullGossiper: gossip.NoOpGossiper{}, + } + + require.NoError(vm.Initialize( + ctx, + snowCtx, + memdb.New(), + genesisBytes, + nil, + nil, + make(chan common.Message), + nil, + &enginetest.SenderStub{}, + )) + require.NoError(vm.SetState(ctx, snow.NormalOp)) + + defer func() { + require.NoError(vm.Shutdown(ctx)) + }() + + // Issue a tx to the VM + utxo, err := addUTXO( + memory, + snowCtx, + ids.GenerateTestID(), + 0, + snowCtx.AVAXAssetID, + 100_000_000_000, + pk.PublicKey().Address(), + ) + require.NoError(err) + tx, err := vm.newImportTxWithUTXOs(vm.ctx.XChainID, address, initialBaseFee, secp256k1fx.NewKeychain(pk), []*avax.UTXO{utxo}) + require.NoError(err) + require.NoError(vm.mempool.AddLocalTx(tx)) + vm.atomicTxPushGossiper.Add(&GossipAtomicTx{tx}) + + gossipedBytes := <-sender.SentAppGossip + require.Equal(byte(atomicTxGossipProtocol), gossipedBytes[0]) + + outboundGossipMsg := &sdk.PushGossip{} + require.NoError(proto.Unmarshal(gossipedBytes[1:], outboundGossipMsg)) + require.Len(outboundGossipMsg.Gossip, 1) + + marshaller := GossipAtomicTxMarshaller{} + gossipedTx, err := marshaller.UnmarshalGossip(outboundGossipMsg.Gossip[0]) + require.NoError(err) + require.Equal(tx.ID(), gossipedTx.Tx.ID()) +} + +// Tests that a tx is gossiped when it is issued +func TestAtomicTxPushGossipInbound(t *testing.T) { + require := require.New(t) + ctx := context.Background() + snowCtx := utils.TestSnowContext() + snowCtx.AVAXAssetID = ids.GenerateTestID() + snowCtx.XChainID = ids.GenerateTestID() + validatorState := &validatorstest.State{ + GetSubnetIDF: func(context.Context, ids.ID) (ids.ID, error) { + return ids.Empty, nil + }, + } + snowCtx.ValidatorState = validatorState + memory := atomic.NewMemory(memdb.New()) + snowCtx.SharedMemory = memory.NewSharedMemory(ids.Empty) + + pk, err := secp256k1.NewPrivateKey() + require.NoError(err) + address := GetEthAddress(pk) + genesis := newPrefundedGenesis(100_000_000_000_000_000, address) + genesisBytes, err := genesis.MarshalJSON() + require.NoError(err) + + sender := &enginetest.Sender{} + vm := &VM{ + p2pSender: sender, + ethTxPullGossiper: gossip.NoOpGossiper{}, + atomicTxPullGossiper: gossip.NoOpGossiper{}, + } + + require.NoError(vm.Initialize( + ctx, + snowCtx, + memdb.New(), + genesisBytes, + nil, + nil, + make(chan common.Message), + nil, + &enginetest.SenderStub{}, + )) + require.NoError(vm.SetState(ctx, snow.NormalOp)) + + defer func() { + require.NoError(vm.Shutdown(ctx)) + }() + + // issue a tx to the vm + utxo, err := addUTXO( + memory, + snowCtx, + ids.GenerateTestID(), + 0, + snowCtx.AVAXAssetID, + 100_000_000_000, + pk.PublicKey().Address(), + ) + require.NoError(err) + tx, err := vm.newImportTxWithUTXOs(vm.ctx.XChainID, address, initialBaseFee, secp256k1fx.NewKeychain(pk), []*avax.UTXO{utxo}) + require.NoError(err) + require.NoError(vm.mempool.AddLocalTx(tx)) + + marshaller := GossipAtomicTxMarshaller{} + gossipedTx := &GossipAtomicTx{ + Tx: tx, + } + gossipBytes, err := marshaller.MarshalGossip(gossipedTx) + require.NoError(err) + + inboundGossip := &sdk.PushGossip{ + Gossip: [][]byte{gossipBytes}, + } + inboundGossipBytes, err := proto.Marshal(inboundGossip) + require.NoError(err) + + inboundGossipMsg := append(binary.AppendUvarint(nil, atomicTxGossipProtocol), inboundGossipBytes...) + + require.NoError(vm.AppGossip(ctx, ids.EmptyNodeID, inboundGossipMsg)) + require.True(vm.mempool.has(tx.ID())) +} diff --git a/plugin/evm/version.go b/plugin/evm/version.go index 149ab0c482..3b6fd52823 100644 --- a/plugin/evm/version.go +++ b/plugin/evm/version.go @@ -10,8 +10,8 @@ import ( var ( // GitCommit is set by the build script GitCommit string - // Version is the version of Subnet EVM - Version string = "v0.6.8" + // Version is the version of Coreth + Version string = "v0.13.7" ) func init() { diff --git a/plugin/evm/version_test.go b/plugin/evm/version_test.go deleted file mode 100644 index b27b9f6c27..0000000000 --- a/plugin/evm/version_test.go +++ /dev/null @@ -1,29 +0,0 @@ -package evm - -import ( - "encoding/json" - "os" - "testing" - - "github.com/ava-labs/avalanchego/version" - "github.com/stretchr/testify/assert" -) - -type rpcChainCompatibility struct { - RPCChainVMProtocolVersion map[string]uint `json:"rpcChainVMProtocolVersion"` -} - -const compatibilityFile = "../../compatibility.json" - -func TestCompatibility(t *testing.T) { - compat, err := os.ReadFile(compatibilityFile) - assert.NoError(t, err) - - var parsedCompat rpcChainCompatibility - err = json.Unmarshal(compat, &parsedCompat) - assert.NoError(t, err) - - rpcChainVMVersion, valueInJSON := parsedCompat.RPCChainVMProtocolVersion[Version] - assert.True(t, valueInJSON) - assert.Equal(t, rpcChainVMVersion, version.RPCChainVMProtocol) -} diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 2c6dfd55b9..acf8b9a64d 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -8,6 +8,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "math/big" "net/http" "os" @@ -18,18 +19,21 @@ import ( "github.com/ava-labs/avalanchego/network/p2p" "github.com/ava-labs/avalanchego/network/p2p/gossip" + "github.com/ava-labs/avalanchego/upgrade" + avalanchegoConstants "github.com/ava-labs/avalanchego/utils/constants" "github.com/prometheus/client_golang/prometheus" - "github.com/ava-labs/subnet-evm/commontype" + "github.com/ava-labs/subnet-evm/consensus/dummy" "github.com/ava-labs/subnet-evm/constants" "github.com/ava-labs/subnet-evm/core" "github.com/ava-labs/subnet-evm/core/rawdb" + "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/core/txpool" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/eth" "github.com/ava-labs/subnet-evm/eth/ethconfig" "github.com/ava-labs/subnet-evm/metrics" - subnetEVMPrometheus "github.com/ava-labs/subnet-evm/metrics/prometheus" + corethPrometheus "github.com/ava-labs/subnet-evm/metrics/prometheus" "github.com/ava-labs/subnet-evm/miner" "github.com/ava-labs/subnet-evm/node" "github.com/ava-labs/subnet-evm/params" @@ -37,10 +41,12 @@ import ( "github.com/ava-labs/subnet-evm/plugin/evm/message" "github.com/ava-labs/subnet-evm/trie/triedb/hashdb" + warpPrecompile "github.com/ava-labs/subnet-evm/precompile/contracts/warp" "github.com/ava-labs/subnet-evm/rpc" statesyncclient "github.com/ava-labs/subnet-evm/sync/client" "github.com/ava-labs/subnet-evm/sync/client/stats" "github.com/ava-labs/subnet-evm/trie" + "github.com/ava-labs/subnet-evm/utils" "github.com/ava-labs/subnet-evm/warp" "github.com/ava-labs/subnet-evm/warp/handlers" warpValidators "github.com/ava-labs/subnet-evm/warp/validators" @@ -64,7 +70,9 @@ import ( avalancheRPC "github.com/gorilla/rpc/v2" + "github.com/ava-labs/avalanchego/cache" "github.com/ava-labs/avalanchego/codec" + "github.com/ava-labs/avalanchego/codec/linearcodec" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/database/prefixdb" "github.com/ava-labs/avalanchego/database/versiondb" @@ -72,11 +80,18 @@ import ( "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/consensus/snowman" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" + "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" + "github.com/ava-labs/avalanchego/utils/formatting/address" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/utils/math" "github.com/ava-labs/avalanchego/utils/perms" "github.com/ava-labs/avalanchego/utils/profiler" + "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/timer/mockable" "github.com/ava-labs/avalanchego/utils/units" + "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/components/chain" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" commonEng "github.com/ava-labs/avalanchego/snow/engine/common" @@ -89,12 +104,32 @@ var ( _ block.BuildBlockWithContextChainVM = &VM{} _ block.StateSyncableVM = &VM{} _ statesyncclient.EthBlockParser = &VM{} + _ secp256k1fx.VM = &VM{} +) + +const ( + x2cRateInt64 int64 = 1_000_000_000 + x2cRateMinus1Int64 int64 = x2cRateInt64 - 1 +) + +var ( + // x2cRate is the conversion rate between the smallest denomination on the X-Chain + // 1 nAVAX and the smallest denomination on the C-Chain 1 wei. Where 1 nAVAX = 1 gWei. + // This is only required for AVAX because the denomination of 1 AVAX is 9 decimal + // places on the X and P chains, but is 18 decimal places within the EVM. + x2cRate = big.NewInt(x2cRateInt64) + x2cRateMinus1 = big.NewInt(x2cRateMinus1Int64) ) const ( // Max time from current time allowed for blocks, before they're considered future blocks // and fail verification - maxFutureBlockTime = 10 * time.Second + maxFutureBlockTime = 10 * time.Second + maxUTXOsToFetch = 1024 + defaultMempoolSize = 4096 + codecVersion = uint16(0) + + secpCacheSize = 1024 decidedCacheSize = 10 * units.MiB missingCacheSize = 50 unverifiedCacheSize = 5 * units.MiB @@ -106,6 +141,12 @@ const ( sdkMetricsPrefix = "sdk" chainStateMetricsPrefix = "chain_state" + targetAtomicTxsSize = 40 * units.KiB + + // p2p app protocols + ethTxGossipProtocol = 0x0 + atomicTxGossipProtocol = 0x1 + // gossip constants pushGossipDiscardedElements = 16_384 txGossipBloomMinTargetElements = 8 * 1024 @@ -121,10 +162,12 @@ const ( // Define the API endpoints for the VM const ( - adminEndpoint = "/admin" - ethRPCEndpoint = "/rpc" - ethWSEndpoint = "/ws" - ethTxGossipNamespace = "eth_tx_gossip" + avaxEndpoint = "/avax" + adminEndpoint = "/admin" + ethRPCEndpoint = "/rpc" + ethWSEndpoint = "/ws" + ethTxGossipNamespace = "eth_tx_gossip" + atomicTxGossipNamespace = "atomic_tx_gossip" ) var ( @@ -134,19 +177,45 @@ var ( metadataPrefix = []byte("metadata") warpPrefix = []byte("warp") ethDBPrefix = []byte("ethdb") + + // Prefixes for atomic trie + atomicTrieDBPrefix = []byte("atomicTrieDB") + atomicTrieMetaDBPrefix = []byte("atomicTrieMetaDB") ) var ( - errEmptyBlock = errors.New("empty block") - errUnsupportedFXs = errors.New("unsupported feature extensions") - errInvalidBlock = errors.New("invalid block") - errInvalidNonce = errors.New("invalid nonce") - errUnclesUnsupported = errors.New("uncles unsupported") - errNilBaseFeeSubnetEVM = errors.New("nil base fee is invalid after subnetEVM") - errNilBlockGasCostSubnetEVM = errors.New("nil blockGasCost is invalid after subnetEVM") - errInvalidHeaderPredicateResults = errors.New("invalid header predicate results") + errEmptyBlock = errors.New("empty block") + errUnsupportedFXs = errors.New("unsupported feature extensions") + errInvalidBlock = errors.New("invalid block") + errInvalidAddr = errors.New("invalid hex address") + errInsufficientAtomicTxFee = errors.New("atomic tx fee too low for atomic mempool") + errAssetIDMismatch = errors.New("asset IDs in the input don't match the utxo") + errNoImportInputs = errors.New("tx has no imported inputs") + errInputsNotSortedUnique = errors.New("inputs not sorted and unique") + errPublicKeySignatureMismatch = errors.New("signature doesn't match public key") + errWrongChainID = errors.New("tx has wrong chain ID") + errInsufficientFunds = errors.New("insufficient funds") + errNoExportOutputs = errors.New("tx has no export outputs") + errOutputsNotSorted = errors.New("tx outputs not sorted") + errOutputsNotSortedUnique = errors.New("outputs not sorted and unique") + errOverflowExport = errors.New("overflow when computing export amount + txFee") + errInvalidNonce = errors.New("invalid nonce") + errConflictingAtomicInputs = errors.New("invalid block due to conflicting atomic inputs") + errUnclesUnsupported = errors.New("uncles unsupported") + errRejectedParent = errors.New("rejected parent") + errInsufficientFundsForFee = errors.New("insufficient AVAX funds to pay transaction fee") + errNoEVMOutputs = errors.New("tx has no EVM outputs") + errNilBaseFeeApricotPhase3 = errors.New("nil base fee is invalid after apricotPhase3") + errNilExtDataGasUsedApricotPhase4 = errors.New("nil extDataGasUsed is invalid after apricotPhase4") + errNilBlockGasCostApricotPhase4 = errors.New("nil blockGasCost is invalid after apricotPhase4") + errConflictingAtomicTx = errors.New("conflicting atomic tx present") + errTooManyAtomicTx = errors.New("too many atomic tx") + errMissingAtomicTxs = errors.New("cannot build a block with non-empty extra data and zero atomic transactions") + errInvalidHeaderPredicateResults = errors.New("invalid header predicate results") ) +var originalStderr *os.File + // legacyApiNames maps pre geth v1.10.20 api names to their updated counterparts. // used in attachEthService for backward configuration compatibility. var legacyApiNames = map[string]string{ @@ -166,6 +235,13 @@ var legacyApiNames = map[string]string{ "private-debug": "debug", } +func init() { + // Preserve [os.Stderr] prior to the call in plugin/main.go to plugin.Serve(...). + // Preserving the log level allows us to update the root handler while writing to the original + // [os.Stderr] that is being piped through to the logger via the rpcchainvm. + originalStderr = os.Stderr +} + // VM implements the snowman.ChainVM interface type VM struct { ctx *snow.Context @@ -177,6 +253,7 @@ type VM struct { config Config + chainID *big.Int networkID uint64 genesisHash common.Hash chainConfig *params.ChainConfig @@ -209,13 +286,28 @@ type VM struct { syntacticBlockValidator BlockValidator + // [atomicTxRepository] maintains two indexes on accepted atomic txs. + // - txID to accepted atomic tx + // - block height to list of atomic txs accepted on block at that height + atomicTxRepository AtomicTxRepository + // [atomicTrie] maintains a merkle forest of [height]=>[atomic txs]. + atomicTrie AtomicTrie + // [atomicBackend] abstracts verification and processing of atomic transactions + atomicBackend AtomicBackend + builder *blockBuilder - clock mockable.Clock + baseCodec codec.Registry + codec codec.Manager + clock mockable.Clock + mempool *Mempool shutdownChan chan struct{} shutdownWg sync.WaitGroup + fx secp256k1fx.Fx + secpCache secp256k1.RecoverCache + // Continuous Profiler profiler profiler.ContinuousProfiler @@ -229,8 +321,9 @@ type VM struct { sdkMetrics *prometheus.Registry bootstrapped bool + IsPlugin bool - logger SubnetEVMLogger + logger CorethLogger // State sync server and client StateSyncServer StateSyncClient @@ -240,10 +333,33 @@ type VM struct { warpBackend warp.Backend // Initialize only sets these if nil so they can be overridden in tests - p2pSender commonEng.AppSender - ethTxGossipHandler p2p.Handler - ethTxPushGossiper avalancheUtils.Atomic[*gossip.PushGossiper[*GossipEthTx]] - ethTxPullGossiper gossip.Gossiper + p2pSender commonEng.AppSender + ethTxGossipHandler p2p.Handler + ethTxPushGossiper avalancheUtils.Atomic[*gossip.PushGossiper[*GossipEthTx]] + ethTxPullGossiper gossip.Gossiper + atomicTxGossipHandler p2p.Handler + atomicTxPushGossiper *gossip.PushGossiper[*GossipAtomicTx] + atomicTxPullGossiper gossip.Gossiper +} + +// CodecRegistry implements the secp256k1fx interface +func (vm *VM) CodecRegistry() codec.Registry { return vm.baseCodec } + +// Clock implements the secp256k1fx interface +func (vm *VM) Clock() *mockable.Clock { return &vm.clock } + +// Logger implements the secp256k1fx interface +func (vm *VM) Logger() logging.Logger { return vm.ctx.Log } + +/* + ****************************************************************************** + ********************************* Snowman API ******************************** + ****************************************************************************** + */ + +// implements SnowmanPlusPlusVM interface +func (vm *VM) GetActivationTime() time.Time { + return utils.Uint64ToTime(vm.chainConfig.ApricotPhase4BlockTimestamp) } // Initialize implements the snowman.ChainVM interface @@ -281,13 +397,18 @@ func (vm *VM) Initialize( alias = vm.ctx.ChainID.String() } - subnetEVMLogger, err := InitLogger(alias, vm.config.LogLevel, vm.config.LogJSONFormat, vm.ctx.Log) + var writer io.Writer = vm.ctx.Log + if vm.IsPlugin { + writer = originalStderr + } + + corethLogger, err := InitLogger(alias, vm.config.LogLevel, vm.config.LogJSONFormat, writer) if err != nil { return fmt.Errorf("failed to initialize logger due to: %w ", err) } - vm.logger = subnetEVMLogger + vm.logger = corethLogger - log.Info("Initializing Subnet EVM VM", "Version", Version, "Config", vm.config) + log.Info("Initializing Coreth VM", "Version", Version, "Config", vm.config) if deprecateMsg != "" { log.Warn("Deprecation Warning", "msg", deprecateMsg) @@ -327,68 +448,69 @@ func (vm *VM) Initialize( return err } - if g.Config == nil { - g.Config = params.SubnetEVMDefaultChainConfig + var extDataHashes map[common.Hash]common.Hash + var chainID *big.Int + // Set the chain config for mainnet/fuji chain IDs + switch chainCtx.NetworkID { + case avalanchegoConstants.MainnetID: + chainID = params.AvalancheMainnetChainID + extDataHashes = mainnetExtDataHashes + case avalanchegoConstants.FujiID: + chainID = params.AvalancheFujiChainID + extDataHashes = fujiExtDataHashes + case avalanchegoConstants.LocalID: + chainID = params.AvalancheLocalChainID + default: + chainID = g.Config.ChainID } - // Set the Avalanche Context on the ChainConfig - g.Config.AvalancheContext = params.AvalancheContext{ - SnowCtx: chainCtx, + // if the chainCtx.NetworkUpgrades is not empty, set the chain config + // normally it should not be empty, but some tests may not set it + if chainCtx.NetworkUpgrades != (upgrade.Config{}) { + g.Config = params.GetChainConfig(chainCtx.NetworkUpgrades, new(big.Int).Set(chainID)) } - g.Config.SetNetworkUpgradeDefaults() - - // Load airdrop file if provided - if vm.config.AirdropFile != "" { - g.AirdropData, err = os.ReadFile(vm.config.AirdropFile) - if err != nil { - return fmt.Errorf("could not read airdrop file '%s': %w", vm.config.AirdropFile, err) - } + // If the Durango is activated, activate the Warp Precompile at the same time + if g.Config.DurangoBlockTimestamp != nil { + g.Config.PrecompileUpgrades = append(g.Config.PrecompileUpgrades, params.PrecompileUpgrade{ + Config: warpPrecompile.NewDefaultConfig(g.Config.DurangoBlockTimestamp), + }) } - vm.syntacticBlockValidator = NewBlockValidator() - - if g.Config.FeeConfig == commontype.EmptyFeeConfig { - log.Info("No fee config given in genesis, setting default fee config", "DefaultFeeConfig", params.DefaultFeeConfig) - g.Config.FeeConfig = params.DefaultFeeConfig + // Set the Avalanche Context on the ChainConfig + g.Config.AvalancheContext = params.AvalancheContext{ + SnowCtx: chainCtx, } + vm.syntacticBlockValidator = NewBlockValidator(extDataHashes) - // Apply upgradeBytes (if any) by unmarshalling them into [chainConfig.UpgradeConfig]. - // Initializing the chain will verify upgradeBytes are compatible with existing values. - // This should be called before g.Verify(). - if len(upgradeBytes) > 0 { - var upgradeConfig params.UpgradeConfig - if err := json.Unmarshal(upgradeBytes, &upgradeConfig); err != nil { - return fmt.Errorf("failed to parse upgrade bytes: %w", err) + // Ensure that non-standard commit interval is not allowed for production networks + if avalanchegoConstants.ProductionNetworkIDs.Contains(chainCtx.NetworkID) { + if vm.config.CommitInterval != defaultCommitInterval { + return fmt.Errorf("cannot start non-local network with commit interval %d", vm.config.CommitInterval) } - g.Config.UpgradeConfig = upgradeConfig - } - - if g.Config.UpgradeConfig.NetworkUpgradeOverrides != nil { - overrides := g.Config.UpgradeConfig.NetworkUpgradeOverrides - marshaled, err := json.Marshal(overrides) - if err != nil { - log.Warn("Failed to marshal network upgrade overrides", "error", err, "overrides", overrides) - } else { - log.Info("Applying network upgrade overrides", "overrides", string(marshaled)) + if vm.config.StateSyncCommitInterval != defaultSyncableCommitInterval { + return fmt.Errorf("cannot start non-local network with syncable interval %d", vm.config.StateSyncCommitInterval) } - g.Config.Override(overrides) } - g.Config.SetEthUpgrades(g.Config.NetworkUpgrades) + // Free the memory of the extDataHash map that is not used (i.e. if mainnet + // config, free fuji) + fujiExtDataHashes = nil + mainnetExtDataHashes = nil - if err := g.Verify(); err != nil { - return fmt.Errorf("failed to verify genesis: %w", err) - } + vm.chainID = g.Config.ChainID + + g.Config.SetEthUpgrades() vm.ethConfig = ethconfig.NewDefaultConfig() vm.ethConfig.Genesis = g - // NetworkID here is different than Avalanche's NetworkID. - // Avalanche's NetworkID represents the Avalanche network is running on - // like Fuji, Mainnet, Local, etc. - // The NetworkId here is kept same as ChainID to be compatible with - // Ethereum tooling. - vm.ethConfig.NetworkId = g.Config.ChainID.Uint64() + vm.ethConfig.NetworkId = vm.chainID.Uint64() + vm.genesisHash = vm.ethConfig.Genesis.ToBlock().Hash() // must create genesis hash before [vm.readLastAccepted] + lastAcceptedHash, lastAcceptedHeight, err := vm.readLastAccepted() + if err != nil { + return err + } + log.Info(fmt.Sprintf("lastAccepted = %s", lastAcceptedHash)) // Set minimum price for mining and default gas price oracle value to the min // gas price to prevent so transactions and blocks all use the correct fees @@ -396,7 +518,6 @@ func (vm *VM) Initialize( vm.ethConfig.RPCEVMTimeout = vm.config.APIMaxDuration.Duration vm.ethConfig.RPCTxFeeCap = vm.config.RPCTxFeeCap - vm.ethConfig.TxPool.Locals = vm.config.PriorityRegossipAddresses vm.ethConfig.TxPool.NoLocals = !vm.config.LocalTxsEnabled vm.ethConfig.TxPool.PriceLimit = vm.config.TxPoolPriceLimit vm.ethConfig.TxPool.PriceBump = vm.config.TxPoolPriceBump @@ -420,7 +541,7 @@ func (vm *VM) Initialize( vm.ethConfig.PopulateMissingTries = vm.config.PopulateMissingTries vm.ethConfig.PopulateMissingTriesParallelism = vm.config.PopulateMissingTriesParallelism vm.ethConfig.AllowMissingTries = vm.config.AllowMissingTries - vm.ethConfig.SnapshotDelayInit = vm.config.StateSyncEnabled + vm.ethConfig.SnapshotDelayInit = vm.stateSyncEnabled(lastAcceptedHeight) vm.ethConfig.SnapshotWait = vm.config.SnapshotWait vm.ethConfig.SnapshotVerify = vm.config.SnapshotVerify vm.ethConfig.OfflinePruning = vm.config.OfflinePruning @@ -429,7 +550,7 @@ func (vm *VM) Initialize( vm.ethConfig.CommitInterval = vm.config.CommitInterval vm.ethConfig.SkipUpgradeCheck = vm.config.SkipUpgradeCheck vm.ethConfig.AcceptedCacheSize = vm.config.AcceptedCacheSize - vm.ethConfig.TransactionHistory = vm.config.TransactionHistory + vm.ethConfig.TxLookupLimit = vm.config.TxLookupLimit vm.ethConfig.SkipTxIndexing = vm.config.SkipTxIndexing // Create directory for offline pruning @@ -440,32 +561,30 @@ func (vm *VM) Initialize( } } - // Handle custom fee recipient - if common.IsHexAddress(vm.config.FeeRecipient) { - address := common.HexToAddress(vm.config.FeeRecipient) - log.Info("Setting fee recipient", "address", address) - vm.ethConfig.Miner.Etherbase = address - } else { - log.Info("Config has not specified any coinbase address. Defaulting to the blackhole address.") - vm.ethConfig.Miner.Etherbase = constants.BlackholeAddr - } - vm.chainConfig = g.Config vm.networkID = vm.ethConfig.NetworkId + vm.secpCache = secp256k1.RecoverCache{ + LRU: cache.LRU[ids.ID, *secp256k1.PublicKey]{ + Size: secpCacheSize, + }, + } - // create genesisHash after applying upgradeBytes in case - // upgradeBytes modifies genesis. - vm.genesisHash = vm.ethConfig.Genesis.ToBlock().Hash() // must create genesis hash before [vm.readLastAccepted] - lastAcceptedHash, lastAcceptedHeight, err := vm.readLastAccepted() - if err != nil { - return err + if err := vm.chainConfig.Verify(); err != nil { + return fmt.Errorf("failed to verify chain config: %w", err) } - log.Info(fmt.Sprintf("lastAccepted = %s", lastAcceptedHash)) + + vm.codec = Codec if err := vm.initializeMetrics(); err != nil { return err } + // TODO: read size from settings + vm.mempool, err = NewMempool(chainCtx, vm.sdkMetrics, defaultMempoolSize, vm.verifyTxAtTip) + if err != nil { + return fmt.Errorf("failed to initialize mempool: %w", err) + } + // initialize peer network if vm.p2pSender == nil { vm.p2pSender = appSender @@ -505,12 +624,46 @@ func (vm *VM) Initialize( } } - if err := vm.initializeChain(lastAcceptedHash, vm.ethConfig); err != nil { + if err := vm.initializeChain(lastAcceptedHash); err != nil { return err } + // initialize bonus blocks on mainnet + var ( + bonusBlockHeights map[uint64]ids.ID + ) + if vm.ctx.NetworkID == avalanchegoConstants.MainnetID { + bonusBlockHeights, err = readMainnetBonusBlocks() + if err != nil { + return fmt.Errorf("failed to read mainnet bonus blocks: %w", err) + } + } + + // initialize atomic repository + vm.atomicTxRepository, err = NewAtomicTxRepository(vm.db, vm.codec, lastAcceptedHeight) + if err != nil { + return fmt.Errorf("failed to create atomic repository: %w", err) + } + vm.atomicBackend, err = NewAtomicBackend( + vm.db, vm.ctx.SharedMemory, bonusBlockHeights, + vm.atomicTxRepository, lastAcceptedHeight, lastAcceptedHash, + vm.config.CommitInterval, + ) + if err != nil { + return fmt.Errorf("failed to create atomic backend: %w", err) + } + vm.atomicTrie = vm.atomicBackend.AtomicTrie() go vm.ctx.Log.RecoverAndPanic(vm.startContinuousProfiler) + // so [vm.baseCodec] is a dummy codec use to fulfill the secp256k1fx VM + // interface. The fx will register all of its types, which can be safely + // ignored by the VM's codec. + vm.baseCodec = linearcodec.NewDefault() + + if err := vm.fx.Initialize(vm); err != nil { + return err + } + vm.initializeHandlers() return vm.initializeStateSyncClient(lastAcceptedHeight) } @@ -522,16 +675,16 @@ func (vm *VM) initializeMetrics() error { return nil } - gatherer := subnetEVMPrometheus.Gatherer(metrics.DefaultRegistry) + gatherer := corethPrometheus.Gatherer(metrics.DefaultRegistry) if err := vm.ctx.Metrics.Register(ethMetricsPrefix, gatherer); err != nil { return err } return vm.ctx.Metrics.Register(sdkMetricsPrefix, vm.sdkMetrics) } -func (vm *VM) initializeChain(lastAcceptedHash common.Hash, ethConfig ethconfig.Config) error { +func (vm *VM) initializeChain(lastAcceptedHash common.Hash) error { nodecfg := &node.Config{ - SubnetEVMVersion: Version, + CorethVersion: Version, KeyStoreDir: vm.config.KeystoreDirectory, ExternalSigner: vm.config.KeystoreExternalSigner, InsecureUnlockAllowed: vm.config.KeystoreInsecureUnlockAllowed, @@ -543,6 +696,7 @@ func (vm *VM) initializeChain(lastAcceptedHash common.Hash, ethConfig ethconfig. vm.eth, err = eth.New( node, &vm.ethConfig, + vm.createConsensusCallbacks(), &EthPushGossiper{vm: vm}, vm.chaindb, vm.config.EthBackendSettings(), @@ -552,24 +706,57 @@ func (vm *VM) initializeChain(lastAcceptedHash common.Hash, ethConfig ethconfig. if err != nil { return err } - vm.eth.SetEtherbase(ethConfig.Miner.Etherbase) + vm.eth.SetEtherbase(constants.BlackholeAddr) vm.txPool = vm.eth.TxPool() - vm.txPool.SetMinFee(vm.chainConfig.FeeConfig.MinBaseFee) - vm.txPool.SetGasTip(big.NewInt(0)) vm.blockChain = vm.eth.BlockChain() vm.miner = vm.eth.Miner() + // Set the gas parameters for the tx pool to the minimum gas price for the + // latest upgrade. + vm.txPool.SetGasTip(big.NewInt(0)) + vm.setMinFeeAtEtna() + vm.eth.Start() return vm.initChainState(vm.blockChain.LastAcceptedBlock()) } +// TODO: remove this after Etna is activated +func (vm *VM) setMinFeeAtEtna() { + now := vm.clock.Time() + if vm.chainConfig.EtnaTimestamp == nil { + // If Etna is not set, set the min fee according to the latest upgrade + vm.txPool.SetMinFee(big.NewInt(params.ApricotPhase4MinBaseFee)) + return + } else if vm.chainConfig.IsEtna(uint64(now.Unix())) { + // If Etna is activated, set the min fee to the Etna min fee + vm.txPool.SetMinFee(big.NewInt(params.EtnaMinBaseFee)) + return + } + + vm.txPool.SetMinFee(big.NewInt(params.ApricotPhase4MinBaseFee)) + vm.shutdownWg.Add(1) + go func() { + defer vm.shutdownWg.Done() + + wait := utils.Uint64ToTime(vm.chainConfig.EtnaTimestamp).Sub(now) + t := time.NewTimer(wait) + select { + case <-t.C: // Wait for Etna to be activated + vm.txPool.SetMinFee(big.NewInt(params.EtnaMinBaseFee)) + case <-vm.shutdownChan: + } + t.Stop() + }() +} + // initializeStateSyncClient initializes the client for performing state sync. // If state sync is disabled, this function will wipe any ongoing summary from // disk to ensure that we do not continue syncing from an invalid snapshot. func (vm *VM) initializeStateSyncClient(lastAcceptedHeight uint64) error { + stateSyncEnabled := vm.stateSyncEnabled(lastAcceptedHeight) // parse nodeIDs from state sync IDs in vm config var stateSyncIDs []ids.NodeID - if vm.config.StateSyncEnabled && len(vm.config.StateSyncIDs) > 0 { + if stateSyncEnabled && len(vm.config.StateSyncIDs) > 0 { nodeIDs := strings.Split(vm.config.StateSyncIDs, ",") stateSyncIDs = make([]ids.NodeID, len(nodeIDs)) for i, nodeIDString := range nodeIDs { @@ -593,7 +780,7 @@ func (vm *VM) initializeStateSyncClient(lastAcceptedHeight uint64) error { BlockParser: vm, }, ), - enabled: vm.config.StateSyncEnabled, + enabled: stateSyncEnabled, skipResume: vm.config.StateSyncSkipResume, stateSyncMinBlocks: vm.config.StateSyncMinBlocks, stateSyncRequestSize: vm.config.StateSyncRequestSize, @@ -602,12 +789,13 @@ func (vm *VM) initializeStateSyncClient(lastAcceptedHeight uint64) error { metadataDB: vm.metadataDB, acceptedBlockDB: vm.acceptedBlockDB, db: vm.db, + atomicBackend: vm.atomicBackend, toEngine: vm.toEngine, }) // If StateSync is disabled, clear any ongoing summary so that we will not attempt to resume // sync using a snapshot that has been modified by the node running normal operations. - if !vm.config.StateSyncEnabled { + if !stateSyncEnabled { return vm.StateSyncClient.ClearOngoingSummary() } @@ -618,6 +806,7 @@ func (vm *VM) initializeStateSyncClient(lastAcceptedHeight uint64) error { func (vm *VM) initializeHandlers() { vm.StateSyncServer = NewStateSyncServer(&stateSyncServerConfig{ Chain: vm.blockChain, + AtomicTrie: vm.atomicTrie, SyncableInterval: vm.config.StateSyncCommitInterval, }) @@ -629,7 +818,10 @@ func (vm *VM) initializeHandlers() { } func (vm *VM) initChainState(lastAcceptedBlock *types.Block) error { - block := vm.newBlock(lastAcceptedBlock) + block, err := vm.newBlock(lastAcceptedBlock) + if err != nil { + return fmt.Errorf("failed to create block wrapper for the last accepted block: %w", err) + } config := &chain.Config{ DecidedCacheSize: decidedCacheSize, @@ -658,6 +850,232 @@ func (vm *VM) initChainState(lastAcceptedBlock *types.Block) error { return vm.ctx.Metrics.Register(chainStateMetricsPrefix, chainStateRegisterer) } +func (vm *VM) createConsensusCallbacks() dummy.ConsensusCallbacks { + return dummy.ConsensusCallbacks{ + OnFinalizeAndAssemble: vm.onFinalizeAndAssemble, + OnExtraStateChange: vm.onExtraStateChange, + } +} + +func (vm *VM) preBatchOnFinalizeAndAssemble(header *types.Header, state *state.StateDB, txs []*types.Transaction) ([]byte, *big.Int, *big.Int, error) { + for { + tx, exists := vm.mempool.NextTx() + if !exists { + break + } + // Take a snapshot of [state] before calling verifyTx so that if the transaction fails verification + // we can revert to [snapshot]. + // Note: snapshot is taken inside the loop because you cannot revert to the same snapshot more than + // once. + snapshot := state.Snapshot() + rules := vm.chainConfig.Rules(header.Number, header.Time) + if err := vm.verifyTx(tx, header.ParentHash, header.BaseFee, state, rules); err != nil { + // Discard the transaction from the mempool on failed verification. + log.Debug("discarding tx from mempool on failed verification", "txID", tx.ID(), "err", err) + vm.mempool.DiscardCurrentTx(tx.ID()) + state.RevertToSnapshot(snapshot) + continue + } + + atomicTxBytes, err := vm.codec.Marshal(codecVersion, tx) + if err != nil { + // Discard the transaction from the mempool and error if the transaction + // cannot be marshalled. This should never happen. + log.Debug("discarding tx due to unmarshal err", "txID", tx.ID(), "err", err) + vm.mempool.DiscardCurrentTx(tx.ID()) + return nil, nil, nil, fmt.Errorf("failed to marshal atomic transaction %s due to %w", tx.ID(), err) + } + var contribution, gasUsed *big.Int + if rules.IsApricotPhase4 { + contribution, gasUsed, err = tx.BlockFeeContribution(rules.IsApricotPhase5, vm.ctx.AVAXAssetID, header.BaseFee) + if err != nil { + return nil, nil, nil, err + } + } + return atomicTxBytes, contribution, gasUsed, nil + } + + if len(txs) == 0 { + // this could happen due to the async logic of geth tx pool + return nil, nil, nil, errEmptyBlock + } + + return nil, nil, nil, nil +} + +// assumes that we are in at least Apricot Phase 5. +func (vm *VM) postBatchOnFinalizeAndAssemble(header *types.Header, state *state.StateDB, txs []*types.Transaction) ([]byte, *big.Int, *big.Int, error) { + var ( + batchAtomicTxs []*Tx + batchAtomicUTXOs set.Set[ids.ID] + batchContribution *big.Int = new(big.Int).Set(common.Big0) + batchGasUsed *big.Int = new(big.Int).Set(common.Big0) + rules = vm.chainConfig.Rules(header.Number, header.Time) + size int + ) + + for { + tx, exists := vm.mempool.NextTx() + if !exists { + break + } + + // Ensure that adding [tx] to the block will not exceed the block size soft limit. + txSize := len(tx.SignedBytes()) + if size+txSize > targetAtomicTxsSize { + vm.mempool.CancelCurrentTx(tx.ID()) + break + } + + var ( + txGasUsed, txContribution *big.Int + err error + ) + + // Note: we do not need to check if we are in at least ApricotPhase4 here because + // we assume that this function will only be called when the block is in at least + // ApricotPhase5. + txContribution, txGasUsed, err = tx.BlockFeeContribution(true, vm.ctx.AVAXAssetID, header.BaseFee) + if err != nil { + return nil, nil, nil, err + } + // ensure [gasUsed] + [batchGasUsed] doesnt exceed the [atomicGasLimit] + if totalGasUsed := new(big.Int).Add(batchGasUsed, txGasUsed); totalGasUsed.Cmp(params.AtomicGasLimit) > 0 { + // Send [tx] back to the mempool's tx heap. + vm.mempool.CancelCurrentTx(tx.ID()) + break + } + + if batchAtomicUTXOs.Overlaps(tx.InputUTXOs()) { + // Discard the transaction from the mempool since it will fail verification + // after this block has been accepted. + // Note: if the proposed block is not accepted, the transaction may still be + // valid, but we discard it early here based on the assumption that the proposed + // block will most likely be accepted. + // Discard the transaction from the mempool on failed verification. + log.Debug("discarding tx due to overlapping input utxos", "txID", tx.ID()) + vm.mempool.DiscardCurrentTx(tx.ID()) + continue + } + + snapshot := state.Snapshot() + if err := vm.verifyTx(tx, header.ParentHash, header.BaseFee, state, rules); err != nil { + // Discard the transaction from the mempool and reset the state to [snapshot] + // if it fails verification here. + // Note: prior to this point, we have not modified [state] so there is no need to + // revert to a snapshot if we discard the transaction prior to this point. + log.Debug("discarding tx from mempool due to failed verification", "txID", tx.ID(), "err", err) + vm.mempool.DiscardCurrentTx(tx.ID()) + state.RevertToSnapshot(snapshot) + continue + } + + batchAtomicTxs = append(batchAtomicTxs, tx) + batchAtomicUTXOs.Union(tx.InputUTXOs()) + // Add the [txGasUsed] to the [batchGasUsed] when the [tx] has passed verification + batchGasUsed.Add(batchGasUsed, txGasUsed) + batchContribution.Add(batchContribution, txContribution) + size += txSize + } + + // If there is a non-zero number of transactions, marshal them and return the byte slice + // for the block's extra data along with the contribution and gas used. + if len(batchAtomicTxs) > 0 { + atomicTxBytes, err := vm.codec.Marshal(codecVersion, batchAtomicTxs) + if err != nil { + // If we fail to marshal the batch of atomic transactions for any reason, + // discard the entire set of current transactions. + log.Debug("discarding txs due to error marshaling atomic transactions", "err", err) + vm.mempool.DiscardCurrentTxs() + return nil, nil, nil, fmt.Errorf("failed to marshal batch of atomic transactions due to %w", err) + } + return atomicTxBytes, batchContribution, batchGasUsed, nil + } + + // If there are no regular transactions and there were also no atomic transactions to be included, + // then the block is empty and should be considered invalid. + if len(txs) == 0 { + // this could happen due to the async logic of geth tx pool + return nil, nil, nil, errEmptyBlock + } + + // If there are no atomic transactions, but there is a non-zero number of regular transactions, then + // we return a nil slice with no contribution from the atomic transactions and a nil error. + return nil, nil, nil, nil +} + +func (vm *VM) onFinalizeAndAssemble(header *types.Header, state *state.StateDB, txs []*types.Transaction) ([]byte, *big.Int, *big.Int, error) { + if !vm.chainConfig.IsApricotPhase5(header.Time) { + return vm.preBatchOnFinalizeAndAssemble(header, state, txs) + } + return vm.postBatchOnFinalizeAndAssemble(header, state, txs) +} + +func (vm *VM) onExtraStateChange(block *types.Block, state *state.StateDB) (*big.Int, *big.Int, error) { + var ( + batchContribution *big.Int = big.NewInt(0) + batchGasUsed *big.Int = big.NewInt(0) + header = block.Header() + rules = vm.chainConfig.Rules(header.Number, header.Time) + ) + + txs, err := ExtractAtomicTxs(block.ExtData(), rules.IsApricotPhase5, vm.codec) + if err != nil { + return nil, nil, err + } + + // If [atomicBackend] is nil, the VM is still initializing and is reprocessing accepted blocks. + if vm.atomicBackend != nil { + if vm.atomicBackend.IsBonus(block.NumberU64(), block.Hash()) { + log.Info("skipping atomic tx verification on bonus block", "block", block.Hash()) + } else { + // Verify [txs] do not conflict with themselves or ancestor blocks. + if err := vm.verifyTxs(txs, block.ParentHash(), block.BaseFee(), block.NumberU64(), rules); err != nil { + return nil, nil, err + } + } + // Update the atomic backend with [txs] from this block. + // + // Note: The atomic trie canonically contains the duplicate operations + // from any bonus blocks. + _, err := vm.atomicBackend.InsertTxs(block.Hash(), block.NumberU64(), block.ParentHash(), txs) + if err != nil { + return nil, nil, err + } + } + + // If there are no transactions, we can return early. + if len(txs) == 0 { + return nil, nil, nil + } + + for _, tx := range txs { + if err := tx.UnsignedAtomicTx.EVMStateTransfer(vm.ctx, state); err != nil { + return nil, nil, err + } + // If ApricotPhase4 is enabled, calculate the block fee contribution + if rules.IsApricotPhase4 { + contribution, gasUsed, err := tx.BlockFeeContribution(rules.IsApricotPhase5, vm.ctx.AVAXAssetID, block.BaseFee()) + if err != nil { + return nil, nil, err + } + + batchContribution.Add(batchContribution, contribution) + batchGasUsed.Add(batchGasUsed, gasUsed) + } + + // If ApricotPhase5 is enabled, enforce that the atomic gas used does not exceed the + // atomic gas limit. + if rules.IsApricotPhase5 { + // Ensure that [tx] does not push [block] above the atomic gas limit. + if batchGasUsed.Cmp(params.AtomicGasLimit) == 1 { + return nil, nil, fmt.Errorf("atomic gas used (%d) by block (%s), exceeds atomic gas limit (%d)", batchGasUsed, block.Hash().Hex(), params.AtomicGasLimit) + } + } + } + return batchContribution, batchGasUsed, nil +} + func (vm *VM) SetState(_ context.Context, state snow.State) error { switch state { case snow.StateSyncing: @@ -675,14 +1093,14 @@ func (vm *VM) SetState(_ context.Context, state snow.State) error { // Ensure snapshots are initialized before bootstrapping (i.e., if state sync is skipped). // Note calling this function has no effect if snapshots are already initialized. vm.blockChain.InitializeSnapshots() - return nil + return vm.fx.Bootstrapping() case snow.NormalOp: // Initialize goroutines related to block building once we enter normal operation as there is no need to handle mempool gossip before this point. if err := vm.initBlockBuilding(); err != nil { return fmt.Errorf("failed to initialize block building: %w", err) } vm.bootstrapped = true - return nil + return vm.fx.Bootstrapped() default: return snow.ErrUnknownState } @@ -694,7 +1112,7 @@ func (vm *VM) initBlockBuilding() error { vm.cancel = cancel ethTxGossipMarshaller := GossipEthTxMarshaller{} - ethTxGossipClient := vm.Network.NewClient(p2p.TxGossipHandlerID, p2p.WithValidatorSampling(vm.validators)) + ethTxGossipClient := vm.Network.NewClient(ethTxGossipProtocol, p2p.WithValidatorSampling(vm.validators)) ethTxGossipMetrics, err := gossip.NewMetrics(vm.sdkMetrics, ethTxGossipNamespace) if err != nil { return fmt.Errorf("failed to initialize eth tx gossip metrics: %w", err) @@ -709,6 +1127,13 @@ func (vm *VM) initBlockBuilding() error { vm.shutdownWg.Done() }() + atomicTxGossipMarshaller := GossipAtomicTxMarshaller{} + atomicTxGossipClient := vm.Network.NewClient(atomicTxGossipProtocol, p2p.WithValidatorSampling(vm.validators)) + atomicTxGossipMetrics, err := gossip.NewMetrics(vm.sdkMetrics, atomicTxGossipNamespace) + if err != nil { + return fmt.Errorf("failed to initialize atomic tx gossip metrics: %w", err) + } + pushGossipParams := gossip.BranchingFactor{ StakePercentage: vm.config.PushGossipPercentStake, Validators: vm.config.PushGossipNumValidators, @@ -739,6 +1164,24 @@ func (vm *VM) initBlockBuilding() error { vm.ethTxPushGossiper.Set(ethTxPushGossiper) } + if vm.atomicTxPushGossiper == nil { + vm.atomicTxPushGossiper, err = gossip.NewPushGossiper[*GossipAtomicTx]( + atomicTxGossipMarshaller, + vm.mempool, + vm.validators, + atomicTxGossipClient, + atomicTxGossipMetrics, + pushGossipParams, + pushRegossipParams, + pushGossipDiscardedElements, + txGossipTargetMessageSize, + vm.config.RegossipFrequency.Duration, + ) + if err != nil { + return fmt.Errorf("failed to initialize atomic tx push gossiper: %w", err) + } + } + // NOTE: gossip network must be initialized first otherwise ETH tx gossip will not work. gossipStats := NewGossipStats() vm.builder = vm.NewBlockBuilder(vm.toEngine) @@ -758,7 +1201,24 @@ func (vm *VM) initBlockBuilding() error { ) } - if err := vm.Network.AddHandler(p2p.TxGossipHandlerID, vm.ethTxGossipHandler); err != nil { + if err := vm.Network.AddHandler(ethTxGossipProtocol, vm.ethTxGossipHandler); err != nil { + return err + } + + if vm.atomicTxGossipHandler == nil { + vm.atomicTxGossipHandler = newTxGossipHandler[*GossipAtomicTx]( + vm.ctx.Log, + atomicTxGossipMarshaller, + vm.mempool, + atomicTxGossipMetrics, + txGossipTargetMessageSize, + txGossipThrottlingPeriod, + txGossipThrottlingLimit, + vm.validators, + ) + } + + if err := vm.Network.AddHandler(atomicTxGossipProtocol, vm.atomicTxGossipHandler); err != nil { return err } @@ -789,6 +1249,33 @@ func (vm *VM) initBlockBuilding() error { vm.shutdownWg.Done() }() + if vm.atomicTxPullGossiper == nil { + atomicTxPullGossiper := gossip.NewPullGossiper[*GossipAtomicTx]( + vm.ctx.Log, + atomicTxGossipMarshaller, + vm.mempool, + atomicTxGossipClient, + atomicTxGossipMetrics, + txGossipPollSize, + ) + + vm.atomicTxPullGossiper = &gossip.ValidatorGossiper{ + Gossiper: atomicTxPullGossiper, + NodeID: vm.ctx.NodeID, + Validators: vm.validators, + } + } + + vm.shutdownWg.Add(2) + go func() { + gossip.Every(ctx, vm.ctx.Log, vm.atomicTxPushGossiper, vm.config.PushGossipFrequency.Duration) + vm.shutdownWg.Done() + }() + go func() { + gossip.Every(ctx, vm.ctx.Log, vm.atomicTxPullGossiper, vm.config.PullGossipFrequency.Duration) + vm.shutdownWg.Done() + }() + return nil } @@ -806,8 +1293,14 @@ func (vm *VM) setAppRequestHandlers() { }, }, ) - - networkHandler := newNetworkHandler(vm.blockChain, vm.chaindb, evmTrieDB, vm.warpBackend, vm.networkCodec) + networkHandler := newNetworkHandler( + vm.blockChain, + vm.chaindb, + evmTrieDB, + vm.atomicTrie.TrieDB(), + vm.warpBackend, + vm.networkCodec, + ) vm.Network.SetRequestHandler(networkHandler) } @@ -825,9 +1318,7 @@ func (vm *VM) Shutdown(context.Context) error { } close(vm.shutdownChan) vm.eth.Stop() - log.Info("Ethereum backend stop completed") vm.shutdownWg.Wait() - log.Info("Subnet-EVM Shutdown completed") return nil } @@ -850,11 +1341,17 @@ func (vm *VM) buildBlockWithContext(ctx context.Context, proposerVMBlockCtx *blo block, err := vm.miner.GenerateBlock(predicateCtx) vm.builder.handleGenerateBlock() if err != nil { + vm.mempool.CancelCurrentTxs() return nil, err } // Note: the status of block is set by ChainState - blk := vm.newBlock(block) + blk, err := vm.newBlock(block) + if err != nil { + log.Debug("discarding txs due to error making new block", "err", err) + vm.mempool.DiscardCurrentTxs() + return nil, err + } // Verify is called on a non-wrapped block here, such that this // does not add [blk] to the processing blocks map in ChainState. @@ -869,12 +1366,14 @@ func (vm *VM) buildBlockWithContext(ctx context.Context, proposerVMBlockCtx *blo // to the blk state root in the triedb when we are going to call verify // again from the consensus engine with writes enabled. if err := blk.verify(predicateCtx, false /*=writes*/); err != nil { + vm.mempool.CancelCurrentTxs() return nil, fmt.Errorf("block failed verification due to: %w", err) } log.Debug(fmt.Sprintf("Built block %s", blk.ID())) // Marks the current transactions from the mempool as being successfully issued // into a block. + vm.mempool.IssueCurrentTxs() return blk, nil } @@ -886,7 +1385,10 @@ func (vm *VM) parseBlock(_ context.Context, b []byte) (snowman.Block, error) { } // Note: the status of block is set by ChainState - block := vm.newBlock(ethBlock) + block, err := vm.newBlock(ethBlock) + if err != nil { + return nil, err + } // Performing syntactic verification in ParseBlock allows for // short-circuiting bad blocks before they are processed by the VM. if err := block.syntacticVerify(); err != nil { @@ -914,7 +1416,7 @@ func (vm *VM) getBlock(_ context.Context, id ids.ID) (snowman.Block, error) { return nil, database.ErrNotFound } // Note: the status of block is set by ChainState - return vm.newBlock(ethBlock), nil + return vm.newBlock(ethBlock) } // GetAcceptedBlock attempts to retrieve block [blkID] from the VM. This method @@ -957,7 +1459,7 @@ func (vm *VM) VerifyHeightIndex(context.Context) error { return nil } -// GetBlockIDAtHeight returns the canonical block at [height]. +// GetBlockAtHeight returns the canonical block at [height]. // Note: the engine assumes that if a block is not found at [height], then // [database.ErrNotFound] will be returned. This indicates that the VM has state // synced and does not have all historical blocks available. @@ -1002,13 +1504,20 @@ func (vm *VM) CreateHandlers(context.Context) (map[string]http.Handler, error) { return nil, fmt.Errorf("failed to get primary alias for chain due to %w", err) } apis := make(map[string]http.Handler) + avaxAPI, err := newHandler("avax", &AvaxAPI{vm}) + if err != nil { + return nil, fmt.Errorf("failed to register service for AVAX API due to %w", err) + } + enabledAPIs = append(enabledAPIs, "avax") + apis[avaxEndpoint] = avaxAPI + if vm.config.AdminAPIEnabled { - adminAPI, err := newHandler("admin", NewAdminService(vm, os.ExpandEnv(fmt.Sprintf("%s_subnet_evm_performance_%s", vm.config.AdminAPIDir, primaryAlias)))) + adminAPI, err := newHandler("admin", NewAdminService(vm, os.ExpandEnv(fmt.Sprintf("%s_coreth_performance_%s", vm.config.AdminAPIDir, primaryAlias)))) if err != nil { return nil, fmt.Errorf("failed to register service for admin API due to %w", err) } apis[adminEndpoint] = adminAPI - enabledAPIs = append(enabledAPIs, "subnet-evm-admin") + enabledAPIs = append(enabledAPIs, "coreth-admin") } if vm.config.SnowmanAPIEnabled { @@ -1056,6 +1565,369 @@ func (vm *VM) CreateStaticHandlers(context.Context) (map[string]http.Handler, er ****************************************************************************** */ +// conflicts returns an error if [inputs] conflicts with any of the atomic inputs contained in [ancestor] +// or any of its ancestor blocks going back to the last accepted block in its ancestry. If [ancestor] is +// accepted, then nil will be returned immediately. +// If the ancestry of [ancestor] cannot be fetched, then [errRejectedParent] may be returned. +func (vm *VM) conflicts(inputs set.Set[ids.ID], ancestor *Block) error { + lastAcceptedBlock := vm.LastAcceptedBlock() + lastAcceptedHeight := lastAcceptedBlock.Height() + for ancestor.Height() > lastAcceptedHeight { + // If any of the atomic transactions in the ancestor conflict with [inputs] + // return an error. + for _, atomicTx := range ancestor.atomicTxs { + if inputs.Overlaps(atomicTx.InputUTXOs()) { + return errConflictingAtomicInputs + } + } + + // Move up the chain. + nextAncestorID := ancestor.Parent() + // If the ancestor is unknown, then the parent failed + // verification when it was called. + // If the ancestor is rejected, then this block shouldn't be + // inserted into the canonical chain because the parent is + // will be missing. + // If the ancestor is processing, then the block may have + // been verified. + nextAncestorIntf, err := vm.GetBlockInternal(context.TODO(), nextAncestorID) + if err != nil { + return errRejectedParent + } + nextAncestor, ok := nextAncestorIntf.(*Block) + if !ok { + return fmt.Errorf("ancestor block %s had unexpected type %T", nextAncestor.ID(), nextAncestorIntf) + } + ancestor = nextAncestor + } + + return nil +} + +// getAtomicTx returns the requested transaction, status, and height. +// If the status is Unknown, then the returned transaction will be nil. +func (vm *VM) getAtomicTx(txID ids.ID) (*Tx, Status, uint64, error) { + if tx, height, err := vm.atomicTxRepository.GetByTxID(txID); err == nil { + return tx, Accepted, height, nil + } else if err != database.ErrNotFound { + return nil, Unknown, 0, err + } + tx, dropped, found := vm.mempool.GetTx(txID) + switch { + case found && dropped: + return tx, Dropped, 0, nil + case found: + return tx, Processing, 0, nil + default: + return nil, Unknown, 0, nil + } +} + +// ParseAddress takes in an address and produces the ID of the chain it's for +// the ID of the address +func (vm *VM) ParseAddress(addrStr string) (ids.ID, ids.ShortID, error) { + chainIDAlias, hrp, addrBytes, err := address.Parse(addrStr) + if err != nil { + return ids.ID{}, ids.ShortID{}, err + } + + chainID, err := vm.ctx.BCLookup.Lookup(chainIDAlias) + if err != nil { + return ids.ID{}, ids.ShortID{}, err + } + + expectedHRP := avalanchegoConstants.GetHRP(vm.ctx.NetworkID) + if hrp != expectedHRP { + return ids.ID{}, ids.ShortID{}, fmt.Errorf("expected hrp %q but got %q", + expectedHRP, hrp) + } + + addr, err := ids.ToShortID(addrBytes) + if err != nil { + return ids.ID{}, ids.ShortID{}, err + } + return chainID, addr, nil +} + +// verifyTxAtTip verifies that [tx] is valid to be issued on top of the currently preferred block +func (vm *VM) verifyTxAtTip(tx *Tx) error { + if txByteLen := len(tx.SignedBytes()); txByteLen > targetAtomicTxsSize { + return fmt.Errorf("tx size (%d) exceeds total atomic txs size target (%d)", txByteLen, targetAtomicTxsSize) + } + gasUsed, err := tx.GasUsed(true) + if err != nil { + return err + } + if new(big.Int).SetUint64(gasUsed).Cmp(params.AtomicGasLimit) > 0 { + return fmt.Errorf("tx gas usage (%d) exceeds atomic gas limit (%d)", gasUsed, params.AtomicGasLimit.Uint64()) + } + + // Note: we fetch the current block and then the state at that block instead of the current state directly + // since we need the header of the current block below. + preferredBlock := vm.blockChain.CurrentBlock() + preferredState, err := vm.blockChain.StateAt(preferredBlock.Root) + if err != nil { + return fmt.Errorf("failed to retrieve block state at tip while verifying atomic tx: %w", err) + } + rules := vm.currentRules() + parentHeader := preferredBlock + var nextBaseFee *big.Int + timestamp := uint64(vm.clock.Time().Unix()) + if vm.chainConfig.IsApricotPhase3(timestamp) { + _, nextBaseFee, err = dummy.EstimateNextBaseFee(vm.chainConfig, parentHeader, timestamp) + if err != nil { + // Return extremely detailed error since CalcBaseFee should never encounter an issue here + return fmt.Errorf("failed to calculate base fee with parent timestamp (%d), parent ExtraData: (0x%x), and current timestamp (%d): %w", parentHeader.Time, parentHeader.Extra, timestamp, err) + } + } + + // We don’t need to revert the state here in case verifyTx errors, because + // [preferredState] is thrown away either way. + return vm.verifyTx(tx, parentHeader.Hash(), nextBaseFee, preferredState, rules) +} + +// verifyTx verifies that [tx] is valid to be issued into a block with parent block [parentHash] +// and validated at [state] using [rules] as the current rule set. +// Note: verifyTx may modify [state]. If [state] needs to be properly maintained, the caller is responsible +// for reverting to the correct snapshot after calling this function. If this function is called with a +// throwaway state, then this is not necessary. +func (vm *VM) verifyTx(tx *Tx, parentHash common.Hash, baseFee *big.Int, state *state.StateDB, rules params.Rules) error { + parentIntf, err := vm.GetBlockInternal(context.TODO(), ids.ID(parentHash)) + if err != nil { + return fmt.Errorf("failed to get parent block: %w", err) + } + parent, ok := parentIntf.(*Block) + if !ok { + return fmt.Errorf("parent block %s had unexpected type %T", parentIntf.ID(), parentIntf) + } + if err := tx.UnsignedAtomicTx.SemanticVerify(vm, tx, parent, baseFee, rules); err != nil { + return err + } + return tx.UnsignedAtomicTx.EVMStateTransfer(vm.ctx, state) +} + +// verifyTxs verifies that [txs] are valid to be issued into a block with parent block [parentHash] +// using [rules] as the current rule set. +func (vm *VM) verifyTxs(txs []*Tx, parentHash common.Hash, baseFee *big.Int, height uint64, rules params.Rules) error { + // Ensure that the parent was verified and inserted correctly. + if !vm.blockChain.HasBlock(parentHash, height-1) { + return errRejectedParent + } + + ancestorID := ids.ID(parentHash) + // If the ancestor is unknown, then the parent failed verification when + // it was called. + // If the ancestor is rejected, then this block shouldn't be inserted + // into the canonical chain because the parent will be missing. + ancestorInf, err := vm.GetBlockInternal(context.TODO(), ancestorID) + if err != nil { + return errRejectedParent + } + ancestor, ok := ancestorInf.(*Block) + if !ok { + return fmt.Errorf("expected parent block %s, to be *Block but is %T", ancestor.ID(), ancestorInf) + } + + // Ensure each tx in [txs] doesn't conflict with any other atomic tx in + // a processing ancestor block. + inputs := set.Set[ids.ID]{} + for _, atomicTx := range txs { + utx := atomicTx.UnsignedAtomicTx + if err := utx.SemanticVerify(vm, atomicTx, ancestor, baseFee, rules); err != nil { + return fmt.Errorf("invalid block due to failed semanatic verify: %w at height %d", err, height) + } + txInputs := utx.InputUTXOs() + if inputs.Overlaps(txInputs) { + return errConflictingAtomicInputs + } + inputs.Union(txInputs) + } + return nil +} + +// GetAtomicUTXOs returns the utxos that at least one of the provided addresses is +// referenced in. +func (vm *VM) GetAtomicUTXOs( + chainID ids.ID, + addrs set.Set[ids.ShortID], + startAddr ids.ShortID, + startUTXOID ids.ID, + limit int, +) ([]*avax.UTXO, ids.ShortID, ids.ID, error) { + if limit <= 0 || limit > maxUTXOsToFetch { + limit = maxUTXOsToFetch + } + + return avax.GetAtomicUTXOs( + vm.ctx.SharedMemory, + vm.codec, + chainID, + addrs, + startAddr, + startUTXOID, + limit, + ) +} + +// GetSpendableFunds returns a list of EVMInputs and keys (in corresponding +// order) to total [amount] of [assetID] owned by [keys]. +// Note: we return [][]*secp256k1.PrivateKey even though each input +// corresponds to a single key, so that the signers can be passed in to +// [tx.Sign] which supports multiple keys on a single input. +func (vm *VM) GetSpendableFunds( + keys []*secp256k1.PrivateKey, + assetID ids.ID, + amount uint64, +) ([]EVMInput, [][]*secp256k1.PrivateKey, error) { + // Note: current state uses the state of the preferred block. + state, err := vm.blockChain.State() + if err != nil { + return nil, nil, err + } + inputs := []EVMInput{} + signers := [][]*secp256k1.PrivateKey{} + // Note: we assume that each key in [keys] is unique, so that iterating over + // the keys will not produce duplicated nonces in the returned EVMInput slice. + for _, key := range keys { + if amount == 0 { + break + } + addr := GetEthAddress(key) + var balance uint64 + if assetID == vm.ctx.AVAXAssetID { + // If the asset is AVAX, we divide by the x2cRate to convert back to the correct + // denomination of AVAX that can be exported. + balance = new(big.Int).Div(state.GetBalance(addr), x2cRate).Uint64() + } else { + balance = state.GetBalanceMultiCoin(addr, common.Hash(assetID)).Uint64() + } + if balance == 0 { + continue + } + if amount < balance { + balance = amount + } + nonce, err := vm.GetCurrentNonce(addr) + if err != nil { + return nil, nil, err + } + inputs = append(inputs, EVMInput{ + Address: addr, + Amount: balance, + AssetID: assetID, + Nonce: nonce, + }) + signers = append(signers, []*secp256k1.PrivateKey{key}) + amount -= balance + } + + if amount > 0 { + return nil, nil, errInsufficientFunds + } + + return inputs, signers, nil +} + +// GetSpendableAVAXWithFee returns a list of EVMInputs and keys (in corresponding +// order) to total [amount] + [fee] of [AVAX] owned by [keys]. +// This function accounts for the added cost of the additional inputs needed to +// create the transaction and makes sure to skip any keys with a balance that is +// insufficient to cover the additional fee. +// Note: we return [][]*secp256k1.PrivateKey even though each input +// corresponds to a single key, so that the signers can be passed in to +// [tx.Sign] which supports multiple keys on a single input. +func (vm *VM) GetSpendableAVAXWithFee( + keys []*secp256k1.PrivateKey, + amount uint64, + cost uint64, + baseFee *big.Int, +) ([]EVMInput, [][]*secp256k1.PrivateKey, error) { + // Note: current state uses the state of the preferred block. + state, err := vm.blockChain.State() + if err != nil { + return nil, nil, err + } + + initialFee, err := CalculateDynamicFee(cost, baseFee) + if err != nil { + return nil, nil, err + } + + newAmount, err := math.Add64(amount, initialFee) + if err != nil { + return nil, nil, err + } + amount = newAmount + + inputs := []EVMInput{} + signers := [][]*secp256k1.PrivateKey{} + // Note: we assume that each key in [keys] is unique, so that iterating over + // the keys will not produce duplicated nonces in the returned EVMInput slice. + for _, key := range keys { + if amount == 0 { + break + } + + prevFee, err := CalculateDynamicFee(cost, baseFee) + if err != nil { + return nil, nil, err + } + + newCost := cost + EVMInputGas + newFee, err := CalculateDynamicFee(newCost, baseFee) + if err != nil { + return nil, nil, err + } + + additionalFee := newFee - prevFee + + addr := GetEthAddress(key) + // Since the asset is AVAX, we divide by the x2cRate to convert back to + // the correct denomination of AVAX that can be exported. + balance := new(big.Int).Div(state.GetBalance(addr), x2cRate).Uint64() + // If the balance for [addr] is insufficient to cover the additional cost + // of adding an input to the transaction, skip adding the input altogether + if balance <= additionalFee { + continue + } + + // Update the cost for the next iteration + cost = newCost + + newAmount, err := math.Add64(amount, additionalFee) + if err != nil { + return nil, nil, err + } + amount = newAmount + + // Use the entire [balance] as an input, but if the required [amount] + // is less than the balance, update the [inputAmount] to spend the + // minimum amount to finish the transaction. + inputAmount := balance + if amount < balance { + inputAmount = amount + } + nonce, err := vm.GetCurrentNonce(addr) + if err != nil { + return nil, nil, err + } + inputs = append(inputs, EVMInput{ + Address: addr, + Amount: inputAmount, + AssetID: vm.ctx.AVAXAssetID, + Nonce: nonce, + }) + signers = append(signers, []*secp256k1.PrivateKey{key}) + amount -= inputAmount + } + + if amount > 0 { + return nil, nil, errInsufficientFunds + } + + return inputs, signers, nil +} + // GetCurrentNonce returns the nonce associated with the address at the // preferred block func (vm *VM) GetCurrentNonce(address common.Address) (uint64, error) { @@ -1067,6 +1939,12 @@ func (vm *VM) GetCurrentNonce(address common.Address) (uint64, error) { return state.GetNonce(address), nil } +// currentRules returns the chain rules for the current block. +func (vm *VM) currentRules() params.Rules { + header := vm.eth.APIBackend.CurrentHeader() + return vm.chainConfig.Rules(header.Number, header.Time) +} + func (vm *VM) startContinuousProfiler() { // If the profiler directory is empty, return immediately // without creating or starting a continuous profiler. @@ -1093,6 +1971,23 @@ func (vm *VM) startContinuousProfiler() { <-vm.shutdownChan } +func (vm *VM) estimateBaseFee(ctx context.Context) (*big.Int, error) { + // Get the base fee to use + baseFee, err := vm.eth.APIBackend.EstimateBaseFee(ctx) + if err != nil { + return nil, err + } + if baseFee == nil { + baseFee = initialBaseFee + } else { + // give some breathing room + baseFee.Mul(baseFee, big.NewInt(11)) + baseFee.Div(baseFee, big.NewInt(10)) + } + + return baseFee, nil +} + // readLastAccepted reads the last accepted hash from [acceptedBlockDB] and returns the // last accepted block hash and height by reading directly from [vm.chaindb] instead of relying // on [chain]. @@ -1155,3 +2050,13 @@ func attachEthService(handler *rpc.Server, apis []rpc.API, names []string) error return nil } + +func (vm *VM) stateSyncEnabled(lastAcceptedHeight uint64) bool { + if vm.config.StateSyncEnabled != nil { + // if the config is set, use that + return *vm.config.StateSyncEnabled + } + + // enable state sync by default if the chain is empty. + return lastAcceptedHeight == 0 +} diff --git a/plugin/evm/vm_test.go b/plugin/evm/vm_test.go index 6818b773bf..774394919e 100644 --- a/plugin/evm/vm_test.go +++ b/plugin/evm/vm_test.go @@ -5,7 +5,6 @@ package evm import ( "context" - "crypto/ecdsa" "crypto/rand" "encoding/json" "errors" @@ -18,9 +17,17 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + "github.com/holiman/uint256" + "github.com/ava-labs/subnet-evm/constants" + "github.com/ava-labs/subnet-evm/eth/filters" + "github.com/ava-labs/subnet-evm/metrics" + "github.com/ava-labs/subnet-evm/trie" + "github.com/ava-labs/subnet-evm/utils" + + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/api/keystore" @@ -30,60 +37,51 @@ import ( "github.com/ava-labs/avalanchego/database/prefixdb" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" - "github.com/ava-labs/avalanchego/snow/consensus/snowman" - commonEng "github.com/ava-labs/avalanchego/snow/engine/common" - "github.com/ava-labs/avalanchego/snow/engine/enginetest" "github.com/ava-labs/avalanchego/snow/validators/validatorstest" - "github.com/ava-labs/avalanchego/upgrade" - avalancheConstants "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/cb58" "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" "github.com/ava-labs/avalanchego/utils/formatting" + "github.com/ava-labs/avalanchego/utils/hashing" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/utils/timer/mockable" + "github.com/ava-labs/avalanchego/utils/units" + "github.com/ava-labs/avalanchego/vms/components/avax" "github.com/ava-labs/avalanchego/vms/components/chain" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" + + commonEng "github.com/ava-labs/avalanchego/snow/engine/common" + "github.com/ava-labs/avalanchego/snow/engine/enginetest" + constantsEng "github.com/ava-labs/avalanchego/utils/constants" - accountKeystore "github.com/ava-labs/subnet-evm/accounts/keystore" - "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/consensus/dummy" - "github.com/ava-labs/subnet-evm/constants" "github.com/ava-labs/subnet-evm/core" - "github.com/ava-labs/subnet-evm/core/txpool" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/eth" - "github.com/ava-labs/subnet-evm/metrics" "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/contracts/deployerallowlist" - "github.com/ava-labs/subnet-evm/precompile/contracts/feemanager" - "github.com/ava-labs/subnet-evm/precompile/contracts/rewardmanager" - "github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist" "github.com/ava-labs/subnet-evm/rpc" - "github.com/ava-labs/subnet-evm/trie" - "github.com/ava-labs/subnet-evm/utils" - "github.com/ava-labs/subnet-evm/vmerrs" - avagoconstants "github.com/ava-labs/avalanchego/utils/constants" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" + accountKeystore "github.com/ava-labs/subnet-evm/accounts/keystore" ) var ( - testNetworkID uint32 = avagoconstants.UnitTestID - testCChainID = ids.ID{'c', 'c', 'h', 'a', 'i', 'n', 't', 'e', 's', 't'} - testXChainID = ids.ID{'t', 'e', 's', 't', 'x'} - testMinGasPrice int64 = 225_000_000_000 - testKeys []*ecdsa.PrivateKey - testEthAddrs []common.Address // testEthAddrs[i] corresponds to testKeys[i] - testAvaxAssetID = ids.ID{1, 2, 3} - username = "Johns" - password = "CjasdjhiPeirbSenfeI13" // #nosec G101 - - firstTxAmount = new(big.Int).Mul(big.NewInt(testMinGasPrice), big.NewInt(21000*100)) - genesisBalance = new(big.Int).Mul(big.NewInt(testMinGasPrice), big.NewInt(21000*1000)) + testNetworkID uint32 = 10 + testCChainID = ids.ID{'c', 'c', 'h', 'a', 'i', 'n', 't', 'e', 's', 't'} + testXChainID = ids.ID{'t', 'e', 's', 't', 'x'} + nonExistentID = ids.ID{'F'} + testKeys []*secp256k1.PrivateKey + testEthAddrs []common.Address // testEthAddrs[i] corresponds to testKeys[i] + testShortIDAddrs []ids.ShortID + testAvaxAssetID = ids.ID{1, 2, 3} + username = "Johns" + password = "CjasdjhiPeirbSenfeI13" // #nosec G101 genesisJSON = func(cfg *params.ChainConfig) string { g := new(core.Genesis) g.Difficulty = big.NewInt(0) - g.GasLimit = 8000000 - g.Timestamp = uint64(upgrade.InitiallyActiveTime.Unix()) + g.GasLimit = 0x5f5e100 // Use chainId: 43111, so that it does not overlap with any Avalanche ChainIDs, which may have their // config overridden in vm.Initialize. @@ -91,8 +89,15 @@ var ( cpy.ChainID = big.NewInt(43111) g.Config = &cpy - allocStr := `{"0x71562b71999873DB5b286dF957af199Ec94617F7": {"balance":"0x4192927743b88000"}, "0x703c4b2bD70c169f5717101CaeE543299Fc946C7": {"balance":"0x4192927743b88000"}}` + allocStr := `{"0100000000000000000000000000000000000000":{"code":"0x7300000000000000000000000000000000000000003014608060405260043610603d5760003560e01c80631e010439146042578063b6510bb314606e575b600080fd5b605c60048036036020811015605657600080fd5b503560b1565b60408051918252519081900360200190f35b818015607957600080fd5b5060af60048036036080811015608e57600080fd5b506001600160a01b03813516906020810135906040810135906060013560b6565b005b30cd90565b836001600160a01b031681836108fc8690811502906040516000604051808303818888878c8acf9550505050505015801560f4573d6000803e3d6000fd5b505050505056fea26469706673582212201eebce970fe3f5cb96bf8ac6ba5f5c133fc2908ae3dcd51082cfee8f583429d064736f6c634300060a0033","balance":"0x0"}}` json.Unmarshal([]byte(allocStr), &g.Alloc) + // After Durango, an additional account is funded in tests to use + // with warp messages. + if cfg.IsDurango(0) { + addr := common.HexToAddress("0x99b9DEA54C48Dfea6aA9A4Ca4623633EE04ddbB5") + balance := new(big.Int).Mul(big.NewInt(params.Ether), big.NewInt(10)) + g.Alloc[addr] = core.GenesisAccount{Balance: balance} + } b, err := json.Marshal(g) if err != nil { @@ -101,37 +106,93 @@ var ( return string(b) } - genesisJSONPreSubnetEVM = genesisJSON(params.TestPreSubnetEVMChainConfig) - genesisJSONSubnetEVM = genesisJSON(params.TestSubnetEVMChainConfig) - genesisJSONDurango = genesisJSON(params.TestDurangoChainConfig) - genesisJSONEtna = genesisJSON(params.TestEtnaChainConfig) - genesisJSONLatest = genesisJSONEtna + activateCancun = func(cfg *params.ChainConfig) *params.ChainConfig { + cpy := *cfg + cpy.CancunTime = utils.NewUint64(0) + return &cpy + } + + activateEtna = func(cfg *params.ChainConfig, etnaTime uint64) *params.ChainConfig { + cpy := *cfg + cpy.EtnaTimestamp = &etnaTime + return &cpy + } + + genesisJSONApricotPhase0 = genesisJSON(params.TestLaunchConfig) + genesisJSONApricotPhase1 = genesisJSON(params.TestApricotPhase1Config) + genesisJSONApricotPhase2 = genesisJSON(params.TestApricotPhase2Config) + genesisJSONApricotPhase3 = genesisJSON(params.TestApricotPhase3Config) + genesisJSONApricotPhase4 = genesisJSON(params.TestApricotPhase4Config) + genesisJSONApricotPhase5 = genesisJSON(params.TestApricotPhase5Config) + genesisJSONApricotPhasePre6 = genesisJSON(params.TestApricotPhasePre6Config) + genesisJSONApricotPhase6 = genesisJSON(params.TestApricotPhase6Config) + genesisJSONApricotPhasePost6 = genesisJSON(params.TestApricotPhasePost6Config) + genesisJSONBanff = genesisJSON(params.TestBanffChainConfig) + genesisJSONCortina = genesisJSON(params.TestCortinaChainConfig) + genesisJSONDurango = genesisJSON(params.TestDurangoChainConfig) + genesisJSONEtna = genesisJSON(params.TestEtnaChainConfig) + genesisJSONLatest = genesisJSONEtna + + genesisJSONCancun = genesisJSON(activateCancun(params.TestChainConfig)) + + apricotRulesPhase0 = params.Rules{} + apricotRulesPhase1 = params.Rules{AvalancheRules: params.AvalancheRules{IsApricotPhase1: true}} + apricotRulesPhase2 = params.Rules{AvalancheRules: params.AvalancheRules{IsApricotPhase1: true, IsApricotPhase2: true}} + apricotRulesPhase3 = params.Rules{AvalancheRules: params.AvalancheRules{IsApricotPhase1: true, IsApricotPhase2: true, IsApricotPhase3: true}} + apricotRulesPhase4 = params.Rules{AvalancheRules: params.AvalancheRules{IsApricotPhase1: true, IsApricotPhase2: true, IsApricotPhase3: true, IsApricotPhase4: true}} + apricotRulesPhase5 = params.Rules{AvalancheRules: params.AvalancheRules{IsApricotPhase1: true, IsApricotPhase2: true, IsApricotPhase3: true, IsApricotPhase4: true, IsApricotPhase5: true}} + apricotRulesPhase6 = params.Rules{AvalancheRules: params.AvalancheRules{IsApricotPhase1: true, IsApricotPhase2: true, IsApricotPhase3: true, IsApricotPhase4: true, IsApricotPhase5: true, IsApricotPhasePre6: true, IsApricotPhase6: true, IsApricotPhasePost6: true}} + banffRules = params.Rules{AvalancheRules: params.AvalancheRules{IsApricotPhase1: true, IsApricotPhase2: true, IsApricotPhase3: true, IsApricotPhase4: true, IsApricotPhase5: true, IsApricotPhasePre6: true, IsApricotPhase6: true, IsApricotPhasePost6: true, IsBanff: true}} + // cortinaRules = params.Rules{AvalancheRules: params.AvalancheRules{IsApricotPhase1: true, IsApricotPhase2: true, IsApricotPhase3: true, IsApricotPhase4: true, IsApricotPhase5: true, IsApricotPhasePre6: true, IsApricotPhase6: true, IsApricotPhasePost6: true, IsBanff: true, IsCortina: true}} ) func init() { - key1, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - key2, _ := crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") - testKeys = append(testKeys, key1, key2) - addr1 := crypto.PubkeyToAddress(key1.PublicKey) - addr2 := crypto.PubkeyToAddress(key2.PublicKey) - testEthAddrs = append(testEthAddrs, addr1, addr2) + var b []byte + + for _, key := range []string{ + "24jUJ9vZexUM6expyMcT48LBx27k1m7xpraoV62oSQAHdziao5", + "2MMvUMsxx6zsHSNXJdFD8yc5XkancvwyKPwpw4xUK3TCGDuNBY", + "cxb7KpGWhDMALTjNNSJ7UQkkomPesyWAPUaWRGdyeBNzR6f35", + } { + b, _ = cb58.Decode(key) + pk, _ := secp256k1.ToPrivateKey(b) + testKeys = append(testKeys, pk) + testEthAddrs = append(testEthAddrs, GetEthAddress(pk)) + testShortIDAddrs = append(testShortIDAddrs, pk.PublicKey().Address()) + } +} + +func newPrefundedGenesis( + balance int, + addresses ...common.Address, +) *core.Genesis { + alloc := core.GenesisAlloc{} + for _, address := range addresses { + alloc[address] = core.GenesisAccount{ + Balance: big.NewInt(int64(balance)), + } + } + + return &core.Genesis{ + Config: params.TestChainConfig, + Difficulty: big.NewInt(0), + Alloc: alloc, + } } -// BuildGenesisTest returns the genesis bytes for Subnet EVM VM to be used in testing -func buildGenesisTest(t *testing.T, genesisJSON string) []byte { - ss := CreateStaticService() +// BuildGenesisTest returns the genesis bytes for Coreth VM to be used in testing +func BuildGenesisTest(t *testing.T, genesisJSON string) []byte { + ss := StaticService{} genesis := &core.Genesis{} if err := json.Unmarshal([]byte(genesisJSON), genesis); err != nil { t.Fatalf("Problem unmarshaling genesis JSON: %s", err) } - args := &BuildGenesisArgs{GenesisData: genesis} - reply := &BuildGenesisReply{} - err := ss.BuildGenesis(nil, args, reply) + genesisReply, err := ss.BuildGenesis(nil, genesis) if err != nil { t.Fatalf("Failed to create test genesis") } - genesisBytes, err := formatting.Decode(reply.Encoding, reply.GenesisBytes) + genesisBytes, err := formatting.Decode(genesisReply.Encoding, genesisReply.Bytes) if err != nil { t.Fatalf("Failed to decode genesis bytes: %s", err) } @@ -145,7 +206,7 @@ func NewContext() *snow.Context { ctx.ChainID = testCChainID ctx.AVAXAssetID = testAvaxAssetID ctx.XChainID = testXChainID - ctx.NetworkUpgrades = upgrade.GetConfig(testNetworkID) + ctx.SharedMemory = testSharedMemory() aliaser := ctx.BCLookup.(ids.Aliaser) _ = aliaser.Alias(testCChainID, "C") _ = aliaser.Alias(testCChainID, testCChainID.String()) @@ -154,9 +215,9 @@ func NewContext() *snow.Context { ctx.ValidatorState = &validatorstest.State{ GetSubnetIDF: func(_ context.Context, chainID ids.ID) (ids.ID, error) { subnetID, ok := map[ids.ID]ids.ID{ - avalancheConstants.PlatformChainID: avalancheConstants.PrimaryNetworkID, - testXChainID: avalancheConstants.PrimaryNetworkID, - testCChainID: avalancheConstants.PrimaryNetworkID, + constantsEng.PlatformChainID: constantsEng.PrimaryNetworkID, + testXChainID: constantsEng.PrimaryNetworkID, + testCChainID: constantsEng.PrimaryNetworkID, }[chainID] if !ok { return ids.Empty, errors.New("unknown chain") @@ -187,7 +248,7 @@ func setupGenesis( if len(genesisJSON) == 0 { genesisJSON = genesisJSONLatest } - genesisBytes := buildGenesisTest(t, genesisJSON) + genesisBytes := BuildGenesisTest(t, genesisJSON) ctx := NewContext() baseDB := memdb.New() @@ -213,7 +274,7 @@ func setupGenesis( // GenesisVM creates a VM instance with the genesis test bytes and returns // the channel use to send messages to the engine, the VM, database manager, -// and sender. +// sender, and atomic memory. // If [genesisJSON] is empty, defaults to using [genesisJSONLatest] func GenesisVM(t *testing.T, finishBootstrapping bool, @@ -222,12 +283,30 @@ func GenesisVM(t *testing.T, upgradeJSON string, ) ( chan commonEng.Message, - *VM, - database.Database, + *VM, database.Database, + *atomic.Memory, + *enginetest.Sender, +) { + return GenesisVMWithClock(t, finishBootstrapping, genesisJSON, configJSON, upgradeJSON, mockable.Clock{}) +} + +// GenesisVMWithClock creates a VM instance as GenesisVM does, but also allows +// setting the vm's time before [Initialize] is called. +func GenesisVMWithClock( + t *testing.T, + finishBootstrapping bool, + genesisJSON string, + configJSON string, + upgradeJSON string, + clock mockable.Clock, +) ( + chan commonEng.Message, + *VM, database.Database, + *atomic.Memory, *enginetest.Sender, ) { - vm := &VM{} - ctx, dbManager, genesisBytes, issuer, _ := setupGenesis(t, genesisJSON) + vm := &VM{clock: clock} + ctx, dbManager, genesisBytes, issuer, m := setupGenesis(t, genesisJSON) appSender := &enginetest.Sender{T: t} appSender.CantSendAppGossip = true appSender.SendAppGossipF = func(context.Context, commonEng.SendConfig, []byte) error { return nil } @@ -249,14 +328,67 @@ func GenesisVM(t *testing.T, require.NoError(t, vm.SetState(context.Background(), snow.NormalOp)) } - return issuer, vm, dbManager, appSender + return issuer, vm, dbManager, m, appSender +} + +func addUTXO(sharedMemory *atomic.Memory, ctx *snow.Context, txID ids.ID, index uint32, assetID ids.ID, amount uint64, addr ids.ShortID) (*avax.UTXO, error) { + utxo := &avax.UTXO{ + UTXOID: avax.UTXOID{ + TxID: txID, + OutputIndex: index, + }, + Asset: avax.Asset{ID: assetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: amount, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{addr}, + }, + }, + } + utxoBytes, err := Codec.Marshal(codecVersion, utxo) + if err != nil { + return nil, err + } + + xChainSharedMemory := sharedMemory.NewSharedMemory(ctx.XChainID) + inputID := utxo.InputID() + if err := xChainSharedMemory.Apply(map[ids.ID]*atomic.Requests{ctx.ChainID: {PutRequests: []*atomic.Element{{ + Key: inputID[:], + Value: utxoBytes, + Traits: [][]byte{ + addr.Bytes(), + }, + }}}}); err != nil { + return nil, err + } + + return utxo, nil +} + +// GenesisVMWithUTXOs creates a GenesisVM and generates UTXOs in the X-Chain Shared Memory containing AVAX based on the [utxos] map +// Generates UTXOIDs by using a hash of the address in the [utxos] map such that the UTXOs will be generated deterministically. +// If [genesisJSON] is empty, defaults to using [genesisJSONLatest] +func GenesisVMWithUTXOs(t *testing.T, finishBootstrapping bool, genesisJSON string, configJSON string, upgradeJSON string, utxos map[ids.ShortID]uint64) (chan commonEng.Message, *VM, database.Database, *atomic.Memory, *enginetest.Sender) { + issuer, vm, db, sharedMemory, sender := GenesisVM(t, finishBootstrapping, genesisJSON, configJSON, upgradeJSON) + for addr, avaxAmount := range utxos { + txID, err := ids.ToID(hashing.ComputeHash256(addr.Bytes())) + if err != nil { + t.Fatalf("Failed to generate txID from addr: %s", err) + } + if _, err := addUTXO(sharedMemory, vm.ctx, txID, 0, vm.ctx.AVAXAssetID, avaxAmount, addr); err != nil { + t.Fatalf("Failed to add UTXO to shared memory: %s", err) + } + } + + return issuer, vm, db, sharedMemory, sender } func TestVMConfig(t *testing.T) { txFeeCap := float64(11) enabledEthAPIs := []string{"debug"} configJSON := fmt.Sprintf(`{"rpc-tx-fee-cap": %g,"eth-apis": %s}`, txFeeCap, fmt.Sprintf("[%q]", enabledEthAPIs[0])) - _, vm, _, _ := GenesisVM(t, false, "", configJSON, "") + _, vm, _, _, _ := GenesisVM(t, false, "", configJSON, "") require.Equal(t, vm.config.RPCTxFeeCap, txFeeCap, "Tx Fee Cap should be set") require.Equal(t, vm.config.EthAPIs(), enabledEthAPIs, "EnabledEthAPIs should be set") require.NoError(t, vm.Shutdown(context.Background())) @@ -266,7 +398,7 @@ func TestVMConfigDefaults(t *testing.T) { txFeeCap := float64(11) enabledEthAPIs := []string{"debug"} configJSON := fmt.Sprintf(`{"rpc-tx-fee-cap": %g,"eth-apis": %s}`, txFeeCap, fmt.Sprintf("[%q]", enabledEthAPIs[0])) - _, vm, _, _ := GenesisVM(t, false, "", configJSON, "") + _, vm, _, _, _ := GenesisVM(t, false, "", configJSON, "") var vmConfig Config vmConfig.SetDefaults() @@ -277,7 +409,7 @@ func TestVMConfigDefaults(t *testing.T) { } func TestVMNilConfig(t *testing.T) { - _, vm, _, _ := GenesisVM(t, false, "", "", "") + _, vm, _, _, _ := GenesisVM(t, false, "", "", "") // VM Config should match defaults if no config is passed in var vmConfig Config @@ -290,7 +422,7 @@ func TestVMContinuousProfiler(t *testing.T) { profilerDir := t.TempDir() profilerFrequency := 500 * time.Millisecond configJSON := fmt.Sprintf(`{"continuous-profiler-dir": %q,"continuous-profiler-frequency": "500ms"}`, profilerDir) - _, vm, _, _ := GenesisVM(t, false, "", configJSON, "") + _, vm, _, _, _ := GenesisVM(t, false, "", configJSON, "") require.Equal(t, vm.config.ContinuousProfilerDir, profilerDir, "profiler dir should be set") require.Equal(t, vm.config.ContinuousProfilerFrequency.Duration, profilerFrequency, "profiler frequency should be set") @@ -312,8 +444,43 @@ func TestVMUpgrades(t *testing.T) { expectedGasPrice *big.Int }{ { - name: "Subnet EVM", - genesis: genesisJSONSubnetEVM, + name: "Apricot Phase 3", + genesis: genesisJSONApricotPhase3, + expectedGasPrice: big.NewInt(0), + }, + { + name: "Apricot Phase 4", + genesis: genesisJSONApricotPhase4, + expectedGasPrice: big.NewInt(0), + }, + { + name: "Apricot Phase 5", + genesis: genesisJSONApricotPhase5, + expectedGasPrice: big.NewInt(0), + }, + { + name: "Apricot Phase Pre 6", + genesis: genesisJSONApricotPhasePre6, + expectedGasPrice: big.NewInt(0), + }, + { + name: "Apricot Phase 6", + genesis: genesisJSONApricotPhase6, + expectedGasPrice: big.NewInt(0), + }, + { + name: "Apricot Phase Post 6", + genesis: genesisJSONApricotPhasePost6, + expectedGasPrice: big.NewInt(0), + }, + { + name: "Banff", + genesis: genesisJSONBanff, + expectedGasPrice: big.NewInt(0), + }, + { + name: "Cortina", + genesis: genesisJSONCortina, expectedGasPrice: big.NewInt(0), }, { @@ -324,7 +491,7 @@ func TestVMUpgrades(t *testing.T) { } for _, test := range genesisTests { t.Run(test.name, func(t *testing.T) { - _, vm, _, _ := GenesisVM(t, true, test.genesis, "", "") + _, vm, _, _, _ := GenesisVM(t, true, test.genesis, "", "") if gasPrice := vm.txPool.GasTip(); gasPrice.Cmp(test.expectedGasPrice) != 0 { t.Fatalf("Expected pool gas price to be %d but found %d", test.expectedGasPrice, gasPrice) @@ -376,8 +543,66 @@ func TestVMUpgrades(t *testing.T) { } } -func issueAndAccept(t *testing.T, issuer <-chan commonEng.Message, vm *VM) snowman.Block { - t.Helper() +func TestImportMissingUTXOs(t *testing.T) { + // make a VM with a shared memory that has an importable UTXO to build a block + importAmount := uint64(50000000) + issuer, vm, _, _, _ := GenesisVMWithUTXOs(t, true, genesisJSONApricotPhase2, "", "", map[ids.ShortID]uint64{ + testShortIDAddrs[0]: importAmount, + }) + defer func() { + err := vm.Shutdown(context.Background()) + require.NoError(t, err) + }() + + importTx, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) + require.NoError(t, err) + err = vm.mempool.AddLocalTx(importTx) + require.NoError(t, err) + <-issuer + blk, err := vm.BuildBlock(context.Background()) + require.NoError(t, err) + + // make another VM which is missing the UTXO in shared memory + _, vm2, _, _, _ := GenesisVM(t, true, genesisJSONApricotPhase2, "", "") + defer func() { + err := vm2.Shutdown(context.Background()) + require.NoError(t, err) + }() + + vm2Blk, err := vm2.ParseBlock(context.Background(), blk.Bytes()) + require.NoError(t, err) + err = vm2Blk.Verify(context.Background()) + require.ErrorIs(t, err, errMissingUTXOs) + + // This should not result in a bad block since the missing UTXO should + // prevent InsertBlockManual from being called. + badBlocks, _ := vm2.blockChain.BadBlocks() + require.Len(t, badBlocks, 0) +} + +// Simple test to ensure we can issue an import transaction followed by an export transaction +// and they will be indexed correctly when accepted. +func TestIssueAtomicTxs(t *testing.T) { + importAmount := uint64(50000000) + issuer, vm, _, _, _ := GenesisVMWithUTXOs(t, true, genesisJSONApricotPhase2, "", "", map[ids.ShortID]uint64{ + testShortIDAddrs[0]: importAmount, + }) + + defer func() { + if err := vm.Shutdown(context.Background()); err != nil { + t.Fatal(err) + } + }() + + importTx, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) + if err != nil { + t.Fatal(err) + } + + if err := vm.mempool.AddLocalTx(importTx); err != nil { + t.Fatal(err) + } + <-issuer blk, err := vm.BuildBlock(context.Background()) @@ -397,12 +622,78 @@ func issueAndAccept(t *testing.T, issuer <-chan commonEng.Message, vm *VM) snowm t.Fatal(err) } - return blk + if lastAcceptedID, err := vm.LastAccepted(context.Background()); err != nil { + t.Fatal(err) + } else if lastAcceptedID != blk.ID() { + t.Fatalf("Expected last accepted blockID to be the accepted block: %s, but found %s", blk.ID(), lastAcceptedID) + } + vm.blockChain.DrainAcceptorQueue() + filterAPI := filters.NewFilterAPI(filters.NewFilterSystem(vm.eth.APIBackend, filters.Config{ + Timeout: 5 * time.Minute, + })) + blockHash := common.Hash(blk.ID()) + logs, err := filterAPI.GetLogs(context.Background(), filters.FilterCriteria{ + BlockHash: &blockHash, + }) + if err != nil { + t.Fatal(err) + } + if len(logs) != 0 { + t.Fatalf("Expected log length to be 0, but found %d", len(logs)) + } + if logs == nil { + t.Fatal("Expected logs to be non-nil") + } + + exportTx, err := vm.newExportTx(vm.ctx.AVAXAssetID, importAmount-(2*params.AvalancheAtomicTxFee), vm.ctx.XChainID, testShortIDAddrs[0], initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) + if err != nil { + t.Fatal(err) + } + + if err := vm.mempool.AddLocalTx(exportTx); err != nil { + t.Fatal(err) + } + + <-issuer + + blk2, err := vm.BuildBlock(context.Background()) + if err != nil { + t.Fatal(err) + } + + if err := blk2.Verify(context.Background()); err != nil { + t.Fatal(err) + } + + if err := blk2.Accept(context.Background()); err != nil { + t.Fatal(err) + } + + if lastAcceptedID, err := vm.LastAccepted(context.Background()); err != nil { + t.Fatal(err) + } else if lastAcceptedID != blk2.ID() { + t.Fatalf("Expected last accepted blockID to be the accepted block: %s, but found %s", blk2.ID(), lastAcceptedID) + } + + // Check that both atomic transactions were indexed as expected. + indexedImportTx, status, height, err := vm.getAtomicTx(importTx.ID()) + assert.NoError(t, err) + assert.Equal(t, Accepted, status) + assert.Equal(t, uint64(1), height, "expected height of indexed import tx to be 1") + assert.Equal(t, indexedImportTx.ID(), importTx.ID(), "expected ID of indexed import tx to match original txID") + + indexedExportTx, status, height, err := vm.getAtomicTx(exportTx.ID()) + assert.NoError(t, err) + assert.Equal(t, Accepted, status) + assert.Equal(t, uint64(2), height, "expected height of indexed export tx to be 2") + assert.Equal(t, indexedExportTx.ID(), exportTx.ID(), "expected ID of indexed import tx to match original txID") } func TestBuildEthTxBlock(t *testing.T) { - // reduce block gas cost - issuer, vm, dbManager, _ := GenesisVM(t, true, genesisJSONSubnetEVM, `{"pruning-enabled":true}`, "") + importAmount := uint64(20000000) + issuer, vm, dbManager, _, _ := GenesisVMWithUTXOs(t, true, genesisJSONApricotPhase2, `{"pruning-enabled":true}`, "", map[ids.ShortID]uint64{ + testShortIDAddrs[0]: importAmount, + }) defer func() { if err := vm.Shutdown(context.Background()); err != nil { @@ -413,24 +704,34 @@ func TestBuildEthTxBlock(t *testing.T) { newTxPoolHeadChan := make(chan core.NewTxPoolReorgEvent, 1) vm.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan) - key, err := accountKeystore.NewKey(rand.Reader) + importTx, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) if err != nil { t.Fatal(err) } - tx := types.NewTransaction(uint64(0), key.Address, firstTxAmount, 21000, big.NewInt(testMinGasPrice), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[0]) + if err := vm.mempool.AddLocalTx(importTx); err != nil { + t.Fatal(err) + } + + <-issuer + + blk1, err := vm.BuildBlock(context.Background()) if err != nil { t.Fatal(err) } - errs := vm.txPool.AddRemotesSync([]*types.Transaction{signedTx}) - for i, err := range errs { - if err != nil { - t.Fatalf("Failed to add tx at index %d: %s", i, err) - } + + if err := blk1.Verify(context.Background()); err != nil { + t.Fatal(err) + } + + if err := vm.SetPreference(context.Background(), blk1.ID()); err != nil { + t.Fatal(err) + } + + if err := blk1.Accept(context.Background()); err != nil { + t.Fatal(err) } - blk1 := issueAndAccept(t, issuer, vm) newHead := <-newTxPoolHeadChan if newHead.Head.Hash() != common.Hash(blk1.ID()) { t.Fatalf("Expected new block to match") @@ -438,22 +739,35 @@ func TestBuildEthTxBlock(t *testing.T) { txs := make([]*types.Transaction, 10) for i := 0; i < 10; i++ { - tx := types.NewTransaction(uint64(i), key.Address, big.NewInt(10), 21000, big.NewInt(testMinGasPrice), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainConfig.ChainID), key.PrivateKey) + tx := types.NewTransaction(uint64(i), testEthAddrs[0], big.NewInt(10), 21000, big.NewInt(params.LaunchMinGasPrice), nil) + signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainID), testKeys[0].ToECDSA()) if err != nil { t.Fatal(err) } txs[i] = signedTx } - errs = vm.txPool.AddRemotesSync(txs) + errs := vm.txPool.AddRemotesSync(txs) for i, err := range errs { if err != nil { t.Fatalf("Failed to add tx at index %d: %s", i, err) } } - vm.clock.Set(vm.clock.Time().Add(2 * time.Second)) - blk2 := issueAndAccept(t, issuer, vm) + <-issuer + + blk2, err := vm.BuildBlock(context.Background()) + if err != nil { + t.Fatal(err) + } + + if err := blk2.Verify(context.Background()); err != nil { + t.Fatal(err) + } + + if err := blk2.Accept(context.Background()); err != nil { + t.Fatal(err) + } + newHead = <-newTxPoolHeadChan if newHead.Head.Hash() != common.Hash(blk2.ID()) { t.Fatalf("Expected new block to match") @@ -490,13 +804,11 @@ func TestBuildEthTxBlock(t *testing.T) { } restartedVM := &VM{} - genesisBytes := buildGenesisTest(t, genesisJSONSubnetEVM) - if err := restartedVM.Initialize( context.Background(), NewContext(), dbManager, - genesisBytes, + []byte(genesisJSONApricotPhase2), []byte(""), []byte(`{"pruning-enabled":true}`), issuer, @@ -518,84 +830,400 @@ func TestBuildEthTxBlock(t *testing.T) { } } -// Regression test to ensure that after accepting block A -// then calling SetPreference on block B (when it becomes preferred) -// and the head of a longer chain (block D) does not corrupt the -// canonical chain. -// -// A -// / \ -// B C -// | -// D -func TestSetPreferenceRace(t *testing.T) { - // Create two VMs which will agree on block A and then - // build the two distinct preferred chains above - issuer1, vm1, _, _ := GenesisVM(t, true, genesisJSONSubnetEVM, `{"pruning-enabled":true}`, "") - issuer2, vm2, _, _ := GenesisVM(t, true, genesisJSONSubnetEVM, `{"pruning-enabled":true}`, "") +func testConflictingImportTxs(t *testing.T, genesis string) { + importAmount := uint64(10000000) + issuer, vm, _, _, _ := GenesisVMWithUTXOs(t, true, genesis, "", "", map[ids.ShortID]uint64{ + testShortIDAddrs[0]: importAmount, + testShortIDAddrs[1]: importAmount, + testShortIDAddrs[2]: importAmount, + }) defer func() { - if err := vm1.Shutdown(context.Background()); err != nil { + if err := vm.Shutdown(context.Background()); err != nil { t.Fatal(err) } + }() - if err := vm2.Shutdown(context.Background()); err != nil { + importTxs := make([]*Tx, 0, 3) + conflictTxs := make([]*Tx, 0, 3) + for i, key := range testKeys { + importTx, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[i], initialBaseFee, []*secp256k1.PrivateKey{key}) + if err != nil { t.Fatal(err) } - }() + importTxs = append(importTxs, importTx) - newTxPoolHeadChan1 := make(chan core.NewTxPoolReorgEvent, 1) - vm1.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan1) - newTxPoolHeadChan2 := make(chan core.NewTxPoolReorgEvent, 1) - vm2.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan2) + conflictAddr := testEthAddrs[(i+1)%len(testEthAddrs)] + conflictTx, err := vm.newImportTx(vm.ctx.XChainID, conflictAddr, initialBaseFee, []*secp256k1.PrivateKey{key}) + if err != nil { + t.Fatal(err) + } + conflictTxs = append(conflictTxs, conflictTx) + } - tx := types.NewTransaction(uint64(0), testEthAddrs[1], firstTxAmount, 21000, big.NewInt(testMinGasPrice), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm1.chainConfig.ChainID), testKeys[0]) + expectedParentBlkID, err := vm.LastAccepted(context.Background()) if err != nil { t.Fatal(err) } + for _, tx := range importTxs[:2] { + if err := vm.mempool.AddLocalTx(tx); err != nil { + t.Fatal(err) + } - txErrors := vm1.txPool.AddRemotesSync([]*types.Transaction{signedTx}) - for i, err := range txErrors { + <-issuer + + vm.clock.Set(vm.clock.Time().Add(2 * time.Second)) + blk, err := vm.BuildBlock(context.Background()) if err != nil { - t.Fatalf("Failed to add tx at index %d: %s", i, err) + t.Fatal(err) } - } - <-issuer1 + if err := blk.Verify(context.Background()); err != nil { + t.Fatal(err) + } - vm1BlkA, err := vm1.BuildBlock(context.Background()) - if err != nil { - t.Fatalf("Failed to build block with import transaction: %s", err) + if parentID := blk.Parent(); parentID != expectedParentBlkID { + t.Fatalf("Expected parent to have blockID %s, but found %s", expectedParentBlkID, parentID) + } + + expectedParentBlkID = blk.ID() + if err := vm.SetPreference(context.Background(), blk.ID()); err != nil { + t.Fatal(err) + } } - if err := vm1BlkA.Verify(context.Background()); err != nil { - t.Fatalf("Block failed verification on VM1: %s", err) + // Check that for each conflict tx (whose conflict is in the chain ancestry) + // the VM returns an error when it attempts to issue the conflict into the mempool + // and when it attempts to build a block with the conflict force added to the mempool. + for i, tx := range conflictTxs[:2] { + if err := vm.mempool.AddLocalTx(tx); err == nil { + t.Fatal("Expected issueTx to fail due to conflicting transaction") + } + // Force issue transaction directly to the mempool + if err := vm.mempool.ForceAddTx(tx); err != nil { + t.Fatal(err) + } + <-issuer + + vm.clock.Set(vm.clock.Time().Add(2 * time.Second)) + _, err = vm.BuildBlock(context.Background()) + // The new block is verified in BuildBlock, so + // BuildBlock should fail due to an attempt to + // double spend an atomic UTXO. + if err == nil { + t.Fatalf("Block verification should have failed in BuildBlock %d due to double spending atomic UTXO", i) + } } - if err := vm1.SetPreference(context.Background(), vm1BlkA.ID()); err != nil { + // Generate one more valid block so that we can copy the header to create an invalid block + // with modified extra data. This new block will be invalid for more than one reason (invalid merkle root) + // so we check to make sure that the expected error is returned from block verification. + if err := vm.mempool.AddLocalTx(importTxs[2]); err != nil { t.Fatal(err) } + <-issuer + vm.clock.Set(vm.clock.Time().Add(2 * time.Second)) - vm2BlkA, err := vm2.ParseBlock(context.Background(), vm1BlkA.Bytes()) + validBlock, err := vm.BuildBlock(context.Background()) if err != nil { - t.Fatalf("Unexpected error parsing block from vm2: %s", err) - } - if err := vm2BlkA.Verify(context.Background()); err != nil { - t.Fatalf("Block failed verification on VM2: %s", err) - } - if err := vm2.SetPreference(context.Background(), vm2BlkA.ID()); err != nil { t.Fatal(err) } - if err := vm1BlkA.Accept(context.Background()); err != nil { - t.Fatalf("VM1 failed to accept block: %s", err) - } - if err := vm2BlkA.Accept(context.Background()); err != nil { - t.Fatalf("VM2 failed to accept block: %s", err) + if err := validBlock.Verify(context.Background()); err != nil { + t.Fatal(err) } - newHead := <-newTxPoolHeadChan1 + validEthBlock := validBlock.(*chain.BlockWrapper).Block.(*Block).ethBlock + + rules := vm.currentRules() + var extraData []byte + switch { + case rules.IsApricotPhase5: + extraData, err = vm.codec.Marshal(codecVersion, []*Tx{conflictTxs[1]}) + default: + extraData, err = vm.codec.Marshal(codecVersion, conflictTxs[1]) + } + if err != nil { + t.Fatal(err) + } + + conflictingAtomicTxBlock := types.NewBlockWithExtData( + types.CopyHeader(validEthBlock.Header()), + nil, + nil, + nil, + new(trie.Trie), + extraData, + true, + ) + + blockBytes, err := rlp.EncodeToBytes(conflictingAtomicTxBlock) + if err != nil { + t.Fatal(err) + } + + parsedBlock, err := vm.ParseBlock(context.Background(), blockBytes) + if err != nil { + t.Fatal(err) + } + + if err := parsedBlock.Verify(context.Background()); !errors.Is(err, errConflictingAtomicInputs) { + t.Fatalf("Expected to fail with err: %s, but found err: %s", errConflictingAtomicInputs, err) + } + + if !rules.IsApricotPhase5 { + return + } + + extraData, err = vm.codec.Marshal(codecVersion, []*Tx{importTxs[2], conflictTxs[2]}) + if err != nil { + t.Fatal(err) + } + + header := types.CopyHeader(validEthBlock.Header()) + header.ExtDataGasUsed.Mul(common.Big2, header.ExtDataGasUsed) + + internalConflictBlock := types.NewBlockWithExtData( + header, + nil, + nil, + nil, + new(trie.Trie), + extraData, + true, + ) + + blockBytes, err = rlp.EncodeToBytes(internalConflictBlock) + if err != nil { + t.Fatal(err) + } + + parsedBlock, err = vm.ParseBlock(context.Background(), blockBytes) + if err != nil { + t.Fatal(err) + } + + if err := parsedBlock.Verify(context.Background()); !errors.Is(err, errConflictingAtomicInputs) { + t.Fatalf("Expected to fail with err: %s, but found err: %s", errConflictingAtomicInputs, err) + } +} + +func TestReissueAtomicTxHigherGasPrice(t *testing.T) { + kc := secp256k1fx.NewKeychain(testKeys...) + + for name, issueTxs := range map[string]func(t *testing.T, vm *VM, sharedMemory *atomic.Memory) (issued []*Tx, discarded []*Tx){ + "single UTXO override": func(t *testing.T, vm *VM, sharedMemory *atomic.Memory) (issued []*Tx, evicted []*Tx) { + utxo, err := addUTXO(sharedMemory, vm.ctx, ids.GenerateTestID(), 0, vm.ctx.AVAXAssetID, units.Avax, testShortIDAddrs[0]) + if err != nil { + t.Fatal(err) + } + tx1, err := vm.newImportTxWithUTXOs(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, kc, []*avax.UTXO{utxo}) + if err != nil { + t.Fatal(err) + } + tx2, err := vm.newImportTxWithUTXOs(vm.ctx.XChainID, testEthAddrs[0], new(big.Int).Mul(common.Big2, initialBaseFee), kc, []*avax.UTXO{utxo}) + if err != nil { + t.Fatal(err) + } + + if err := vm.mempool.AddLocalTx(tx1); err != nil { + t.Fatal(err) + } + if err := vm.mempool.AddLocalTx(tx2); err != nil { + t.Fatal(err) + } + + return []*Tx{tx2}, []*Tx{tx1} + }, + "one of two UTXOs overrides": func(t *testing.T, vm *VM, sharedMemory *atomic.Memory) (issued []*Tx, evicted []*Tx) { + utxo1, err := addUTXO(sharedMemory, vm.ctx, ids.GenerateTestID(), 0, vm.ctx.AVAXAssetID, units.Avax, testShortIDAddrs[0]) + if err != nil { + t.Fatal(err) + } + utxo2, err := addUTXO(sharedMemory, vm.ctx, ids.GenerateTestID(), 0, vm.ctx.AVAXAssetID, units.Avax, testShortIDAddrs[0]) + if err != nil { + t.Fatal(err) + } + tx1, err := vm.newImportTxWithUTXOs(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, kc, []*avax.UTXO{utxo1, utxo2}) + if err != nil { + t.Fatal(err) + } + tx2, err := vm.newImportTxWithUTXOs(vm.ctx.XChainID, testEthAddrs[0], new(big.Int).Mul(common.Big2, initialBaseFee), kc, []*avax.UTXO{utxo1}) + if err != nil { + t.Fatal(err) + } + + if err := vm.mempool.AddLocalTx(tx1); err != nil { + t.Fatal(err) + } + if err := vm.mempool.AddLocalTx(tx2); err != nil { + t.Fatal(err) + } + + return []*Tx{tx2}, []*Tx{tx1} + }, + "hola": func(t *testing.T, vm *VM, sharedMemory *atomic.Memory) (issued []*Tx, evicted []*Tx) { + utxo1, err := addUTXO(sharedMemory, vm.ctx, ids.GenerateTestID(), 0, vm.ctx.AVAXAssetID, units.Avax, testShortIDAddrs[0]) + if err != nil { + t.Fatal(err) + } + utxo2, err := addUTXO(sharedMemory, vm.ctx, ids.GenerateTestID(), 0, vm.ctx.AVAXAssetID, units.Avax, testShortIDAddrs[0]) + if err != nil { + t.Fatal(err) + } + + importTx1, err := vm.newImportTxWithUTXOs(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, kc, []*avax.UTXO{utxo1}) + if err != nil { + t.Fatal(err) + } + + importTx2, err := vm.newImportTxWithUTXOs(vm.ctx.XChainID, testEthAddrs[0], new(big.Int).Mul(big.NewInt(3), initialBaseFee), kc, []*avax.UTXO{utxo2}) + if err != nil { + t.Fatal(err) + } + + reissuanceTx1, err := vm.newImportTxWithUTXOs(vm.ctx.XChainID, testEthAddrs[0], new(big.Int).Mul(big.NewInt(2), initialBaseFee), kc, []*avax.UTXO{utxo1, utxo2}) + if err != nil { + t.Fatal(err) + } + if err := vm.mempool.AddLocalTx(importTx1); err != nil { + t.Fatal(err) + } + + if err := vm.mempool.AddLocalTx(importTx2); err != nil { + t.Fatal(err) + } + + if err := vm.mempool.AddLocalTx(reissuanceTx1); !errors.Is(err, errConflictingAtomicTx) { + t.Fatalf("Expected to fail with err: %s, but found err: %s", errConflictingAtomicTx, err) + } + + assert.True(t, vm.mempool.has(importTx1.ID())) + assert.True(t, vm.mempool.has(importTx2.ID())) + assert.False(t, vm.mempool.has(reissuanceTx1.ID())) + + reissuanceTx2, err := vm.newImportTxWithUTXOs(vm.ctx.XChainID, testEthAddrs[0], new(big.Int).Mul(big.NewInt(4), initialBaseFee), kc, []*avax.UTXO{utxo1, utxo2}) + if err != nil { + t.Fatal(err) + } + if err := vm.mempool.AddLocalTx(reissuanceTx2); err != nil { + t.Fatal(err) + } + + return []*Tx{reissuanceTx2}, []*Tx{importTx1, importTx2} + }, + } { + t.Run(name, func(t *testing.T) { + _, vm, _, sharedMemory, _ := GenesisVM(t, true, genesisJSONApricotPhase5, "", "") + issuedTxs, evictedTxs := issueTxs(t, vm, sharedMemory) + + for i, tx := range issuedTxs { + _, issued := vm.mempool.txHeap.Get(tx.ID()) + assert.True(t, issued, "expected issued tx at index %d to be issued", i) + } + + for i, tx := range evictedTxs { + _, discarded := vm.mempool.discardedTxs.Get(tx.ID()) + assert.True(t, discarded, "expected discarded tx at index %d to be discarded", i) + } + }) + } +} + +func TestConflictingImportTxsAcrossBlocks(t *testing.T) { + for name, genesis := range map[string]string{ + "apricotPhase1": genesisJSONApricotPhase1, + "apricotPhase2": genesisJSONApricotPhase2, + "apricotPhase3": genesisJSONApricotPhase3, + "apricotPhase4": genesisJSONApricotPhase4, + "apricotPhase5": genesisJSONApricotPhase5, + } { + genesis := genesis + t.Run(name, func(t *testing.T) { + testConflictingImportTxs(t, genesis) + }) + } +} + +// Regression test to ensure that after accepting block A +// then calling SetPreference on block B (when it becomes preferred) +// and the head of a longer chain (block D) does not corrupt the +// canonical chain. +// +// A +// / \ +// B C +// | +// D +func TestSetPreferenceRace(t *testing.T) { + // Create two VMs which will agree on block A and then + // build the two distinct preferred chains above + importAmount := uint64(1000000000) + issuer1, vm1, _, _, _ := GenesisVMWithUTXOs(t, true, genesisJSONApricotPhase0, `{"pruning-enabled":true}`, "", map[ids.ShortID]uint64{ + testShortIDAddrs[0]: importAmount, + }) + issuer2, vm2, _, _, _ := GenesisVMWithUTXOs(t, true, genesisJSONApricotPhase0, `{"pruning-enabled":true}`, "", map[ids.ShortID]uint64{ + testShortIDAddrs[0]: importAmount, + }) + + defer func() { + if err := vm1.Shutdown(context.Background()); err != nil { + t.Fatal(err) + } + + if err := vm2.Shutdown(context.Background()); err != nil { + t.Fatal(err) + } + }() + + newTxPoolHeadChan1 := make(chan core.NewTxPoolReorgEvent, 1) + vm1.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan1) + newTxPoolHeadChan2 := make(chan core.NewTxPoolReorgEvent, 1) + vm2.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan2) + + importTx, err := vm1.newImportTx(vm1.ctx.XChainID, testEthAddrs[1], initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) + if err != nil { + t.Fatal(err) + } + + if err := vm1.mempool.AddLocalTx(importTx); err != nil { + t.Fatal(err) + } + + <-issuer1 + + vm1BlkA, err := vm1.BuildBlock(context.Background()) + if err != nil { + t.Fatalf("Failed to build block with import transaction: %s", err) + } + + if err := vm1BlkA.Verify(context.Background()); err != nil { + t.Fatalf("Block failed verification on VM1: %s", err) + } + + if err := vm1.SetPreference(context.Background(), vm1BlkA.ID()); err != nil { + t.Fatal(err) + } + + vm2BlkA, err := vm2.ParseBlock(context.Background(), vm1BlkA.Bytes()) + if err != nil { + t.Fatalf("Unexpected error parsing block from vm2: %s", err) + } + if err := vm2BlkA.Verify(context.Background()); err != nil { + t.Fatalf("Block failed verification on VM2: %s", err) + } + if err := vm2.SetPreference(context.Background(), vm2BlkA.ID()); err != nil { + t.Fatal(err) + } + + if err := vm1BlkA.Accept(context.Background()); err != nil { + t.Fatalf("VM1 failed to accept block: %s", err) + } + if err := vm2BlkA.Accept(context.Background()); err != nil { + t.Fatalf("VM2 failed to accept block: %s", err) + } + + newHead := <-newTxPoolHeadChan1 if newHead.Head.Hash() != common.Hash(vm1BlkA.ID()) { t.Fatalf("Expected new block to match") } @@ -608,8 +1236,8 @@ func TestSetPreferenceRace(t *testing.T) { // and to be split into two separate blocks on VM2 txs := make([]*types.Transaction, 10) for i := 0; i < 10; i++ { - tx := types.NewTransaction(uint64(i), testEthAddrs[0], big.NewInt(10), 21000, big.NewInt(testMinGasPrice), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm1.chainConfig.ChainID), testKeys[1]) + tx := types.NewTransaction(uint64(i), testEthAddrs[1], big.NewInt(10), 21000, big.NewInt(params.LaunchMinGasPrice), nil) + signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm1.chainID), testKeys[1].ToECDSA()) if err != nil { t.Fatal(err) } @@ -749,6 +1377,222 @@ func TestSetPreferenceRace(t *testing.T) { } } +func TestConflictingTransitiveAncestryWithGap(t *testing.T) { + key, err := accountKeystore.NewKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + + key0 := testKeys[0] + addr0 := key0.PublicKey().Address() + + key1 := testKeys[1] + addr1 := key1.PublicKey().Address() + + importAmount := uint64(1000000000) + + issuer, vm, _, _, _ := GenesisVMWithUTXOs(t, true, genesisJSONApricotPhase0, "", "", + map[ids.ShortID]uint64{ + addr0: importAmount, + addr1: importAmount, + }) + + defer func() { + if err := vm.Shutdown(context.Background()); err != nil { + t.Fatal(err) + } + }() + + newTxPoolHeadChan := make(chan core.NewTxPoolReorgEvent, 1) + vm.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan) + + importTx0A, err := vm.newImportTx(vm.ctx.XChainID, key.Address, initialBaseFee, []*secp256k1.PrivateKey{key0}) + if err != nil { + t.Fatal(err) + } + // Create a conflicting transaction + importTx0B, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[2], initialBaseFee, []*secp256k1.PrivateKey{key0}) + if err != nil { + t.Fatal(err) + } + + if err := vm.mempool.AddLocalTx(importTx0A); err != nil { + t.Fatalf("Failed to issue importTx0A: %s", err) + } + + <-issuer + + blk0, err := vm.BuildBlock(context.Background()) + if err != nil { + t.Fatalf("Failed to build block with import transaction: %s", err) + } + + if err := blk0.Verify(context.Background()); err != nil { + t.Fatalf("Block failed verification: %s", err) + } + + if err := vm.SetPreference(context.Background(), blk0.ID()); err != nil { + t.Fatal(err) + } + + newHead := <-newTxPoolHeadChan + if newHead.Head.Hash() != common.Hash(blk0.ID()) { + t.Fatalf("Expected new block to match") + } + + tx := types.NewTransaction(0, key.Address, big.NewInt(10), 21000, big.NewInt(params.LaunchMinGasPrice), nil) + signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainID), key.PrivateKey) + if err != nil { + t.Fatal(err) + } + + // Add the remote transactions, build the block, and set VM1's preference for block A + errs := vm.txPool.AddRemotesSync([]*types.Transaction{signedTx}) + for i, err := range errs { + if err != nil { + t.Fatalf("Failed to add transaction to VM1 at index %d: %s", i, err) + } + } + + <-issuer + + blk1, err := vm.BuildBlock(context.Background()) + if err != nil { + t.Fatalf("Failed to build blk1: %s", err) + } + + if err := blk1.Verify(context.Background()); err != nil { + t.Fatalf("blk1 failed verification due to %s", err) + } + + if err := vm.SetPreference(context.Background(), blk1.ID()); err != nil { + t.Fatal(err) + } + + importTx1, err := vm.newImportTx(vm.ctx.XChainID, key.Address, initialBaseFee, []*secp256k1.PrivateKey{key1}) + if err != nil { + t.Fatalf("Failed to issue importTx1 due to: %s", err) + } + + if err := vm.mempool.AddLocalTx(importTx1); err != nil { + t.Fatal(err) + } + + <-issuer + + blk2, err := vm.BuildBlock(context.Background()) + if err != nil { + t.Fatalf("Failed to build block with import transaction: %s", err) + } + + if err := blk2.Verify(context.Background()); err != nil { + t.Fatalf("Block failed verification: %s", err) + } + + if err := vm.SetPreference(context.Background(), blk2.ID()); err != nil { + t.Fatal(err) + } + + if err := vm.mempool.AddLocalTx(importTx0B); err == nil { + t.Fatalf("Should not have been able to issue import tx with conflict") + } + // Force issue transaction directly into the mempool + if err := vm.mempool.ForceAddTx(importTx0B); err != nil { + t.Fatal(err) + } + <-issuer + + _, err = vm.BuildBlock(context.Background()) + if err == nil { + t.Fatal("Shouldn't have been able to build an invalid block") + } +} + +func TestBonusBlocksTxs(t *testing.T) { + issuer, vm, _, sharedMemory, _ := GenesisVM(t, true, genesisJSONApricotPhase0, "", "") + + defer func() { + if err := vm.Shutdown(context.Background()); err != nil { + t.Fatal(err) + } + }() + + importAmount := uint64(10000000) + utxoID := avax.UTXOID{TxID: ids.GenerateTestID()} + + utxo := &avax.UTXO{ + UTXOID: utxoID, + Asset: avax.Asset{ID: vm.ctx.AVAXAssetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: importAmount, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{testKeys[0].PublicKey().Address()}, + }, + }, + } + utxoBytes, err := vm.codec.Marshal(codecVersion, utxo) + if err != nil { + t.Fatal(err) + } + + xChainSharedMemory := sharedMemory.NewSharedMemory(vm.ctx.XChainID) + inputID := utxo.InputID() + if err := xChainSharedMemory.Apply(map[ids.ID]*atomic.Requests{vm.ctx.ChainID: {PutRequests: []*atomic.Element{{ + Key: inputID[:], + Value: utxoBytes, + Traits: [][]byte{ + testKeys[0].PublicKey().Address().Bytes(), + }, + }}}}); err != nil { + t.Fatal(err) + } + + importTx, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) + if err != nil { + t.Fatal(err) + } + + if err := vm.mempool.AddLocalTx(importTx); err != nil { + t.Fatal(err) + } + + <-issuer + + blk, err := vm.BuildBlock(context.Background()) + if err != nil { + t.Fatal(err) + } + + // Make [blk] a bonus block. + vm.atomicBackend.(*atomicBackend).bonusBlocks = map[uint64]ids.ID{blk.Height(): blk.ID()} + + // Remove the UTXOs from shared memory, so that non-bonus blocks will fail verification + if err := vm.ctx.SharedMemory.Apply(map[ids.ID]*atomic.Requests{vm.ctx.XChainID: {RemoveRequests: [][]byte{inputID[:]}}}); err != nil { + t.Fatal(err) + } + + if err := blk.Verify(context.Background()); err != nil { + t.Fatal(err) + } + + if err := vm.SetPreference(context.Background(), blk.ID()); err != nil { + t.Fatal(err) + } + + if err := blk.Accept(context.Background()); err != nil { + t.Fatal(err) + } + + lastAcceptedID, err := vm.LastAccepted(context.Background()) + if err != nil { + t.Fatal(err) + } + if lastAcceptedID != blk.ID() { + t.Fatalf("Expected last accepted blockID to be the accepted block: %s, but found %s", blk.ID(), lastAcceptedID) + } +} + // Regression test to ensure that a VM that accepts block A and B // will not attempt to orphan either when verifying blocks C and D // from another VM (which have a common ancestor under the finalized @@ -763,8 +1607,13 @@ func TestSetPreferenceRace(t *testing.T) { // accept block C, which should be an orphaned block at this point and // get rejected. func TestReorgProtection(t *testing.T) { - issuer1, vm1, _, _ := GenesisVM(t, true, genesisJSONSubnetEVM, `{"pruning-enabled":false}`, "") - issuer2, vm2, _, _ := GenesisVM(t, true, genesisJSONSubnetEVM, `{"pruning-enabled":false}`, "") + importAmount := uint64(1000000000) + issuer1, vm1, _, _, _ := GenesisVMWithUTXOs(t, true, genesisJSONApricotPhase0, `{"pruning-enabled":false}`, "", map[ids.ShortID]uint64{ + testShortIDAddrs[0]: importAmount, + }) + issuer2, vm2, _, _, _ := GenesisVMWithUTXOs(t, true, genesisJSONApricotPhase0, `{"pruning-enabled":false}`, "", map[ids.ShortID]uint64{ + testShortIDAddrs[0]: importAmount, + }) defer func() { if err := vm1.Shutdown(context.Background()); err != nil { @@ -781,17 +1630,16 @@ func TestReorgProtection(t *testing.T) { newTxPoolHeadChan2 := make(chan core.NewTxPoolReorgEvent, 1) vm2.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan2) - tx := types.NewTransaction(uint64(0), testEthAddrs[1], firstTxAmount, 21000, big.NewInt(testMinGasPrice), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm1.chainConfig.ChainID), testKeys[0]) + key := testKeys[0].ToECDSA() + address := testEthAddrs[0] + + importTx, err := vm1.newImportTx(vm1.ctx.XChainID, address, initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) if err != nil { t.Fatal(err) } - txErrors := vm1.txPool.AddRemotesSync([]*types.Transaction{signedTx}) - for i, err := range txErrors { - if err != nil { - t.Fatalf("Failed to add tx at index %d: %s", i, err) - } + if err := vm1.mempool.AddLocalTx(importTx); err != nil { + t.Fatal(err) } <-issuer1 @@ -840,8 +1688,8 @@ func TestReorgProtection(t *testing.T) { // and to be split into two separate blocks on VM2 txs := make([]*types.Transaction, 10) for i := 0; i < 10; i++ { - tx := types.NewTransaction(uint64(i), testEthAddrs[0], big.NewInt(10), 21000, big.NewInt(testMinGasPrice), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm1.chainConfig.ChainID), testKeys[1]) + tx := types.NewTransaction(uint64(i), address, big.NewInt(10), 21000, big.NewInt(params.LaunchMinGasPrice), nil) + signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm1.chainID), key) if err != nil { t.Fatal(err) } @@ -927,8 +1775,13 @@ func TestReorgProtection(t *testing.T) { // / \ // B C func TestNonCanonicalAccept(t *testing.T) { - issuer1, vm1, _, _ := GenesisVM(t, true, genesisJSONSubnetEVM, "", "") - issuer2, vm2, _, _ := GenesisVM(t, true, genesisJSONSubnetEVM, "", "") + importAmount := uint64(1000000000) + issuer1, vm1, _, _, _ := GenesisVMWithUTXOs(t, true, genesisJSONApricotPhase0, "", "", map[ids.ShortID]uint64{ + testShortIDAddrs[0]: importAmount, + }) + issuer2, vm2, _, _, _ := GenesisVMWithUTXOs(t, true, genesisJSONApricotPhase0, "", "", map[ids.ShortID]uint64{ + testShortIDAddrs[0]: importAmount, + }) defer func() { if err := vm1.Shutdown(context.Background()); err != nil { @@ -945,17 +1798,16 @@ func TestNonCanonicalAccept(t *testing.T) { newTxPoolHeadChan2 := make(chan core.NewTxPoolReorgEvent, 1) vm2.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan2) - tx := types.NewTransaction(uint64(0), testEthAddrs[1], firstTxAmount, 21000, big.NewInt(testMinGasPrice), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm1.chainConfig.ChainID), testKeys[0]) + key := testKeys[0].ToECDSA() + address := testEthAddrs[0] + + importTx, err := vm1.newImportTx(vm1.ctx.XChainID, address, initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) if err != nil { t.Fatal(err) } - txErrors := vm1.txPool.AddRemotesSync([]*types.Transaction{signedTx}) - for i, err := range txErrors { - if err != nil { - t.Fatalf("Failed to add tx at index %d: %s", i, err) - } + if err := vm1.mempool.AddLocalTx(importTx); err != nil { + t.Fatal(err) } <-issuer1 @@ -1021,8 +1873,8 @@ func TestNonCanonicalAccept(t *testing.T) { // and to be split into two separate blocks on VM2 txs := make([]*types.Transaction, 10) for i := 0; i < 10; i++ { - tx := types.NewTransaction(uint64(i), testEthAddrs[0], big.NewInt(10), 21000, big.NewInt(testMinGasPrice), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm1.chainConfig.ChainID), testKeys[1]) + tx := types.NewTransaction(uint64(i), address, big.NewInt(10), 21000, big.NewInt(params.LaunchMinGasPrice), nil) + signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm1.chainID), key) if err != nil { t.Fatal(err) } @@ -1058,6 +1910,8 @@ func TestNonCanonicalAccept(t *testing.T) { t.Fatal(err) } + vm1.eth.APIBackend.SetAllowUnfinalizedQueries(true) + blkBHeight := vm1BlkB.Height() blkBHash := vm1BlkB.(*chain.BlockWrapper).Block.(*Block).ethBlock.Hash() if b := vm1.blockChain.GetBlockByNumber(blkBHeight); b.Hash() != blkBHash { @@ -1116,8 +1970,13 @@ func TestNonCanonicalAccept(t *testing.T) { // | // D func TestStickyPreference(t *testing.T) { - issuer1, vm1, _, _ := GenesisVM(t, true, genesisJSONSubnetEVM, "", "") - issuer2, vm2, _, _ := GenesisVM(t, true, genesisJSONSubnetEVM, "", "") + importAmount := uint64(1000000000) + issuer1, vm1, _, _, _ := GenesisVMWithUTXOs(t, true, genesisJSONApricotPhase0, "", "", map[ids.ShortID]uint64{ + testShortIDAddrs[0]: importAmount, + }) + issuer2, vm2, _, _, _ := GenesisVMWithUTXOs(t, true, genesisJSONApricotPhase0, "", "", map[ids.ShortID]uint64{ + testShortIDAddrs[0]: importAmount, + }) defer func() { if err := vm1.Shutdown(context.Background()); err != nil { @@ -1134,17 +1993,16 @@ func TestStickyPreference(t *testing.T) { newTxPoolHeadChan2 := make(chan core.NewTxPoolReorgEvent, 1) vm2.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan2) - tx := types.NewTransaction(uint64(0), testEthAddrs[1], firstTxAmount, 21000, big.NewInt(testMinGasPrice), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm1.chainConfig.ChainID), testKeys[0]) + key := testKeys[0].ToECDSA() + address := testEthAddrs[0] + + importTx, err := vm1.newImportTx(vm1.ctx.XChainID, address, initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) if err != nil { t.Fatal(err) } - txErrors := vm1.txPool.AddRemotesSync([]*types.Transaction{signedTx}) - for i, err := range txErrors { - if err != nil { - t.Fatalf("Failed to add tx at index %d: %s", i, err) - } + if err := vm1.mempool.AddLocalTx(importTx); err != nil { + t.Fatal(err) } <-issuer1 @@ -1193,8 +2051,8 @@ func TestStickyPreference(t *testing.T) { // and to be split into two separate blocks on VM2 txs := make([]*types.Transaction, 10) for i := 0; i < 10; i++ { - tx := types.NewTransaction(uint64(i), testEthAddrs[0], big.NewInt(10), 21000, big.NewInt(testMinGasPrice), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm1.chainConfig.ChainID), testKeys[1]) + tx := types.NewTransaction(uint64(i), address, big.NewInt(10), 21000, big.NewInt(params.LaunchMinGasPrice), nil) + signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm1.chainID), key) if err != nil { t.Fatal(err) } @@ -1226,6 +2084,8 @@ func TestStickyPreference(t *testing.T) { t.Fatal(err) } + vm1.eth.APIBackend.SetAllowUnfinalizedQueries(true) + blkBHeight := vm1BlkB.Height() blkBHash := vm1BlkB.(*chain.BlockWrapper).Block.(*Block).ethBlock.Hash() if b := vm1.blockChain.GetBlockByNumber(blkBHeight); b.Hash() != blkBHash { @@ -1369,8 +2229,13 @@ func TestStickyPreference(t *testing.T) { // | // D func TestUncleBlock(t *testing.T) { - issuer1, vm1, _, _ := GenesisVM(t, true, genesisJSONSubnetEVM, "", "") - issuer2, vm2, _, _ := GenesisVM(t, true, genesisJSONSubnetEVM, "", "") + importAmount := uint64(1000000000) + issuer1, vm1, _, _, _ := GenesisVMWithUTXOs(t, true, genesisJSONApricotPhase0, "", "", map[ids.ShortID]uint64{ + testShortIDAddrs[0]: importAmount, + }) + issuer2, vm2, _, _, _ := GenesisVMWithUTXOs(t, true, genesisJSONApricotPhase0, "", "", map[ids.ShortID]uint64{ + testShortIDAddrs[0]: importAmount, + }) defer func() { if err := vm1.Shutdown(context.Background()); err != nil { @@ -1386,17 +2251,16 @@ func TestUncleBlock(t *testing.T) { newTxPoolHeadChan2 := make(chan core.NewTxPoolReorgEvent, 1) vm2.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan2) - tx := types.NewTransaction(uint64(0), testEthAddrs[1], firstTxAmount, 21000, big.NewInt(testMinGasPrice), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm1.chainConfig.ChainID), testKeys[0]) + key := testKeys[0].ToECDSA() + address := testEthAddrs[0] + + importTx, err := vm1.newImportTx(vm1.ctx.XChainID, address, initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) if err != nil { t.Fatal(err) } - txErrors := vm1.txPool.AddRemotesSync([]*types.Transaction{signedTx}) - for i, err := range txErrors { - if err != nil { - t.Fatalf("Failed to add tx at index %d: %s", i, err) - } + if err := vm1.mempool.AddLocalTx(importTx); err != nil { + t.Fatal(err) } <-issuer1 @@ -1443,8 +2307,8 @@ func TestUncleBlock(t *testing.T) { txs := make([]*types.Transaction, 10) for i := 0; i < 10; i++ { - tx := types.NewTransaction(uint64(i), testEthAddrs[0], big.NewInt(10), 21000, big.NewInt(testMinGasPrice), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm1.chainConfig.ChainID), testKeys[1]) + tx := types.NewTransaction(uint64(i), address, big.NewInt(10), 21000, big.NewInt(params.LaunchMinGasPrice), nil) + signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm1.chainID), key) if err != nil { t.Fatal(err) } @@ -1520,15 +2384,19 @@ func TestUncleBlock(t *testing.T) { uncleBlockHeader := types.CopyHeader(blkDEthBlock.Header()) uncleBlockHeader.UncleHash = types.CalcUncleHash(uncles) - uncleEthBlock := types.NewBlock( + uncleEthBlock := types.NewBlockWithExtData( uncleBlockHeader, blkDEthBlock.Transactions(), uncles, nil, trie.NewStackTrie(nil), + blkDEthBlock.ExtData(), + false, ) - uncleBlock := vm2.newBlock(uncleEthBlock) - + uncleBlock, err := vm2.newBlock(uncleEthBlock) + if err != nil { + t.Fatal(err) + } if err := uncleBlock.Verify(context.Background()); !errors.Is(err, errUnclesUnsupported) { t.Fatalf("VM2 should have failed with %q but got %q", errUnclesUnsupported, err.Error()) } @@ -1543,7 +2411,10 @@ func TestUncleBlock(t *testing.T) { // Regression test to ensure that a VM that is not able to parse a block that // contains no transactions. func TestEmptyBlock(t *testing.T) { - issuer, vm, _, _ := GenesisVM(t, true, genesisJSONSubnetEVM, "", "") + importAmount := uint64(1000000000) + issuer, vm, _, _, _ := GenesisVMWithUTXOs(t, true, genesisJSONApricotPhase0, "", "", map[ids.ShortID]uint64{ + testShortIDAddrs[0]: importAmount, + }) defer func() { if err := vm.Shutdown(context.Background()); err != nil { @@ -1551,17 +2422,13 @@ func TestEmptyBlock(t *testing.T) { } }() - tx := types.NewTransaction(uint64(0), testEthAddrs[1], firstTxAmount, 21000, big.NewInt(testMinGasPrice), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[0]) + importTx, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) if err != nil { t.Fatal(err) } - txErrors := vm.txPool.AddRemotesSync([]*types.Transaction{signedTx}) - for i, err := range txErrors { - if err != nil { - t.Fatalf("Failed to add tx at index %d: %s", i, err) - } + if err := vm.mempool.AddLocalTx(importTx); err != nil { + t.Fatal(err) } <-issuer @@ -1574,15 +2441,24 @@ func TestEmptyBlock(t *testing.T) { // Create empty block from blkA ethBlock := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - emptyEthBlock := types.NewBlock( + emptyEthBlock := types.NewBlockWithExtData( types.CopyHeader(ethBlock.Header()), nil, nil, nil, new(trie.Trie), + nil, + false, ) - emptyBlock := vm.newBlock(emptyEthBlock) + if len(emptyEthBlock.ExtData()) != 0 || emptyEthBlock.Header().ExtDataHash != (common.Hash{}) { + t.Fatalf("emptyEthBlock should not have any extra data") + } + + emptyBlock, err := vm.newBlock(emptyEthBlock) + if err != nil { + t.Fatal(err) + } if _, err := vm.ParseBlock(context.Background(), emptyBlock.Bytes()); !errors.Is(err, errEmptyBlock) { t.Fatalf("VM should have failed with errEmptyBlock but got %s", err.Error()) @@ -1601,8 +2477,13 @@ func TestEmptyBlock(t *testing.T) { // | // D func TestAcceptReorg(t *testing.T) { - issuer1, vm1, _, _ := GenesisVM(t, true, genesisJSONSubnetEVM, "", "") - issuer2, vm2, _, _ := GenesisVM(t, true, genesisJSONSubnetEVM, "", "") + importAmount := uint64(1000000000) + issuer1, vm1, _, _, _ := GenesisVMWithUTXOs(t, true, genesisJSONApricotPhase0, "", "", map[ids.ShortID]uint64{ + testShortIDAddrs[0]: importAmount, + }) + issuer2, vm2, _, _, _ := GenesisVMWithUTXOs(t, true, genesisJSONApricotPhase0, "", "", map[ids.ShortID]uint64{ + testShortIDAddrs[0]: importAmount, + }) defer func() { if err := vm1.Shutdown(context.Background()); err != nil { @@ -1619,17 +2500,16 @@ func TestAcceptReorg(t *testing.T) { newTxPoolHeadChan2 := make(chan core.NewTxPoolReorgEvent, 1) vm2.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan2) - tx := types.NewTransaction(uint64(0), testEthAddrs[1], firstTxAmount, 21000, big.NewInt(testMinGasPrice), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm1.chainConfig.ChainID), testKeys[0]) + key := testKeys[0].ToECDSA() + address := testEthAddrs[0] + + importTx, err := vm1.newImportTx(vm1.ctx.XChainID, address, initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) if err != nil { t.Fatal(err) } - txErrors := vm1.txPool.AddRemotesSync([]*types.Transaction{signedTx}) - for i, err := range txErrors { - if err != nil { - t.Fatalf("Failed to add tx at index %d: %s", i, err) - } + if err := vm1.mempool.AddLocalTx(importTx); err != nil { + t.Fatal(err) } <-issuer1 @@ -1678,8 +2558,8 @@ func TestAcceptReorg(t *testing.T) { // and to be split into two separate blocks on VM2 txs := make([]*types.Transaction, 10) for i := 0; i < 10; i++ { - tx := types.NewTransaction(uint64(i), testEthAddrs[0], big.NewInt(10), 21000, big.NewInt(testMinGasPrice), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm1.chainConfig.ChainID), testKeys[1]) + tx := types.NewTransaction(uint64(i), address, big.NewInt(10), 21000, big.NewInt(params.LaunchMinGasPrice), nil) + signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm1.chainID), key) if err != nil { t.Fatal(err) } @@ -1796,7 +2676,10 @@ func TestAcceptReorg(t *testing.T) { } func TestFutureBlock(t *testing.T) { - issuer, vm, _, _ := GenesisVM(t, true, genesisJSONSubnetEVM, "", "") + importAmount := uint64(1000000000) + issuer, vm, _, _, _ := GenesisVMWithUTXOs(t, true, genesisJSONApricotPhase0, "", "", map[ids.ShortID]uint64{ + testShortIDAddrs[0]: importAmount, + }) defer func() { if err := vm.Shutdown(context.Background()); err != nil { @@ -1804,17 +2687,13 @@ func TestFutureBlock(t *testing.T) { } }() - tx := types.NewTransaction(uint64(0), testEthAddrs[1], firstTxAmount, 21000, big.NewInt(testMinGasPrice), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[0]) + importTx, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) if err != nil { t.Fatal(err) } - txErrors := vm.txPool.AddRemotesSync([]*types.Transaction{signedTx}) - for i, err := range txErrors { - if err != nil { - t.Fatalf("Failed to add tx at index %d: %s", i, err) - } + if err := vm.mempool.AddLocalTx(importTx); err != nil { + t.Fatal(err) } <-issuer @@ -1832,15 +2711,20 @@ func TestFutureBlock(t *testing.T) { // Set the modified time to exceed the allowed future time modifiedTime := modifiedHeader.Time + uint64(maxFutureBlockTime.Seconds()+1) modifiedHeader.Time = modifiedTime - modifiedBlock := types.NewBlock( + modifiedBlock := types.NewBlockWithExtData( modifiedHeader, - internalBlkA.ethBlock.Transactions(), nil, nil, - trie.NewStackTrie(nil), + nil, + new(trie.Trie), + internalBlkA.ethBlock.ExtData(), + false, ) - futureBlock := vm.newBlock(modifiedBlock) + futureBlock, err := vm.newBlock(modifiedBlock) + if err != nil { + t.Fatal(err) + } if err := futureBlock.Verify(context.Background()); err == nil { t.Fatal("Future block should have failed verification due to block timestamp too far in the future") @@ -1849,23 +2733,77 @@ func TestFutureBlock(t *testing.T) { } } -func TestLastAcceptedBlockNumberAllow(t *testing.T) { - issuer, vm, _, _ := GenesisVM(t, true, genesisJSONSubnetEVM, "", "") - +// Regression test to ensure we can build blocks if we are starting with the +// Apricot Phase 1 ruleset in genesis. +func TestBuildApricotPhase1Block(t *testing.T) { + importAmount := uint64(1000000000) + issuer, vm, _, _, _ := GenesisVMWithUTXOs(t, true, genesisJSONApricotPhase1, "", "", map[ids.ShortID]uint64{ + testShortIDAddrs[0]: importAmount, + }) defer func() { if err := vm.Shutdown(context.Background()); err != nil { t.Fatal(err) } }() - tx := types.NewTransaction(uint64(0), testEthAddrs[1], firstTxAmount, 21000, big.NewInt(testMinGasPrice), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[0]) + newTxPoolHeadChan := make(chan core.NewTxPoolReorgEvent, 1) + vm.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan) + + key := testKeys[0].ToECDSA() + address := testEthAddrs[0] + + importTx, err := vm.newImportTx(vm.ctx.XChainID, address, initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) + if err != nil { + t.Fatal(err) + } + + if err := vm.mempool.AddLocalTx(importTx); err != nil { + t.Fatal(err) + } + + <-issuer + + blk, err := vm.BuildBlock(context.Background()) if err != nil { t.Fatal(err) } - txErrors := vm.txPool.AddRemotesSync([]*types.Transaction{signedTx}) - for i, err := range txErrors { + if err := blk.Verify(context.Background()); err != nil { + t.Fatal(err) + } + + if err := vm.SetPreference(context.Background(), blk.ID()); err != nil { + t.Fatal(err) + } + + if err := blk.Accept(context.Background()); err != nil { + t.Fatal(err) + } + + newHead := <-newTxPoolHeadChan + if newHead.Head.Hash() != common.Hash(blk.ID()) { + t.Fatalf("Expected new block to match") + } + + txs := make([]*types.Transaction, 10) + for i := 0; i < 5; i++ { + tx := types.NewTransaction(uint64(i), address, big.NewInt(10), 21000, big.NewInt(params.LaunchMinGasPrice), nil) + signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainID), key) + if err != nil { + t.Fatal(err) + } + txs[i] = signedTx + } + for i := 5; i < 10; i++ { + tx := types.NewTransaction(uint64(i), address, big.NewInt(10), 21000, big.NewInt(params.ApricotPhase1MinGasPrice), nil) + signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainID), key) + if err != nil { + t.Fatal(err) + } + txs[i] = signedTx + } + errs := vm.txPool.AddRemotesSync(txs) + for i, err := range errs { if err != nil { t.Fatalf("Failed to add tx at index %d: %s", i, err) } @@ -1873,6 +2811,62 @@ func TestLastAcceptedBlockNumberAllow(t *testing.T) { <-issuer + blk, err = vm.BuildBlock(context.Background()) + if err != nil { + t.Fatal(err) + } + + if err := blk.Verify(context.Background()); err != nil { + t.Fatal(err) + } + + if err := blk.Accept(context.Background()); err != nil { + t.Fatal(err) + } + + lastAcceptedID, err := vm.LastAccepted(context.Background()) + if err != nil { + t.Fatal(err) + } + if lastAcceptedID != blk.ID() { + t.Fatalf("Expected last accepted blockID to be the accepted block: %s, but found %s", blk.ID(), lastAcceptedID) + } + + // Confirm all txs are present + ethBlkTxs := vm.blockChain.GetBlockByNumber(2).Transactions() + for i, tx := range txs { + if len(ethBlkTxs) <= i { + t.Fatalf("missing transactions expected: %d but found: %d", len(txs), len(ethBlkTxs)) + } + if ethBlkTxs[i].Hash() != tx.Hash() { + t.Fatalf("expected tx at index %d to have hash: %x but has: %x", i, txs[i].Hash(), tx.Hash()) + } + } +} + +func TestLastAcceptedBlockNumberAllow(t *testing.T) { + importAmount := uint64(1000000000) + issuer, vm, _, _, _ := GenesisVMWithUTXOs(t, true, genesisJSONApricotPhase0, "", "", map[ids.ShortID]uint64{ + testShortIDAddrs[0]: importAmount, + }) + + defer func() { + if err := vm.Shutdown(context.Background()); err != nil { + t.Fatal(err) + } + }() + + importTx, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) + if err != nil { + t.Fatal(err) + } + + if err := vm.mempool.AddLocalTx(importTx); err != nil { + t.Fatal(err) + } + + <-issuer + blk, err := vm.BuildBlock(context.Background()) if err != nil { t.Fatalf("Failed to build block with import transaction: %s", err) @@ -1916,6 +2910,215 @@ func TestLastAcceptedBlockNumberAllow(t *testing.T) { } } +// Builds [blkA] with a virtuous import transaction and [blkB] with a separate import transaction +// that does not conflict. Accepts [blkB] and rejects [blkA], then asserts that the virtuous atomic +// transaction in [blkA] is correctly re-issued into the atomic transaction mempool. +func TestReissueAtomicTx(t *testing.T) { + issuer, vm, _, _, _ := GenesisVMWithUTXOs(t, true, genesisJSONApricotPhase1, "", "", map[ids.ShortID]uint64{ + testShortIDAddrs[0]: 10000000, + testShortIDAddrs[1]: 10000000, + }) + + defer func() { + if err := vm.Shutdown(context.Background()); err != nil { + t.Fatal(err) + } + }() + + genesisBlkID, err := vm.LastAccepted(context.Background()) + if err != nil { + t.Fatal(err) + } + + importTx, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) + if err != nil { + t.Fatal(err) + } + + if err := vm.mempool.AddLocalTx(importTx); err != nil { + t.Fatal(err) + } + + <-issuer + + blkA, err := vm.BuildBlock(context.Background()) + if err != nil { + t.Fatal(err) + } + + if err := blkA.Verify(context.Background()); err != nil { + t.Fatal(err) + } + + if err := vm.SetPreference(context.Background(), blkA.ID()); err != nil { + t.Fatal(err) + } + + // SetPreference to parent before rejecting (will rollback state to genesis + // so that atomic transaction can be reissued, otherwise current block will + // conflict with UTXO to be reissued) + if err := vm.SetPreference(context.Background(), genesisBlkID); err != nil { + t.Fatal(err) + } + + // Rejecting [blkA] should cause [importTx] to be re-issued into the mempool. + if err := blkA.Reject(context.Background()); err != nil { + t.Fatal(err) + } + + // Sleep for a minimum of two seconds to ensure that [blkB] will have a different timestamp + // than [blkA] so that the block will be unique. This is necessary since we have marked [blkA] + // as Rejected. + time.Sleep(2 * time.Second) + <-issuer + blkB, err := vm.BuildBlock(context.Background()) + if err != nil { + t.Fatal(err) + } + + if blkB.Height() != blkA.Height() { + t.Fatalf("Expected blkB (%d) to have the same height as blkA (%d)", blkB.Height(), blkA.Height()) + } + + if err := blkB.Verify(context.Background()); err != nil { + t.Fatal(err) + } + + if err := vm.SetPreference(context.Background(), blkB.ID()); err != nil { + t.Fatal(err) + } + + if err := blkB.Accept(context.Background()); err != nil { + t.Fatal(err) + } + + if lastAcceptedID, err := vm.LastAccepted(context.Background()); err != nil { + t.Fatal(err) + } else if lastAcceptedID != blkB.ID() { + t.Fatalf("Expected last accepted blockID to be the accepted block: %s, but found %s", blkB.ID(), lastAcceptedID) + } + + // Check that [importTx] has been indexed correctly after [blkB] is accepted. + _, height, err := vm.atomicTxRepository.GetByTxID(importTx.ID()) + if err != nil { + t.Fatal(err) + } else if height != blkB.Height() { + t.Fatalf("Expected indexed height of import tx to be %d, but found %d", blkB.Height(), height) + } +} + +func TestAtomicTxFailsEVMStateTransferBuildBlock(t *testing.T) { + issuer, vm, _, sharedMemory, _ := GenesisVM(t, true, genesisJSONApricotPhase1, "", "") + + defer func() { + if err := vm.Shutdown(context.Background()); err != nil { + t.Fatal(err) + } + }() + + exportTxs := createExportTxOptions(t, vm, issuer, sharedMemory) + exportTx1, exportTx2 := exportTxs[0], exportTxs[1] + + if err := vm.mempool.AddLocalTx(exportTx1); err != nil { + t.Fatal(err) + } + <-issuer + exportBlk1, err := vm.BuildBlock(context.Background()) + if err != nil { + t.Fatal(err) + } + if err := exportBlk1.Verify(context.Background()); err != nil { + t.Fatal(err) + } + + if err := vm.SetPreference(context.Background(), exportBlk1.ID()); err != nil { + t.Fatal(err) + } + + if err := vm.mempool.AddLocalTx(exportTx2); err == nil { + t.Fatal("Should have failed to issue due to an invalid export tx") + } + + if err := vm.mempool.AddTx(exportTx2); err == nil { + t.Fatal("Should have failed to add because conflicting") + } + + // Manually add transaction to mempool to bypass validation + if err := vm.mempool.ForceAddTx(exportTx2); err != nil { + t.Fatal(err) + } + <-issuer + + _, err = vm.BuildBlock(context.Background()) + if err == nil { + t.Fatal("BuildBlock should have returned an error due to invalid export transaction") + } +} + +func TestBuildInvalidBlockHead(t *testing.T) { + issuer, vm, _, _, _ := GenesisVM(t, true, genesisJSONApricotPhase0, "", "") + + defer func() { + if err := vm.Shutdown(context.Background()); err != nil { + t.Fatal(err) + } + }() + + key0 := testKeys[0] + addr0 := key0.PublicKey().Address() + + // Create the transaction + utx := &UnsignedImportTx{ + NetworkID: vm.ctx.NetworkID, + BlockchainID: vm.ctx.ChainID, + Outs: []EVMOutput{{ + Address: common.Address(addr0), + Amount: 1 * units.Avax, + AssetID: vm.ctx.AVAXAssetID, + }}, + ImportedInputs: []*avax.TransferableInput{ + { + Asset: avax.Asset{ID: vm.ctx.AVAXAssetID}, + In: &secp256k1fx.TransferInput{ + Amt: 1 * units.Avax, + Input: secp256k1fx.Input{ + SigIndices: []uint32{0}, + }, + }, + }, + }, + SourceChain: vm.ctx.XChainID, + } + tx := &Tx{UnsignedAtomicTx: utx} + if err := tx.Sign(vm.codec, [][]*secp256k1.PrivateKey{{key0}}); err != nil { + t.Fatal(err) + } + + currentBlock := vm.blockChain.CurrentBlock() + + // Verify that the transaction fails verification when attempting to issue + // it into the atomic mempool. + if err := vm.mempool.AddLocalTx(tx); err == nil { + t.Fatal("Should have failed to issue invalid transaction") + } + // Force issue the transaction directly to the mempool + if err := vm.mempool.ForceAddTx(tx); err != nil { + t.Fatal(err) + } + + <-issuer + + if _, err := vm.BuildBlock(context.Background()); err == nil { + t.Fatalf("Unexpectedly created a block") + } + + newCurrentBlock := vm.blockChain.CurrentBlock() + + if currentBlock.Hash() != newCurrentBlock.Hash() { + t.Fatal("current block changed") + } +} + func TestConfigureLogLevel(t *testing.T) { configTests := []struct { name string @@ -1926,10 +3129,17 @@ func TestConfigureLogLevel(t *testing.T) { { name: "Log level info", logConfig: `{"log-level": "info"}`, - genesisJSON: genesisJSONSubnetEVM, + genesisJSON: genesisJSONApricotPhase2, upgradeJSON: "", expectedErr: "", }, + { + name: "Invalid log level", + logConfig: `{"log-level": "cchain"}`, + genesisJSON: genesisJSONApricotPhase3, + upgradeJSON: "", + expectedErr: "failed to initialize logger due to", + }, } for _, test := range configTests { t.Run(test.name, func(t *testing.T) { @@ -1986,9 +3196,9 @@ func TestConfigureLogLevel(t *testing.T) { } // Regression test to ensure we can build blocks if we are starting with the -// Subnet EVM ruleset in genesis. -func TestBuildSubnetEVMBlock(t *testing.T) { - issuer, vm, _, _ := GenesisVM(t, true, genesisJSONSubnetEVM, "", "") +// Apricot Phase 4 ruleset in genesis. +func TestBuildApricotPhase4Block(t *testing.T) { + issuer, vm, _, sharedMemory, _ := GenesisVM(t, true, genesisJSONApricotPhase4, "", "") defer func() { if err := vm.Shutdown(context.Background()); err != nil { @@ -1999,29 +3209,100 @@ func TestBuildSubnetEVMBlock(t *testing.T) { newTxPoolHeadChan := make(chan core.NewTxPoolReorgEvent, 1) vm.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan) - tx := types.NewTransaction(uint64(0), testEthAddrs[1], new(big.Int).Mul(firstTxAmount, big.NewInt(4)), 21000, big.NewInt(testMinGasPrice*3), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[0]) + key := testKeys[0].ToECDSA() + address := testEthAddrs[0] + + importAmount := uint64(1000000000) + utxoID := avax.UTXOID{TxID: ids.GenerateTestID()} + + utxo := &avax.UTXO{ + UTXOID: utxoID, + Asset: avax.Asset{ID: vm.ctx.AVAXAssetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: importAmount, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{testKeys[0].PublicKey().Address()}, + }, + }, + } + utxoBytes, err := vm.codec.Marshal(codecVersion, utxo) if err != nil { t.Fatal(err) } - txErrors := vm.txPool.AddRemotesSync([]*types.Transaction{signedTx}) - for i, err := range txErrors { - if err != nil { - t.Fatalf("Failed to add tx at index %d: %s", i, err) - } + xChainSharedMemory := sharedMemory.NewSharedMemory(vm.ctx.XChainID) + inputID := utxo.InputID() + if err := xChainSharedMemory.Apply(map[ids.ID]*atomic.Requests{vm.ctx.ChainID: {PutRequests: []*atomic.Element{{ + Key: inputID[:], + Value: utxoBytes, + Traits: [][]byte{ + testKeys[0].PublicKey().Address().Bytes(), + }, + }}}}); err != nil { + t.Fatal(err) + } + + importTx, err := vm.newImportTx(vm.ctx.XChainID, address, initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) + if err != nil { + t.Fatal(err) + } + + if err := vm.mempool.AddLocalTx(importTx); err != nil { + t.Fatal(err) + } + + <-issuer + + blk, err := vm.BuildBlock(context.Background()) + if err != nil { + t.Fatal(err) + } + + if err := blk.Verify(context.Background()); err != nil { + t.Fatal(err) + } + + if err := vm.SetPreference(context.Background(), blk.ID()); err != nil { + t.Fatal(err) + } + + if err := blk.Accept(context.Background()); err != nil { + t.Fatal(err) + } + + ethBlk := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock + if eBlockGasCost := ethBlk.BlockGasCost(); eBlockGasCost == nil || eBlockGasCost.Cmp(common.Big0) != 0 { + t.Fatalf("expected blockGasCost to be 0 but got %d", eBlockGasCost) + } + if eExtDataGasUsed := ethBlk.ExtDataGasUsed(); eExtDataGasUsed == nil || eExtDataGasUsed.Cmp(big.NewInt(1230)) != 0 { + t.Fatalf("expected extDataGasUsed to be 1000 but got %d", eExtDataGasUsed) + } + minRequiredTip, err := dummy.MinRequiredTip(vm.chainConfig, ethBlk.Header()) + if err != nil { + t.Fatal(err) + } + if minRequiredTip == nil || minRequiredTip.Cmp(common.Big0) != 0 { + t.Fatalf("expected minRequiredTip to be 0 but got %d", minRequiredTip) } - blk := issueAndAccept(t, issuer, vm) newHead := <-newTxPoolHeadChan if newHead.Head.Hash() != common.Hash(blk.ID()) { t.Fatalf("Expected new block to match") } txs := make([]*types.Transaction, 10) - for i := 0; i < 10; i++ { - tx := types.NewTransaction(uint64(i), testEthAddrs[0], big.NewInt(10), 21000, big.NewInt(testMinGasPrice*3), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[1]) + for i := 0; i < 5; i++ { + tx := types.NewTransaction(uint64(i), address, big.NewInt(10), 21000, big.NewInt(params.LaunchMinGasPrice), nil) + signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainID), key) + if err != nil { + t.Fatal(err) + } + txs[i] = signedTx + } + for i := 5; i < 10; i++ { + tx := types.NewTransaction(uint64(i), address, big.NewInt(10), 21000, big.NewInt(params.ApricotPhase1MinGasPrice), nil) + signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainID), key) if err != nil { t.Fatal(err) } @@ -2034,12 +3315,29 @@ func TestBuildSubnetEVMBlock(t *testing.T) { } } - blk = issueAndAccept(t, issuer, vm) - ethBlk := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock + <-issuer + + blk, err = vm.BuildBlock(context.Background()) + if err != nil { + t.Fatal(err) + } + + if err := blk.Verify(context.Background()); err != nil { + t.Fatal(err) + } + + if err := blk.Accept(context.Background()); err != nil { + t.Fatal(err) + } + + ethBlk = blk.(*chain.BlockWrapper).Block.(*Block).ethBlock if ethBlk.BlockGasCost() == nil || ethBlk.BlockGasCost().Cmp(big.NewInt(100)) < 0 { t.Fatalf("expected blockGasCost to be at least 100 but got %d", ethBlk.BlockGasCost()) } - minRequiredTip, err := dummy.MinRequiredTip(vm.chainConfig, ethBlk.Header()) + if ethBlk.ExtDataGasUsed() == nil || ethBlk.ExtDataGasUsed().Cmp(common.Big0) != 0 { + t.Fatalf("expected extDataGasUsed to be 0 but got %d", ethBlk.ExtDataGasUsed()) + } + minRequiredTip, err = dummy.MinRequiredTip(vm.chainConfig, ethBlk.Header()) if err != nil { t.Fatal(err) } @@ -2067,20 +3365,10 @@ func TestBuildSubnetEVMBlock(t *testing.T) { } } -func TestBuildAllowListActivationBlock(t *testing.T) { - genesis := &core.Genesis{} - if err := genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM)); err != nil { - t.Fatal(err) - } - genesis.Config.GenesisPrecompiles = params.Precompiles{ - deployerallowlist.ConfigKey: deployerallowlist.NewConfig(utils.TimeToNewUint64(time.Now()), testEthAddrs, nil, nil), - } - - genesisJSON, err := genesis.MarshalJSON() - if err != nil { - t.Fatal(err) - } - issuer, vm, _, _ := GenesisVM(t, true, string(genesisJSON), "", "") +// Regression test to ensure we can build blocks if we are starting with the +// Apricot Phase 5 ruleset in genesis. +func TestBuildApricotPhase5Block(t *testing.T) { + issuer, vm, _, sharedMemory, _ := GenesisVM(t, true, genesisJSONApricotPhase5, "", "") defer func() { if err := vm.Shutdown(context.Background()); err != nil { @@ -2091,411 +3379,159 @@ func TestBuildAllowListActivationBlock(t *testing.T) { newTxPoolHeadChan := make(chan core.NewTxPoolReorgEvent, 1) vm.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan) - genesisState, err := vm.blockChain.StateAt(vm.blockChain.Genesis().Root()) + key := testKeys[0].ToECDSA() + address := testEthAddrs[0] + + importAmount := uint64(1000000000) + utxoID := avax.UTXOID{TxID: ids.GenerateTestID()} + + utxo := &avax.UTXO{ + UTXOID: utxoID, + Asset: avax.Asset{ID: vm.ctx.AVAXAssetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: importAmount, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{testKeys[0].PublicKey().Address()}, + }, + }, + } + utxoBytes, err := vm.codec.Marshal(codecVersion, utxo) if err != nil { t.Fatal(err) } - role := deployerallowlist.GetContractDeployerAllowListStatus(genesisState, testEthAddrs[0]) - if role != allowlist.NoRole { - t.Fatalf("Expected allow list status to be set to no role: %s, but found: %s", allowlist.NoRole, role) + + xChainSharedMemory := sharedMemory.NewSharedMemory(vm.ctx.XChainID) + inputID := utxo.InputID() + if err := xChainSharedMemory.Apply(map[ids.ID]*atomic.Requests{vm.ctx.ChainID: {PutRequests: []*atomic.Element{{ + Key: inputID[:], + Value: utxoBytes, + Traits: [][]byte{ + testKeys[0].PublicKey().Address().Bytes(), + }, + }}}}); err != nil { + t.Fatal(err) } - // Send basic transaction to construct a simple block and confirm that the precompile state configuration in the worker behaves correctly. - tx := types.NewTransaction(uint64(0), testEthAddrs[1], new(big.Int).Mul(firstTxAmount, big.NewInt(4)), 21000, big.NewInt(testMinGasPrice*3), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[0]) + importTx, err := vm.newImportTx(vm.ctx.XChainID, address, initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) if err != nil { t.Fatal(err) } - txErrors := vm.txPool.AddRemotesSync([]*types.Transaction{signedTx}) - for i, err := range txErrors { - if err != nil { - t.Fatalf("Failed to add tx at index %d: %s", i, err) - } + if err := vm.mempool.AddLocalTx(importTx); err != nil { + t.Fatal(err) } - blk := issueAndAccept(t, issuer, vm) - newHead := <-newTxPoolHeadChan - if newHead.Head.Hash() != common.Hash(blk.ID()) { - t.Fatalf("Expected new block to match") - } + <-issuer - // Verify that the allow list config activation was handled correctly in the first block. - blkState, err := vm.blockChain.StateAt(blk.(*chain.BlockWrapper).Block.(*Block).ethBlock.Root()) + blk, err := vm.BuildBlock(context.Background()) if err != nil { t.Fatal(err) } - role = deployerallowlist.GetContractDeployerAllowListStatus(blkState, testEthAddrs[0]) - if role != allowlist.AdminRole { - t.Fatalf("Expected allow list status to be set role %s, but found: %s", allowlist.AdminRole, role) - } -} -// Test that the tx allow list allows whitelisted transactions and blocks non-whitelisted addresses -func TestTxAllowListSuccessfulTx(t *testing.T) { - // Setup chain params - managerKey := testKeys[1] - managerAddress := testEthAddrs[1] - genesis := &core.Genesis{} - if err := genesis.UnmarshalJSON([]byte(genesisJSONDurango)); err != nil { - t.Fatal(err) - } - // this manager role should not be activated because DurangoTimestamp is in the future - genesis.Config.GenesisPrecompiles = params.Precompiles{ - txallowlist.ConfigKey: txallowlist.NewConfig(utils.NewUint64(0), testEthAddrs[0:1], nil, nil), - } - durangoTime := time.Now().Add(10 * time.Hour) - genesis.Config.DurangoTimestamp = utils.TimeToNewUint64(durangoTime) - genesisJSON, err := genesis.MarshalJSON() - if err != nil { + if err := blk.Verify(context.Background()); err != nil { t.Fatal(err) } - // prepare the new upgrade bytes to disable the TxAllowList - disableAllowListTime := durangoTime.Add(10 * time.Hour) - reenableAllowlistTime := disableAllowListTime.Add(10 * time.Hour) - upgradeConfig := ¶ms.UpgradeConfig{ - PrecompileUpgrades: []params.PrecompileUpgrade{ - { - Config: txallowlist.NewDisableConfig(utils.TimeToNewUint64(disableAllowListTime)), - }, - // re-enable the tx allowlist after Durango to set the manager role - { - Config: txallowlist.NewConfig(utils.TimeToNewUint64(reenableAllowlistTime), testEthAddrs[0:1], nil, []common.Address{managerAddress}), - }, - }, + if err := vm.SetPreference(context.Background(), blk.ID()); err != nil { + t.Fatal(err) } - upgradeBytesJSON, err := json.Marshal(upgradeConfig) - require.NoError(t, err) - issuer, vm, _, _ := GenesisVM(t, true, string(genesisJSON), "", string(upgradeBytesJSON)) - - defer func() { - if err := vm.Shutdown(context.Background()); err != nil { - t.Fatal(err) - } - }() - - newTxPoolHeadChan := make(chan core.NewTxPoolReorgEvent, 1) - vm.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan) - genesisState, err := vm.blockChain.StateAt(vm.blockChain.Genesis().Root()) - if err != nil { + if err := blk.Accept(context.Background()); err != nil { t.Fatal(err) } - // Check that address 0 is whitelisted and address 1 is not - role := txallowlist.GetTxAllowListStatus(genesisState, testEthAddrs[0]) - if role != allowlist.AdminRole { - t.Fatalf("Expected allow list status to be set to admin: %s, but found: %s", allowlist.AdminRole, role) - } - role = txallowlist.GetTxAllowListStatus(genesisState, testEthAddrs[1]) - if role != allowlist.NoRole { - t.Fatalf("Expected allow list status to be set to no role: %s, but found: %s", allowlist.NoRole, role) + ethBlk := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock + if eBlockGasCost := ethBlk.BlockGasCost(); eBlockGasCost == nil || eBlockGasCost.Cmp(common.Big0) != 0 { + t.Fatalf("expected blockGasCost to be 0 but got %d", eBlockGasCost) } - // Should not be a manager role because Durango has not activated yet - role = txallowlist.GetTxAllowListStatus(genesisState, managerAddress) - require.Equal(t, allowlist.NoRole, role) - - // Submit a successful transaction - tx0 := types.NewTransaction(uint64(0), testEthAddrs[0], big.NewInt(1), 21000, big.NewInt(testMinGasPrice), nil) - signedTx0, err := types.SignTx(tx0, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[0]) - require.NoError(t, err) - - errs := vm.txPool.AddRemotesSync([]*types.Transaction{signedTx0}) - if err := errs[0]; err != nil { - t.Fatalf("Failed to add tx at index: %s", err) + if eExtDataGasUsed := ethBlk.ExtDataGasUsed(); eExtDataGasUsed == nil || eExtDataGasUsed.Cmp(big.NewInt(11230)) != 0 { + t.Fatalf("expected extDataGasUsed to be 11230 but got %d", eExtDataGasUsed) } - - // Submit a rejected transaction, should throw an error - tx1 := types.NewTransaction(uint64(0), testEthAddrs[1], big.NewInt(2), 21000, big.NewInt(testMinGasPrice), nil) - signedTx1, err := types.SignTx(tx1, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[1]) + minRequiredTip, err := dummy.MinRequiredTip(vm.chainConfig, ethBlk.Header()) if err != nil { t.Fatal(err) } - - errs = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx1}) - if err := errs[0]; !errors.Is(err, vmerrs.ErrSenderAddressNotAllowListed) { - t.Fatalf("expected ErrSenderAddressNotAllowListed, got: %s", err) + if minRequiredTip == nil || minRequiredTip.Cmp(common.Big0) != 0 { + t.Fatalf("expected minRequiredTip to be 0 but got %d", minRequiredTip) } - // Submit a rejected transaction, should throw an error because manager is not activated - tx2 := types.NewTransaction(uint64(0), managerAddress, big.NewInt(2), 21000, big.NewInt(testMinGasPrice), nil) - signedTx2, err := types.SignTx(tx2, types.NewEIP155Signer(vm.chainConfig.ChainID), managerKey) - require.NoError(t, err) - - errs = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx2}) - require.ErrorIs(t, errs[0], vmerrs.ErrSenderAddressNotAllowListed) - - blk := issueAndAccept(t, issuer, vm) newHead := <-newTxPoolHeadChan - require.Equal(t, newHead.Head.Hash(), common.Hash(blk.ID())) - - // Verify that the constructed block only has the whitelisted tx - block := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - - txs := block.Transactions() - - if txs.Len() != 1 { - t.Fatalf("Expected number of txs to be %d, but found %d", 1, txs.Len()) + if newHead.Head.Hash() != common.Hash(blk.ID()) { + t.Fatalf("Expected new block to match") } - require.Equal(t, signedTx0.Hash(), txs[0].Hash()) - - vm.clock.Set(reenableAllowlistTime.Add(time.Hour)) - - // Re-Submit a successful transaction - tx0 = types.NewTransaction(uint64(1), testEthAddrs[0], big.NewInt(1), 21000, big.NewInt(testMinGasPrice), nil) - signedTx0, err = types.SignTx(tx0, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[0]) - require.NoError(t, err) - - errs = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx0}) - require.NoError(t, errs[0]) - - // accept block to trigger upgrade - blk = issueAndAccept(t, issuer, vm) - newHead = <-newTxPoolHeadChan - require.Equal(t, newHead.Head.Hash(), common.Hash(blk.ID())) - block = blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - - blkState, err := vm.blockChain.StateAt(block.Root()) - require.NoError(t, err) - - // Check that address 0 is admin and address 1 is manager - role = txallowlist.GetTxAllowListStatus(blkState, testEthAddrs[0]) - require.Equal(t, allowlist.AdminRole, role) - role = txallowlist.GetTxAllowListStatus(blkState, managerAddress) - require.Equal(t, allowlist.ManagerRole, role) - - vm.clock.Set(vm.clock.Time().Add(2 * time.Second)) // add 2 seconds for gas fee to adjust - // Submit a successful transaction, should not throw an error because manager is activated - tx3 := types.NewTransaction(uint64(0), managerAddress, big.NewInt(1), 21000, big.NewInt(testMinGasPrice), nil) - signedTx3, err := types.SignTx(tx3, types.NewEIP155Signer(vm.chainConfig.ChainID), managerKey) - require.NoError(t, err) - - vm.clock.Set(vm.clock.Time().Add(2 * time.Second)) // add 2 seconds for gas fee to adjust - errs = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx3}) - require.NoError(t, errs[0]) - - blk = issueAndAccept(t, issuer, vm) - newHead = <-newTxPoolHeadChan - require.Equal(t, newHead.Head.Hash(), common.Hash(blk.ID())) - - // Verify that the constructed block only has the whitelisted tx - block = blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - txs = block.Transactions() - - require.Len(t, txs, 1) - require.Equal(t, signedTx3.Hash(), txs[0].Hash()) -} - -func TestVerifyManagerConfig(t *testing.T) { - genesis := &core.Genesis{} - require.NoError(t, genesis.UnmarshalJSON([]byte(genesisJSONDurango))) - - durangoTimestamp := time.Now().Add(10 * time.Hour) - genesis.Config.DurangoTimestamp = utils.TimeToNewUint64(durangoTimestamp) - // this manager role should not be activated because DurangoTimestamp is in the future - genesis.Config.GenesisPrecompiles = params.Precompiles{ - txallowlist.ConfigKey: txallowlist.NewConfig(utils.NewUint64(0), testEthAddrs[0:1], nil, []common.Address{testEthAddrs[1]}), + txs := make([]*types.Transaction, 10) + for i := 0; i < 10; i++ { + tx := types.NewTransaction(uint64(i), address, big.NewInt(10), 21000, big.NewInt(params.LaunchMinGasPrice*3), nil) + signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainID), key) + if err != nil { + t.Fatal(err) + } + txs[i] = signedTx } - - genesisJSON, err := genesis.MarshalJSON() - require.NoError(t, err) - - vm := &VM{} - ctx, dbManager, genesisBytes, issuer, _ := setupGenesis(t, string(genesisJSON)) - err = vm.Initialize( - context.Background(), - ctx, - dbManager, - genesisBytes, - []byte(""), - []byte(""), - issuer, - []*commonEng.Fx{}, - nil, - ) - require.ErrorIs(t, err, allowlist.ErrCannotAddManagersBeforeDurango) - - genesis = &core.Genesis{} - require.NoError(t, genesis.UnmarshalJSON([]byte(genesisJSONDurango))) - genesis.Config.DurangoTimestamp = utils.TimeToNewUint64(durangoTimestamp) - genesisJSON, err = genesis.MarshalJSON() - require.NoError(t, err) - // use an invalid upgrade now with managers set before Durango - upgradeConfig := ¶ms.UpgradeConfig{ - PrecompileUpgrades: []params.PrecompileUpgrade{ - { - Config: txallowlist.NewConfig(utils.TimeToNewUint64(durangoTimestamp.Add(-time.Second)), nil, nil, []common.Address{testEthAddrs[1]}), - }, - }, + errs := vm.txPool.Add(txs, false, false) + for i, err := range errs { + if err != nil { + t.Fatalf("Failed to add tx at index %d: %s", i, err) + } } - upgradeBytesJSON, err := json.Marshal(upgradeConfig) - require.NoError(t, err) - vm = &VM{} - ctx, dbManager, genesisBytes, issuer, _ = setupGenesis(t, string(genesisJSON)) - err = vm.Initialize( - context.Background(), - ctx, - dbManager, - genesisBytes, - upgradeBytesJSON, - []byte(""), - issuer, - []*commonEng.Fx{}, - nil, - ) - require.ErrorIs(t, err, allowlist.ErrCannotAddManagersBeforeDurango) -} + <-issuer -// Test that the tx allow list allows whitelisted transactions and blocks non-whitelisted addresses -// and the allowlist is removed after the precompile is disabled. -func TestTxAllowListDisablePrecompile(t *testing.T) { - // Setup chain params - genesis := &core.Genesis{} - if err := genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM)); err != nil { - t.Fatal(err) - } - enableAllowListTimestamp := upgrade.InitiallyActiveTime // enable at initially active time - genesis.Config.GenesisPrecompiles = params.Precompiles{ - txallowlist.ConfigKey: txallowlist.NewConfig(utils.TimeToNewUint64(enableAllowListTimestamp), testEthAddrs[0:1], nil, nil), - } - genesisJSON, err := genesis.MarshalJSON() + blk, err = vm.BuildBlock(context.Background()) if err != nil { t.Fatal(err) } - // arbitrary choice ahead of enableAllowListTimestamp - disableAllowListTimestamp := enableAllowListTimestamp.Add(10 * time.Hour) - // configure a network upgrade to remove the allowlist - upgradeConfig := fmt.Sprintf(` - { - "precompileUpgrades": [ - { - "txAllowListConfig": { - "blockTimestamp": %d, - "disable": true - } - } - ] + if err := blk.Verify(context.Background()); err != nil { + t.Fatal(err) } - `, disableAllowListTimestamp.Unix()) - - issuer, vm, _, _ := GenesisVM(t, true, string(genesisJSON), "", upgradeConfig) - vm.clock.Set(disableAllowListTimestamp) // upgrade takes effect after a block is issued, so we can set vm's clock here. - - defer func() { - if err := vm.Shutdown(context.Background()); err != nil { - t.Fatal(err) - } - }() - - newTxPoolHeadChan := make(chan core.NewTxPoolReorgEvent, 1) - vm.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan) - - genesisState, err := vm.blockChain.StateAt(vm.blockChain.Genesis().Root()) - if err != nil { + if err := blk.Accept(context.Background()); err != nil { t.Fatal(err) } - // Check that address 0 is whitelisted and address 1 is not - role := txallowlist.GetTxAllowListStatus(genesisState, testEthAddrs[0]) - if role != allowlist.AdminRole { - t.Fatalf("Expected allow list status to be set to admin: %s, but found: %s", allowlist.AdminRole, role) - } - role = txallowlist.GetTxAllowListStatus(genesisState, testEthAddrs[1]) - if role != allowlist.NoRole { - t.Fatalf("Expected allow list status to be set to no role: %s, but found: %s", allowlist.NoRole, role) + ethBlk = blk.(*chain.BlockWrapper).Block.(*Block).ethBlock + if ethBlk.BlockGasCost() == nil || ethBlk.BlockGasCost().Cmp(big.NewInt(100)) < 0 { + t.Fatalf("expected blockGasCost to be at least 100 but got %d", ethBlk.BlockGasCost()) } - - // Submit a successful transaction - tx0 := types.NewTransaction(uint64(0), testEthAddrs[0], big.NewInt(1), 21000, big.NewInt(testMinGasPrice), nil) - signedTx0, err := types.SignTx(tx0, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[0]) - require.NoError(t, err) - - errs := vm.txPool.AddRemotesSync([]*types.Transaction{signedTx0}) - if err := errs[0]; err != nil { - t.Fatalf("Failed to add tx at index: %s", err) + if ethBlk.ExtDataGasUsed() == nil || ethBlk.ExtDataGasUsed().Cmp(common.Big0) != 0 { + t.Fatalf("expected extDataGasUsed to be 0 but got %d", ethBlk.ExtDataGasUsed()) } - - // Submit a rejected transaction, should throw an error - tx1 := types.NewTransaction(uint64(0), testEthAddrs[1], big.NewInt(2), 21000, big.NewInt(testMinGasPrice), nil) - signedTx1, err := types.SignTx(tx1, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[1]) + minRequiredTip, err = dummy.MinRequiredTip(vm.chainConfig, ethBlk.Header()) if err != nil { t.Fatal(err) } - - errs = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx1}) - if err := errs[0]; !errors.Is(err, vmerrs.ErrSenderAddressNotAllowListed) { - t.Fatalf("expected ErrSenderAddressNotAllowListed, got: %s", err) - } - - blk := issueAndAccept(t, issuer, vm) - - // Verify that the constructed block only has the whitelisted tx - block := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - txs := block.Transactions() - if txs.Len() != 1 { - t.Fatalf("Expected number of txs to be %d, but found %d", 1, txs.Len()) - } - require.Equal(t, signedTx0.Hash(), txs[0].Hash()) - - // verify the issued block is after the network upgrade - require.GreaterOrEqual(t, int64(block.Timestamp()), disableAllowListTimestamp.Unix()) - - <-newTxPoolHeadChan // wait for new head in tx pool - - // retry the rejected Tx, which should now succeed - errs = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx1}) - if err := errs[0]; err != nil { - t.Fatalf("Failed to add tx at index: %s", err) - } - - vm.clock.Set(vm.clock.Time().Add(2 * time.Second)) // add 2 seconds for gas fee to adjust - blk = issueAndAccept(t, issuer, vm) - - // Verify that the constructed block only has the previously rejected tx - block = blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - txs = block.Transactions() - if txs.Len() != 1 { - t.Fatalf("Expected number of txs to be %d, but found %d", 1, txs.Len()) + if minRequiredTip == nil || minRequiredTip.Cmp(big.NewInt(0.05*params.GWei)) < 0 { + t.Fatalf("expected minRequiredTip to be at least 0.05 gwei but got %d", minRequiredTip) } - require.Equal(t, signedTx1.Hash(), txs[0].Hash()) -} -// Test that the fee manager changes fee configuration -func TestFeeManagerChangeFee(t *testing.T) { - // Setup chain params - genesis := &core.Genesis{} - if err := genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM)); err != nil { + lastAcceptedID, err := vm.LastAccepted(context.Background()) + if err != nil { t.Fatal(err) } - genesis.Config.GenesisPrecompiles = params.Precompiles{ - feemanager.ConfigKey: feemanager.NewConfig(utils.NewUint64(0), testEthAddrs[0:1], nil, nil, nil), + if lastAcceptedID != blk.ID() { + t.Fatalf("Expected last accepted blockID to be the accepted block: %s, but found %s", blk.ID(), lastAcceptedID) } - // set a lower fee config now - testLowFeeConfig := commontype.FeeConfig{ - GasLimit: big.NewInt(8_000_000), - TargetBlockRate: 5, // in seconds - - MinBaseFee: big.NewInt(5_000_000_000), - TargetGas: big.NewInt(18_000_000), - BaseFeeChangeDenominator: big.NewInt(3396), - - MinBlockGasCost: big.NewInt(0), - MaxBlockGasCost: big.NewInt(4_000_000), - BlockGasCostStep: big.NewInt(500_000), + // Confirm all txs are present + ethBlkTxs := vm.blockChain.GetBlockByNumber(2).Transactions() + for i, tx := range txs { + if len(ethBlkTxs) <= i { + t.Fatalf("missing transactions expected: %d but found: %d", len(txs), len(ethBlkTxs)) + } + if ethBlkTxs[i].Hash() != tx.Hash() { + t.Fatalf("expected tx at index %d to have hash: %x but has: %x", i, txs[i].Hash(), tx.Hash()) + } } +} - genesis.Config.FeeConfig = testLowFeeConfig - genesisJSON, err := genesis.MarshalJSON() - if err != nil { - t.Fatal(err) - } - issuer, vm, _, _ := GenesisVM(t, true, string(genesisJSON), "", "") +// This is a regression test to ensure that if two consecutive atomic transactions fail verification +// in onFinalizeAndAssemble it will not cause a panic due to calling RevertToSnapshot(revID) on the +// same revision ID twice. +func TestConsecutiveAtomicTransactionsRevertSnapshot(t *testing.T) { + issuer, vm, _, sharedMemory, _ := GenesisVM(t, true, genesisJSONApricotPhase1, "", "") defer func() { if err := vm.Shutdown(context.Background()); err != nil { @@ -2506,170 +3542,120 @@ func TestFeeManagerChangeFee(t *testing.T) { newTxPoolHeadChan := make(chan core.NewTxPoolReorgEvent, 1) vm.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan) - genesisState, err := vm.blockChain.StateAt(vm.blockChain.Genesis().Root()) - if err != nil { - t.Fatal(err) - } + // Create three conflicting import transactions + importTxs := createImportTxOptions(t, vm, sharedMemory) - // Check that address 0 is whitelisted and address 1 is not - role := feemanager.GetFeeManagerStatus(genesisState, testEthAddrs[0]) - if role != allowlist.AdminRole { - t.Fatalf("Expected fee manager list status to be set to admin: %s, but found: %s", allowlist.AdminRole, role) - } - role = feemanager.GetFeeManagerStatus(genesisState, testEthAddrs[1]) - if role != allowlist.NoRole { - t.Fatalf("Expected fee manager list status to be set to no role: %s, but found: %s", allowlist.NoRole, role) + // Issue the first import transaction, build, and accept the block. + if err := vm.mempool.AddLocalTx(importTxs[0]); err != nil { + t.Fatal(err) } - // Contract is initialized but no preconfig is given, reader should return genesis fee config - feeConfig, lastChangedAt, err := vm.blockChain.GetFeeConfigAt(vm.blockChain.Genesis().Header()) - require.NoError(t, err) - require.EqualValues(t, feeConfig, testLowFeeConfig) - require.Zero(t, vm.blockChain.CurrentBlock().Number.Cmp(lastChangedAt)) - // set a different fee config now - testHighFeeConfig := testLowFeeConfig - testHighFeeConfig.MinBaseFee = big.NewInt(28_000_000_000) + <-issuer - data, err := feemanager.PackSetFeeConfig(testHighFeeConfig) - require.NoError(t, err) + blk, err := vm.BuildBlock(context.Background()) + if err != nil { + t.Fatal(err) + } - tx := types.NewTx(&types.DynamicFeeTx{ - ChainID: genesis.Config.ChainID, - Nonce: uint64(0), - To: &feemanager.ContractAddress, - Gas: testLowFeeConfig.GasLimit.Uint64(), - Value: common.Big0, - GasFeeCap: testLowFeeConfig.MinBaseFee, // give low fee, it should work since we still haven't applied high fees - GasTipCap: common.Big0, - Data: data, - }) + if err := blk.Verify(context.Background()); err != nil { + t.Fatal(err) + } - signedTx, err := types.SignTx(tx, types.LatestSigner(genesis.Config), testKeys[0]) - if err != nil { + if err := vm.SetPreference(context.Background(), blk.ID()); err != nil { t.Fatal(err) } - errs := vm.txPool.AddRemotesSync([]*types.Transaction{signedTx}) - if err := errs[0]; err != nil { - t.Fatalf("Failed to add tx at index: %s", err) + if err := blk.Accept(context.Background()); err != nil { + t.Fatal(err) } - blk := issueAndAccept(t, issuer, vm) newHead := <-newTxPoolHeadChan if newHead.Head.Hash() != common.Hash(blk.ID()) { t.Fatalf("Expected new block to match") } - block := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - - // Contract is initialized but no state is given, reader should return genesis fee config - feeConfig, lastChangedAt, err = vm.blockChain.GetFeeConfigAt(block.Header()) - require.NoError(t, err) - require.EqualValues(t, testHighFeeConfig, feeConfig) - require.EqualValues(t, vm.blockChain.CurrentBlock().Number, lastChangedAt) - - // should fail, with same params since fee is higher now - tx2 := types.NewTx(&types.DynamicFeeTx{ - ChainID: genesis.Config.ChainID, - Nonce: uint64(1), - To: &feemanager.ContractAddress, - Gas: genesis.Config.FeeConfig.GasLimit.Uint64(), - Value: common.Big0, - GasFeeCap: testLowFeeConfig.MinBaseFee, // this is too low for applied config, should fail - GasTipCap: common.Big0, - Data: data, - }) + // Add the two conflicting transactions directly to the mempool, so that two consecutive transactions + // will fail verification when build block is called. + vm.mempool.AddTx(importTxs[1]) + vm.mempool.AddTx(importTxs[2]) - signedTx2, err := types.SignTx(tx2, types.LatestSigner(genesis.Config), testKeys[0]) - if err != nil { - t.Fatal(err) + if _, err := vm.BuildBlock(context.Background()); err == nil { + t.Fatal("Expected build block to fail due to empty block") } - - err = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx2})[0] - require.ErrorIs(t, err, txpool.ErrUnderpriced) } -// Test Allow Fee Recipients is disabled and, etherbase must be blackhole address -func TestAllowFeeRecipientDisabled(t *testing.T) { - genesis := &core.Genesis{} - if err := genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM)); err != nil { - t.Fatal(err) - } - genesis.Config.AllowFeeRecipients = false // set to false initially - genesisJSON, err := genesis.MarshalJSON() +func TestAtomicTxBuildBlockDropsConflicts(t *testing.T) { + importAmount := uint64(10000000) + issuer, vm, _, _, _ := GenesisVMWithUTXOs(t, true, genesisJSONApricotPhase5, "", "", map[ids.ShortID]uint64{ + testShortIDAddrs[0]: importAmount, + testShortIDAddrs[1]: importAmount, + testShortIDAddrs[2]: importAmount, + }) + conflictKey, err := accountKeystore.NewKey(rand.Reader) if err != nil { t.Fatal(err) } - issuer, vm, _, _ := GenesisVM(t, true, string(genesisJSON), "", "") - - vm.miner.SetEtherbase(common.HexToAddress("0x0123456789")) // set non-blackhole address by force defer func() { if err := vm.Shutdown(context.Background()); err != nil { t.Fatal(err) } }() - newTxPoolHeadChan := make(chan core.NewTxPoolReorgEvent, 1) - vm.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan) - - tx := types.NewTransaction(uint64(0), testEthAddrs[1], new(big.Int).Mul(firstTxAmount, big.NewInt(4)), 21000, big.NewInt(testMinGasPrice*3), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[0]) - if err != nil { - t.Fatal(err) - } - - txErrors := vm.txPool.AddRemotesSync([]*types.Transaction{signedTx}) - for i, err := range txErrors { + // Create a conflict set for each pair of transactions + conflictSets := make([]set.Set[ids.ID], len(testKeys)) + for index, key := range testKeys { + importTx, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[index], initialBaseFee, []*secp256k1.PrivateKey{key}) if err != nil { - t.Fatalf("Failed to add tx at index %d: %s", i, err) + t.Fatal(err) + } + if err := vm.mempool.AddLocalTx(importTx); err != nil { + t.Fatal(err) } + conflictSets[index].Add(importTx.ID()) + conflictTx, err := vm.newImportTx(vm.ctx.XChainID, conflictKey.Address, initialBaseFee, []*secp256k1.PrivateKey{key}) + if err != nil { + t.Fatal(err) + } + if err := vm.mempool.AddLocalTx(conflictTx); err == nil { + t.Fatal("should conflict with the utxoSet in the mempool") + } + // force add the tx + vm.mempool.ForceAddTx(conflictTx) + conflictSets[index].Add(conflictTx.ID()) } - <-issuer - + // Note: this only checks the path through OnFinalizeAndAssemble, we should make sure to add a test + // that verifies blocks received from the network will also fail verification blk, err := vm.BuildBlock(context.Background()) - require.NoError(t, err) // this won't return an error since miner will set the etherbase to blackhole address - - ethBlock := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - require.Equal(t, constants.BlackholeAddr, ethBlock.Coinbase()) - - // Create empty block from blk - internalBlk := blk.(*chain.BlockWrapper).Block.(*Block) - modifiedHeader := types.CopyHeader(internalBlk.ethBlock.Header()) - modifiedHeader.Coinbase = common.HexToAddress("0x0123456789") // set non-blackhole address by force - modifiedBlock := types.NewBlock( - modifiedHeader, - internalBlk.ethBlock.Transactions(), - nil, - nil, - trie.NewStackTrie(nil), - ) - - modifiedBlk := vm.newBlock(modifiedBlock) + if err != nil { + t.Fatal(err) + } + atomicTxs := blk.(*chain.BlockWrapper).Block.(*Block).atomicTxs + assert.True(t, len(atomicTxs) == len(testKeys), "Conflict transactions should be out of the batch") + atomicTxIDs := set.Set[ids.ID]{} + for _, tx := range atomicTxs { + atomicTxIDs.Add(tx.ID()) + } - require.ErrorIs(t, modifiedBlk.Verify(context.Background()), vmerrs.ErrInvalidCoinbase) -} + // Check that removing the txIDs actually included in the block from each conflict set + // leaves one item remaining for each conflict set ie. only one tx from each conflict set + // has been included in the block. + for _, conflictSet := range conflictSets { + conflictSet.Difference(atomicTxIDs) + assert.Equal(t, 1, conflictSet.Len()) + } -func TestAllowFeeRecipientEnabled(t *testing.T) { - genesis := &core.Genesis{} - if err := genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM)); err != nil { + if err := blk.Verify(context.Background()); err != nil { t.Fatal(err) } - genesis.Config.AllowFeeRecipients = true - genesisJSON, err := genesis.MarshalJSON() - if err != nil { + if err := blk.Accept(context.Background()); err != nil { t.Fatal(err) } +} - etherBase := common.HexToAddress("0x0123456789") - c := Config{} - c.SetDefaults() - c.FeeRecipient = etherBase.String() - configJSON, err := json.Marshal(c) - if err != nil { - t.Fatal(err) - } - issuer, vm, _, _ := GenesisVM(t, true, string(genesisJSON), string(configJSON), "") +func TestBuildBlockDoesNotExceedAtomicGasLimit(t *testing.T) { + importAmount := uint64(10000000) + issuer, vm, _, sharedMemory, _ := GenesisVM(t, true, genesisJSONApricotPhase5, "", "") defer func() { if err := vm.Shutdown(context.Background()); err != nil { @@ -2677,367 +3663,157 @@ func TestAllowFeeRecipientEnabled(t *testing.T) { } }() - newTxPoolHeadChan := make(chan core.NewTxPoolReorgEvent, 1) - vm.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan) + kc := secp256k1fx.NewKeychain() + kc.Add(testKeys[0]) + txID, err := ids.ToID(hashing.ComputeHash256(testShortIDAddrs[0][:])) + assert.NoError(t, err) - tx := types.NewTransaction(uint64(0), testEthAddrs[1], new(big.Int).Mul(firstTxAmount, big.NewInt(4)), 21000, big.NewInt(testMinGasPrice*3), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[0]) - if err != nil { - t.Fatal(err) - } + mempoolTxs := 200 + for i := 0; i < mempoolTxs; i++ { + utxo, err := addUTXO(sharedMemory, vm.ctx, txID, uint32(i), vm.ctx.AVAXAssetID, importAmount, testShortIDAddrs[0]) + assert.NoError(t, err) - txErrors := vm.txPool.AddRemotesSync([]*types.Transaction{signedTx}) - for i, err := range txErrors { + importTx, err := vm.newImportTxWithUTXOs(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, kc, []*avax.UTXO{utxo}) if err != nil { - t.Fatalf("Failed to add tx at index %d: %s", i, err) + t.Fatal(err) + } + if err := vm.mempool.AddLocalTx(importTx); err != nil { + t.Fatal(err) } } - blk := issueAndAccept(t, issuer, vm) - newHead := <-newTxPoolHeadChan - if newHead.Head.Hash() != common.Hash(blk.ID()) { - t.Fatalf("Expected new block to match") - } - ethBlock := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - require.Equal(t, etherBase, ethBlock.Coinbase()) - // Verify that etherBase has received fees - blkState, err := vm.blockChain.StateAt(ethBlock.Root()) + <-issuer + blk, err := vm.BuildBlock(context.Background()) if err != nil { t.Fatal(err) } - balance := blkState.GetBalance(etherBase) - require.Equal(t, 1, balance.Cmp(common.Big0)) -} - -func TestRewardManagerPrecompileSetRewardAddress(t *testing.T) { - genesis := &core.Genesis{} - require.NoError(t, genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM))) + atomicTxs := blk.(*chain.BlockWrapper).Block.(*Block).atomicTxs - genesis.Config.GenesisPrecompiles = params.Precompiles{ - rewardmanager.ConfigKey: rewardmanager.NewConfig(utils.NewUint64(0), testEthAddrs[0:1], nil, nil, nil), + // Need to ensure that not all of the transactions in the mempool are included in the block. + // This ensures that we hit the atomic gas limit while building the block before we hit the + // upper limit on the size of the codec for marshalling the atomic transactions. + if len(atomicTxs) >= mempoolTxs { + t.Fatalf("Expected number of atomic transactions included in the block (%d) to be less than the number of transactions added to the mempool (%d)", len(atomicTxs), mempoolTxs) } - genesis.Config.AllowFeeRecipients = true // enable this in genesis to test if this is recognized by the reward manager - genesisJSON, err := genesis.MarshalJSON() - require.NoError(t, err) - - etherBase := common.HexToAddress("0x0123456789") // give custom ether base - c := Config{} - c.SetDefaults() - c.FeeRecipient = etherBase.String() - configJSON, err := json.Marshal(c) - require.NoError(t, err) - - // arbitrary choice ahead of enableAllowListTimestamp - // configure a network upgrade to remove the reward manager - disableTime := time.Now().Add(10 * time.Hour) - - // configure a network upgrade to remove the allowlist - upgradeConfig := fmt.Sprintf(` - { - "precompileUpgrades": [ - { - "rewardManagerConfig": { - "blockTimestamp": %d, - "disable": true - } - } - ] - } - `, disableTime.Unix()) +} - issuer, vm, _, _ := GenesisVM(t, true, string(genesisJSON), string(configJSON), upgradeConfig) +func TestExtraStateChangeAtomicGasLimitExceeded(t *testing.T) { + importAmount := uint64(10000000) + // We create two VMs one in ApriotPhase4 and one in ApricotPhase5, so that we can construct a block + // containing a large enough atomic transaction that it will exceed the atomic gas limit in + // ApricotPhase5. + issuer, vm1, _, sharedMemory1, _ := GenesisVM(t, true, genesisJSONApricotPhase4, "", "") + _, vm2, _, sharedMemory2, _ := GenesisVM(t, true, genesisJSONApricotPhase5, "", "") defer func() { - err := vm.Shutdown(context.Background()) - require.NoError(t, err) + if err := vm1.Shutdown(context.Background()); err != nil { + t.Fatal(err) + } + if err := vm2.Shutdown(context.Background()); err != nil { + t.Fatal(err) + } }() - newTxPoolHeadChan := make(chan core.NewTxPoolReorgEvent, 1) - vm.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan) - - testAddr := common.HexToAddress("0x9999991111") - data, err := rewardmanager.PackSetRewardAddress(testAddr) - require.NoError(t, err) - - gas := 21000 + 240 + rewardmanager.SetRewardAddressGasCost + rewardmanager.RewardAddressChangedEventGasCost // 21000 for tx, 240 for tx data - - tx := types.NewTransaction(uint64(0), rewardmanager.ContractAddress, big.NewInt(1), gas, big.NewInt(testMinGasPrice), data) - - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[0]) - require.NoError(t, err) - - txErrors := vm.txPool.AddRemotesSync([]*types.Transaction{signedTx}) - for _, err := range txErrors { - require.NoError(t, err) - } - - blk := issueAndAccept(t, issuer, vm) - newHead := <-newTxPoolHeadChan - require.Equal(t, newHead.Head.Hash(), common.Hash(blk.ID())) - ethBlock := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - require.Equal(t, etherBase, ethBlock.Coinbase()) // reward address is activated at this block so this is fine + kc := secp256k1fx.NewKeychain() + kc.Add(testKeys[0]) + txID, err := ids.ToID(hashing.ComputeHash256(testShortIDAddrs[0][:])) + assert.NoError(t, err) - tx1 := types.NewTransaction(uint64(0), testEthAddrs[0], big.NewInt(2), 21000, big.NewInt(testMinGasPrice*3), nil) - signedTx1, err := types.SignTx(tx1, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[1]) - require.NoError(t, err) + // Add enough UTXOs, such that the created import transaction will attempt to consume more gas than allowed + // in ApricotPhase5. + for i := 0; i < 100; i++ { + _, err := addUTXO(sharedMemory1, vm1.ctx, txID, uint32(i), vm1.ctx.AVAXAssetID, importAmount, testShortIDAddrs[0]) + assert.NoError(t, err) - txErrors = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx1}) - for _, err := range txErrors { - require.NoError(t, err) + _, err = addUTXO(sharedMemory2, vm2.ctx, txID, uint32(i), vm2.ctx.AVAXAssetID, importAmount, testShortIDAddrs[0]) + assert.NoError(t, err) } - blk = issueAndAccept(t, issuer, vm) - newHead = <-newTxPoolHeadChan - require.Equal(t, newHead.Head.Hash(), common.Hash(blk.ID())) - ethBlock = blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - require.Equal(t, testAddr, ethBlock.Coinbase()) // reward address was activated at previous block - // Verify that etherBase has received fees - blkState, err := vm.blockChain.StateAt(ethBlock.Root()) - require.NoError(t, err) - - balance := blkState.GetBalance(testAddr) - require.Equal(t, 1, balance.Cmp(common.Big0)) - - // Test Case: Disable reward manager - // This should revert back to enabling fee recipients - previousBalance := blkState.GetBalance(etherBase) - - // issue a new block to trigger the upgrade - vm.clock.Set(disableTime) // upgrade takes effect after a block is issued, so we can set vm's clock here. - tx2 := types.NewTransaction(uint64(1), testEthAddrs[0], big.NewInt(2), 21000, big.NewInt(testMinGasPrice), nil) - signedTx2, err := types.SignTx(tx2, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[1]) - require.NoError(t, err) - - txErrors = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx2}) - for _, err := range txErrors { - require.NoError(t, err) + // Double the initial base fee used when estimating the cost of this transaction to ensure that when it is + // used in ApricotPhase5 it still pays a sufficient fee with the fixed fee per atomic transaction. + importTx, err := vm1.newImportTx(vm1.ctx.XChainID, testEthAddrs[0], new(big.Int).Mul(common.Big2, initialBaseFee), []*secp256k1.PrivateKey{testKeys[0]}) + if err != nil { + t.Fatal(err) } - - blk = issueAndAccept(t, issuer, vm) - newHead = <-newTxPoolHeadChan - require.Equal(t, newHead.Head.Hash(), common.Hash(blk.ID())) - ethBlock = blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - // Reward manager deactivated at this block, so we expect the parent state - // to determine the coinbase for this block before full deactivation in the - // next block. - require.Equal(t, testAddr, ethBlock.Coinbase()) - require.GreaterOrEqual(t, int64(ethBlock.Timestamp()), disableTime.Unix()) - - vm.clock.Set(vm.clock.Time().Add(3 * time.Hour)) // let time pass to decrease gas price - // issue another block to verify that the reward manager is disabled - tx2 = types.NewTransaction(uint64(2), testEthAddrs[0], big.NewInt(2), 21000, big.NewInt(testMinGasPrice), nil) - signedTx2, err = types.SignTx(tx2, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[1]) - require.NoError(t, err) - - txErrors = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx2}) - for _, err := range txErrors { - require.NoError(t, err) + if err := vm1.mempool.ForceAddTx(importTx); err != nil { + t.Fatal(err) } - blk = issueAndAccept(t, issuer, vm) - newHead = <-newTxPoolHeadChan - require.Equal(t, newHead.Head.Hash(), common.Hash(blk.ID())) - ethBlock = blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - // reward manager was disabled at previous block - // so this block should revert back to enabling fee recipients - require.Equal(t, etherBase, ethBlock.Coinbase()) - require.GreaterOrEqual(t, int64(ethBlock.Timestamp()), disableTime.Unix()) - - // Verify that Blackhole has received fees - blkState, err = vm.blockChain.StateAt(ethBlock.Root()) - require.NoError(t, err) - - balance = blkState.GetBalance(etherBase) - require.Equal(t, 1, balance.Cmp(previousBalance)) -} - -func TestRewardManagerPrecompileAllowFeeRecipients(t *testing.T) { - genesis := &core.Genesis{} - require.NoError(t, genesis.UnmarshalJSON([]byte(genesisJSONSubnetEVM))) - - genesis.Config.GenesisPrecompiles = params.Precompiles{ - rewardmanager.ConfigKey: rewardmanager.NewConfig(utils.NewUint64(0), testEthAddrs[0:1], nil, nil, nil), + <-issuer + blk1, err := vm1.BuildBlock(context.Background()) + if err != nil { + t.Fatal(err) } - genesis.Config.AllowFeeRecipients = false // disable this in genesis - genesisJSON, err := genesis.MarshalJSON() - require.NoError(t, err) - etherBase := common.HexToAddress("0x0123456789") // give custom ether base - c := Config{} - c.SetDefaults() - c.FeeRecipient = etherBase.String() - configJSON, err := json.Marshal(c) - require.NoError(t, err) - // configure a network upgrade to remove the reward manager - // arbitrary choice ahead of enableAllowListTimestamp - // configure a network upgrade to remove the reward manager - disableTime := time.Now().Add(10 * time.Hour) - - // configure a network upgrade to remove the allowlist - upgradeConfig := fmt.Sprintf(` - { - "precompileUpgrades": [ - { - "rewardManagerConfig": { - "blockTimestamp": %d, - "disable": true - } - } - ] - } - `, disableTime.Unix()) - issuer, vm, _, _ := GenesisVM(t, true, string(genesisJSON), string(configJSON), upgradeConfig) - - defer func() { - require.NoError(t, vm.Shutdown(context.Background())) - }() - - newTxPoolHeadChan := make(chan core.NewTxPoolReorgEvent, 1) - vm.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan) - - data, err := rewardmanager.PackAllowFeeRecipients() - require.NoError(t, err) - - gas := 21000 + 240 + rewardmanager.SetRewardAddressGasCost + rewardmanager.RewardAddressChangedEventGasCost // 21000 for tx, 240 for tx data - - tx := types.NewTransaction(uint64(0), rewardmanager.ContractAddress, big.NewInt(1), gas, big.NewInt(testMinGasPrice), data) - - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[0]) - require.NoError(t, err) - - txErrors := vm.txPool.AddRemotesSync([]*types.Transaction{signedTx}) - for _, err := range txErrors { - require.NoError(t, err) + if err := blk1.Verify(context.Background()); err != nil { + t.Fatal(err) } - blk := issueAndAccept(t, issuer, vm) - newHead := <-newTxPoolHeadChan - require.Equal(t, newHead.Head.Hash(), common.Hash(blk.ID())) - ethBlock := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - require.Equal(t, constants.BlackholeAddr, ethBlock.Coinbase()) // reward address is activated at this block so this is fine - - tx1 := types.NewTransaction(uint64(0), testEthAddrs[0], big.NewInt(2), 21000, big.NewInt(testMinGasPrice*3), nil) - signedTx1, err := types.SignTx(tx1, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[1]) - require.NoError(t, err) + validEthBlock := blk1.(*chain.BlockWrapper).Block.(*Block).ethBlock - txErrors = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx1}) - for _, err := range txErrors { - require.NoError(t, err) + extraData, err := vm2.codec.Marshal(codecVersion, []*Tx{importTx}) + if err != nil { + t.Fatal(err) } - blk = issueAndAccept(t, issuer, vm) - newHead = <-newTxPoolHeadChan - require.Equal(t, newHead.Head.Hash(), common.Hash(blk.ID())) - ethBlock = blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - require.Equal(t, etherBase, ethBlock.Coinbase()) // reward address was activated at previous block - // Verify that etherBase has received fees - blkState, err := vm.blockChain.StateAt(ethBlock.Root()) - require.NoError(t, err) - - balance := blkState.GetBalance(etherBase) - require.Equal(t, 1, balance.Cmp(common.Big0)) - - // Test Case: Disable reward manager - // This should revert back to burning fees - previousBalance := blkState.GetBalance(constants.BlackholeAddr) - - vm.clock.Set(disableTime) // upgrade takes effect after a block is issued, so we can set vm's clock here. - tx2 := types.NewTransaction(uint64(1), testEthAddrs[0], big.NewInt(2), 21000, big.NewInt(testMinGasPrice), nil) - signedTx2, err := types.SignTx(tx2, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[1]) - require.NoError(t, err) + // Construct the new block with the extra data in the new format (slice of atomic transactions). + ethBlk2 := types.NewBlockWithExtData( + types.CopyHeader(validEthBlock.Header()), + nil, + nil, + nil, + new(trie.Trie), + extraData, + true, + ) - txErrors = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx2}) - for _, err := range txErrors { - require.NoError(t, err) + state, err := vm2.blockChain.State() + if err != nil { + t.Fatal(err) } - blk = issueAndAccept(t, issuer, vm) - newHead = <-newTxPoolHeadChan - require.Equal(t, newHead.Head.Hash(), common.Hash(blk.ID())) - ethBlock = blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - require.Equal(t, etherBase, ethBlock.Coinbase()) // reward address was activated at previous block - require.GreaterOrEqual(t, int64(ethBlock.Timestamp()), disableTime.Unix()) - - vm.clock.Set(vm.clock.Time().Add(3 * time.Hour)) // let time pass so that gas price is reduced - tx2 = types.NewTransaction(uint64(2), testEthAddrs[0], big.NewInt(2), 21000, big.NewInt(testMinGasPrice), nil) - signedTx2, err = types.SignTx(tx2, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[1]) - require.NoError(t, err) - - txErrors = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx2}) - for _, err := range txErrors { - require.NoError(t, err) + // Hack: test [onExtraStateChange] directly to ensure it catches the atomic gas limit error correctly. + if _, _, err := vm2.onExtraStateChange(ethBlk2, state); err == nil || !strings.Contains(err.Error(), "exceeds atomic gas limit") { + t.Fatalf("Expected block to fail verification due to exceeded atomic gas limit, but found error: %v", err) } - - blk = issueAndAccept(t, issuer, vm) - newHead = <-newTxPoolHeadChan - require.Equal(t, newHead.Head.Hash(), common.Hash(blk.ID())) - ethBlock = blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - require.Equal(t, constants.BlackholeAddr, ethBlock.Coinbase()) // reward address was activated at previous block - require.Greater(t, int64(ethBlock.Timestamp()), disableTime.Unix()) - - // Verify that Blackhole has received fees - blkState, err = vm.blockChain.StateAt(ethBlock.Root()) - require.NoError(t, err) - - balance = blkState.GetBalance(constants.BlackholeAddr) - require.Equal(t, 1, balance.Cmp(previousBalance)) } func TestSkipChainConfigCheckCompatible(t *testing.T) { - // The most recent network upgrade in Subnet-EVM is SubnetEVM itself, which cannot be disabled for this test since it results in - // disabling dynamic fees and causes a panic since some code assumes that this is enabled. - // TODO update this test when there is a future network upgrade that can be skipped in the config. - t.Skip("no skippable upgrades") // Hack: registering metrics uses global variables, so we need to disable metrics here so that we can initialize the VM twice. metrics.Enabled = false defer func() { metrics.Enabled = true }() - issuer, vm, dbManager, appSender := GenesisVM(t, true, genesisJSONPreSubnetEVM, `{"pruning-enabled":true}`, "") - - defer func() { - if err := vm.Shutdown(context.Background()); err != nil { - t.Fatal(err) - } - }() - - newTxPoolHeadChan := make(chan core.NewTxPoolReorgEvent, 1) - vm.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan) - - key, err := accountKeystore.NewKey(rand.Reader) - if err != nil { - t.Fatal(err) - } + importAmount := uint64(50000000) + issuer, vm, dbManager, _, appSender := GenesisVMWithUTXOs(t, true, genesisJSONApricotPhase1, "", "", map[ids.ShortID]uint64{ + testShortIDAddrs[0]: importAmount, + }) + defer func() { require.NoError(t, vm.Shutdown(context.Background())) }() - tx := types.NewTransaction(uint64(0), key.Address, firstTxAmount, 21000, big.NewInt(testMinGasPrice), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[0]) - if err != nil { - t.Fatal(err) - } - errs := vm.txPool.AddRemotesSync([]*types.Transaction{signedTx}) - for i, err := range errs { - if err != nil { - t.Fatalf("Failed to add tx at index %d: %s", i, err) - } - } + // Since rewinding is permitted for last accepted height of 0, we must + // accept one block to test the SkipUpgradeCheck functionality. + importTx, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) + require.NoError(t, err) + require.NoError(t, vm.mempool.AddLocalTx(importTx)) + <-issuer - blk := issueAndAccept(t, issuer, vm) - newHead := <-newTxPoolHeadChan - if newHead.Head.Hash() != common.Hash(blk.ID()) { - t.Fatalf("Expected new block to match") - } + blk, err := vm.BuildBlock(context.Background()) + require.NoError(t, err) + require.NoError(t, blk.Verify(context.Background())) + require.NoError(t, vm.SetPreference(context.Background(), blk.ID())) + require.NoError(t, blk.Accept(context.Background())) reinitVM := &VM{} // use the block's timestamp instead of 0 since rewind to genesis // is hardcoded to be allowed in core/genesis.go. genesisWithUpgrade := &core.Genesis{} - require.NoError(t, json.Unmarshal([]byte(genesisJSONPreSubnetEVM), genesisWithUpgrade)) - genesisWithUpgrade.Config.SubnetEVMTimestamp = utils.TimeToNewUint64(blk.Timestamp()) + require.NoError(t, json.Unmarshal([]byte(genesisJSONApricotPhase1), genesisWithUpgrade)) + genesisWithUpgrade.Config.ApricotPhase2BlockTimestamp = utils.TimeToNewUint64(blk.Timestamp()) genesisWithUpgradeBytes, err := json.Marshal(genesisWithUpgrade) require.NoError(t, err) // this will not be allowed err = reinitVM.Initialize(context.Background(), vm.ctx, dbManager, genesisWithUpgradeBytes, []byte{}, []byte{}, issuer, []*commonEng.Fx{}, appSender) - require.ErrorContains(t, err, "mismatching SubnetEVM fork block timestamp in database") + require.ErrorContains(t, err, "mismatching ApricotPhase2 fork block timestamp in database") // try again with skip-upgrade-check config := []byte(`{"skip-upgrade-check": true}`) @@ -3074,21 +3850,21 @@ func TestParentBeaconRootBlock(t *testing.T) { expectedError: false, }, { - name: "non-empty parent beacon root in E-Upgrade (Cancun)", - genesisJSON: genesisJSONEtna, + name: "non-empty parent beacon root in Cancun", + genesisJSON: genesisJSONCancun, beaconRoot: &common.Hash{0x01}, expectedError: true, errString: "expected empty hash", }, { - name: "empty parent beacon root in E-Upgrade (Cancun)", - genesisJSON: genesisJSONEtna, + name: "empty parent beacon root in Cancun", + genesisJSON: genesisJSONCancun, beaconRoot: &common.Hash{}, expectedError: false, }, { - name: "nil parent beacon root in E-Upgrade (Cancun)", - genesisJSON: genesisJSONEtna, + name: "nil parent beacon root in Cancun", + genesisJSON: genesisJSONCancun, beaconRoot: nil, expectedError: true, errString: "header is missing parentBeaconRoot", @@ -3097,7 +3873,10 @@ func TestParentBeaconRootBlock(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - issuer, vm, _, _ := GenesisVM(t, true, test.genesisJSON, "", "") + importAmount := uint64(1000000000) + issuer, vm, _, _, _ := GenesisVMWithUTXOs(t, true, test.genesisJSON, "", "", map[ids.ShortID]uint64{ + testShortIDAddrs[0]: importAmount, + }) defer func() { if err := vm.Shutdown(context.Background()); err != nil { @@ -3105,17 +3884,13 @@ func TestParentBeaconRootBlock(t *testing.T) { } }() - tx := types.NewTransaction(uint64(0), testEthAddrs[1], firstTxAmount, 21000, big.NewInt(testMinGasPrice), nil) - signedTx, err := types.SignTx(tx, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[0]) + importTx, err := vm.newImportTx(vm.ctx.XChainID, testEthAddrs[0], initialBaseFee, []*secp256k1.PrivateKey{testKeys[0]}) if err != nil { t.Fatal(err) } - txErrors := vm.txPool.AddRemotesSync([]*types.Transaction{signedTx}) - for i, err := range txErrors { - if err != nil { - t.Fatalf("Failed to add tx at index %d: %s", i, err) - } + if err := vm.mempool.AddLocalTx(importTx); err != nil { + t.Fatal(err) } <-issuer @@ -3129,9 +3904,20 @@ func TestParentBeaconRootBlock(t *testing.T) { ethBlock := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock header := types.CopyHeader(ethBlock.Header()) header.ParentBeaconRoot = test.beaconRoot - parentBeaconEthBlock := ethBlock.WithSeal(header) + parentBeaconEthBlock := types.NewBlockWithExtData( + header, + nil, + nil, + nil, + new(trie.Trie), + ethBlock.ExtData(), + false, + ) - parentBeaconBlock := vm.newBlock(parentBeaconEthBlock) + parentBeaconBlock, err := vm.newBlock(parentBeaconEthBlock) + if err != nil { + t.Fatal(err) + } errCheck := func(err error) { if test.expectedError { @@ -3152,3 +3938,69 @@ func TestParentBeaconRootBlock(t *testing.T) { }) } } + +func TestNoBlobsAllowed(t *testing.T) { + ctx := context.Background() + require := require.New(t) + + gspec := new(core.Genesis) + err := json.Unmarshal([]byte(genesisJSONCancun), gspec) + require.NoError(err) + + // Make one block with a single blob tx + signer := types.NewCancunSigner(gspec.Config.ChainID) + blockGen := func(_ int, b *core.BlockGen) { + b.SetCoinbase(constants.BlackholeAddr) + fee := big.NewInt(500) + fee.Add(fee, b.BaseFee()) + tx, err := types.SignTx(types.NewTx(&types.BlobTx{ + Nonce: 0, + GasTipCap: uint256.NewInt(1), + GasFeeCap: uint256.MustFromBig(fee), + Gas: params.TxGas, + To: testEthAddrs[0], + BlobFeeCap: uint256.NewInt(1), + BlobHashes: []common.Hash{{1}}, // This blob is expected to cause verification to fail + Value: new(uint256.Int), + }), signer, testKeys[0].ToECDSA()) + require.NoError(err) + b.AddTx(tx) + } + // FullFaker used to skip header verification so we can generate a block with blobs + _, blocks, _, err := core.GenerateChainWithGenesis(gspec, dummy.NewFullFaker(), 1, 10, blockGen) + require.NoError(err) + + // Create a VM with the genesis (will use header verification) + _, vm, _, _, _ := GenesisVM(t, true, genesisJSONCancun, "", "") + defer func() { require.NoError(vm.Shutdown(ctx)) }() + + // Verification should fail + vmBlock, err := vm.newBlock(blocks[0]) + require.NoError(err) + _, err = vm.ParseBlock(ctx, vmBlock.Bytes()) + require.ErrorContains(err, "blobs not enabled on avalanche networks") + err = vmBlock.Verify(ctx) + require.ErrorContains(err, "blobs not enabled on avalanche networks") +} + +func TestMinFeeSetAtEtna(t *testing.T) { + require := require.New(t) + now := time.Now() + etnaTime := uint64(now.Add(1 * time.Second).Unix()) + + genesis := genesisJSON( + activateEtna(params.TestEtnaChainConfig, etnaTime), + ) + clock := mockable.Clock{} + clock.Set(now) + + _, vm, _, _, _ := GenesisVMWithClock(t, false, genesis, "", "", clock) + initial := vm.txPool.MinFee() + require.Equal(params.ApricotPhase4MinBaseFee, initial.Int64()) + + require.Eventually( + func() bool { return params.EtnaMinBaseFee == vm.txPool.MinFee().Int64() }, + 5*time.Second, + 1*time.Second, + ) +} diff --git a/plugin/evm/vm_upgrade_bytes_test.go b/plugin/evm/vm_upgrade_bytes_test.go deleted file mode 100644 index 1960077647..0000000000 --- a/plugin/evm/vm_upgrade_bytes_test.go +++ /dev/null @@ -1,388 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package evm - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "math/big" - "testing" - "time" - - "github.com/ava-labs/avalanchego/snow" - commonEng "github.com/ava-labs/avalanchego/snow/engine/common" - "github.com/ava-labs/avalanchego/snow/engine/enginetest" - "github.com/ava-labs/avalanchego/upgrade" - "github.com/ava-labs/avalanchego/vms/components/chain" - "github.com/ava-labs/subnet-evm/core" - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/metrics" - "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist" - "github.com/ava-labs/subnet-evm/utils" - "github.com/ava-labs/subnet-evm/vmerrs" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/crypto" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -var ( - DefaultEtnaTime = uint64(upgrade.GetConfig(testNetworkID).EtnaTime.Unix()) -) - -func TestVMUpgradeBytesPrecompile(t *testing.T) { - // Make a TxAllowListConfig upgrade at genesis and convert it to JSON to apply as upgradeBytes. - enableAllowListTimestamp := upgrade.InitiallyActiveTime // enable at initial time - upgradeConfig := ¶ms.UpgradeConfig{ - PrecompileUpgrades: []params.PrecompileUpgrade{ - { - Config: txallowlist.NewConfig(utils.TimeToNewUint64(enableAllowListTimestamp), testEthAddrs[0:1], nil, nil), - }, - }, - } - upgradeBytesJSON, err := json.Marshal(upgradeConfig) - if err != nil { - t.Fatalf("could not marshal upgradeConfig to json: %s", err) - } - - // initialize the VM with these upgrade bytes - issuer, vm, dbManager, appSender := GenesisVM(t, true, genesisJSONSubnetEVM, "", string(upgradeBytesJSON)) - vm.clock.Set(enableAllowListTimestamp) - - // Submit a successful transaction - tx0 := types.NewTransaction(uint64(0), testEthAddrs[0], big.NewInt(1), 21000, big.NewInt(testMinGasPrice), nil) - signedTx0, err := types.SignTx(tx0, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[0]) - assert.NoError(t, err) - - errs := vm.txPool.AddRemotesSync([]*types.Transaction{signedTx0}) - if err := errs[0]; err != nil { - t.Fatalf("Failed to add tx at index: %s", err) - } - - // Submit a rejected transaction, should throw an error - tx1 := types.NewTransaction(uint64(0), testEthAddrs[1], big.NewInt(2), 21000, big.NewInt(testMinGasPrice), nil) - signedTx1, err := types.SignTx(tx1, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[1]) - if err != nil { - t.Fatal(err) - } - errs = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx1}) - if err := errs[0]; !errors.Is(err, vmerrs.ErrSenderAddressNotAllowListed) { - t.Fatalf("expected ErrSenderAddressNotAllowListed, got: %s", err) - } - - // shutdown the vm - if err := vm.Shutdown(context.Background()); err != nil { - t.Fatal(err) - } - - // prepare the new upgrade bytes to disable the TxAllowList - disableAllowListTimestamp := vm.clock.Time().Add(10 * time.Hour) // arbitrary choice - upgradeConfig.PrecompileUpgrades = append( - upgradeConfig.PrecompileUpgrades, - params.PrecompileUpgrade{ - Config: txallowlist.NewDisableConfig(utils.TimeToNewUint64(disableAllowListTimestamp)), - }, - ) - upgradeBytesJSON, err = json.Marshal(upgradeConfig) - if err != nil { - t.Fatalf("could not marshal upgradeConfig to json: %s", err) - } - - // restart the vm - // Hack: registering metrics uses global variables, so we need to disable metrics here so that we - // can initialize the VM twice. - metrics.Enabled = false - defer func() { - metrics.Enabled = true - }() - if err := vm.Initialize( - context.Background(), vm.ctx, dbManager, []byte(genesisJSONSubnetEVM), upgradeBytesJSON, []byte{}, issuer, []*commonEng.Fx{}, appSender, - ); err != nil { - t.Fatal(err) - } - defer func() { - if err := vm.Shutdown(context.Background()); err != nil { - t.Fatal(err) - } - }() - // Set the VM's state to NormalOp to initialize the tx pool. - if err := vm.SetState(context.Background(), snow.NormalOp); err != nil { - t.Fatal(err) - } - newTxPoolHeadChan := make(chan core.NewTxPoolReorgEvent, 1) - vm.txPool.SubscribeNewReorgEvent(newTxPoolHeadChan) - vm.clock.Set(disableAllowListTimestamp) - - // Make a block, previous rules still apply (TxAllowList is active) - // Submit a successful transaction - errs = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx0}) - if err := errs[0]; err != nil { - t.Fatalf("Failed to add tx at index: %s", err) - } - - // Submit a rejected transaction, should throw an error - errs = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx1}) - if err := errs[0]; !errors.Is(err, vmerrs.ErrSenderAddressNotAllowListed) { - t.Fatalf("expected ErrSenderAddressNotAllowListed, got: %s", err) - } - - blk := issueAndAccept(t, issuer, vm) - - // Verify that the constructed block only has the whitelisted tx - block := blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - txs := block.Transactions() - if txs.Len() != 1 { - t.Fatalf("Expected number of txs to be %d, but found %d", 1, txs.Len()) - } - assert.Equal(t, signedTx0.Hash(), txs[0].Hash()) - - // verify the issued block is after the network upgrade - assert.GreaterOrEqual(t, int64(block.Timestamp()), disableAllowListTimestamp.Unix()) - - <-newTxPoolHeadChan // wait for new head in tx pool - - // retry the rejected Tx, which should now succeed - errs = vm.txPool.AddRemotesSync([]*types.Transaction{signedTx1}) - if err := errs[0]; err != nil { - t.Fatalf("Failed to add tx at index: %s", err) - } - - vm.clock.Set(vm.clock.Time().Add(2 * time.Second)) // add 2 seconds for gas fee to adjust - blk = issueAndAccept(t, issuer, vm) - - // Verify that the constructed block only has the previously rejected tx - block = blk.(*chain.BlockWrapper).Block.(*Block).ethBlock - txs = block.Transactions() - if txs.Len() != 1 { - t.Fatalf("Expected number of txs to be %d, but found %d", 1, txs.Len()) - } - assert.Equal(t, signedTx1.Hash(), txs[0].Hash()) -} - -func TestNetworkUpgradesOverriden(t *testing.T) { - var genesis core.Genesis - if err := json.Unmarshal([]byte(genesisJSONPreSubnetEVM), &genesis); err != nil { - t.Fatalf("could not unmarshal genesis bytes: %s", err) - } - genesisBytes, err := json.Marshal(&genesis) - if err != nil { - t.Fatalf("could not unmarshal genesis bytes: %s", err) - } - - upgradeBytesJSON := `{ - "networkUpgradeOverrides": { - "subnetEVMTimestamp": 2, - "durangoTimestamp": 1607144402 - } - }` - - vm := &VM{} - ctx, dbManager, genesisBytes, issuer, _ := setupGenesis(t, string(genesisBytes)) - appSender := &enginetest.Sender{T: t} - appSender.CantSendAppGossip = true - appSender.SendAppGossipF = func(context.Context, commonEng.SendConfig, []byte) error { return nil } - err = vm.Initialize( - context.Background(), - ctx, - dbManager, - genesisBytes, - []byte(upgradeBytesJSON), - nil, - issuer, - []*commonEng.Fx{}, - appSender, - ) - require.NoError(t, err, "error initializing GenesisVM") - - require.NoError(t, vm.SetState(context.Background(), snow.Bootstrapping)) - require.NoError(t, vm.SetState(context.Background(), snow.NormalOp)) - - defer func() { - if err := vm.Shutdown(context.Background()); err != nil { - t.Fatal(err) - } - }() - - // verify upgrade overrides - require.False(t, vm.chainConfig.IsSubnetEVM(0)) - require.True(t, vm.chainConfig.IsSubnetEVM(2)) - require.False(t, vm.chainConfig.IsDurango(0)) - require.False(t, vm.chainConfig.IsDurango(uint64(upgrade.InitiallyActiveTime.Unix()))) - require.True(t, vm.chainConfig.IsDurango(1607144402)) -} - -func mustMarshal(t *testing.T, v interface{}) string { - b, err := json.Marshal(v) - require.NoError(t, err) - return string(b) -} - -func TestVMStateUpgrade(t *testing.T) { - // modify genesis to add a key to the state - genesis := &core.Genesis{} - err := json.Unmarshal([]byte(genesisJSONSubnetEVM), genesis) - require.NoError(t, err) - genesisAccount, ok := genesis.Alloc[testEthAddrs[0]] - require.True(t, ok) - storageKey := common.HexToHash("0x1234") - genesisAccount.Storage = map[common.Hash]common.Hash{storageKey: common.HexToHash("0x5555")} - genesisCode, err := hexutil.Decode("0xabcd") - require.NoError(t, err) - genesisAccount.Code = genesisCode - genesisAccount.Nonce = 2 // set to a non-zero value to test that it is preserved - genesis.Alloc[testEthAddrs[0]] = genesisAccount // have to assign this back to the map for changes to take effect. - genesisStr := mustMarshal(t, genesis) - - upgradedCodeStr := "0xdeadbeef" // this code will be applied during the upgrade - upgradedCode, err := hexutil.Decode(upgradedCodeStr) - // This modification will be applied to an existing account - genesisAccountUpgrade := ¶ms.StateUpgradeAccount{ - BalanceChange: (*math.HexOrDecimal256)(big.NewInt(100)), - Storage: map[common.Hash]common.Hash{storageKey: {}}, - Code: upgradedCode, - } - - // This modification will be applied to a new account - newAccount := common.Address{42} - require.NoError(t, err) - newAccountUpgrade := ¶ms.StateUpgradeAccount{ - BalanceChange: (*math.HexOrDecimal256)(big.NewInt(100)), - Storage: map[common.Hash]common.Hash{storageKey: common.HexToHash("0x6666")}, - Code: upgradedCode, - } - - upgradeTimestamp := upgrade.InitiallyActiveTime.Add(10 * time.Hour) - upgradeBytesJSON := fmt.Sprintf( - `{ - "stateUpgrades": [ - { - "blockTimestamp": %d, - "accounts": { - "%s": %s, - "%s": %s - } - } - ] - }`, - upgradeTimestamp.Unix(), - testEthAddrs[0].Hex(), - mustMarshal(t, genesisAccountUpgrade), - newAccount.Hex(), - mustMarshal(t, newAccountUpgrade), - ) - require.Contains(t, upgradeBytesJSON, upgradedCodeStr) - - // initialize the VM with these upgrade bytes - issuer, vm, _, _ := GenesisVM(t, true, genesisStr, "", upgradeBytesJSON) - defer func() { require.NoError(t, vm.Shutdown(context.Background())) }() - - // Verify the new account doesn't exist yet - genesisState, err := vm.blockChain.State() - require.NoError(t, err) - require.Equal(t, common.Big0, genesisState.GetBalance(newAccount)) - - // Advance the chain to the upgrade time - vm.clock.Set(upgradeTimestamp) - - // Submit a successful (unrelated) transaction, so we can build a block - // in this tx, testEthAddrs[1] sends 1 wei to itself. - tx0 := types.NewTransaction(uint64(0), testEthAddrs[1], big.NewInt(1), 21000, big.NewInt(testMinGasPrice), nil) - signedTx0, err := types.SignTx(tx0, types.NewEIP155Signer(vm.chainConfig.ChainID), testKeys[1]) - require.NoError(t, err) - - errs := vm.txPool.AddRemotesSync([]*types.Transaction{signedTx0}) - require.NoError(t, errs[0], "Failed to add tx") - - blk := issueAndAccept(t, issuer, vm) - require.NotNil(t, blk) - require.EqualValues(t, 1, blk.Height()) - - // Verify the state upgrade was applied - state, err := vm.blockChain.State() - require.NoError(t, err) - - // Existing account - expectedGenesisAccountBalance := new(big.Int).Add( - genesisAccount.Balance, - (*big.Int)(genesisAccountUpgrade.BalanceChange), - ) - require.Equal(t, state.GetBalance(testEthAddrs[0]), expectedGenesisAccountBalance) - require.Equal(t, state.GetState(testEthAddrs[0], storageKey), genesisAccountUpgrade.Storage[storageKey]) - require.Equal(t, state.GetCode(testEthAddrs[0]), upgradedCode) - require.Equal(t, state.GetCodeHash(testEthAddrs[0]), crypto.Keccak256Hash(upgradedCode)) - require.Equal(t, state.GetNonce(testEthAddrs[0]), genesisAccount.Nonce) // Nonce should be preserved since it was non-zero - - // New account - expectedNewAccountBalance := newAccountUpgrade.BalanceChange - require.Equal(t, state.GetBalance(newAccount), (*big.Int)(expectedNewAccountBalance)) - require.Equal(t, state.GetCode(newAccount), upgradedCode) - require.Equal(t, state.GetCodeHash(newAccount), crypto.Keccak256Hash(upgradedCode)) - require.Equal(t, state.GetNonce(newAccount), uint64(1)) // Nonce should be set to 1 when code is set if nonce was 0 - require.Equal(t, state.GetState(newAccount, storageKey), newAccountUpgrade.Storage[storageKey]) -} - -func TestVMEupgradeActivatesCancun(t *testing.T) { - tests := []struct { - name string - genesisJSON string - upgradeJSON string - check func(*testing.T, *VM) // function to check the VM state - }{ - { - name: "Etna activates Cancun", - genesisJSON: genesisJSONEtna, - check: func(t *testing.T, vm *VM) { - require.True(t, vm.chainConfig.IsCancun(common.Big0, DefaultEtnaTime)) - }, - }, - { - name: "Later Etna activates Cancun", - genesisJSON: genesisJSONDurango, - upgradeJSON: func() string { - upgrade := ¶ms.UpgradeConfig{ - NetworkUpgradeOverrides: ¶ms.NetworkUpgrades{ - EtnaTimestamp: utils.NewUint64(DefaultEtnaTime + 2), - }, - } - b, err := json.Marshal(upgrade) - require.NoError(t, err) - return string(b) - }(), - check: func(t *testing.T, vm *VM) { - require.False(t, vm.chainConfig.IsCancun(common.Big0, DefaultEtnaTime)) - require.True(t, vm.chainConfig.IsCancun(common.Big0, DefaultEtnaTime+2)) - }, - }, - { - name: "Changed Etna changes Cancun", - genesisJSON: genesisJSONEtna, - upgradeJSON: func() string { - upgrade := ¶ms.UpgradeConfig{ - NetworkUpgradeOverrides: ¶ms.NetworkUpgrades{ - EtnaTimestamp: utils.NewUint64(DefaultEtnaTime + 2), - }, - } - b, err := json.Marshal(upgrade) - require.NoError(t, err) - return string(b) - }(), - check: func(t *testing.T, vm *VM) { - require.False(t, vm.chainConfig.IsCancun(common.Big0, DefaultEtnaTime)) - require.True(t, vm.chainConfig.IsCancun(common.Big0, DefaultEtnaTime+2)) - }, - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - _, vm, _, _ := GenesisVM(t, true, test.genesisJSON, "", test.upgradeJSON) - defer func() { require.NoError(t, vm.Shutdown(context.Background())) }() - test.check(t, vm) - }) - } -} diff --git a/plugin/evm/vm_warp_test.go b/plugin/evm/vm_warp_test.go index 2b4109bae2..2a4550684e 100644 --- a/plugin/evm/vm_warp_test.go +++ b/plugin/evm/vm_warp_test.go @@ -16,14 +16,12 @@ import ( "github.com/ava-labs/avalanchego/snow/engine/snowman/block" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/snow/validators/validatorstest" - "github.com/ava-labs/avalanchego/upgrade" - avagoUtils "github.com/ava-labs/avalanchego/utils" + "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/vms/components/chain" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" - "github.com/ava-labs/subnet-evm/core" "github.com/ava-labs/subnet-evm/core/rawdb" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/eth/tracers" @@ -32,7 +30,6 @@ import ( "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ava-labs/subnet-evm/precompile/contracts/warp" "github.com/ava-labs/subnet-evm/predicate" - "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" @@ -47,14 +44,7 @@ var ( func TestSendWarpMessage(t *testing.T) { require := require.New(t) - genesis := &core.Genesis{} - require.NoError(genesis.UnmarshalJSON([]byte(genesisJSONDurango))) - genesis.Config.GenesisPrecompiles = params.Precompiles{ - warp.ConfigKey: warp.NewDefaultConfig(utils.TimeToNewUint64(upgrade.InitiallyActiveTime)), - } - genesisJSON, err := genesis.MarshalJSON() - require.NoError(err) - issuer, vm, _, _ := GenesisVM(t, true, string(genesisJSON), "", "") + issuer, vm, _, _, _ := GenesisVM(t, true, genesisJSONDurango, "", "") defer func() { require.NoError(vm.Shutdown(context.Background())) @@ -64,7 +54,7 @@ func TestSendWarpMessage(t *testing.T) { logsSub := vm.eth.APIBackend.SubscribeAcceptedLogsEvent(acceptedLogsChan) defer logsSub.Unsubscribe() - payloadData := avagoUtils.RandomBytes(100) + payloadData := utils.RandomBytes(100) warpSendMessageInput, err := warp.PackSendWarpMessage(payloadData) require.NoError(err) @@ -81,8 +71,8 @@ func TestSendWarpMessage(t *testing.T) { require.NoError(err) // Submit a transaction to trigger sending a warp message - tx0 := types.NewTransaction(uint64(0), warp.ContractAddress, big.NewInt(1), 100_000, big.NewInt(testMinGasPrice), warpSendMessageInput) - signedTx0, err := types.SignTx(tx0, types.LatestSignerForChainID(vm.chainConfig.ChainID), testKeys[0]) + tx0 := types.NewTransaction(uint64(0), warp.ContractAddress, big.NewInt(1), 100_000, big.NewInt(params.LaunchMinGasPrice), warpSendMessageInput) + signedTx0, err := types.SignTx(tx0, types.LatestSignerForChainID(vm.chainConfig.ChainID), testKeys[0].ToECDSA()) require.NoError(err) errs := vm.txPool.AddRemotesSync([]*types.Transaction{signedTx0}) @@ -245,14 +235,7 @@ func TestValidateInvalidWarpBlockHash(t *testing.T) { func testWarpVMTransaction(t *testing.T, unsignedMessage *avalancheWarp.UnsignedMessage, validSignature bool, txPayload []byte) { require := require.New(t) - genesis := &core.Genesis{} - require.NoError(genesis.UnmarshalJSON([]byte(genesisJSONDurango))) - genesis.Config.GenesisPrecompiles = params.Precompiles{ - warp.ConfigKey: warp.NewDefaultConfig(utils.TimeToNewUint64(upgrade.InitiallyActiveTime)), - } - genesisJSON, err := genesis.MarshalJSON() - require.NoError(err) - issuer, vm, _, _ := GenesisVM(t, true, string(genesisJSON), "", "") + issuer, vm, _, _, _ := GenesisVM(t, true, genesisJSONDurango, "", "") defer func() { require.NoError(vm.Shutdown(context.Background())) @@ -324,7 +307,7 @@ func testWarpVMTransaction(t *testing.T, unsignedMessage *avalancheWarp.Unsigned createTx, err := types.SignTx( types.NewContractCreation(0, common.Big0, 7_000_000, big.NewInt(225*params.GWei), common.Hex2Bytes(exampleWarpBin)), types.LatestSignerForChainID(vm.chainConfig.ChainID), - testKeys[0], + testKeys[0].ToECDSA(), ) require.NoError(err) exampleWarpAddress := crypto.CreateAddress(testEthAddrs[0], 0) @@ -344,7 +327,7 @@ func testWarpVMTransaction(t *testing.T, unsignedMessage *avalancheWarp.Unsigned signedMessage.Bytes(), ), types.LatestSignerForChainID(vm.chainConfig.ChainID), - testKeys[0], + testKeys[0].ToECDSA(), ) require.NoError(err) errs := vm.txPool.AddRemotesSync([]*types.Transaction{createTx, tx}) @@ -401,14 +384,7 @@ func testWarpVMTransaction(t *testing.T, unsignedMessage *avalancheWarp.Unsigned func TestReceiveWarpMessage(t *testing.T) { require := require.New(t) - genesis := &core.Genesis{} - require.NoError(genesis.UnmarshalJSON([]byte(genesisJSONDurango))) - genesis.Config.GenesisPrecompiles = params.Precompiles{ - warp.ConfigKey: warp.NewDefaultConfig(utils.TimeToNewUint64(upgrade.InitiallyActiveTime)), - } - genesisJSON, err := genesis.MarshalJSON() - require.NoError(err) - issuer, vm, _, _ := GenesisVM(t, true, string(genesisJSON), "", "") + issuer, vm, _, _, _ := GenesisVM(t, true, genesisJSONDurango, "", "") defer func() { require.NoError(vm.Shutdown(context.Background())) @@ -418,7 +394,7 @@ func TestReceiveWarpMessage(t *testing.T) { logsSub := vm.eth.APIBackend.SubscribeAcceptedLogsEvent(acceptedLogsChan) defer logsSub.Unsubscribe() - payloadData := avagoUtils.RandomBytes(100) + payloadData := utils.RandomBytes(100) addressedPayload, err := payload.NewAddressedCall( testEthAddrs[0].Bytes(), @@ -507,7 +483,7 @@ func TestReceiveWarpMessage(t *testing.T) { signedMessage.Bytes(), ), types.LatestSignerForChainID(vm.chainConfig.ChainID), - testKeys[0], + testKeys[0].ToECDSA(), ) require.NoError(err) errs := vm.txPool.AddRemotesSync([]*types.Transaction{getVerifiedWarpMessageTx}) @@ -582,7 +558,7 @@ func TestReceiveWarpMessage(t *testing.T) { } func TestMessageSignatureRequestsToVM(t *testing.T) { - _, vm, _, appSender := GenesisVM(t, true, genesisJSONSubnetEVM, "", "") + _, vm, _, _, appSender := GenesisVM(t, true, genesisJSONDurango, "", "") defer func() { err := vm.Shutdown(context.Background()) @@ -642,7 +618,7 @@ func TestMessageSignatureRequestsToVM(t *testing.T) { } func TestBlockSignatureRequestsToVM(t *testing.T) { - _, vm, _, appSender := GenesisVM(t, true, genesisJSONSubnetEVM, "", "") + _, vm, _, _, appSender := GenesisVM(t, true, genesisJSONDurango, "", "") defer func() { err := vm.Shutdown(context.Background()) diff --git a/plugin/main.go b/plugin/main.go index afdd416d2a..b063709044 100644 --- a/plugin/main.go +++ b/plugin/main.go @@ -4,14 +4,31 @@ package main import ( + "context" "fmt" + "os" + + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/utils/ulimit" + "github.com/ava-labs/avalanchego/vms/rpcchainvm" - "github.com/ava-labs/avalanchego/version" "github.com/ava-labs/subnet-evm/plugin/evm" - "github.com/ava-labs/subnet-evm/plugin/runner" ) func main() { - versionString := fmt.Sprintf("Subnet-EVM/%s [AvalancheGo=%s, rpcchainvm=%d]", evm.Version, version.Current, version.RPCChainVMProtocol) - runner.Run(versionString) + version, err := PrintVersion() + if err != nil { + fmt.Printf("couldn't get config: %s", err) + os.Exit(1) + } + if version { + fmt.Println(evm.Version) + os.Exit(0) + } + if err := ulimit.Set(ulimit.DefaultFDLimit, logging.NoLog{}); err != nil { + fmt.Printf("failed to set fd limit correctly due to: %s", err) + os.Exit(1) + } + + rpcchainvm.Serve(context.Background(), &evm.VM{IsPlugin: true}) } diff --git a/plugin/runner/keys.go b/plugin/runner/keys.go deleted file mode 100644 index 5302b4a4db..0000000000 --- a/plugin/runner/keys.go +++ /dev/null @@ -1,8 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package runner - -const ( - versionKey = "version" -) diff --git a/plugin/runner/params.go b/plugin/runner/params.go deleted file mode 100644 index 01a80f2c6a..0000000000 --- a/plugin/runner/params.go +++ /dev/null @@ -1,45 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package runner - -import ( - "flag" - - "github.com/spf13/pflag" - "github.com/spf13/viper" -) - -func subnetEVMFlagSet() *flag.FlagSet { - fs := flag.NewFlagSet("subnet-evm", flag.ContinueOnError) - - fs.Bool(versionKey, false, "If true, print version and quit") - - return fs -} - -// getViper returns the viper environment for the plugin binary -func getViper() (*viper.Viper, error) { - v := viper.New() - - fs := subnetEVMFlagSet() - pflag.CommandLine.AddGoFlagSet(fs) - pflag.Parse() - if err := v.BindPFlags(pflag.CommandLine); err != nil { - return nil, err - } - - return v, nil -} - -func PrintVersion() (bool, error) { - v, err := getViper() - if err != nil { - return false, err - } - - if v.GetBool(versionKey) { - return true, nil - } - return false, nil -} diff --git a/plugin/runner/runner.go b/plugin/runner/runner.go deleted file mode 100644 index 9b5b5efe29..0000000000 --- a/plugin/runner/runner.go +++ /dev/null @@ -1,33 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package runner - -import ( - "context" - "fmt" - "os" - - "github.com/ava-labs/avalanchego/utils/logging" - "github.com/ava-labs/avalanchego/utils/ulimit" - "github.com/ava-labs/avalanchego/vms/rpcchainvm" - - "github.com/ava-labs/subnet-evm/plugin/evm" -) - -func Run(versionStr string) { - printVersion, err := PrintVersion() - if err != nil { - fmt.Printf("couldn't get config: %s", err) - os.Exit(1) - } - if printVersion && versionStr != "" { - fmt.Printf(versionStr) - os.Exit(0) - } - if err := ulimit.Set(ulimit.DefaultFDLimit, logging.NoLog{}); err != nil { - fmt.Printf("failed to set fd limit correctly due to: %s", err) - os.Exit(1) - } - rpcchainvm.Serve(context.Background(), &evm.VM{}) -} diff --git a/precompile/allowlist/allowlist.abi b/precompile/allowlist/allowlist.abi deleted file mode 100644 index 4e1c4f1fcb..0000000000 --- a/precompile/allowlist/allowlist.abi +++ /dev/null @@ -1,104 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "uint256", - "name": "role", - "type": "uint256" - }, - { - "indexed": true, - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "oldRole", - "type": "uint256" - } - ], - "name": "RoleSet", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "readAllowList", - "outputs": [ - { - "internalType": "uint256", - "name": "role", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "setAdmin", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "setEnabled", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "setManager", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "setNone", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/precompile/allowlist/allowlist.go b/precompile/allowlist/allowlist.go deleted file mode 100644 index c5e60458cc..0000000000 --- a/precompile/allowlist/allowlist.go +++ /dev/null @@ -1,210 +0,0 @@ -// (c) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package allowlist - -import ( - _ "embed" - "errors" - "fmt" - "math/big" - - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ava-labs/subnet-evm/vmerrs" - "github.com/ethereum/go-ethereum/common" -) - -// AllowList is an abstraction that allows other precompiles to manage -// which addresses can call the precompile by maintaining an allowlist -// in the storage trie. - -const ( - ModifyAllowListGasCost = contract.WriteGasCostPerSlot - ReadAllowListGasCost = contract.ReadGasCostPerSlot - - allowListInputLen = common.HashLength -) - -var ( - // Error returned when an invalid write is attempted - ErrCannotModifyAllowList = errors.New("cannot modify allow list") - - // AllowListRawABI contains the raw ABI of AllowList library interface. - //go:embed allowlist.abi - AllowListRawABI string - - AllowListABI = contract.ParseABI(AllowListRawABI) -) - -// GetAllowListStatus returns the allow list role of [address] for the precompile -// at [precompileAddr] -func GetAllowListStatus(state contract.StateDB, precompileAddr common.Address, address common.Address) Role { - // Generate the state key for [address] - addressKey := common.BytesToHash(address.Bytes()) - return Role(state.GetState(precompileAddr, addressKey)) -} - -// SetAllowListRole sets the permissions of [address] to [role] for the precompile -// at [precompileAddr]. -// assumes [role] has already been verified as valid. -func SetAllowListRole(stateDB contract.StateDB, precompileAddr, address common.Address, role Role) { - // Generate the state key for [address] - addressKey := common.BytesToHash(address.Bytes()) - // Assign [role] to the address - // This stores the [role] in the contract storage with address [precompileAddr] - // and [addressKey] hash. It means that any reusage of the [addressKey] for different value - // conflicts with the same slot [role] is stored. - // Precompile implementations must use a different key than [addressKey] - stateDB.SetState(precompileAddr, addressKey, role.Hash()) -} - -func PackModifyAllowList(address common.Address, role Role) ([]byte, error) { - funcName, err := role.GetSetterFunctionName() - if err != nil { - return nil, err - } - return AllowListABI.Pack(funcName, address) -} - -func UnpackModifyAllowListInput(input []byte, r Role, useStrictMode bool) (common.Address, error) { - if useStrictMode && len(input) != allowListInputLen { - return common.Address{}, fmt.Errorf("invalid input length for modifying allow list: %d", len(input)) - } - - funcName, err := r.GetSetterFunctionName() - if err != nil { - return common.Address{}, err - } - var modifyAddress common.Address - err = AllowListABI.UnpackInputIntoInterface(&modifyAddress, funcName, input, useStrictMode) - return modifyAddress, err -} - -// createAllowListRoleSetter returns an execution function for setting the allow list status of the input address argument to [role]. -// This execution function is speciifc to [precompileAddr]. -func createAllowListRoleSetter(precompileAddr common.Address, role Role) contract.RunStatefulPrecompileFunc { - return func(evm contract.AccessibleState, callerAddr, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = contract.DeductGas(suppliedGas, ModifyAllowListGasCost); err != nil { - return nil, 0, err - } - - // do not use strict mode after Durango - useStrictMode := !contract.IsDurangoActivated(evm) - modifyAddress, err := UnpackModifyAllowListInput(input, role, useStrictMode) - - if err != nil { - return nil, remainingGas, err - } - - if readOnly { - return nil, remainingGas, vmerrs.ErrWriteProtection - } - - stateDB := evm.GetStateDB() - - // Verify that the caller is an admin with permission to modify the allow list - callerStatus := GetAllowListStatus(stateDB, precompileAddr, callerAddr) - // Verify that the address we are trying to modify has a status that allows it to be modified - modifyStatus := GetAllowListStatus(stateDB, precompileAddr, modifyAddress) - if !callerStatus.CanModify(modifyStatus, role) { - return nil, remainingGas, fmt.Errorf("%w: modify address: %s, from role: %s, to role: %s", ErrCannotModifyAllowList, callerAddr, modifyStatus, role) - } - if contract.IsDurangoActivated(evm) { - if remainingGas, err = contract.DeductGas(remainingGas, AllowListEventGasCost); err != nil { - return nil, 0, err - } - topics, data, err := PackRoleSetEvent(role, modifyAddress, callerAddr, modifyStatus) - if err != nil { - return nil, remainingGas, err - } - stateDB.AddLog( - precompileAddr, - topics, - data, - evm.GetBlockContext().Number().Uint64(), - ) - } - - SetAllowListRole(stateDB, precompileAddr, modifyAddress, role) - - return []byte{}, remainingGas, nil - } -} - -// PackReadAllowList packs [address] into the input data to the read allow list function -func PackReadAllowList(address common.Address) ([]byte, error) { - return AllowListABI.Pack("readAllowList", address) -} - -func UnpackReadAllowListInput(input []byte, useStrictMode bool) (common.Address, error) { - if useStrictMode && len(input) != allowListInputLen { - return common.Address{}, fmt.Errorf("invalid input length for read allow list: %d", len(input)) - } - - var modifyAddress common.Address - err := AllowListABI.UnpackInputIntoInterface(&modifyAddress, "readAllowList", input, useStrictMode) - return modifyAddress, err -} - -func PackReadAllowListOutput(roleNumber *big.Int) ([]byte, error) { - return AllowListABI.PackOutput("readAllowList", roleNumber) -} - -// createReadAllowList returns an execution function that reads the allow list for the given [precompileAddr]. -// The execution function parses the input into a single address and returns the 32 byte hash that specifies the -// designated role of that address -func createReadAllowList(precompileAddr common.Address) contract.RunStatefulPrecompileFunc { - return func(evm contract.AccessibleState, callerAddr common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = contract.DeductGas(suppliedGas, ReadAllowListGasCost); err != nil { - return nil, 0, err - } - - // We skip the fixed length check with Durango - useStrictMode := !contract.IsDurangoActivated(evm) - readAddress, err := UnpackReadAllowListInput(input, useStrictMode) - if err != nil { - return nil, remainingGas, err - } - - role := GetAllowListStatus(evm.GetStateDB(), precompileAddr, readAddress) - packedOutput, err := PackReadAllowListOutput(role.Big()) - if err != nil { - return nil, remainingGas, err - } - return packedOutput, remainingGas, nil - } -} - -// CreateAllowListPrecompile returns a StatefulPrecompiledContract with R/W control of an allow list at [precompileAddr] -func CreateAllowListPrecompile(precompileAddr common.Address) contract.StatefulPrecompiledContract { - // Construct the contract with no fallback function. - allowListFuncs := CreateAllowListFunctions(precompileAddr) - contract, err := contract.NewStatefulPrecompileContract(nil, allowListFuncs) - if err != nil { - panic(err) - } - return contract -} - -func CreateAllowListFunctions(precompileAddr common.Address) []*contract.StatefulPrecompileFunction { - var functions []*contract.StatefulPrecompileFunction - - for name, method := range AllowListABI.Methods { - var fn *contract.StatefulPrecompileFunction - if name == "readAllowList" { - fn = contract.NewStatefulPrecompileFunction(method.ID, createReadAllowList(precompileAddr)) - } else if adminFnName, _ := AdminRole.GetSetterFunctionName(); name == adminFnName { - fn = contract.NewStatefulPrecompileFunction(method.ID, createAllowListRoleSetter(precompileAddr, AdminRole)) - } else if enabledFnName, _ := EnabledRole.GetSetterFunctionName(); name == enabledFnName { - fn = contract.NewStatefulPrecompileFunction(method.ID, createAllowListRoleSetter(precompileAddr, EnabledRole)) - } else if noRoleFnName, _ := NoRole.GetSetterFunctionName(); name == noRoleFnName { - fn = contract.NewStatefulPrecompileFunction(method.ID, createAllowListRoleSetter(precompileAddr, NoRole)) - } else if managerFnName, _ := ManagerRole.GetSetterFunctionName(); name == managerFnName { - fn = contract.NewStatefulPrecompileFunctionWithActivator(method.ID, createAllowListRoleSetter(precompileAddr, ManagerRole), contract.IsDurangoActivated) - } else { - panic(fmt.Sprintf("unexpected method name: %s", name)) - } - functions = append(functions, fn) - } - return functions -} diff --git a/precompile/allowlist/allowlist_test.go b/precompile/allowlist/allowlist_test.go deleted file mode 100644 index ebbcf6b69e..0000000000 --- a/precompile/allowlist/allowlist_test.go +++ /dev/null @@ -1,76 +0,0 @@ -// (c) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package allowlist - -import ( - "testing" - - "github.com/ava-labs/subnet-evm/core/state" - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ava-labs/subnet-evm/precompile/modules" - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - "github.com/ethereum/go-ethereum/common" -) - -var ( - _ precompileconfig.Config = &dummyConfig{} - _ contract.Configurator = &dummyConfigurator{} - - dummyAddr = common.Address{1} -) - -type dummyConfig struct { - precompileconfig.Upgrade - AllowListConfig -} - -func (d *dummyConfig) Key() string { return "dummy" } -func (d *dummyConfig) IsDisabled() bool { return false } -func (d *dummyConfig) Verify(chainConfig precompileconfig.ChainConfig) error { - return d.AllowListConfig.Verify(chainConfig, d.Upgrade) -} - -func (d *dummyConfig) Equal(cfg precompileconfig.Config) bool { - other, ok := (cfg).(*dummyConfig) - if !ok { - return false - } - return d.AllowListConfig.Equal(&other.AllowListConfig) -} - -type dummyConfigurator struct{} - -func (d *dummyConfigurator) MakeConfig() precompileconfig.Config { - return &dummyConfig{} -} - -func (d *dummyConfigurator) Configure( - chainConfig precompileconfig.ChainConfig, - precompileConfig precompileconfig.Config, - state contract.StateDB, - blockContext contract.ConfigurationBlockContext, -) error { - cfg := precompileConfig.(*dummyConfig) - return cfg.AllowListConfig.Configure(chainConfig, dummyAddr, state, blockContext) -} - -func TestAllowListRun(t *testing.T) { - dummyModule := modules.Module{ - Address: dummyAddr, - Contract: CreateAllowListPrecompile(dummyAddr), - Configurator: &dummyConfigurator{}, - ConfigKey: "dummy", - } - RunPrecompileWithAllowListTests(t, dummyModule, state.NewTestStateDB, nil) -} - -func BenchmarkAllowList(b *testing.B) { - dummyModule := modules.Module{ - Address: dummyAddr, - Contract: CreateAllowListPrecompile(dummyAddr), - Configurator: &dummyConfigurator{}, - ConfigKey: "dummy", - } - BenchPrecompileWithAllowList(b, dummyModule, state.NewTestStateDB, nil) -} diff --git a/precompile/allowlist/config.go b/precompile/allowlist/config.go deleted file mode 100644 index 520021f511..0000000000 --- a/precompile/allowlist/config.go +++ /dev/null @@ -1,112 +0,0 @@ -// (c) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package allowlist - -import ( - "fmt" - - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - "github.com/ethereum/go-ethereum/common" -) - -var ErrCannotAddManagersBeforeDurango = fmt.Errorf("cannot add managers before Durango") - -// AllowListConfig specifies the initial set of addresses with Admin or Enabled roles. -type AllowListConfig struct { - AdminAddresses []common.Address `json:"adminAddresses,omitempty"` // initial admin addresses - ManagerAddresses []common.Address `json:"managerAddresses,omitempty"` // initial manager addresses - EnabledAddresses []common.Address `json:"enabledAddresses,omitempty"` // initial enabled addresses -} - -// Configure initializes the address space of [precompileAddr] by initializing the role of each of -// the addresses in [AllowListAdmins]. -func (c *AllowListConfig) Configure(chainConfig precompileconfig.ChainConfig, precompileAddr common.Address, state contract.StateDB, blockContext contract.ConfigurationBlockContext) error { - for _, enabledAddr := range c.EnabledAddresses { - SetAllowListRole(state, precompileAddr, enabledAddr, EnabledRole) - } - for _, adminAddr := range c.AdminAddresses { - SetAllowListRole(state, precompileAddr, adminAddr, AdminRole) - } - // Verify() should have been called before Configure() - // so we know manager role is activated - for _, managerAddr := range c.ManagerAddresses { - SetAllowListRole(state, precompileAddr, managerAddr, ManagerRole) - } - return nil -} - -// Equal returns true iff [other] has the same admins in the same order in its allow list. -func (c *AllowListConfig) Equal(other *AllowListConfig) bool { - if other == nil { - return false - } - - return areEqualAddressLists(c.AdminAddresses, other.AdminAddresses) && - areEqualAddressLists(c.ManagerAddresses, other.ManagerAddresses) && - areEqualAddressLists(c.EnabledAddresses, other.EnabledAddresses) -} - -// areEqualAddressLists returns true iff [a] and [b] have the same addresses in the same order. -func areEqualAddressLists(current []common.Address, other []common.Address) bool { - if len(current) != len(other) { - return false - } - for i, address := range current { - if address != other[i] { - return false - } - } - return true -} - -// Verify returns an error if there is an overlapping address between admin and enabled roles -func (c *AllowListConfig) Verify(chainConfig precompileconfig.ChainConfig, upgrade precompileconfig.Upgrade) error { - addressMap := make(map[common.Address]Role) // tracks which addresses we have seen and their role - - // check for duplicates in enabled list - for _, enabledAddr := range c.EnabledAddresses { - if _, ok := addressMap[enabledAddr]; ok { - return fmt.Errorf("duplicate address in enabled list: %s", enabledAddr) - } - addressMap[enabledAddr] = EnabledRole - } - - // check for overlap between enabled and admin lists or duplicates in admin list - for _, adminAddr := range c.AdminAddresses { - if role, ok := addressMap[adminAddr]; ok { - if role == AdminRole { - return fmt.Errorf("duplicate address in admin list: %s", adminAddr) - } else { - return fmt.Errorf("cannot set address as both admin and enabled: %s", adminAddr) - } - } - addressMap[adminAddr] = AdminRole - } - - if len(c.ManagerAddresses) != 0 && upgrade.Timestamp() != nil { - // If the config attempts to activate a manager before the Durango, fail verification - timestamp := *upgrade.Timestamp() - if !chainConfig.IsDurango(timestamp) { - return ErrCannotAddManagersBeforeDurango - } - } - - // check for overlap between admin and manager lists or duplicates in manager list - for _, managerAddr := range c.ManagerAddresses { - if role, ok := addressMap[managerAddr]; ok { - switch role { - case ManagerRole: - return fmt.Errorf("duplicate address in manager list: %s", managerAddr) - case AdminRole: - return fmt.Errorf("cannot set address as both admin and manager: %s", managerAddr) - case EnabledRole: - return fmt.Errorf("cannot set address as both enabled and manager: %s", managerAddr) - } - } - addressMap[managerAddr] = ManagerRole - } - - return nil -} diff --git a/precompile/allowlist/config_test.go b/precompile/allowlist/config_test.go deleted file mode 100644 index 4a553d3330..0000000000 --- a/precompile/allowlist/config_test.go +++ /dev/null @@ -1,25 +0,0 @@ -// (c) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package allowlist - -import ( - "testing" - - "github.com/ava-labs/subnet-evm/precompile/modules" -) - -var testModule = modules.Module{ - Address: dummyAddr, - Contract: CreateAllowListPrecompile(dummyAddr), - Configurator: &dummyConfigurator{}, - ConfigKey: "dummy", -} - -func TestVerifyAllowlist(t *testing.T) { - VerifyPrecompileWithAllowListTests(t, testModule, nil) -} - -func TestEqualAllowList(t *testing.T) { - EqualPrecompileWithAllowListTests(t, testModule, nil) -} diff --git a/precompile/allowlist/event.go b/precompile/allowlist/event.go deleted file mode 100644 index 8c0f3e44f3..0000000000 --- a/precompile/allowlist/event.go +++ /dev/null @@ -1,37 +0,0 @@ -// Code generated -// This file is a generated precompile contract config with stubbed abstract functions. -// The file is generated by a template. Please inspect every code and comment in this file before use. - -package allowlist - -import ( - "math/big" - - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ethereum/go-ethereum/common" -) - -const ( - // AllowListEventGasCost is the gas cost of a call to the AllowList contract's event. - // It is the base gas cost + the gas cost of the topics (signature, role, account, caller) - // and the gas cost of the non-indexed data (oldRole). - AllowListEventGasCost = contract.LogGas + contract.LogTopicGas*4 + contract.LogDataGas*common.HashLength -) - -// PackRoleSetEvent packs the event into the appropriate arguments for RoleSet. -// It returns topic hashes and the encoded non-indexed data. -func PackRoleSetEvent(role Role, account common.Address, caller common.Address, oldRole Role) ([]common.Hash, []byte, error) { - return AllowListABI.PackEvent("RoleSet", role.Big(), account, caller, oldRole.Big()) -} - -// UnpackRoleSetEventData attempts to unpack non-indexed [dataBytes]. -func UnpackRoleSetEventData(dataBytes []byte) (Role, error) { - eventData := struct { - OldRole *big.Int - }{} - err := AllowListABI.UnpackIntoInterface(&eventData, "RoleSet", dataBytes) - if err != nil { - return Role{}, err - } - return FromBig(eventData.OldRole) -} diff --git a/precompile/allowlist/role.go b/precompile/allowlist/role.go deleted file mode 100644 index 4cd55dcd3d..0000000000 --- a/precompile/allowlist/role.go +++ /dev/null @@ -1,122 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package allowlist - -import ( - "errors" - "math/big" - - "github.com/ethereum/go-ethereum/common" -) - -// 1. NoRole - this is equivalent to common.Hash{} and deletes the key from the DB when set -// 2. EnabledRole - allowed to call the precompile -// 3. Admin - allowed to both modify the allowlist and call the precompile -// 4. Manager - allowed to add and remove only enabled addresses and also call the precompile. (only after Durango) -var ( - NoRole = Role(common.BigToHash(common.Big0)) - EnabledRole = Role(common.BigToHash(common.Big1)) - AdminRole = Role(common.BigToHash(common.Big2)) - ManagerRole = Role(common.BigToHash(common.Big3)) - // Roles should be incremented and not changed. - - ErrInvalidRole = errors.New("invalid role") -) - -// Enum constants for valid Role -type Role common.Hash - -// IsNoRole returns true if [r] indicates no specific role. -func (r Role) IsNoRole() bool { - switch r { - case NoRole: - return true - default: - return false - } -} - -// IsAdmin returns true if [r] indicates the permission to modify the allow list. -func (r Role) IsAdmin() bool { - switch r { - case AdminRole: - return true - default: - return false - } -} - -// IsEnabled returns true if [r] indicates that it has permission to access the resource. -func (r Role) IsEnabled() bool { - switch r { - case AdminRole, EnabledRole, ManagerRole: - return true - default: - return false - } -} - -func (r Role) CanModify(from, target Role) bool { - switch r { - case AdminRole: - return true - case ManagerRole: - return (from == EnabledRole || from == NoRole) && (target == EnabledRole || target == NoRole) - default: - return false - } -} - -func (r Role) Bytes() []byte { - return common.Hash(r).Bytes() -} - -func (r Role) Big() *big.Int { - return common.Hash(r).Big() -} - -func (r Role) Hash() common.Hash { - return common.Hash(r) -} - -func (r Role) GetSetterFunctionName() (string, error) { - switch r { - case AdminRole: - return "setAdmin", nil - case ManagerRole: - return "setManager", nil - case EnabledRole: - return "setEnabled", nil - case NoRole: - return "setNone", nil - default: - return "", ErrInvalidRole - } -} - -// String returns a string representation of [r]. -func (r Role) String() string { - switch r { - case NoRole: - return "NoRole" - case EnabledRole: - return "EnabledRole" - case ManagerRole: - return "ManagerRole" - case AdminRole: - return "AdminRole" - default: - return "UnknownRole" - } -} - -func FromBig(b *big.Int) (Role, error) { - role := Role(common.BigToHash(b)) - switch role { - case NoRole, EnabledRole, ManagerRole, AdminRole: - return role, nil - default: - return Role{}, ErrInvalidRole - } -} diff --git a/precompile/allowlist/role_test.go b/precompile/allowlist/role_test.go deleted file mode 100644 index 3d9bb6552a..0000000000 --- a/precompile/allowlist/role_test.go +++ /dev/null @@ -1,149 +0,0 @@ -// (c) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package allowlist - -import ( - "fmt" - "testing" - - "github.com/stretchr/testify/require" -) - -func TestIsNoRole(t *testing.T) { - tests := []struct { - role Role - expected bool - }{ - { - role: ManagerRole, - expected: false, - }, - { - role: AdminRole, - expected: false, - }, - { - role: EnabledRole, - expected: false, - }, - { - role: NoRole, - expected: true, - }, - } - - for index, test := range tests { - isNoRole := test.role.IsNoRole() - require.Equal(t, test.expected, isNoRole, fmt.Sprintf("test index: %d", index)) - } -} - -func TestIsEnabled(t *testing.T) { - tests := []struct { - role Role - expected bool - }{ - { - role: ManagerRole, - expected: true, - }, - { - role: AdminRole, - expected: true, - }, - { - role: EnabledRole, - expected: true, - }, - { - role: NoRole, - expected: false, - }, - } - - for index, test := range tests { - isEnabled := test.role.IsEnabled() - require.Equal(t, test.expected, isEnabled, fmt.Sprintf("test index: %d", index)) - } -} - -func TestCanModify(t *testing.T) { - tests := []struct { - role Role - expected bool - from Role - target Role - }{ - { - role: ManagerRole, - expected: true, - from: EnabledRole, - target: NoRole, - }, - { - role: ManagerRole, - expected: true, - from: NoRole, - target: NoRole, - }, - { - role: ManagerRole, - expected: true, - from: EnabledRole, - target: EnabledRole, - }, - { - role: ManagerRole, - expected: false, - from: ManagerRole, - target: ManagerRole, - }, - { - role: ManagerRole, - expected: true, - from: NoRole, - target: EnabledRole, - }, - { - role: ManagerRole, - expected: false, - from: ManagerRole, - target: EnabledRole, - }, - { - role: ManagerRole, - expected: false, - from: AdminRole, - target: EnabledRole, - }, - { - role: AdminRole, - expected: true, - from: EnabledRole, - target: NoRole, - }, - { - role: AdminRole, - expected: true, - from: AdminRole, - target: NoRole, - }, - { - role: EnabledRole, - expected: false, - from: EnabledRole, - target: NoRole, - }, - { - role: NoRole, - expected: false, - from: EnabledRole, - target: NoRole, - }, - } - for index, test := range tests { - canModify := test.role.CanModify(test.from, test.target) - require.Equal(t, test.expected, canModify, fmt.Sprintf("test index: %d", index)) - } -} diff --git a/precompile/allowlist/test_allowlist.go b/precompile/allowlist/test_allowlist.go deleted file mode 100644 index 48e81f18fd..0000000000 --- a/precompile/allowlist/test_allowlist.go +++ /dev/null @@ -1,712 +0,0 @@ -// (c) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package allowlist - -import ( - "testing" - - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ava-labs/subnet-evm/precompile/modules" - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - "github.com/ava-labs/subnet-evm/precompile/testutils" - "github.com/ava-labs/subnet-evm/vmerrs" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -var ( - TestAdminAddr = common.HexToAddress("0x0000000000000000000000000000000000000011") - TestEnabledAddr = common.HexToAddress("0x0000000000000000000000000000000000000022") - TestNoRoleAddr = common.HexToAddress("0x0000000000000000000000000000000000000033") - TestManagerAddr = common.HexToAddress("0x0000000000000000000000000000000000000044") -) - -func AllowListTests(t testing.TB, module modules.Module) map[string]testutils.PrecompileTest { - contractAddress := module.Address - return map[string]testutils.PrecompileTest{ - "admin set admin": { - Caller: TestAdminAddr, - BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestNoRoleAddr, AdminRole) - require.NoError(t, err) - - return input - }, - SuppliedGas: ModifyAllowListGasCost + AllowListEventGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, state contract.StateDB) { - res := GetAllowListStatus(state, contractAddress, TestNoRoleAddr) - require.Equal(t, AdminRole, res) - // Check logs are stored in state - logsTopics, logsData := state.GetLogData() - assertSetRoleEvent(t, logsTopics, logsData, AdminRole, TestNoRoleAddr, TestAdminAddr, NoRole) - }, - }, - "admin set enabled": { - Caller: TestAdminAddr, - BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestNoRoleAddr, EnabledRole) - require.NoError(t, err) - - return input - }, - SuppliedGas: ModifyAllowListGasCost + AllowListEventGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, state contract.StateDB) { - res := GetAllowListStatus(state, contractAddress, TestNoRoleAddr) - require.Equal(t, EnabledRole, res) - // Check logs are stored in state - logsTopics, logsData := state.GetLogData() - assertSetRoleEvent(t, logsTopics, logsData, EnabledRole, TestNoRoleAddr, TestAdminAddr, NoRole) - }, - }, - "admin set no role": { - Caller: TestAdminAddr, - BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestEnabledAddr, NoRole) - require.NoError(t, err) - - return input - }, - SuppliedGas: ModifyAllowListGasCost + AllowListEventGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, state contract.StateDB) { - res := GetAllowListStatus(state, contractAddress, TestEnabledAddr) - require.Equal(t, NoRole, res) - // Check logs are stored in state - logsTopics, logsData := state.GetLogData() - assertSetRoleEvent(t, logsTopics, logsData, NoRole, TestEnabledAddr, TestAdminAddr, EnabledRole) - }, - }, - "no role set no role": { - Caller: TestNoRoleAddr, - BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestEnabledAddr, NoRole) - require.NoError(t, err) - - return input - }, - SuppliedGas: ModifyAllowListGasCost, - ReadOnly: false, - ExpectedErr: ErrCannotModifyAllowList.Error(), - }, - "no role set enabled": { - Caller: TestNoRoleAddr, - BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestNoRoleAddr, EnabledRole) - require.NoError(t, err) - - return input - }, - SuppliedGas: ModifyAllowListGasCost, - ReadOnly: false, - ExpectedErr: ErrCannotModifyAllowList.Error(), - }, - "no role set admin": { - Caller: TestNoRoleAddr, - BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestEnabledAddr, AdminRole) - require.NoError(t, err) - - return input - }, - SuppliedGas: ModifyAllowListGasCost, - ReadOnly: false, - ExpectedErr: ErrCannotModifyAllowList.Error(), - }, - "enabled set no role": { - Caller: TestEnabledAddr, - BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestAdminAddr, NoRole) - require.NoError(t, err) - - return input - }, - SuppliedGas: ModifyAllowListGasCost, - ReadOnly: false, - ExpectedErr: ErrCannotModifyAllowList.Error(), - }, - "enabled set enabled": { - Caller: TestEnabledAddr, - BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestNoRoleAddr, EnabledRole) - require.NoError(t, err) - - return input - }, - SuppliedGas: ModifyAllowListGasCost, - ReadOnly: false, - ExpectedErr: ErrCannotModifyAllowList.Error(), - }, - "enabled set admin": { - Caller: TestEnabledAddr, - BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestNoRoleAddr, AdminRole) - require.NoError(t, err) - - return input - }, - SuppliedGas: ModifyAllowListGasCost, - ReadOnly: false, - ExpectedErr: ErrCannotModifyAllowList.Error(), - }, - "no role set manager pre-Durango": { - Caller: TestNoRoleAddr, - BeforeHook: SetDefaultRoles(contractAddress), - ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig { - config := precompileconfig.NewMockChainConfig(ctrl) - config.EXPECT().IsDurango(gomock.Any()).Return(false).AnyTimes() - return config - }, - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestNoRoleAddr, ManagerRole) - require.NoError(t, err) - - return input - }, - SuppliedGas: 0, - ReadOnly: false, - ExpectedErr: "invalid non-activated function selector", - }, - "no role set manager": { - Caller: TestNoRoleAddr, - BeforeHook: SetDefaultRoles(contractAddress), - ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig { - config := precompileconfig.NewMockChainConfig(ctrl) - config.EXPECT().IsDurango(gomock.Any()).Return(true).AnyTimes() - return config - }, - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestNoRoleAddr, ManagerRole) - require.NoError(t, err) - - return input - }, - SuppliedGas: ModifyAllowListGasCost, - ReadOnly: false, - ExpectedErr: ErrCannotModifyAllowList.Error(), - }, - "enabled role set manager pre-Durango": { - Caller: TestEnabledAddr, - BeforeHook: SetDefaultRoles(contractAddress), - ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig { - config := precompileconfig.NewMockChainConfig(ctrl) - config.EXPECT().IsDurango(gomock.Any()).Return(false).AnyTimes() - return config - }, - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestNoRoleAddr, ManagerRole) - require.NoError(t, err) - - return input - }, - SuppliedGas: 0, - ReadOnly: false, - ExpectedErr: "invalid non-activated function selector", - }, - "enabled set manager": { - Caller: TestNoRoleAddr, - BeforeHook: SetDefaultRoles(contractAddress), - ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig { - config := precompileconfig.NewMockChainConfig(ctrl) - config.EXPECT().IsDurango(gomock.Any()).Return(true).AnyTimes() - return config - }, - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestNoRoleAddr, ManagerRole) - require.NoError(t, err) - - return input - }, - SuppliedGas: ModifyAllowListGasCost, - ReadOnly: false, - ExpectedErr: ErrCannotModifyAllowList.Error(), - }, - "admin set manager pre-DUpgarde": { - Caller: TestAdminAddr, - BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestNoRoleAddr, ManagerRole) - require.NoError(t, err) - - return input - }, - ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig { - config := precompileconfig.NewMockChainConfig(ctrl) - config.EXPECT().IsDurango(gomock.Any()).Return(false).AnyTimes() - return config - }, - SuppliedGas: 0, - ReadOnly: false, - ExpectedErr: "invalid non-activated function selector", - }, - "admin set manager": { - Caller: TestAdminAddr, - BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestNoRoleAddr, ManagerRole) - require.NoError(t, err) - - return input - }, - ExpectedRes: []byte{}, - ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig { - config := precompileconfig.NewMockChainConfig(ctrl) - config.EXPECT().IsDurango(gomock.Any()).Return(true).AnyTimes() - return config - }, - SuppliedGas: ModifyAllowListGasCost + AllowListEventGasCost, - ReadOnly: false, - AfterHook: func(t testing.TB, state contract.StateDB) { - res := GetAllowListStatus(state, contractAddress, TestNoRoleAddr) - require.Equal(t, ManagerRole, res) - // Check logs are stored in state - logsTopics, logsData := state.GetLogData() - assertSetRoleEvent(t, logsTopics, logsData, ManagerRole, TestNoRoleAddr, TestAdminAddr, NoRole) - }, - }, - "manager set no role to no role": { - Caller: TestManagerAddr, - BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestNoRoleAddr, NoRole) - require.NoError(t, err) - - return input - }, - SuppliedGas: ModifyAllowListGasCost + AllowListEventGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - ExpectedErr: "", - AfterHook: func(t testing.TB, state contract.StateDB) { - res := GetAllowListStatus(state, contractAddress, TestNoRoleAddr) - require.Equal(t, NoRole, res) - // Check logs are stored in state - logsTopics, logsData := state.GetLogData() - assertSetRoleEvent(t, logsTopics, logsData, NoRole, TestNoRoleAddr, TestManagerAddr, NoRole) - }, - }, - "manager set no role to enabled": { - Caller: TestManagerAddr, - BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestNoRoleAddr, EnabledRole) - require.NoError(t, err) - - return input - }, - SuppliedGas: ModifyAllowListGasCost + AllowListEventGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - ExpectedErr: "", - AfterHook: func(t testing.TB, state contract.StateDB) { - res := GetAllowListStatus(state, contractAddress, TestNoRoleAddr) - require.Equal(t, EnabledRole, res) - - // Check logs are stored in state - logsTopics, logsData := state.GetLogData() - assertSetRoleEvent(t, logsTopics, logsData, EnabledRole, TestNoRoleAddr, TestManagerAddr, NoRole) - }, - }, - "manager set no role to manager": { - Caller: TestManagerAddr, - BeforeHook: SetDefaultRoles(contractAddress), - ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig { - config := precompileconfig.NewMockChainConfig(ctrl) - config.EXPECT().IsDurango(gomock.Any()).Return(true).AnyTimes() - return config - }, - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestNoRoleAddr, ManagerRole) - require.NoError(t, err) - - return input - }, - SuppliedGas: ModifyAllowListGasCost, - ReadOnly: false, - ExpectedErr: ErrCannotModifyAllowList.Error(), - }, - "manager set no role to admin": { - Caller: TestManagerAddr, - BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestNoRoleAddr, AdminRole) - require.NoError(t, err) - - return input - }, - SuppliedGas: ModifyAllowListGasCost, - ReadOnly: false, - ExpectedErr: ErrCannotModifyAllowList.Error(), - }, - "manager set enabled to admin": { - Caller: TestManagerAddr, - BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestEnabledAddr, AdminRole) - require.NoError(t, err) - - return input - }, - SuppliedGas: ModifyAllowListGasCost, - ReadOnly: false, - ExpectedErr: ErrCannotModifyAllowList.Error(), - }, - "manager set enabled role to manager": { - Caller: TestManagerAddr, - BeforeHook: SetDefaultRoles(contractAddress), - ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig { - config := precompileconfig.NewMockChainConfig(ctrl) - config.EXPECT().IsDurango(gomock.Any()).Return(true).AnyTimes() - return config - }, - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestEnabledAddr, ManagerRole) - require.NoError(t, err) - - return input - }, - SuppliedGas: ModifyAllowListGasCost, - ReadOnly: false, - ExpectedErr: ErrCannotModifyAllowList.Error(), - }, - "manager set enabled role to no role": { - Caller: TestManagerAddr, - BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestEnabledAddr, NoRole) - require.NoError(t, err) - - return input - }, - SuppliedGas: ModifyAllowListGasCost + AllowListEventGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, state contract.StateDB) { - res := GetAllowListStatus(state, contractAddress, TestNoRoleAddr) - require.Equal(t, NoRole, res) - - // Check logs are stored in state - logsTopics, logsData := state.GetLogData() - assertSetRoleEvent(t, logsTopics, logsData, NoRole, TestEnabledAddr, TestManagerAddr, EnabledRole) - }, - }, - "manager set admin to no role": { - Caller: TestManagerAddr, - BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestAdminAddr, NoRole) - require.NoError(t, err) - - return input - }, - SuppliedGas: ModifyAllowListGasCost, - ReadOnly: false, - ExpectedErr: ErrCannotModifyAllowList.Error(), - }, - "manager set admin role to enabled": { - Caller: TestManagerAddr, - BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestAdminAddr, EnabledRole) - require.NoError(t, err) - - return input - }, - SuppliedGas: ModifyAllowListGasCost, - ReadOnly: false, - ExpectedErr: ErrCannotModifyAllowList.Error(), - }, - "manager set admin to manager": { - Caller: TestManagerAddr, - BeforeHook: SetDefaultRoles(contractAddress), - ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig { - config := precompileconfig.NewMockChainConfig(ctrl) - config.EXPECT().IsDurango(gomock.Any()).Return(true).AnyTimes() - return config - }, - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestAdminAddr, ManagerRole) - require.NoError(t, err) - - return input - }, - SuppliedGas: ModifyAllowListGasCost, - ReadOnly: false, - ExpectedErr: ErrCannotModifyAllowList.Error(), - }, - "manager set manager to no role": { - Caller: TestManagerAddr, - BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestManagerAddr, NoRole) - require.NoError(t, err) - - return input - }, - SuppliedGas: ModifyAllowListGasCost, - ReadOnly: false, - ExpectedErr: ErrCannotModifyAllowList.Error(), - }, - "admin set no role with readOnly enabled": { - Caller: TestAdminAddr, - BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestEnabledAddr, NoRole) - require.NoError(t, err) - - return input - }, - SuppliedGas: ModifyAllowListGasCost, - ReadOnly: true, - ExpectedErr: vmerrs.ErrWriteProtection.Error(), - }, - "admin set no role insufficient gas": { - Caller: TestAdminAddr, - BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestEnabledAddr, NoRole) - require.NoError(t, err) - - return input - }, - SuppliedGas: ModifyAllowListGasCost - 1, - ReadOnly: false, - ExpectedErr: vmerrs.ErrOutOfGas.Error(), - }, - "no role read allow list": { - Caller: TestNoRoleAddr, - BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t testing.TB) []byte { - input, err := PackReadAllowList(TestNoRoleAddr) - require.NoError(t, err) - - return input - }, - SuppliedGas: ReadAllowListGasCost, - ReadOnly: false, - ExpectedRes: common.Hash(NoRole).Bytes(), - }, - "admin role read allow list": { - Caller: TestAdminAddr, - BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t testing.TB) []byte { - input, err := PackReadAllowList(TestAdminAddr) - require.NoError(t, err) - - return input - }, SuppliedGas: ReadAllowListGasCost, - ReadOnly: false, - ExpectedRes: common.Hash(AdminRole).Bytes(), - }, - "admin read allow list with readOnly enabled": { - Caller: TestAdminAddr, - BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t testing.TB) []byte { - input, err := PackReadAllowList(TestNoRoleAddr) - require.NoError(t, err) - - return input - }, SuppliedGas: ReadAllowListGasCost, - ReadOnly: true, - ExpectedRes: common.Hash(NoRole).Bytes(), - }, - "radmin read allow list out of gas": { - Caller: TestAdminAddr, - BeforeHook: SetDefaultRoles(contractAddress), - InputFn: func(t testing.TB) []byte { - input, err := PackReadAllowList(TestNoRoleAddr) - require.NoError(t, err) - - return input - }, SuppliedGas: ReadAllowListGasCost - 1, - ReadOnly: true, - ExpectedErr: vmerrs.ErrOutOfGas.Error(), - }, - "initial config sets admins": { - Config: mkConfigWithAllowList( - module, - &AllowListConfig{ - AdminAddresses: []common.Address{TestNoRoleAddr, TestEnabledAddr}, - }, - ), - SuppliedGas: 0, - ReadOnly: false, - AfterHook: func(t testing.TB, state contract.StateDB) { - require.Equal(t, AdminRole, GetAllowListStatus(state, contractAddress, TestNoRoleAddr)) - require.Equal(t, AdminRole, GetAllowListStatus(state, contractAddress, TestEnabledAddr)) - }, - }, - "initial config sets managers": { - Config: mkConfigWithAllowList( - module, - &AllowListConfig{ - ManagerAddresses: []common.Address{TestNoRoleAddr, TestEnabledAddr}, - }, - ), - SuppliedGas: 0, - ReadOnly: false, - AfterHook: func(t testing.TB, state contract.StateDB) { - require.Equal(t, ManagerRole, GetAllowListStatus(state, contractAddress, TestNoRoleAddr)) - require.Equal(t, ManagerRole, GetAllowListStatus(state, contractAddress, TestEnabledAddr)) - }, - }, - "initial config sets enabled": { - Config: mkConfigWithAllowList( - module, - &AllowListConfig{ - EnabledAddresses: []common.Address{TestNoRoleAddr, TestAdminAddr}, - }, - ), - SuppliedGas: 0, - ReadOnly: false, - AfterHook: func(t testing.TB, state contract.StateDB) { - require.Equal(t, EnabledRole, GetAllowListStatus(state, contractAddress, TestAdminAddr)) - require.Equal(t, EnabledRole, GetAllowListStatus(state, contractAddress, TestNoRoleAddr)) - }, - }, - "admin set admin pre-Durango": { - Caller: TestAdminAddr, - BeforeHook: SetDefaultRoles(contractAddress), - ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig { - config := precompileconfig.NewMockChainConfig(ctrl) - config.EXPECT().IsDurango(gomock.Any()).Return(false).AnyTimes() - return config - }, - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestNoRoleAddr, AdminRole) - require.NoError(t, err) - return input - }, - SuppliedGas: ModifyAllowListGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, stateDB contract.StateDB) { - // Check no logs are stored in state - topics, data := stateDB.GetLogData() - require.Len(t, topics, 0) - require.Len(t, data, 0) - }, - }, - "admin set enabled pre-Durango": { - Caller: TestAdminAddr, - BeforeHook: SetDefaultRoles(contractAddress), - ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig { - config := precompileconfig.NewMockChainConfig(ctrl) - config.EXPECT().IsDurango(gomock.Any()).Return(false).AnyTimes() - return config - }, - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestNoRoleAddr, EnabledRole) - require.NoError(t, err) - return input - }, - SuppliedGas: ModifyAllowListGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, stateDB contract.StateDB) { - // Check no logs are stored in state - topics, data := stateDB.GetLogData() - require.Len(t, topics, 0) - require.Len(t, data, 0) - }, - }, - "admin set no role pre-Durango": { - Caller: TestAdminAddr, - BeforeHook: SetDefaultRoles(contractAddress), - ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig { - config := precompileconfig.NewMockChainConfig(ctrl) - config.EXPECT().IsDurango(gomock.Any()).Return(false).AnyTimes() - return config - }, - InputFn: func(t testing.TB) []byte { - input, err := PackModifyAllowList(TestEnabledAddr, NoRole) - require.NoError(t, err) - return input - }, - SuppliedGas: ModifyAllowListGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, stateDB contract.StateDB) { - // Check no logs are stored in state - topics, data := stateDB.GetLogData() - require.Len(t, topics, 0) - require.Len(t, data, 0) - }, - }, - } -} - -// SetDefaultRoles returns a BeforeHook that sets roles TestAdminAddr and TestEnabledAddr -// to have the AdminRole and EnabledRole respectively. -func SetDefaultRoles(contractAddress common.Address) func(t testing.TB, state contract.StateDB) { - return func(t testing.TB, state contract.StateDB) { - SetAllowListRole(state, contractAddress, TestAdminAddr, AdminRole) - SetAllowListRole(state, contractAddress, TestManagerAddr, ManagerRole) - SetAllowListRole(state, contractAddress, TestEnabledAddr, EnabledRole) - require.Equal(t, AdminRole, GetAllowListStatus(state, contractAddress, TestAdminAddr)) - require.Equal(t, ManagerRole, GetAllowListStatus(state, contractAddress, TestManagerAddr)) - require.Equal(t, EnabledRole, GetAllowListStatus(state, contractAddress, TestEnabledAddr)) - require.Equal(t, NoRole, GetAllowListStatus(state, contractAddress, TestNoRoleAddr)) - } -} - -func RunPrecompileWithAllowListTests(t *testing.T, module modules.Module, newStateDB func(t testing.TB) contract.StateDB, contractTests map[string]testutils.PrecompileTest) { - t.Helper() - tests := AllowListTests(t, module) - // Add the contract specific tests to the map of tests to run. - for name, test := range contractTests { - if _, exists := tests[name]; exists { - t.Fatalf("duplicate test name: %s", name) - } - tests[name] = test - } - - testutils.RunPrecompileTests(t, module, newStateDB, tests) -} - -func BenchPrecompileWithAllowList(b *testing.B, module modules.Module, newStateDB func(t testing.TB) contract.StateDB, contractTests map[string]testutils.PrecompileTest) { - b.Helper() - - tests := AllowListTests(b, module) - // Add the contract specific tests to the map of tests to run. - for name, test := range contractTests { - if _, exists := tests[name]; exists { - b.Fatalf("duplicate bench name: %s", name) - } - tests[name] = test - } - - for name, test := range tests { - b.Run(name, func(b *testing.B) { - test.Bench(b, module, newStateDB(b)) - }) - } -} - -func assertSetRoleEvent(t testing.TB, logsTopics [][]common.Hash, logsData [][]byte, role Role, addr common.Address, caller common.Address, oldRole Role) { - require.Len(t, logsTopics, 1) - require.Len(t, logsData, 1) - topics := logsTopics[0] - require.Len(t, topics, 4) - require.Equal(t, AllowListABI.Events["RoleSet"].ID, topics[0]) - require.Equal(t, role.Hash(), topics[1]) - require.Equal(t, common.BytesToHash(addr[:]), topics[2]) - require.Equal(t, common.BytesToHash(caller[:]), topics[3]) - data := logsData[0] - require.Equal(t, oldRole.Bytes(), data) -} diff --git a/precompile/allowlist/test_allowlist_config.go b/precompile/allowlist/test_allowlist_config.go deleted file mode 100644 index 27c649f520..0000000000 --- a/precompile/allowlist/test_allowlist_config.go +++ /dev/null @@ -1,233 +0,0 @@ -// (c) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package allowlist - -import ( - "encoding/json" - "testing" - - "github.com/ava-labs/subnet-evm/precompile/modules" - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - "github.com/ava-labs/subnet-evm/precompile/testutils" - "github.com/ava-labs/subnet-evm/utils" - "github.com/ethereum/go-ethereum/common" - "go.uber.org/mock/gomock" -) - -// mkConfigWithAllowList creates a new config with the correct type for [module] -// by marshalling [cfg] to JSON and then unmarshalling it into the config. -func mkConfigWithAllowList(module modules.Module, cfg *AllowListConfig) precompileconfig.Config { - jsonBytes, err := json.Marshal(cfg) - if err != nil { - panic(err) - } - - moduleCfg := module.MakeConfig() - err = json.Unmarshal(jsonBytes, moduleCfg) - if err != nil { - panic(err) - } - - return moduleCfg -} - -func mkConfigWithUpgradeAndAllowList(module modules.Module, cfg *AllowListConfig, update precompileconfig.Upgrade) precompileconfig.Config { - jsonUpgradeBytes, err := json.Marshal(update) - if err != nil { - panic(err) - } - - moduleCfg := mkConfigWithAllowList(module, cfg) - err = json.Unmarshal(jsonUpgradeBytes, moduleCfg) - if err != nil { - panic(err) - } - return moduleCfg -} - -func AllowListConfigVerifyTests(t testing.TB, module modules.Module) map[string]testutils.ConfigVerifyTest { - return map[string]testutils.ConfigVerifyTest{ - "invalid allow list config with duplicate admins in allowlist": { - Config: mkConfigWithAllowList(module, &AllowListConfig{ - AdminAddresses: []common.Address{TestAdminAddr, TestAdminAddr}, - ManagerAddresses: nil, - EnabledAddresses: nil, - }), - ExpectedError: "duplicate address in admin list", - }, - "invalid allow list config with duplicate enableds in allowlist": { - Config: mkConfigWithAllowList(module, &AllowListConfig{ - AdminAddresses: nil, - ManagerAddresses: nil, - EnabledAddresses: []common.Address{TestEnabledAddr, TestEnabledAddr}, - }), - ExpectedError: "duplicate address in enabled list", - }, - "invalid allow list config with duplicate managers in allowlist": { - Config: mkConfigWithAllowList(module, &AllowListConfig{ - AdminAddresses: nil, - ManagerAddresses: []common.Address{TestManagerAddr, TestManagerAddr}, - EnabledAddresses: nil, - }), - ExpectedError: "duplicate address in manager list", - }, - "invalid allow list config with same admin and enabled in allowlist": { - Config: mkConfigWithAllowList(module, &AllowListConfig{ - AdminAddresses: []common.Address{TestAdminAddr}, - ManagerAddresses: nil, - EnabledAddresses: []common.Address{TestAdminAddr}, - }), - ExpectedError: "cannot set address as both admin and enabled", - }, - "invalid allow list config with same admin and manager in allowlist": { - Config: mkConfigWithAllowList(module, &AllowListConfig{ - AdminAddresses: []common.Address{TestAdminAddr}, - ManagerAddresses: []common.Address{TestAdminAddr}, - EnabledAddresses: nil, - }), - ExpectedError: "cannot set address as both admin and manager", - }, - "invalid allow list config with same manager and enabled in allowlist": { - Config: mkConfigWithAllowList(module, &AllowListConfig{ - AdminAddresses: nil, - ManagerAddresses: []common.Address{TestManagerAddr}, - EnabledAddresses: []common.Address{TestManagerAddr}, - }), - ExpectedError: "cannot set address as both enabled and manager", - }, - "invalid allow list config with manager role before activation": { - Config: mkConfigWithUpgradeAndAllowList(module, &AllowListConfig{ - AdminAddresses: nil, - ManagerAddresses: []common.Address{TestManagerAddr}, - EnabledAddresses: nil, - }, precompileconfig.Upgrade{ - BlockTimestamp: utils.NewUint64(1), - }), - ChainConfig: func() precompileconfig.ChainConfig { - config := precompileconfig.NewMockChainConfig(gomock.NewController(t)) - config.EXPECT().IsDurango(gomock.Any()).Return(false) - return config - }(), - ExpectedError: ErrCannotAddManagersBeforeDurango.Error(), - }, - "nil member allow list config in allowlist": { - Config: mkConfigWithAllowList(module, &AllowListConfig{ - AdminAddresses: nil, - ManagerAddresses: nil, - EnabledAddresses: nil, - }), - ExpectedError: "", - }, - "empty member allow list config in allowlist": { - Config: mkConfigWithAllowList(module, &AllowListConfig{ - AdminAddresses: []common.Address{}, - ManagerAddresses: []common.Address{}, - EnabledAddresses: []common.Address{}, - }), - ExpectedError: "", - }, - "valid allow list config in allowlist": { - Config: mkConfigWithAllowList(module, &AllowListConfig{ - AdminAddresses: []common.Address{TestAdminAddr}, - ManagerAddresses: []common.Address{TestManagerAddr}, - EnabledAddresses: []common.Address{TestEnabledAddr}, - }), - ExpectedError: "", - }, - } -} - -func AllowListConfigEqualTests(t testing.TB, module modules.Module) map[string]testutils.ConfigEqualTest { - return map[string]testutils.ConfigEqualTest{ - "allowlist non-nil config and nil other": { - Config: mkConfigWithAllowList(module, &AllowListConfig{ - AdminAddresses: []common.Address{TestAdminAddr}, - ManagerAddresses: []common.Address{TestManagerAddr}, - EnabledAddresses: []common.Address{TestEnabledAddr}, - }), - Other: nil, - Expected: false, - }, - "allowlist different admin": { - Config: mkConfigWithAllowList(module, &AllowListConfig{ - AdminAddresses: []common.Address{TestAdminAddr}, - ManagerAddresses: []common.Address{TestManagerAddr}, - EnabledAddresses: []common.Address{TestEnabledAddr}, - }), - Other: mkConfigWithAllowList(module, &AllowListConfig{ - AdminAddresses: []common.Address{{3}}, - ManagerAddresses: []common.Address{TestManagerAddr}, - EnabledAddresses: []common.Address{TestEnabledAddr}, - }), - Expected: false, - }, - "allowlist different manager": { - Config: mkConfigWithAllowList(module, &AllowListConfig{ - AdminAddresses: []common.Address{TestAdminAddr}, - ManagerAddresses: []common.Address{TestManagerAddr}, - EnabledAddresses: []common.Address{TestEnabledAddr}, - }), - Other: mkConfigWithAllowList(module, &AllowListConfig{ - AdminAddresses: []common.Address{TestAdminAddr}, - ManagerAddresses: []common.Address{{3}}, - EnabledAddresses: []common.Address{TestEnabledAddr}, - }), - Expected: false, - }, - "allowlist different enabled": { - Config: mkConfigWithAllowList(module, &AllowListConfig{ - AdminAddresses: []common.Address{TestAdminAddr}, - ManagerAddresses: []common.Address{TestManagerAddr}, - EnabledAddresses: []common.Address{TestEnabledAddr}, - }), - Other: mkConfigWithAllowList(module, &AllowListConfig{ - AdminAddresses: []common.Address{TestAdminAddr}, - ManagerAddresses: []common.Address{TestManagerAddr}, - EnabledAddresses: []common.Address{{3}}, - }), - Expected: false, - }, - "allowlist same config": { - Config: mkConfigWithAllowList(module, &AllowListConfig{ - AdminAddresses: []common.Address{TestAdminAddr}, - ManagerAddresses: []common.Address{TestManagerAddr}, - EnabledAddresses: []common.Address{TestEnabledAddr}, - }), - Other: mkConfigWithAllowList(module, &AllowListConfig{ - AdminAddresses: []common.Address{TestAdminAddr}, - ManagerAddresses: []common.Address{TestManagerAddr}, - EnabledAddresses: []common.Address{TestEnabledAddr}, - }), - Expected: true, - }, - } -} - -func VerifyPrecompileWithAllowListTests(t *testing.T, module modules.Module, verifyTests map[string]testutils.ConfigVerifyTest) { - t.Helper() - tests := AllowListConfigVerifyTests(t, module) - // Add the contract specific tests to the map of tests to run. - for name, test := range verifyTests { - if _, exists := tests[name]; exists { - t.Fatalf("duplicate test name: %s", name) - } - tests[name] = test - } - - testutils.RunVerifyTests(t, tests) -} - -func EqualPrecompileWithAllowListTests(t *testing.T, module modules.Module, equalTests map[string]testutils.ConfigEqualTest) { - t.Helper() - tests := AllowListConfigEqualTests(t, module) - // Add the contract specific tests to the map of tests to run. - for name, test := range equalTests { - if _, exists := tests[name]; exists { - t.Fatalf("duplicate test name: %s", name) - } - tests[name] = test - } - - testutils.RunEqualTests(t, tests) -} diff --git a/precompile/allowlist/unpack_pack_test.go b/precompile/allowlist/unpack_pack_test.go deleted file mode 100644 index 5d39c35d86..0000000000 --- a/precompile/allowlist/unpack_pack_test.go +++ /dev/null @@ -1,243 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package allowlist - -import ( - "fmt" - "testing" - - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/stretchr/testify/require" -) - -var ( - setAdminSignature = contract.CalculateFunctionSelector("setAdmin(address)") - setManagerSignature = contract.CalculateFunctionSelector("setManager(address)") - setEnabledSignature = contract.CalculateFunctionSelector("setEnabled(address)") - setNoneSignature = contract.CalculateFunctionSelector("setNone(address)") - readAllowListSignature = contract.CalculateFunctionSelector("readAllowList(address)") -) - -func TestFunctionSignatures(t *testing.T) { - require := require.New(t) - setAdminABI := AllowListABI.Methods["setAdmin"] - require.Equal(setAdminSignature, setAdminABI.ID) - - setManagerABI := AllowListABI.Methods["setManager"] - require.Equal(setManagerSignature, setManagerABI.ID) - - setEnabledABI := AllowListABI.Methods["setEnabled"] - require.Equal(setEnabledSignature, setEnabledABI.ID) - - setNoneABI := AllowListABI.Methods["setNone"] - require.Equal(setNoneSignature, setNoneABI.ID) - - readAllowlistABI := AllowListABI.Methods["readAllowList"] - require.Equal(readAllowListSignature, readAllowlistABI.ID) -} - -func FuzzPackReadAllowlistTest(f *testing.F) { - f.Add(common.Address{}.Bytes()) - key, err := crypto.GenerateKey() - require.NoError(f, err) - addr := crypto.PubkeyToAddress(key.PublicKey) - f.Add(addr.Bytes()) - f.Fuzz(func(t *testing.T, b []byte) { - testPackReadAllowlistTest(t, common.BytesToAddress(b)) - }) -} - -func FuzzPackReadAllowlistTestSkipCheck(f *testing.F) { - f.Fuzz(func(t *testing.T, b []byte) { - require := require.New(t) - res, err := UnpackReadAllowListInput(b, true) - oldRes, oldErr := OldUnpackReadAllowList(b) - if oldErr != nil { - require.ErrorContains(err, oldErr.Error()) - } else { - require.NoError(err) - } - require.Equal(oldRes, res) - }) -} - -func TestPackReadAllowlistTest(f *testing.T) { - testPackReadAllowlistTest(f, common.Address{}) -} - -func testPackReadAllowlistTest(t *testing.T, address common.Address) { - t.Helper() - require := require.New(t) - t.Run(fmt.Sprintf("TestPackReadAllowlistTest, address %v", address), func(t *testing.T) { - // use new Pack/Unpack methods - input, err := PackReadAllowList(address) - require.NoError(err) - // exclude 4 bytes for function selector - input = input[4:] - unpacked, err := UnpackReadAllowListInput(input, true) - require.NoError(err) - require.Equal(address, unpacked) - - // use old Pack/Unpack methods - input = OldPackReadAllowList(address) - // exclude 4 bytes for function selector - input = input[4:] - require.NoError(err) - unpacked, err = OldUnpackReadAllowList(input) - require.NoError(err) - require.Equal(address, unpacked) - - // now mix and match old and new methods - input, err = PackReadAllowList(address) - require.NoError(err) - // exclude 4 bytes for function selector - input = input[4:] - input2 := OldPackReadAllowList(address) - // exclude 4 bytes for function selector - input2 = input2[4:] - require.Equal(input, input2) - unpacked, err = UnpackReadAllowListInput(input2, true) - require.NoError(err) - unpacked2, err := OldUnpackReadAllowList(input) - require.NoError(err) - require.Equal(unpacked, unpacked2) - }) -} - -func FuzzPackModifyAllowListTest(f *testing.F) { - f.Add(common.Address{}.Bytes(), uint(0)) - key, err := crypto.GenerateKey() - require.NoError(f, err) - addr := crypto.PubkeyToAddress(key.PublicKey) - f.Add(addr.Bytes(), uint(0)) - f.Fuzz(func(t *testing.T, b []byte, roleIndex uint) { - testPackModifyAllowListTest(t, common.BytesToAddress(b), getRole(roleIndex)) - }) -} - -func FuzzPackModifyAllowlistTestSkipCheck(f *testing.F) { - f.Fuzz(func(t *testing.T, b []byte) { - require := require.New(t) - res, err := UnpackModifyAllowListInput(b, AdminRole, true) - oldRes, oldErr := OldUnpackModifyAllowList(b, AdminRole) - if oldErr != nil { - require.ErrorContains(err, oldErr.Error()) - } else { - require.NoError(err) - } - require.Equal(oldRes, res) - }) -} - -func testPackModifyAllowListTest(t *testing.T, address common.Address, role Role) { - t.Helper() - require := require.New(t) - t.Run(fmt.Sprintf("TestPackModifyAllowlistTest, address %v, role %s", address, role.String()), func(t *testing.T) { - // use new Pack/Unpack methods - input, err := PackModifyAllowList(address, role) - require.NoError(err) - // exclude 4 bytes for function selector - input = input[4:] - unpacked, err := UnpackModifyAllowListInput(input, role, true) - require.NoError(err) - require.Equal(address, unpacked) - - // use old Pack/Unpack methods - input, err = OldPackModifyAllowList(address, role) - require.NoError(err) - // exclude 4 bytes for function selector - input = input[4:] - require.NoError(err) - - unpacked, err = OldUnpackModifyAllowList(input, role) - require.NoError(err) - - require.Equal(address, unpacked) - - // now mix and match new and old methods - input, err = PackModifyAllowList(address, role) - require.NoError(err) - // exclude 4 bytes for function selector - input = input[4:] - input2, err := OldPackModifyAllowList(address, role) - require.NoError(err) - // exclude 4 bytes for function selector - input2 = input2[4:] - require.Equal(input, input2) - unpacked, err = UnpackModifyAllowListInput(input2, role, true) - require.NoError(err) - unpacked2, err := OldUnpackModifyAllowList(input, role) - require.NoError(err) - require.Equal(unpacked, unpacked2) - }) -} - -func FuzzPackReadAllowListOutputTest(f *testing.F) { - f.Fuzz(func(t *testing.T, roleIndex uint) { - role := getRole(roleIndex) - packedOutput, err := PackReadAllowListOutput(role.Big()) - require.NoError(t, err) - require.Equal(t, packedOutput, role.Bytes()) - }) -} - -func OldPackReadAllowList(address common.Address) []byte { - input := make([]byte, 0, contract.SelectorLen+common.HashLength) - input = append(input, readAllowListSignature...) - input = append(input, common.BytesToHash(address[:]).Bytes()...) - return input -} - -func OldUnpackReadAllowList(input []byte) (common.Address, error) { - if len(input) != allowListInputLen { - return common.Address{}, fmt.Errorf("invalid input length for read allow list: %d", len(input)) - } - return common.BytesToAddress(input), nil -} - -func OldPackModifyAllowList(address common.Address, role Role) ([]byte, error) { - // function selector (4 bytes) + hash for address - input := make([]byte, 0, contract.SelectorLen+common.HashLength) - - switch role { - case AdminRole: - input = append(input, setAdminSignature...) - case ManagerRole: - input = append(input, setManagerSignature...) - case EnabledRole: - input = append(input, setEnabledSignature...) - case NoRole: - input = append(input, setNoneSignature...) - default: - return nil, fmt.Errorf("cannot pack modify list input with invalid role: %s", role) - } - - input = append(input, common.BytesToHash(address[:]).Bytes()...) - return input, nil -} - -func OldUnpackModifyAllowList(input []byte, role Role) (common.Address, error) { - if len(input) != allowListInputLen { - return common.Address{}, fmt.Errorf("invalid input length for modifying allow list: %d", len(input)) - } - return common.BytesToAddress(input), nil -} - -func getRole(roleIndex uint) Role { - index := roleIndex % 4 - switch index { - case 0: - return NoRole - case 1: - return EnabledRole - case 2: - return AdminRole - case 3: - return ManagerRole - default: - panic("unknown role") - } -} diff --git a/precompile/contract/interfaces.go b/precompile/contract/interfaces.go index 5ac6baa486..975220d00f 100644 --- a/precompile/contract/interfaces.go +++ b/precompile/contract/interfaces.go @@ -28,6 +28,7 @@ type StateDB interface { GetBalance(common.Address) *big.Int AddBalance(common.Address, *big.Int) + GetBalanceMultiCoin(common.Address, common.Hash) *big.Int CreateAccount(common.Address) Exist(common.Address) bool @@ -49,6 +50,7 @@ type AccessibleState interface { GetBlockContext() BlockContext GetSnowContext() *snow.Context GetChainConfig() precompileconfig.ChainConfig + NativeAssetCall(caller common.Address, input []byte, suppliedGas uint64, gasCost uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) } // ConfigurationBlockContext defines the interface required to configure a precompile. diff --git a/precompile/contract/mocks.go b/precompile/contract/mocks.go index 6510d2d738..19aa5b5881 100644 --- a/precompile/contract/mocks.go +++ b/precompile/contract/mocks.go @@ -163,6 +163,22 @@ func (mr *MockAccessibleStateMockRecorder) GetStateDB() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetStateDB", reflect.TypeOf((*MockAccessibleState)(nil).GetStateDB)) } +// NativeAssetCall mocks base method. +func (m *MockAccessibleState) NativeAssetCall(arg0 common.Address, arg1 []byte, arg2, arg3 uint64, arg4 bool) ([]byte, uint64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NativeAssetCall", arg0, arg1, arg2, arg3, arg4) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(uint64) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// NativeAssetCall indicates an expected call of NativeAssetCall. +func (mr *MockAccessibleStateMockRecorder) NativeAssetCall(arg0, arg1, arg2, arg3, arg4 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NativeAssetCall", reflect.TypeOf((*MockAccessibleState)(nil).NativeAssetCall), arg0, arg1, arg2, arg3, arg4) +} + // MockStateDB is a mock of StateDB interface. type MockStateDB struct { ctrl *gomock.Controller @@ -250,6 +266,20 @@ func (mr *MockStateDBMockRecorder) GetBalance(arg0 any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBalance", reflect.TypeOf((*MockStateDB)(nil).GetBalance), arg0) } +// GetBalanceMultiCoin mocks base method. +func (m *MockStateDB) GetBalanceMultiCoin(arg0 common.Address, arg1 common.Hash) *big.Int { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBalanceMultiCoin", arg0, arg1) + ret0, _ := ret[0].(*big.Int) + return ret0 +} + +// GetBalanceMultiCoin indicates an expected call of GetBalanceMultiCoin. +func (mr *MockStateDBMockRecorder) GetBalanceMultiCoin(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBalanceMultiCoin", reflect.TypeOf((*MockStateDB)(nil).GetBalanceMultiCoin), arg0, arg1) +} + // GetLogData mocks base method. func (m *MockStateDB) GetLogData() ([][]common.Hash, [][]byte) { m.ctrl.T.Helper() diff --git a/precompile/contract/test_utils.go b/precompile/contract/test_utils.go deleted file mode 100644 index cf38b5db77..0000000000 --- a/precompile/contract/test_utils.go +++ /dev/null @@ -1,49 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package contract - -import ( - "fmt" - - "github.com/ethereum/go-ethereum/common" -) - -// PackOrderedHashesWithSelector packs the function selector and ordered list of hashes into [dst] -// byte slice. -// assumes that [dst] has sufficient room for [functionSelector] and [hashes]. -// Kept for testing backwards compatibility. -func PackOrderedHashesWithSelector(dst []byte, functionSelector []byte, hashes []common.Hash) error { - copy(dst[:len(functionSelector)], functionSelector) - return PackOrderedHashes(dst[len(functionSelector):], hashes) -} - -// PackOrderedHashes packs the ordered list of [hashes] into the [dst] byte buffer. -// assumes that [dst] has sufficient space to pack [hashes] or else this function will panic. -// Kept for testing backwards compatibility. -func PackOrderedHashes(dst []byte, hashes []common.Hash) error { - if len(dst) != len(hashes)*common.HashLength { - return fmt.Errorf("destination byte buffer has insufficient length (%d) for %d hashes", len(dst), len(hashes)) - } - - var ( - start = 0 - end = common.HashLength - ) - for _, hash := range hashes { - copy(dst[start:end], hash.Bytes()) - start += common.HashLength - end += common.HashLength - } - return nil -} - -// PackedHash returns packed the byte slice with common.HashLength from [packed] -// at the given [index]. -// Assumes that [packed] is composed entirely of packed 32 byte segments. -// Kept for testing backwards compatibility. -func PackedHash(packed []byte, index int) []byte { - start := common.HashLength * index - end := start + common.HashLength - return packed[start:end] -} diff --git a/precompile/contract/utils.go b/precompile/contract/utils.go index 4b7eff94bb..60752a2177 100644 --- a/precompile/contract/utils.go +++ b/precompile/contract/utils.go @@ -58,7 +58,3 @@ func ParseABI(rawABI string) abi.ABI { return parsed } - -func IsDurangoActivated(evm AccessibleState) bool { - return evm.GetChainConfig().IsDurango(evm.GetBlockContext().Timestamp()) -} diff --git a/precompile/contracts/deployerallowlist/config.go b/precompile/contracts/deployerallowlist/config.go deleted file mode 100644 index a588101dc3..0000000000 --- a/precompile/contracts/deployerallowlist/config.go +++ /dev/null @@ -1,59 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package deployerallowlist - -import ( - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - "github.com/ethereum/go-ethereum/common" -) - -var _ precompileconfig.Config = &Config{} - -// Config contains the configuration for the ContractDeployerAllowList precompile, -// consisting of the initial allowlist and the timestamp for the network upgrade. -type Config struct { - allowlist.AllowListConfig - precompileconfig.Upgrade -} - -// NewConfig returns a config for a network upgrade at [blockTimestamp] that enables -// ContractDeployerAllowList with [admins], [enableds] and [managers] as members of the allowlist. -func NewConfig(blockTimestamp *uint64, admins []common.Address, enableds []common.Address, managers []common.Address) *Config { - return &Config{ - AllowListConfig: allowlist.AllowListConfig{ - AdminAddresses: admins, - EnabledAddresses: enableds, - ManagerAddresses: managers, - }, - Upgrade: precompileconfig.Upgrade{BlockTimestamp: blockTimestamp}, - } -} - -// NewDisableConfig returns config for a network upgrade at [blockTimestamp] -// that disables ContractDeployerAllowList. -func NewDisableConfig(blockTimestamp *uint64) *Config { - return &Config{ - Upgrade: precompileconfig.Upgrade{ - BlockTimestamp: blockTimestamp, - Disable: true, - }, - } -} - -func (*Config) Key() string { return ConfigKey } - -// Equal returns true if [cfg] is a [*ContractDeployerAllowListConfig] and it has been configured identical to [c]. -func (c *Config) Equal(cfg precompileconfig.Config) bool { - // typecast before comparison - other, ok := (cfg).(*Config) - if !ok { - return false - } - return c.Upgrade.Equal(&other.Upgrade) && c.AllowListConfig.Equal(&other.AllowListConfig) -} - -func (c *Config) Verify(chainConfig precompileconfig.ChainConfig) error { - return c.AllowListConfig.Verify(chainConfig, c.Upgrade) -} diff --git a/precompile/contracts/deployerallowlist/config_test.go b/precompile/contracts/deployerallowlist/config_test.go deleted file mode 100644 index f0ad2bddf7..0000000000 --- a/precompile/contracts/deployerallowlist/config_test.go +++ /dev/null @@ -1,48 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package deployerallowlist - -import ( - "testing" - - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - "github.com/ava-labs/subnet-evm/precompile/testutils" - "github.com/ava-labs/subnet-evm/utils" - "github.com/ethereum/go-ethereum/common" - "go.uber.org/mock/gomock" -) - -func TestVerify(t *testing.T) { - allowlist.VerifyPrecompileWithAllowListTests(t, Module, nil) -} - -func TestEqual(t *testing.T) { - admins := []common.Address{allowlist.TestAdminAddr} - enableds := []common.Address{allowlist.TestEnabledAddr} - managers := []common.Address{allowlist.TestManagerAddr} - tests := map[string]testutils.ConfigEqualTest{ - "non-nil config and nil other": { - Config: NewConfig(utils.NewUint64(3), admins, enableds, managers), - Other: nil, - Expected: false, - }, - "different type": { - Config: NewConfig(nil, nil, nil, nil), - Other: precompileconfig.NewMockConfig(gomock.NewController(t)), - Expected: false, - }, - "different timestamp": { - Config: NewConfig(utils.NewUint64(3), admins, enableds, managers), - Other: NewConfig(utils.NewUint64(4), admins, enableds, managers), - Expected: false, - }, - "same config": { - Config: NewConfig(utils.NewUint64(3), admins, enableds, managers), - Other: NewConfig(utils.NewUint64(3), admins, enableds, managers), - Expected: true, - }, - } - allowlist.EqualPrecompileWithAllowListTests(t, Module, tests) -} diff --git a/precompile/contracts/deployerallowlist/contract.go b/precompile/contracts/deployerallowlist/contract.go deleted file mode 100644 index bb4b97e95b..0000000000 --- a/precompile/contracts/deployerallowlist/contract.go +++ /dev/null @@ -1,26 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package deployerallowlist - -import ( - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ethereum/go-ethereum/common" -) - -// Singleton StatefulPrecompiledContract for W/R access to the contract deployer allow list. -var ContractDeployerAllowListPrecompile contract.StatefulPrecompiledContract = allowlist.CreateAllowListPrecompile(ContractAddress) - -// GetContractDeployerAllowListStatus returns the role of [address] for the contract deployer -// allow list. -func GetContractDeployerAllowListStatus(stateDB contract.StateDB, address common.Address) allowlist.Role { - return allowlist.GetAllowListStatus(stateDB, ContractAddress, address) -} - -// SetContractDeployerAllowListStatus sets the permissions of [address] to [role] for the -// contract deployer allow list. -// assumes [role] has already been verified as valid. -func SetContractDeployerAllowListStatus(stateDB contract.StateDB, address common.Address, role allowlist.Role) { - allowlist.SetAllowListRole(stateDB, ContractAddress, address, role) -} diff --git a/precompile/contracts/deployerallowlist/contract_test.go b/precompile/contracts/deployerallowlist/contract_test.go deleted file mode 100644 index d5037444a5..0000000000 --- a/precompile/contracts/deployerallowlist/contract_test.go +++ /dev/null @@ -1,19 +0,0 @@ -// (c) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package deployerallowlist - -import ( - "testing" - - "github.com/ava-labs/subnet-evm/core/state" - "github.com/ava-labs/subnet-evm/precompile/allowlist" -) - -func TestContractDeployerAllowListRun(t *testing.T) { - allowlist.RunPrecompileWithAllowListTests(t, Module, state.NewTestStateDB, nil) -} - -func BenchmarkContractDeployerAllowList(b *testing.B) { - allowlist.BenchPrecompileWithAllowList(b, Module, state.NewTestStateDB, nil) -} diff --git a/precompile/contracts/deployerallowlist/module.go b/precompile/contracts/deployerallowlist/module.go deleted file mode 100644 index 17f7431ab0..0000000000 --- a/precompile/contracts/deployerallowlist/module.go +++ /dev/null @@ -1,52 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package deployerallowlist - -import ( - "fmt" - - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ava-labs/subnet-evm/precompile/modules" - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - "github.com/ethereum/go-ethereum/common" -) - -var _ contract.Configurator = &configurator{} - -// ConfigKey is the key used in json config files to specify this precompile config. -// must be unique across all precompiles. -const ConfigKey = "contractDeployerAllowListConfig" - -var ContractAddress = common.HexToAddress("0x0200000000000000000000000000000000000000") - -var Module = modules.Module{ - ConfigKey: ConfigKey, - Address: ContractAddress, - Contract: ContractDeployerAllowListPrecompile, - Configurator: &configurator{}, -} - -type configurator struct{} - -func init() { - if err := modules.RegisterModule(Module); err != nil { - panic(err) - } -} - -// MakeConfig returns a new precompile config instance. -// This is required to Marshal/Unmarshal the precompile config. -func (*configurator) MakeConfig() precompileconfig.Config { - return new(Config) -} - -// Configure configures [state] with the given [cfg] precompileconfig. -// This function is called by the EVM once per precompile contract activation. -func (c *configurator) Configure(chainConfig precompileconfig.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, blockContext contract.ConfigurationBlockContext) error { - config, ok := cfg.(*Config) - if !ok { - return fmt.Errorf("expected config type %T, got %T: %v", &Config{}, cfg, cfg) - } - return config.AllowListConfig.Configure(chainConfig, ContractAddress, state, blockContext) -} diff --git a/precompile/contracts/feemanager/config.go b/precompile/contracts/feemanager/config.go deleted file mode 100644 index 9dcfc307d2..0000000000 --- a/precompile/contracts/feemanager/config.go +++ /dev/null @@ -1,82 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package feemanager - -import ( - "github.com/ava-labs/subnet-evm/commontype" - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - "github.com/ethereum/go-ethereum/common" -) - -var _ precompileconfig.Config = &Config{} - -// Config implements the StatefulPrecompileConfig interface while adding in the -// FeeManager specific precompile config. -type Config struct { - allowlist.AllowListConfig // Config for the fee config manager allow list - precompileconfig.Upgrade - InitialFeeConfig *commontype.FeeConfig `json:"initialFeeConfig,omitempty"` // initial fee config to be immediately activated -} - -// NewConfig returns a config for a network upgrade at [blockTimestamp] that enables -// FeeManager with the given [admins], [enableds] and [managers] as members of the -// allowlist with [initialConfig] as initial fee config if specified. -func NewConfig(blockTimestamp *uint64, admins []common.Address, enableds []common.Address, managers []common.Address, initialConfig *commontype.FeeConfig) *Config { - return &Config{ - AllowListConfig: allowlist.AllowListConfig{ - AdminAddresses: admins, - EnabledAddresses: enableds, - ManagerAddresses: managers, - }, - Upgrade: precompileconfig.Upgrade{BlockTimestamp: blockTimestamp}, - InitialFeeConfig: initialConfig, - } -} - -// NewDisableConfig returns config for a network upgrade at [blockTimestamp] -// that disables FeeManager. -func NewDisableConfig(blockTimestamp *uint64) *Config { - return &Config{ - Upgrade: precompileconfig.Upgrade{ - BlockTimestamp: blockTimestamp, - Disable: true, - }, - } -} - -// Key returns the key for the FeeManager precompileconfig. -// This should be the same key as used in the precompile module. -func (*Config) Key() string { return ConfigKey } - -// Equal returns true if [cfg] is a [*FeeManagerConfig] and it has been configured identical to [c]. -func (c *Config) Equal(cfg precompileconfig.Config) bool { - // typecast before comparison - other, ok := (cfg).(*Config) - if !ok { - return false - } - eq := c.Upgrade.Equal(&other.Upgrade) && c.AllowListConfig.Equal(&other.AllowListConfig) - if !eq { - return false - } - - if c.InitialFeeConfig == nil { - return other.InitialFeeConfig == nil - } - - return c.InitialFeeConfig.Equal(other.InitialFeeConfig) -} - -// Verify tries to verify Config and returns an error accordingly. -func (c *Config) Verify(chainConfig precompileconfig.ChainConfig) error { - if err := c.AllowListConfig.Verify(chainConfig, c.Upgrade); err != nil { - return err - } - if c.InitialFeeConfig == nil { - return nil - } - - return c.InitialFeeConfig.Verify() -} diff --git a/precompile/contracts/feemanager/config_test.go b/precompile/contracts/feemanager/config_test.go deleted file mode 100644 index 4182ec4716..0000000000 --- a/precompile/contracts/feemanager/config_test.go +++ /dev/null @@ -1,90 +0,0 @@ -// (c) 2022 Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package feemanager - -import ( - "math/big" - "testing" - - "github.com/ava-labs/subnet-evm/commontype" - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - "github.com/ava-labs/subnet-evm/precompile/testutils" - "github.com/ava-labs/subnet-evm/utils" - "github.com/ethereum/go-ethereum/common" - "go.uber.org/mock/gomock" -) - -var validFeeConfig = commontype.FeeConfig{ - GasLimit: big.NewInt(8_000_000), - TargetBlockRate: 2, // in seconds - - MinBaseFee: big.NewInt(25_000_000_000), - TargetGas: big.NewInt(15_000_000), - BaseFeeChangeDenominator: big.NewInt(36), - - MinBlockGasCost: big.NewInt(0), - MaxBlockGasCost: big.NewInt(1_000_000), - BlockGasCostStep: big.NewInt(200_000), -} - -func TestVerify(t *testing.T) { - admins := []common.Address{allowlist.TestAdminAddr} - invalidFeeConfig := validFeeConfig - invalidFeeConfig.GasLimit = big.NewInt(0) - tests := map[string]testutils.ConfigVerifyTest{ - "invalid initial fee manager config": { - Config: NewConfig(utils.NewUint64(3), admins, nil, nil, &invalidFeeConfig), - ExpectedError: "gasLimit = 0 cannot be less than or equal to 0", - }, - "nil initial fee manager config": { - Config: NewConfig(utils.NewUint64(3), admins, nil, nil, &commontype.FeeConfig{}), - ExpectedError: "gasLimit cannot be nil", - }, - } - allowlist.VerifyPrecompileWithAllowListTests(t, Module, tests) -} - -func TestEqual(t *testing.T) { - admins := []common.Address{allowlist.TestAdminAddr} - enableds := []common.Address{allowlist.TestEnabledAddr} - tests := map[string]testutils.ConfigEqualTest{ - "non-nil config and nil other": { - Config: NewConfig(utils.NewUint64(3), admins, enableds, nil, nil), - Other: nil, - Expected: false, - }, - "different type": { - Config: NewConfig(utils.NewUint64(3), admins, enableds, nil, nil), - Other: precompileconfig.NewMockConfig(gomock.NewController(t)), - Expected: false, - }, - "different timestamp": { - Config: NewConfig(utils.NewUint64(3), admins, nil, nil, nil), - Other: NewConfig(utils.NewUint64(4), admins, nil, nil, nil), - Expected: false, - }, - "non-nil initial config and nil initial config": { - Config: NewConfig(utils.NewUint64(3), admins, nil, nil, &validFeeConfig), - Other: NewConfig(utils.NewUint64(3), admins, nil, nil, nil), - Expected: false, - }, - "different initial config": { - Config: NewConfig(utils.NewUint64(3), admins, nil, nil, &validFeeConfig), - Other: NewConfig(utils.NewUint64(3), admins, nil, nil, - func() *commontype.FeeConfig { - c := validFeeConfig - c.GasLimit = big.NewInt(123) - return &c - }()), - Expected: false, - }, - "same config": { - Config: NewConfig(utils.NewUint64(3), admins, nil, nil, &validFeeConfig), - Other: NewConfig(utils.NewUint64(3), admins, nil, nil, &validFeeConfig), - Expected: true, - }, - } - allowlist.EqualPrecompileWithAllowListTests(t, Module, tests) -} diff --git a/precompile/contracts/feemanager/contract.abi b/precompile/contracts/feemanager/contract.abi deleted file mode 100644 index 0e49755fbf..0000000000 --- a/precompile/contracts/feemanager/contract.abi +++ /dev/null @@ -1,291 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "gasLimit", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "targetBlockRate", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "minBaseFee", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "targetGas", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "baseFeeChangeDenominator", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "minBlockGasCost", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "maxBlockGasCost", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "blockGasCostStep", - "type": "uint256" - } - ], - "indexed": false, - "internalType": "struct IFeeManager.FeeConfig", - "name": "oldFeeConfig", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "gasLimit", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "targetBlockRate", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "minBaseFee", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "targetGas", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "baseFeeChangeDenominator", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "minBlockGasCost", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "maxBlockGasCost", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "blockGasCostStep", - "type": "uint256" - } - ], - "indexed": false, - "internalType": "struct IFeeManager.FeeConfig", - "name": "newFeeConfig", - "type": "tuple" - } - ], - "name": "FeeConfigChanged", - "type": "event" - }, - { - "inputs": [], - "name": "getFeeConfig", - "outputs": [ - { - "internalType": "uint256", - "name": "gasLimit", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "targetBlockRate", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "minBaseFee", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "targetGas", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "baseFeeChangeDenominator", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "minBlockGasCost", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "maxBlockGasCost", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "blockGasCostStep", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "getFeeConfigLastChangedAt", - "outputs": [ - { - "internalType": "uint256", - "name": "blockNumber", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "readAllowList", - "outputs": [ - { - "internalType": "uint256", - "name": "role", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "setAdmin", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "setEnabled", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "gasLimit", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "targetBlockRate", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "minBaseFee", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "targetGas", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "baseFeeChangeDenominator", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "minBlockGasCost", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "maxBlockGasCost", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "blockGasCostStep", - "type": "uint256" - } - ], - "name": "setFeeConfig", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "setManager", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "setNone", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/precompile/contracts/feemanager/contract.go b/precompile/contracts/feemanager/contract.go deleted file mode 100644 index 2e7cad7ec5..0000000000 --- a/precompile/contracts/feemanager/contract.go +++ /dev/null @@ -1,399 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package feemanager - -import ( - _ "embed" - "errors" - "fmt" - "math/big" - - "github.com/ava-labs/subnet-evm/accounts/abi" - "github.com/ava-labs/subnet-evm/commontype" - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ava-labs/subnet-evm/vmerrs" - "github.com/ethereum/go-ethereum/common" -) - -const ( - minFeeConfigFieldKey = iota + 1 - // add new fields below this - // must preserve order of these fields - gasLimitKey = iota - targetBlockRateKey - minBaseFeeKey - targetGasKey - baseFeeChangeDenominatorKey - minBlockGasCostKey - maxBlockGasCostKey - blockGasCostStepKey - // add new fields above this - numFeeConfigField = iota - 1 - - // [numFeeConfigField] fields in FeeConfig struct - feeConfigInputLen = common.HashLength * numFeeConfigField - - SetFeeConfigGasCost uint64 = contract.WriteGasCostPerSlot * (numFeeConfigField + 1) // plus one for setting last changed at - GetFeeConfigGasCost uint64 = contract.ReadGasCostPerSlot * numFeeConfigField - GetLastChangedAtGasCost uint64 = contract.ReadGasCostPerSlot -) - -var ( - - // Singleton StatefulPrecompiledContract for setting fee configs by permissioned callers. - FeeManagerPrecompile contract.StatefulPrecompiledContract = createFeeManagerPrecompile() - - feeConfigLastChangedAtKey = common.Hash{'l', 'c', 'a'} - - ErrCannotChangeFee = errors.New("non-enabled cannot change fee config") - ErrInvalidLen = errors.New("invalid input length for fee config Input") - - // IFeeManagerRawABI contains the raw ABI of FeeManager contract. - //go:embed contract.abi - FeeManagerRawABI string - - FeeManagerABI = contract.ParseABI(FeeManagerRawABI) -) - -// FeeConfigABIStruct is the ABI struct for FeeConfig type. -type FeeConfigABIStruct struct { - GasLimit *big.Int - TargetBlockRate *big.Int - MinBaseFee *big.Int - TargetGas *big.Int - BaseFeeChangeDenominator *big.Int - MinBlockGasCost *big.Int - MaxBlockGasCost *big.Int - BlockGasCostStep *big.Int -} - -// GetFeeManagerStatus returns the role of [address] for the fee config manager list. -func GetFeeManagerStatus(stateDB contract.StateDB, address common.Address) allowlist.Role { - return allowlist.GetAllowListStatus(stateDB, ContractAddress, address) -} - -// SetFeeManagerStatus sets the permissions of [address] to [role] for the -// fee config manager list. assumes [role] has already been verified as valid. -func SetFeeManagerStatus(stateDB contract.StateDB, address common.Address, role allowlist.Role) { - allowlist.SetAllowListRole(stateDB, ContractAddress, address, role) -} - -// GetStoredFeeConfig returns fee config from contract storage in given state -func GetStoredFeeConfig(stateDB contract.StateDB) commontype.FeeConfig { - feeConfig := commontype.FeeConfig{} - for i := minFeeConfigFieldKey; i <= numFeeConfigField; i++ { - val := stateDB.GetState(ContractAddress, common.Hash{byte(i)}) - switch i { - case gasLimitKey: - feeConfig.GasLimit = new(big.Int).Set(val.Big()) - case targetBlockRateKey: - feeConfig.TargetBlockRate = val.Big().Uint64() - case minBaseFeeKey: - feeConfig.MinBaseFee = new(big.Int).Set(val.Big()) - case targetGasKey: - feeConfig.TargetGas = new(big.Int).Set(val.Big()) - case baseFeeChangeDenominatorKey: - feeConfig.BaseFeeChangeDenominator = new(big.Int).Set(val.Big()) - case minBlockGasCostKey: - feeConfig.MinBlockGasCost = new(big.Int).Set(val.Big()) - case maxBlockGasCostKey: - feeConfig.MaxBlockGasCost = new(big.Int).Set(val.Big()) - case blockGasCostStepKey: - feeConfig.BlockGasCostStep = new(big.Int).Set(val.Big()) - default: - // This should never encounter an unknown fee config key - panic(fmt.Sprintf("unknown fee config key: %d", i)) - } - } - return feeConfig -} - -func GetFeeConfigLastChangedAt(stateDB contract.StateDB) *big.Int { - val := stateDB.GetState(ContractAddress, feeConfigLastChangedAtKey) - return val.Big() -} - -// StoreFeeConfig stores given [feeConfig] and block number in the [blockContext] to the [stateDB]. -// A validation on [feeConfig] is done before storing. -func StoreFeeConfig(stateDB contract.StateDB, feeConfig commontype.FeeConfig, blockContext contract.ConfigurationBlockContext) error { - if err := feeConfig.Verify(); err != nil { - return fmt.Errorf("cannot verify fee config: %w", err) - } - - for i := minFeeConfigFieldKey; i <= numFeeConfigField; i++ { - var input common.Hash - switch i { - case gasLimitKey: - input = common.BigToHash(feeConfig.GasLimit) - case targetBlockRateKey: - input = common.BigToHash(new(big.Int).SetUint64(feeConfig.TargetBlockRate)) - case minBaseFeeKey: - input = common.BigToHash(feeConfig.MinBaseFee) - case targetGasKey: - input = common.BigToHash(feeConfig.TargetGas) - case baseFeeChangeDenominatorKey: - input = common.BigToHash(feeConfig.BaseFeeChangeDenominator) - case minBlockGasCostKey: - input = common.BigToHash(feeConfig.MinBlockGasCost) - case maxBlockGasCostKey: - input = common.BigToHash(feeConfig.MaxBlockGasCost) - case blockGasCostStepKey: - input = common.BigToHash(feeConfig.BlockGasCostStep) - default: - // This should never encounter an unknown fee config key - panic(fmt.Sprintf("unknown fee config key: %d", i)) - } - stateDB.SetState(ContractAddress, common.Hash{byte(i)}, input) - } - - blockNumber := blockContext.Number() - if blockNumber == nil { - return fmt.Errorf("blockNumber cannot be nil") - } - stateDB.SetState(ContractAddress, feeConfigLastChangedAtKey, common.BigToHash(blockNumber)) - return nil -} - -// PackSetFeeConfig packs [inputStruct] of type SetFeeConfigInput into the appropriate arguments for setFeeConfig. -func PackSetFeeConfig(input commontype.FeeConfig) ([]byte, error) { - inputStruct := FeeConfigABIStruct{ - GasLimit: input.GasLimit, - TargetBlockRate: new(big.Int).SetUint64(input.TargetBlockRate), - MinBaseFee: input.MinBaseFee, - TargetGas: input.TargetGas, - BaseFeeChangeDenominator: input.BaseFeeChangeDenominator, - MinBlockGasCost: input.MinBlockGasCost, - MaxBlockGasCost: input.MaxBlockGasCost, - BlockGasCostStep: input.BlockGasCostStep, - } - return FeeManagerABI.Pack("setFeeConfig", inputStruct.GasLimit, inputStruct.TargetBlockRate, inputStruct.MinBaseFee, inputStruct.TargetGas, inputStruct.BaseFeeChangeDenominator, inputStruct.MinBlockGasCost, inputStruct.MaxBlockGasCost, inputStruct.BlockGasCostStep) -} - -// UnpackSetFeeConfigInput attempts to unpack [input] as SetFeeConfigInput -// assumes that [input] does not include selector (omits first 4 func signature bytes) -// if [useStrictMode] is true, it will return an error if the length of [input] is not [feeConfigInputLen] -func UnpackSetFeeConfigInput(input []byte, useStrictMode bool) (commontype.FeeConfig, error) { - // Initially we had this check to ensure that the input was the correct length. - // However solidity does not always pack the input to the correct length, and allows - // for extra padding bytes to be added to the end of the input. Therefore, we have removed - // this check with the Durango. We still need to keep this check for backwards compatibility. - if useStrictMode && len(input) != feeConfigInputLen { - return commontype.FeeConfig{}, fmt.Errorf("%w: %d", ErrInvalidLen, len(input)) - } - inputStruct := FeeConfigABIStruct{} - err := FeeManagerABI.UnpackInputIntoInterface(&inputStruct, "setFeeConfig", input, useStrictMode) - if err != nil { - return commontype.FeeConfig{}, err - } - - result := commontype.FeeConfig{ - GasLimit: inputStruct.GasLimit, - TargetBlockRate: inputStruct.TargetBlockRate.Uint64(), - MinBaseFee: inputStruct.MinBaseFee, - TargetGas: inputStruct.TargetGas, - BaseFeeChangeDenominator: inputStruct.BaseFeeChangeDenominator, - MinBlockGasCost: inputStruct.MinBlockGasCost, - MaxBlockGasCost: inputStruct.MaxBlockGasCost, - BlockGasCostStep: inputStruct.BlockGasCostStep, - } - - return result, nil -} - -// setFeeConfig checks if the caller has permissions to set the fee config. -// The execution function parses [input] into FeeConfig structure and sets contract storage accordingly. -func setFeeConfig(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = contract.DeductGas(suppliedGas, SetFeeConfigGasCost); err != nil { - return nil, 0, err - } - - if readOnly { - return nil, remainingGas, vmerrs.ErrWriteProtection - } - - // do not use strict mode after Durango - useStrictMode := !contract.IsDurangoActivated(accessibleState) - feeConfig, err := UnpackSetFeeConfigInput(input, useStrictMode) - if err != nil { - return nil, remainingGas, err - } - - stateDB := accessibleState.GetStateDB() - // Verify that the caller is in the allow list and therefore has the right to call this function. - callerStatus := GetFeeManagerStatus(stateDB, caller) - if !callerStatus.IsEnabled() { - return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotChangeFee, caller) - } - - if contract.IsDurangoActivated(accessibleState) { - if remainingGas, err = contract.DeductGas(remainingGas, FeeConfigChangedEventGasCost); err != nil { - return nil, 0, err - } - oldConfig := GetStoredFeeConfig(stateDB) - topics, data, err := PackFeeConfigChangedEvent( - caller, - oldConfig, - feeConfig, - ) - if err != nil { - return nil, remainingGas, err - } - - stateDB.AddLog( - ContractAddress, - topics, - data, - accessibleState.GetBlockContext().Number().Uint64(), - ) - } - - if err := StoreFeeConfig(stateDB, feeConfig, accessibleState.GetBlockContext()); err != nil { - return nil, remainingGas, err - } - - // Return an empty output and the remaining gas - return []byte{}, remainingGas, nil -} - -// PackGetFeeConfig packs the include selector (first 4 func signature bytes). -// This function is mostly used for tests. -func PackGetFeeConfig() ([]byte, error) { - return FeeManagerABI.Pack("getFeeConfig") -} - -// PackGetFeeConfigOutput attempts to pack given [outputStruct] of type GetFeeConfigOutput -// to conform the ABI outputs. -func PackGetFeeConfigOutput(output commontype.FeeConfig) ([]byte, error) { - outputStruct := FeeConfigABIStruct{ - GasLimit: output.GasLimit, - TargetBlockRate: new(big.Int).SetUint64(output.TargetBlockRate), - MinBaseFee: output.MinBaseFee, - TargetGas: output.TargetGas, - BaseFeeChangeDenominator: output.BaseFeeChangeDenominator, - MinBlockGasCost: output.MinBlockGasCost, - MaxBlockGasCost: output.MaxBlockGasCost, - BlockGasCostStep: output.BlockGasCostStep, - } - return FeeManagerABI.PackOutput("getFeeConfig", - outputStruct.GasLimit, - outputStruct.TargetBlockRate, - outputStruct.MinBaseFee, - outputStruct.TargetGas, - outputStruct.BaseFeeChangeDenominator, - outputStruct.MinBlockGasCost, - outputStruct.MaxBlockGasCost, - outputStruct.BlockGasCostStep, - ) -} - -// UnpackGetFeeConfigOutput attempts to unpack [output] as GetFeeConfigOutput -// assumes that [output] does not include selector (omits first 4 func signature bytes) -func UnpackGetFeeConfigOutput(output []byte, skipLenCheck bool) (commontype.FeeConfig, error) { - if !skipLenCheck && len(output) != feeConfigInputLen { - return commontype.FeeConfig{}, fmt.Errorf("%w: %d", ErrInvalidLen, len(output)) - } - outputStruct := FeeConfigABIStruct{} - err := FeeManagerABI.UnpackIntoInterface(&outputStruct, "getFeeConfig", output) - - if err != nil { - return commontype.FeeConfig{}, err - } - - result := commontype.FeeConfig{ - GasLimit: outputStruct.GasLimit, - TargetBlockRate: outputStruct.TargetBlockRate.Uint64(), - MinBaseFee: outputStruct.MinBaseFee, - TargetGas: outputStruct.TargetGas, - BaseFeeChangeDenominator: outputStruct.BaseFeeChangeDenominator, - MinBlockGasCost: outputStruct.MinBlockGasCost, - MaxBlockGasCost: outputStruct.MaxBlockGasCost, - BlockGasCostStep: outputStruct.BlockGasCostStep, - } - return result, nil -} - -// getFeeConfig returns the stored fee config as an output. -// The execution function reads the contract state for the stored fee config and returns the output. -func getFeeConfig(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = contract.DeductGas(suppliedGas, GetFeeConfigGasCost); err != nil { - return nil, 0, err - } - - feeConfig := GetStoredFeeConfig(accessibleState.GetStateDB()) - - output, err := PackGetFeeConfigOutput(feeConfig) - if err != nil { - return nil, remainingGas, err - } - - // Return the fee config as output and the remaining gas - return output, remainingGas, err -} - -// PackGetFeeConfigLastChangedAt packs the include selector (first 4 func signature bytes). -// This function is mostly used for tests. -func PackGetFeeConfigLastChangedAt() ([]byte, error) { - return FeeManagerABI.Pack("getFeeConfigLastChangedAt") -} - -// PackGetFeeConfigLastChangedAtOutput attempts to pack given blockNumber of type *big.Int -// to conform the ABI outputs. -func PackGetFeeConfigLastChangedAtOutput(blockNumber *big.Int) ([]byte, error) { - return FeeManagerABI.PackOutput("getFeeConfigLastChangedAt", blockNumber) -} - -// UnpackGetFeeConfigLastChangedAtOutput attempts to unpack given [output] into the *big.Int type output -// assumes that [output] does not include selector (omits first 4 func signature bytes) -func UnpackGetFeeConfigLastChangedAtOutput(output []byte) (*big.Int, error) { - res, err := FeeManagerABI.Unpack("getFeeConfigLastChangedAt", output) - if err != nil { - return new(big.Int), err - } - unpacked := *abi.ConvertType(res[0], new(*big.Int)).(**big.Int) - return unpacked, nil -} - -// getFeeConfigLastChangedAt returns the block number that fee config was last changed in. -// The execution function reads the contract state for the stored block number and returns the output. -func getFeeConfigLastChangedAt(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = contract.DeductGas(suppliedGas, GetLastChangedAtGasCost); err != nil { - return nil, 0, err - } - - lastChangedAt := GetFeeConfigLastChangedAt(accessibleState.GetStateDB()) - packedOutput, err := PackGetFeeConfigLastChangedAtOutput(lastChangedAt) - if err != nil { - return nil, remainingGas, err - } - - return packedOutput, remainingGas, err -} - -// createFeeManagerPrecompile returns a StatefulPrecompiledContract with getters and setters for the precompile. -// Access to the getters/setters is controlled by an allow list for ContractAddress. -func createFeeManagerPrecompile() contract.StatefulPrecompiledContract { - var functions []*contract.StatefulPrecompileFunction - functions = append(functions, allowlist.CreateAllowListFunctions(ContractAddress)...) - - abiFunctionMap := map[string]contract.RunStatefulPrecompileFunc{ - "getFeeConfig": getFeeConfig, - "getFeeConfigLastChangedAt": getFeeConfigLastChangedAt, - "setFeeConfig": setFeeConfig, - } - - for name, function := range abiFunctionMap { - method, ok := FeeManagerABI.Methods[name] - if !ok { - panic(fmt.Errorf("given method (%s) does not exist in the ABI", name)) - } - functions = append(functions, contract.NewStatefulPrecompileFunction(method.ID, function)) - } - // Construct the contract with no fallback function. - statefulContract, err := contract.NewStatefulPrecompileContract(nil, functions) - if err != nil { - panic(err) - } - return statefulContract -} diff --git a/precompile/contracts/feemanager/contract_test.go b/precompile/contracts/feemanager/contract_test.go deleted file mode 100644 index ee4c6cf1f7..0000000000 --- a/precompile/contracts/feemanager/contract_test.go +++ /dev/null @@ -1,457 +0,0 @@ -// (c) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package feemanager - -import ( - "math/big" - "testing" - - "github.com/ava-labs/subnet-evm/commontype" - "github.com/ava-labs/subnet-evm/core/state" - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - "github.com/ava-labs/subnet-evm/precompile/testutils" - "github.com/ava-labs/subnet-evm/vmerrs" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -var ( - regressionBytes = "8f10b58600000000000000000000000000000000000000000000000000000000017d78400000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000012a05f20000000000000000000000000000000000000000000000000000000000047868c0000000000000000000000000000000000000000000000000000000000000005400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001bc16d674ec800000000000000000000000000000000000000000000000000000de0b6b3a764000000000000000000000000000000000000000000000000000000000000" - regressionFeeConfig = commontype.FeeConfig{ - GasLimit: big.NewInt(25000000), - TargetBlockRate: 2, - MinBaseFee: big.NewInt(5000000000), - TargetGas: big.NewInt(75000000), - BaseFeeChangeDenominator: big.NewInt(84), - MinBlockGasCost: big.NewInt(0), - MaxBlockGasCost: big.NewInt(2000000000000000000), - BlockGasCostStep: big.NewInt(1000000000000000000), - } - testFeeConfig = commontype.FeeConfig{ - GasLimit: big.NewInt(8_000_000), - TargetBlockRate: 2, // in seconds - - MinBaseFee: big.NewInt(25_000_000_000), - TargetGas: big.NewInt(15_000_000), - BaseFeeChangeDenominator: big.NewInt(36), - - MinBlockGasCost: big.NewInt(0), - MaxBlockGasCost: big.NewInt(1_000_000), - BlockGasCostStep: big.NewInt(200_000), - } - zeroFeeConfig = commontype.FeeConfig{ - GasLimit: new(big.Int), - MinBaseFee: new(big.Int), - TargetGas: new(big.Int), - BaseFeeChangeDenominator: new(big.Int), - - MinBlockGasCost: new(big.Int), - MaxBlockGasCost: new(big.Int), - BlockGasCostStep: new(big.Int), - } - testBlockNumber = big.NewInt(7) - tests = map[string]testutils.PrecompileTest{ - "set config from no role fails": { - Caller: allowlist.TestNoRoleAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackSetFeeConfig(testFeeConfig) - require.NoError(t, err) - - return input - }, - SuppliedGas: SetFeeConfigGasCost, - ReadOnly: false, - ExpectedErr: ErrCannotChangeFee.Error(), - }, - "set config from enabled address succeeds and emits logs": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackSetFeeConfig(testFeeConfig) - require.NoError(t, err) - - return input - }, - SuppliedGas: SetFeeConfigGasCost + FeeConfigChangedEventGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, state contract.StateDB) { - feeConfig := GetStoredFeeConfig(state) - require.Equal(t, testFeeConfig, feeConfig) - - logsTopics, logsData := state.GetLogData() - assertFeeEvent(t, logsTopics, logsData, allowlist.TestEnabledAddr, zeroFeeConfig, testFeeConfig) - }, - }, - "set config from manager succeeds": { - Caller: allowlist.TestManagerAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackSetFeeConfig(testFeeConfig) - require.NoError(t, err) - - return input - }, - SuppliedGas: SetFeeConfigGasCost + FeeConfigChangedEventGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, state contract.StateDB) { - feeConfig := GetStoredFeeConfig(state) - require.Equal(t, testFeeConfig, feeConfig) - - logsTopics, logsData := state.GetLogData() - assertFeeEvent(t, logsTopics, logsData, allowlist.TestManagerAddr, zeroFeeConfig, testFeeConfig) - }, - }, - "set invalid config from enabled address": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - feeConfig := testFeeConfig - feeConfig.MinBlockGasCost = new(big.Int).Mul(feeConfig.MaxBlockGasCost, common.Big2) - input, err := PackSetFeeConfig(feeConfig) - require.NoError(t, err) - - return input - }, - SuppliedGas: SetFeeConfigGasCost + FeeConfigChangedEventGasCost, - ReadOnly: false, - Config: &Config{ - InitialFeeConfig: &testFeeConfig, - }, - ExpectedErr: "cannot be greater than maxBlockGasCost", - AfterHook: func(t testing.TB, state contract.StateDB) { - feeConfig := GetStoredFeeConfig(state) - require.Equal(t, testFeeConfig, feeConfig) - }, - }, - "set config from admin address": { - Caller: allowlist.TestAdminAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackSetFeeConfig(testFeeConfig) - require.NoError(t, err) - - return input - }, - SuppliedGas: SetFeeConfigGasCost + FeeConfigChangedEventGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - SetupBlockContext: func(mbc *contract.MockBlockContext) { - mbc.EXPECT().Number().Return(testBlockNumber).AnyTimes() - mbc.EXPECT().Timestamp().Return(uint64(0)).AnyTimes() - }, - AfterHook: func(t testing.TB, state contract.StateDB) { - feeConfig := GetStoredFeeConfig(state) - require.Equal(t, testFeeConfig, feeConfig) - lastChangedAt := GetFeeConfigLastChangedAt(state) - require.EqualValues(t, testBlockNumber, lastChangedAt) - - logsTopics, logsData := state.GetLogData() - assertFeeEvent(t, logsTopics, logsData, allowlist.TestAdminAddr, zeroFeeConfig, testFeeConfig) - }, - }, - "get fee config from non-enabled address": { - Caller: allowlist.TestNoRoleAddr, - BeforeHook: func(t testing.TB, state contract.StateDB) { - blockContext := contract.NewMockBlockContext(gomock.NewController(t)) - blockContext.EXPECT().Number().Return(big.NewInt(6)).Times(1) - allowlist.SetDefaultRoles(Module.Address)(t, state) - err := StoreFeeConfig(state, testFeeConfig, blockContext) - require.NoError(t, err) - }, - InputFn: func(t testing.TB) []byte { - input, err := PackGetFeeConfig() - require.NoError(t, err) - - return input - }, - SuppliedGas: GetFeeConfigGasCost, - ReadOnly: true, - ExpectedRes: func() []byte { - res, err := PackGetFeeConfigOutput(testFeeConfig) - if err != nil { - panic(err) - } - return res - }(), - AfterHook: func(t testing.TB, state contract.StateDB) { - feeConfig := GetStoredFeeConfig(state) - lastChangedAt := GetFeeConfigLastChangedAt(state) - require.Equal(t, testFeeConfig, feeConfig) - require.EqualValues(t, big.NewInt(6), lastChangedAt) - }, - }, - "get initial fee config": { - Caller: allowlist.TestNoRoleAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackGetFeeConfig() - require.NoError(t, err) - - return input - }, - SuppliedGas: GetFeeConfigGasCost, - Config: &Config{ - InitialFeeConfig: &testFeeConfig, - }, - ReadOnly: true, - ExpectedRes: func() []byte { - res, err := PackGetFeeConfigOutput(testFeeConfig) - if err != nil { - panic(err) - } - return res - }(), - SetupBlockContext: func(mbc *contract.MockBlockContext) { - mbc.EXPECT().Number().Return(testBlockNumber) - }, - AfterHook: func(t testing.TB, state contract.StateDB) { - feeConfig := GetStoredFeeConfig(state) - lastChangedAt := GetFeeConfigLastChangedAt(state) - require.Equal(t, testFeeConfig, feeConfig) - require.EqualValues(t, testBlockNumber, lastChangedAt) - }, - }, - "get last changed at from non-enabled address": { - Caller: allowlist.TestNoRoleAddr, - BeforeHook: func(t testing.TB, state contract.StateDB) { - blockContext := contract.NewMockBlockContext(gomock.NewController(t)) - blockContext.EXPECT().Number().Return(testBlockNumber).Times(1) - allowlist.SetDefaultRoles(Module.Address)(t, state) - err := StoreFeeConfig(state, testFeeConfig, blockContext) - require.NoError(t, err) - }, - InputFn: func(t testing.TB) []byte { - input, err := PackGetFeeConfigLastChangedAt() - require.NoError(t, err) - - return input - }, - SuppliedGas: GetLastChangedAtGasCost, - ReadOnly: true, - ExpectedRes: func() []byte { - res, err := PackGetFeeConfigLastChangedAtOutput(testBlockNumber) - if err != nil { - panic(err) - } - return res - }(), - AfterHook: func(t testing.TB, state contract.StateDB) { - feeConfig := GetStoredFeeConfig(state) - lastChangedAt := GetFeeConfigLastChangedAt(state) - require.Equal(t, testFeeConfig, feeConfig) - require.Equal(t, testBlockNumber, lastChangedAt) - }, - }, - "readOnly setFeeConfig with noRole fails": { - Caller: allowlist.TestNoRoleAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackSetFeeConfig(testFeeConfig) - require.NoError(t, err) - - return input - }, - SuppliedGas: SetFeeConfigGasCost, - ReadOnly: true, - ExpectedErr: vmerrs.ErrWriteProtection.Error(), - }, - "readOnly setFeeConfig with allow role fails": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackSetFeeConfig(testFeeConfig) - require.NoError(t, err) - - return input - }, - SuppliedGas: SetFeeConfigGasCost, - ReadOnly: true, - ExpectedErr: vmerrs.ErrWriteProtection.Error(), - }, - "readOnly setFeeConfig with admin role fails": { - Caller: allowlist.TestAdminAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackSetFeeConfig(testFeeConfig) - require.NoError(t, err) - - return input - }, - SuppliedGas: SetFeeConfigGasCost, - ReadOnly: true, - ExpectedErr: vmerrs.ErrWriteProtection.Error(), - }, - "insufficient gas setFeeConfig from admin": { - Caller: allowlist.TestAdminAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackSetFeeConfig(testFeeConfig) - require.NoError(t, err) - - return input - }, - SuppliedGas: SetFeeConfigGasCost - 1, - ReadOnly: false, - ExpectedErr: vmerrs.ErrOutOfGas.Error(), - }, - "set config with extra padded bytes should fail before Durango": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackSetFeeConfig(testFeeConfig) - require.NoError(t, err) - - input = append(input, make([]byte, 32)...) - return input - }, - ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig { - config := precompileconfig.NewMockChainConfig(ctrl) - config.EXPECT().IsDurango(gomock.Any()).Return(false).AnyTimes() - return config - }, - SuppliedGas: SetFeeConfigGasCost, - ReadOnly: false, - ExpectedErr: ErrInvalidLen.Error(), - SetupBlockContext: func(mbc *contract.MockBlockContext) { - mbc.EXPECT().Number().Return(testBlockNumber).AnyTimes() - mbc.EXPECT().Timestamp().Return(uint64(0)).AnyTimes() - }, - }, - "set config with extra padded bytes should succeed with Durango": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackSetFeeConfig(testFeeConfig) - require.NoError(t, err) - - input = append(input, make([]byte, 32)...) - return input - }, - ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig { - config := precompileconfig.NewMockChainConfig(ctrl) - config.EXPECT().IsDurango(gomock.Any()).Return(true).AnyTimes() - return config - }, - SuppliedGas: SetFeeConfigGasCost + FeeConfigChangedEventGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - SetupBlockContext: func(mbc *contract.MockBlockContext) { - mbc.EXPECT().Number().Return(testBlockNumber).AnyTimes() - mbc.EXPECT().Timestamp().Return(uint64(0)).AnyTimes() - }, - AfterHook: func(t testing.TB, state contract.StateDB) { - feeConfig := GetStoredFeeConfig(state) - require.Equal(t, testFeeConfig, feeConfig) - lastChangedAt := GetFeeConfigLastChangedAt(state) - require.EqualValues(t, testBlockNumber, lastChangedAt) - - logsTopics, logsData := state.GetLogData() - assertFeeEvent(t, logsTopics, logsData, allowlist.TestEnabledAddr, zeroFeeConfig, testFeeConfig) - }, - }, - // from https://github.com/ava-labs/subnet-evm/issues/487 - "setFeeConfig regression test should fail before Durango": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - Input: common.Hex2Bytes(regressionBytes), - ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig { - config := precompileconfig.NewMockChainConfig(ctrl) - config.EXPECT().IsDurango(gomock.Any()).Return(false).AnyTimes() - return config - }, - SuppliedGas: SetFeeConfigGasCost, - ExpectedErr: ErrInvalidLen.Error(), - ReadOnly: false, - SetupBlockContext: func(mbc *contract.MockBlockContext) { - mbc.EXPECT().Number().Return(testBlockNumber).AnyTimes() - mbc.EXPECT().Timestamp().Return(uint64(0)).AnyTimes() - }, - }, - "setFeeConfig regression test should succeed after Durango": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - Input: common.Hex2Bytes(regressionBytes), - ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig { - config := precompileconfig.NewMockChainConfig(ctrl) - config.EXPECT().IsDurango(gomock.Any()).Return(true).AnyTimes() - return config - }, - SuppliedGas: SetFeeConfigGasCost + FeeConfigChangedEventGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - SetupBlockContext: func(mbc *contract.MockBlockContext) { - mbc.EXPECT().Number().Return(testBlockNumber).AnyTimes() - mbc.EXPECT().Timestamp().Return(uint64(0)).AnyTimes() - }, - AfterHook: func(t testing.TB, state contract.StateDB) { - feeConfig := GetStoredFeeConfig(state) - require.Equal(t, regressionFeeConfig, feeConfig) - lastChangedAt := GetFeeConfigLastChangedAt(state) - require.EqualValues(t, testBlockNumber, lastChangedAt) - - logsTopics, logsData := state.GetLogData() - assertFeeEvent(t, logsTopics, logsData, allowlist.TestEnabledAddr, zeroFeeConfig, regressionFeeConfig) - }, - }, - "set config should not emit event before Durango": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig { - config := precompileconfig.NewMockChainConfig(ctrl) - config.EXPECT().IsDurango(gomock.Any()).Return(false).AnyTimes() - return config - }, - InputFn: func(t testing.TB) []byte { - input, err := PackSetFeeConfig(testFeeConfig) - require.NoError(t, err) - return input - }, - SuppliedGas: SetFeeConfigGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, state contract.StateDB) { - logsTopics, logsData := state.GetLogData() - require.Len(t, logsTopics, 0) - require.Len(t, logsData, 0) - }, - }, - } -) - -func TestFeeManager(t *testing.T) { - allowlist.RunPrecompileWithAllowListTests(t, Module, state.NewTestStateDB, tests) -} - -func BenchmarkFeeManager(b *testing.B) { - allowlist.BenchPrecompileWithAllowList(b, Module, state.NewTestStateDB, tests) -} - -func assertFeeEvent( - t testing.TB, - logsTopics [][]common.Hash, - logsData [][]byte, - sender common.Address, - expectedOldFeeConfig commontype.FeeConfig, - expectedNewFeeConfig commontype.FeeConfig, -) { - require.Len(t, logsTopics, 1) - require.Len(t, logsData, 1) - - topics := logsTopics[0] - require.Len(t, topics, 2) - require.Equal(t, FeeManagerABI.Events["FeeConfigChanged"].ID, topics[0]) - require.Equal(t, common.BytesToHash(sender[:]), topics[1]) - - logData := logsData[0] - oldFeeConfig, resFeeConfig, err := UnpackFeeConfigChangedEventData(logData) - require.NoError(t, err) - require.True(t, expectedOldFeeConfig.Equal(&oldFeeConfig), "expected %v, got %v", expectedOldFeeConfig, oldFeeConfig) - require.True(t, expectedNewFeeConfig.Equal(&resFeeConfig), "expected %v, got %v", expectedNewFeeConfig, resFeeConfig) -} diff --git a/precompile/contracts/feemanager/event.go b/precompile/contracts/feemanager/event.go deleted file mode 100644 index f9ba375b51..0000000000 --- a/precompile/contracts/feemanager/event.go +++ /dev/null @@ -1,80 +0,0 @@ -// Code generated -// This file is a generated precompile contract config with stubbed abstract functions. -// The file is generated by a template. Please inspect every code and comment in this file before use. - -package feemanager - -import ( - "math/big" - - "github.com/ava-labs/subnet-evm/commontype" - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ethereum/go-ethereum/common" -) - -// FeeConfigChangedEventGasCost is the gas cost of a FeeConfigChanged event. -// It is the base gas cost + the gas cost of the topics (signature, sender) -// and the gas cost of the non-indexed data len(oldConfig) + len(newConfig). -const FeeConfigChangedEventGasCost = GetFeeConfigGasCost + contract.LogGas + contract.LogTopicGas*2 + 2*(feeConfigInputLen)*contract.LogDataGas - -// changeFeeConfigEventData represents a ChangeFeeConfig non-indexed event data raised by the contract. -// This represents a different struct than commontype.FeeConfig, because in the contract TargetBlockRate is defined as uint256. -// uint256 must be unpacked into *big.Int -type changeFeeConfigEventData struct { - GasLimit *big.Int - TargetBlockRate *big.Int - MinBaseFee *big.Int - TargetGas *big.Int - BaseFeeChangeDenominator *big.Int - MinBlockGasCost *big.Int - MaxBlockGasCost *big.Int - BlockGasCostStep *big.Int -} - -// PackFeeConfigChangedEvent packs the event into the appropriate arguments for changeFeeConfig. -// It returns topic hashes and the encoded non-indexed data. -func PackFeeConfigChangedEvent(sender common.Address, oldConfig commontype.FeeConfig, newConfig commontype.FeeConfig) ([]common.Hash, []byte, error) { - oldConfigC := convertFromCommonConfig(oldConfig) - newConfigC := convertFromCommonConfig(newConfig) - return FeeManagerABI.PackEvent("FeeConfigChanged", sender, oldConfigC, newConfigC) -} - -// UnpackFeeConfigChangedEventData attempts to unpack non-indexed [dataBytes]. -func UnpackFeeConfigChangedEventData(dataBytes []byte) (commontype.FeeConfig, commontype.FeeConfig, error) { - eventData := make([]changeFeeConfigEventData, 2) - err := FeeManagerABI.UnpackIntoInterface(&eventData, "FeeConfigChanged", dataBytes) - if err != nil { - return commontype.FeeConfig{}, commontype.FeeConfig{}, err - } - return convertToCommonConfig(eventData[0]), convertToCommonConfig(eventData[1]), err -} - -func convertFromCommonConfig(config commontype.FeeConfig) changeFeeConfigEventData { - return changeFeeConfigEventData{ - GasLimit: config.GasLimit, - TargetBlockRate: new(big.Int).SetUint64(config.TargetBlockRate), - MinBaseFee: config.MinBaseFee, - TargetGas: config.TargetGas, - BaseFeeChangeDenominator: config.BaseFeeChangeDenominator, - MinBlockGasCost: config.MinBlockGasCost, - MaxBlockGasCost: config.MaxBlockGasCost, - BlockGasCostStep: config.BlockGasCostStep, - } -} - -func convertToCommonConfig(config changeFeeConfigEventData) commontype.FeeConfig { - var targetBlockRate uint64 - if config.TargetBlockRate != nil { - targetBlockRate = config.TargetBlockRate.Uint64() - } - return commontype.FeeConfig{ - GasLimit: config.GasLimit, - TargetBlockRate: targetBlockRate, - MinBaseFee: config.MinBaseFee, - TargetGas: config.TargetGas, - BaseFeeChangeDenominator: config.BaseFeeChangeDenominator, - MinBlockGasCost: config.MinBlockGasCost, - MaxBlockGasCost: config.MaxBlockGasCost, - BlockGasCostStep: config.BlockGasCostStep, - } -} diff --git a/precompile/contracts/feemanager/module.go b/precompile/contracts/feemanager/module.go deleted file mode 100644 index e67e5e1115..0000000000 --- a/precompile/contracts/feemanager/module.go +++ /dev/null @@ -1,67 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package feemanager - -import ( - "fmt" - - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ava-labs/subnet-evm/precompile/modules" - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - "github.com/ethereum/go-ethereum/common" -) - -var _ contract.Configurator = &configurator{} - -// ConfigKey is the key used in json config files to specify this precompile config. -// must be unique across all precompiles. -const ConfigKey = "feeManagerConfig" - -var ContractAddress = common.HexToAddress("0x0200000000000000000000000000000000000003") - -// Module is the precompile module. It is used to register the precompile contract. -var Module = modules.Module{ - ConfigKey: ConfigKey, - Address: ContractAddress, - Contract: FeeManagerPrecompile, - Configurator: &configurator{}, -} - -type configurator struct{} - -func init() { - // Register the precompile module. - // Each precompile contract registers itself through [RegisterModule] function. - if err := modules.RegisterModule(Module); err != nil { - panic(err) - } -} - -// MakeConfig returns a new precompile config instance. -// This is required to Marshal/Unmarshal the precompile config. -func (*configurator) MakeConfig() precompileconfig.Config { - return new(Config) -} - -// Configure configures [state] with the given [cfg] precompileconfig. -// This function is called by the EVM once per precompile contract activation. -func (*configurator) Configure(chainConfig precompileconfig.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, blockContext contract.ConfigurationBlockContext) error { - config, ok := cfg.(*Config) - if !ok { - return fmt.Errorf("expected config type %T, got %T: %v", &Config{}, cfg, cfg) - } - // Store the initial fee config into the state when the fee manager activates. - if config.InitialFeeConfig != nil { - if err := StoreFeeConfig(state, *config.InitialFeeConfig, blockContext); err != nil { - // This should not happen since we already checked this config with Verify() - return fmt.Errorf("cannot configure given initial fee config: %w", err) - } - } else { - if err := StoreFeeConfig(state, chainConfig.GetFeeConfig(), blockContext); err != nil { - // This should not happen since we already checked the chain config in the genesis creation. - return fmt.Errorf("cannot configure fee config in chain config: %w", err) - } - } - return config.AllowListConfig.Configure(chainConfig, ContractAddress, state, blockContext) -} diff --git a/precompile/contracts/feemanager/unpack_pack_test.go b/precompile/contracts/feemanager/unpack_pack_test.go deleted file mode 100644 index e2afc6b3d5..0000000000 --- a/precompile/contracts/feemanager/unpack_pack_test.go +++ /dev/null @@ -1,485 +0,0 @@ -// (c) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package feemanager - -import ( - "fmt" - "math/big" - "testing" - - "github.com/ava-labs/subnet-evm/accounts/abi" - "github.com/ava-labs/subnet-evm/commontype" - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/math" - "github.com/stretchr/testify/require" -) - -var ( - setFeeConfigSignature = contract.CalculateFunctionSelector("setFeeConfig(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256)") - getFeeConfigSignature = contract.CalculateFunctionSelector("getFeeConfig()") - getFeeConfigLastChangedAtSignature = contract.CalculateFunctionSelector("getFeeConfigLastChangedAt()") -) - -func FuzzPackGetFeeConfigOutputEqualTest(f *testing.F) { - f.Add([]byte{}, uint64(0)) - f.Add(big.NewInt(0).Bytes(), uint64(0)) - f.Add(big.NewInt(1).Bytes(), uint64(math.MaxUint64)) - f.Add(math.MaxBig256.Bytes(), uint64(0)) - f.Add(math.MaxBig256.Sub(math.MaxBig256, common.Big1).Bytes(), uint64(0)) - f.Add(math.MaxBig256.Add(math.MaxBig256, common.Big1).Bytes(), uint64(0)) - f.Fuzz(func(t *testing.T, bigIntBytes []byte, blockRate uint64) { - bigIntVal := new(big.Int).SetBytes(bigIntBytes) - feeConfig := commontype.FeeConfig{ - GasLimit: bigIntVal, - TargetBlockRate: blockRate, - MinBaseFee: bigIntVal, - TargetGas: bigIntVal, - BaseFeeChangeDenominator: bigIntVal, - MinBlockGasCost: bigIntVal, - MaxBlockGasCost: bigIntVal, - BlockGasCostStep: bigIntVal, - } - doCheckOutputs := true - // we can only check if outputs are correct if the value is less than MaxUint256 - // otherwise the value will be truncated when packed, - // and thus unpacked output will not be equal to the value - if bigIntVal.Cmp(abi.MaxUint256) > 0 { - doCheckOutputs = false - } - testOldPackGetFeeConfigOutputEqual(t, feeConfig, doCheckOutputs) - }) -} - -func TestOldPackGetFeeConfigOutputEqual(t *testing.T) { - testOldPackGetFeeConfigOutputEqual(t, testFeeConfig, true) -} -func TestPackGetFeeConfigOutputPanic(t *testing.T) { - require.Panics(t, func() { - _, _ = OldPackFeeConfig(commontype.FeeConfig{}) - }) - require.Panics(t, func() { - _, _ = PackGetFeeConfigOutput(commontype.FeeConfig{}) - }) -} - -func TestPackGetFeeConfigOutput(t *testing.T) { - testInputBytes, err := PackGetFeeConfigOutput(testFeeConfig) - require.NoError(t, err) - tests := []struct { - name string - input []byte - skipLenCheck bool - expectedErr string - expectedOldErr string - expectedOutput commontype.FeeConfig - }{ - { - name: "empty input", - input: []byte{}, - skipLenCheck: false, - expectedErr: ErrInvalidLen.Error(), - expectedOldErr: ErrInvalidLen.Error(), - }, - { - name: "empty input skip len check", - input: []byte{}, - skipLenCheck: true, - expectedErr: "attempting to unmarshal an empty string", - expectedOldErr: ErrInvalidLen.Error(), - }, - { - name: "input with extra bytes", - input: append(testInputBytes, make([]byte, 32)...), - skipLenCheck: false, - expectedErr: ErrInvalidLen.Error(), - expectedOldErr: ErrInvalidLen.Error(), - }, - { - name: "input with extra bytes skip len check", - input: append(testInputBytes, make([]byte, 32)...), - skipLenCheck: true, - expectedErr: "", - expectedOldErr: ErrInvalidLen.Error(), - expectedOutput: testFeeConfig, - }, - { - name: "input with extra bytes (not divisible by 32)", - input: append(testInputBytes, make([]byte, 33)...), - skipLenCheck: false, - expectedErr: ErrInvalidLen.Error(), - expectedOldErr: ErrInvalidLen.Error(), - }, - { - name: "input with extra bytes (not divisible by 32) skip len check", - input: append(testInputBytes, make([]byte, 33)...), - skipLenCheck: true, - expectedErr: "improperly formatted output", - expectedOldErr: ErrInvalidLen.Error(), - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - unpacked, err := UnpackGetFeeConfigOutput(test.input, test.skipLenCheck) - if test.expectedErr != "" { - require.ErrorContains(t, err, test.expectedErr) - } else { - require.NoError(t, err) - require.True(t, test.expectedOutput.Equal(&unpacked), "not equal: expectedOutput %v, unpacked %v", test.expectedOutput, unpacked) - } - oldUnpacked, oldErr := OldUnpackFeeConfig(test.input) - if test.expectedOldErr != "" { - require.ErrorContains(t, oldErr, test.expectedOldErr) - } else { - require.NoError(t, oldErr) - require.True(t, test.expectedOutput.Equal(&oldUnpacked), "not equal: expectedOutput %v, oldUnpacked %v", test.expectedOutput, oldUnpacked) - } - }) - } -} - -func TestGetFeeConfig(t *testing.T) { - // Compare OldPackGetFeeConfigInput vs PackGetFeeConfig - // to see if they are equivalent - input := OldPackGetFeeConfigInput() - - input2, err := PackGetFeeConfig() - require.NoError(t, err) - - require.Equal(t, input, input2) -} - -func TestGetLastChangedAtInput(t *testing.T) { - // Compare OldPackGetFeeConfigInput vs PackGetFeeConfigLastChangedAt - // to see if they are equivalent - - input := OldPackGetLastChangedAtInput() - - input2, err := PackGetFeeConfigLastChangedAt() - require.NoError(t, err) - - require.Equal(t, input, input2) -} - -func FuzzPackGetLastChangedAtOutput(f *testing.F) { - f.Add([]byte{}) - f.Add(big.NewInt(0).Bytes()) - f.Add(big.NewInt(1).Bytes()) - f.Add(math.MaxBig256.Bytes()) - f.Add(math.MaxBig256.Sub(math.MaxBig256, common.Big1).Bytes()) - f.Add(math.MaxBig256.Add(math.MaxBig256, common.Big1).Bytes()) - f.Fuzz(func(t *testing.T, bigIntBytes []byte) { - bigIntVal := new(big.Int).SetBytes(bigIntBytes) - doCheckOutputs := true - // we can only check if outputs are correct if the value is less than MaxUint256 - // otherwise the value will be truncated when packed, - // and thus unpacked output will not be equal to the value - if bigIntVal.Cmp(abi.MaxUint256) > 0 { - doCheckOutputs = false - } - testOldPackGetLastChangedAtOutputEqual(t, bigIntVal, doCheckOutputs) - }) -} - -func FuzzPackSetFeeConfigEqualTest(f *testing.F) { - f.Add([]byte{}, uint64(0)) - f.Add(big.NewInt(0).Bytes(), uint64(0)) - f.Add(big.NewInt(1).Bytes(), uint64(math.MaxUint64)) - f.Add(math.MaxBig256.Bytes(), uint64(0)) - f.Add(math.MaxBig256.Sub(math.MaxBig256, common.Big1).Bytes(), uint64(0)) - f.Add(math.MaxBig256.Add(math.MaxBig256, common.Big1).Bytes(), uint64(0)) - f.Fuzz(func(t *testing.T, bigIntBytes []byte, blockRate uint64) { - bigIntVal := new(big.Int).SetBytes(bigIntBytes) - feeConfig := commontype.FeeConfig{ - GasLimit: bigIntVal, - TargetBlockRate: blockRate, - MinBaseFee: bigIntVal, - TargetGas: bigIntVal, - BaseFeeChangeDenominator: bigIntVal, - MinBlockGasCost: bigIntVal, - MaxBlockGasCost: bigIntVal, - BlockGasCostStep: bigIntVal, - } - doCheckOutputs := true - // we can only check if outputs are correct if the value is less than MaxUint256 - // otherwise the value will be truncated when packed, - // and thus unpacked output will not be equal to the value - if bigIntVal.Cmp(abi.MaxUint256) > 0 { - doCheckOutputs = false - } - testOldPackSetFeeConfigInputEqual(t, feeConfig, doCheckOutputs) - }) -} - -func TestOldPackSetFeeConfigInputEqual(t *testing.T) { - testOldPackSetFeeConfigInputEqual(t, testFeeConfig, true) -} - -func TestPackSetFeeConfigInputPanic(t *testing.T) { - require.Panics(t, func() { - _, _ = OldPackSetFeeConfig(commontype.FeeConfig{}) - }) - require.Panics(t, func() { - _, _ = PackSetFeeConfig(commontype.FeeConfig{}) - }) -} - -func TestPackSetFeeConfigInput(t *testing.T) { - testInputBytes, err := PackSetFeeConfig(testFeeConfig) - require.NoError(t, err) - // exclude 4 bytes for function selector - testInputBytes = testInputBytes[4:] - tests := []struct { - name string - input []byte - strictMode bool - expectedErr string - expectedOldErr string - expectedOutput commontype.FeeConfig - }{ - { - name: "empty input strict mode", - input: []byte{}, - strictMode: true, - expectedErr: ErrInvalidLen.Error(), - expectedOldErr: ErrInvalidLen.Error(), - }, - { - name: "empty input", - input: []byte{}, - strictMode: false, - expectedErr: "attempting to unmarshal an empty string", - expectedOldErr: ErrInvalidLen.Error(), - }, - { - name: "input with insufficient len strict mode", - input: []byte{123}, - strictMode: true, - expectedErr: ErrInvalidLen.Error(), - expectedOldErr: ErrInvalidLen.Error(), - }, - { - name: "input with insufficient len", - input: []byte{123}, - strictMode: false, - expectedErr: "length insufficient", - expectedOldErr: ErrInvalidLen.Error(), - }, - { - name: "input with extra bytes strict mode", - input: append(testInputBytes, make([]byte, 32)...), - strictMode: true, - expectedErr: ErrInvalidLen.Error(), - expectedOldErr: ErrInvalidLen.Error(), - }, - { - name: "input with extra bytes", - input: append(testInputBytes, make([]byte, 32)...), - strictMode: false, - expectedErr: "", - expectedOldErr: ErrInvalidLen.Error(), - expectedOutput: testFeeConfig, - }, - { - name: "input with extra bytes (not divisible by 32) strict mode", - input: append(testInputBytes, make([]byte, 33)...), - strictMode: true, - expectedErr: ErrInvalidLen.Error(), - expectedOldErr: ErrInvalidLen.Error(), - }, - { - name: "input with extra bytes (not divisible by 32)", - input: append(testInputBytes, make([]byte, 33)...), - strictMode: false, - expectedOutput: testFeeConfig, - expectedOldErr: ErrInvalidLen.Error(), - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - unpacked, err := UnpackSetFeeConfigInput(test.input, test.strictMode) - if test.expectedErr != "" { - require.ErrorContains(t, err, test.expectedErr) - } else { - require.NoError(t, err) - require.True(t, test.expectedOutput.Equal(&unpacked), "not equal: expectedOutput %v, unpacked %v", test.expectedOutput, unpacked) - } - oldUnpacked, oldErr := OldUnpackFeeConfig(test.input) - if test.expectedOldErr != "" { - require.ErrorContains(t, oldErr, test.expectedOldErr) - } else { - require.NoError(t, oldErr) - require.True(t, test.expectedOutput.Equal(&oldUnpacked), "not equal: expectedOutput %v, oldUnpacked %v", test.expectedOutput, oldUnpacked) - } - }) - } -} - -func TestFunctionSignatures(t *testing.T) { - abiSetFeeConfig := FeeManagerABI.Methods["setFeeConfig"] - require.Equal(t, setFeeConfigSignature, abiSetFeeConfig.ID) - - abiGetFeeConfig := FeeManagerABI.Methods["getFeeConfig"] - require.Equal(t, getFeeConfigSignature, abiGetFeeConfig.ID) - - abiGetFeeConfigLastChangedAt := FeeManagerABI.Methods["getFeeConfigLastChangedAt"] - require.Equal(t, getFeeConfigLastChangedAtSignature, abiGetFeeConfigLastChangedAt.ID) -} - -func testOldPackGetFeeConfigOutputEqual(t *testing.T, feeConfig commontype.FeeConfig, checkOutputs bool) { - t.Helper() - t.Run(fmt.Sprintf("TestGetFeeConfigOutput, feeConfig %v", feeConfig), func(t *testing.T) { - input, err := OldPackFeeConfig(feeConfig) - input2, err2 := PackGetFeeConfigOutput(feeConfig) - if err != nil { - require.ErrorContains(t, err2, err.Error()) - return - } - require.NoError(t, err2) - require.Equal(t, input, input2) - - config, err := OldUnpackFeeConfig(input) - unpacked, err2 := UnpackGetFeeConfigOutput(input, false) - if err != nil { - require.ErrorContains(t, err2, err.Error()) - return - } - require.NoError(t, err2) - require.True(t, config.Equal(&unpacked), "not equal: config %v, unpacked %v", feeConfig, unpacked) - if checkOutputs { - require.True(t, feeConfig.Equal(&unpacked), "not equal: feeConfig %v, unpacked %v", feeConfig, unpacked) - } - }) -} - -func testOldPackGetLastChangedAtOutputEqual(t *testing.T, blockNumber *big.Int, checkOutputs bool) { - t.Helper() - t.Run(fmt.Sprintf("TestGetLastChangedAtOutput, blockNumber %v", blockNumber), func(t *testing.T) { - input := OldPackGetLastChangedAtOutput(blockNumber) - input2, err2 := PackGetFeeConfigLastChangedAtOutput(blockNumber) - require.NoError(t, err2) - require.Equal(t, input, input2) - - value, err := OldUnpackGetLastChangedAtOutput(input) - unpacked, err2 := UnpackGetFeeConfigLastChangedAtOutput(input) - if err != nil { - require.ErrorContains(t, err2, err.Error()) - return - } - require.NoError(t, err2) - require.True(t, value.Cmp(unpacked) == 0, "not equal: value %v, unpacked %v", value, unpacked) - if checkOutputs { - require.True(t, blockNumber.Cmp(unpacked) == 0, "not equal: blockNumber %v, unpacked %v", blockNumber, unpacked) - } - }) -} - -func testOldPackSetFeeConfigInputEqual(t *testing.T, feeConfig commontype.FeeConfig, checkOutputs bool) { - t.Helper() - t.Run(fmt.Sprintf("TestSetFeeConfigInput, feeConfig %v", feeConfig), func(t *testing.T) { - input, err := OldPackSetFeeConfig(feeConfig) - input2, err2 := PackSetFeeConfig(feeConfig) - if err != nil { - require.ErrorContains(t, err2, err.Error()) - return - } - require.NoError(t, err2) - require.Equal(t, input, input2) - - value, err := OldUnpackFeeConfig(input) - unpacked, err2 := UnpackSetFeeConfigInput(input, true) - if err != nil { - require.ErrorContains(t, err2, err.Error()) - return - } - require.NoError(t, err2) - require.True(t, value.Equal(&unpacked), "not equal: value %v, unpacked %v", value, unpacked) - if checkOutputs { - require.True(t, feeConfig.Equal(&unpacked), "not equal: feeConfig %v, unpacked %v", feeConfig, unpacked) - } - }) -} - -func OldPackFeeConfig(feeConfig commontype.FeeConfig) ([]byte, error) { - return packFeeConfigHelper(feeConfig, false) -} - -func OldUnpackFeeConfig(input []byte) (commontype.FeeConfig, error) { - if len(input) != feeConfigInputLen { - return commontype.FeeConfig{}, fmt.Errorf("%w: %d", ErrInvalidLen, len(input)) - } - feeConfig := commontype.FeeConfig{} - for i := minFeeConfigFieldKey; i <= numFeeConfigField; i++ { - listIndex := i - 1 - packedElement := contract.PackedHash(input, listIndex) - switch i { - case gasLimitKey: - feeConfig.GasLimit = new(big.Int).SetBytes(packedElement) - case targetBlockRateKey: - feeConfig.TargetBlockRate = new(big.Int).SetBytes(packedElement).Uint64() - case minBaseFeeKey: - feeConfig.MinBaseFee = new(big.Int).SetBytes(packedElement) - case targetGasKey: - feeConfig.TargetGas = new(big.Int).SetBytes(packedElement) - case baseFeeChangeDenominatorKey: - feeConfig.BaseFeeChangeDenominator = new(big.Int).SetBytes(packedElement) - case minBlockGasCostKey: - feeConfig.MinBlockGasCost = new(big.Int).SetBytes(packedElement) - case maxBlockGasCostKey: - feeConfig.MaxBlockGasCost = new(big.Int).SetBytes(packedElement) - case blockGasCostStepKey: - feeConfig.BlockGasCostStep = new(big.Int).SetBytes(packedElement) - default: - // This should never encounter an unknown fee config key - panic(fmt.Sprintf("unknown fee config key: %d", i)) - } - } - return feeConfig, nil -} - -func packFeeConfigHelper(feeConfig commontype.FeeConfig, useSelector bool) ([]byte, error) { - hashes := []common.Hash{ - common.BigToHash(feeConfig.GasLimit), - common.BigToHash(new(big.Int).SetUint64(feeConfig.TargetBlockRate)), - common.BigToHash(feeConfig.MinBaseFee), - common.BigToHash(feeConfig.TargetGas), - common.BigToHash(feeConfig.BaseFeeChangeDenominator), - common.BigToHash(feeConfig.MinBlockGasCost), - common.BigToHash(feeConfig.MaxBlockGasCost), - common.BigToHash(feeConfig.BlockGasCostStep), - } - - if useSelector { - res := make([]byte, len(setFeeConfigSignature)+feeConfigInputLen) - err := contract.PackOrderedHashesWithSelector(res, setFeeConfigSignature, hashes) - return res, err - } - - res := make([]byte, len(hashes)*common.HashLength) - err := contract.PackOrderedHashes(res, hashes) - return res, err -} - -// PackGetFeeConfigInput packs the getFeeConfig signature -func OldPackGetFeeConfigInput() []byte { - return getFeeConfigSignature -} - -// PackGetLastChangedAtInput packs the getFeeConfigLastChangedAt signature -func OldPackGetLastChangedAtInput() []byte { - return getFeeConfigLastChangedAtSignature -} - -func OldPackGetLastChangedAtOutput(lastChangedAt *big.Int) []byte { - return common.BigToHash(lastChangedAt).Bytes() -} - -func OldUnpackGetLastChangedAtOutput(input []byte) (*big.Int, error) { - return new(big.Int).SetBytes(input), nil -} - -func OldPackSetFeeConfig(feeConfig commontype.FeeConfig) ([]byte, error) { - // function selector (4 bytes) + input(feeConfig) - return packFeeConfigHelper(feeConfig, true) -} diff --git a/precompile/contracts/nativeminter/config.go b/precompile/contracts/nativeminter/config.go deleted file mode 100644 index 38a65ee6c8..0000000000 --- a/precompile/contracts/nativeminter/config.go +++ /dev/null @@ -1,100 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package nativeminter - -import ( - "fmt" - "math/big" - - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - "github.com/ava-labs/subnet-evm/utils" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/math" -) - -var _ precompileconfig.Config = &Config{} - -// Config implements the precompileconfig.Config interface while adding in the -// ContractNativeMinter specific precompile config. -type Config struct { - allowlist.AllowListConfig - precompileconfig.Upgrade - InitialMint map[common.Address]*math.HexOrDecimal256 `json:"initialMint,omitempty"` // addresses to receive the initial mint mapped to the amount to mint -} - -// NewConfig returns a config for a network upgrade at [blockTimestamp] that enables -// ContractNativeMinter with the given [admins], [enableds] and [managers] as members of the allowlist. -// Also mints balances according to [initialMint] when the upgrade activates. -func NewConfig(blockTimestamp *uint64, admins []common.Address, enableds []common.Address, managers []common.Address, initialMint map[common.Address]*math.HexOrDecimal256) *Config { - return &Config{ - AllowListConfig: allowlist.AllowListConfig{ - AdminAddresses: admins, - EnabledAddresses: enableds, - ManagerAddresses: managers, - }, - Upgrade: precompileconfig.Upgrade{BlockTimestamp: blockTimestamp}, - InitialMint: initialMint, - } -} - -// NewDisableConfig returns config for a network upgrade at [blockTimestamp] -// that disables ContractNativeMinter. -func NewDisableConfig(blockTimestamp *uint64) *Config { - return &Config{ - Upgrade: precompileconfig.Upgrade{ - BlockTimestamp: blockTimestamp, - Disable: true, - }, - } -} - -// Key returns the key for the ContractNativeMinter precompileconfig. -// This should be the same key as used in the precompile module. -func (*Config) Key() string { return ConfigKey } - -// Equal returns true if [cfg] is a [*ContractNativeMinterConfig] and it has been configured identical to [c]. -func (c *Config) Equal(cfg precompileconfig.Config) bool { - // typecast before comparison - other, ok := (cfg).(*Config) - if !ok { - return false - } - eq := c.Upgrade.Equal(&other.Upgrade) && c.AllowListConfig.Equal(&other.AllowListConfig) - if !eq { - return false - } - - if len(c.InitialMint) != len(other.InitialMint) { - return false - } - - for address, amount := range c.InitialMint { - val, ok := other.InitialMint[address] - if !ok { - return false - } - bigIntAmount := (*big.Int)(amount) - bigIntVal := (*big.Int)(val) - if !utils.BigNumEqual(bigIntAmount, bigIntVal) { - return false - } - } - - return true -} - -func (c *Config) Verify(chainConfig precompileconfig.ChainConfig) error { - // ensure that all of the initial mint values in the map are non-nil positive values - for addr, amount := range c.InitialMint { - if amount == nil { - return fmt.Errorf("initial mint cannot contain nil amount for address %s", addr) - } - bigIntAmount := (*big.Int)(amount) - if bigIntAmount.Sign() < 1 { - return fmt.Errorf("initial mint cannot contain invalid amount %v for address %s", bigIntAmount, addr) - } - } - return c.AllowListConfig.Verify(chainConfig, c.Upgrade) -} diff --git a/precompile/contracts/nativeminter/config_test.go b/precompile/contracts/nativeminter/config_test.go deleted file mode 100644 index ca0a63ce4a..0000000000 --- a/precompile/contracts/nativeminter/config_test.go +++ /dev/null @@ -1,119 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package nativeminter - -import ( - "testing" - - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - "github.com/ava-labs/subnet-evm/precompile/testutils" - "github.com/ava-labs/subnet-evm/utils" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/math" - "go.uber.org/mock/gomock" -) - -func TestVerify(t *testing.T) { - admins := []common.Address{allowlist.TestAdminAddr} - enableds := []common.Address{allowlist.TestEnabledAddr} - managers := []common.Address{allowlist.TestManagerAddr} - tests := map[string]testutils.ConfigVerifyTest{ - "valid config": { - Config: NewConfig(utils.NewUint64(3), admins, enableds, managers, nil), - ChainConfig: func() precompileconfig.ChainConfig { - config := precompileconfig.NewMockChainConfig(gomock.NewController(t)) - config.EXPECT().IsDurango(gomock.Any()).Return(true).AnyTimes() - return config - }(), - ExpectedError: "", - }, - "invalid allow list config in native minter allowlist": { - Config: NewConfig(utils.NewUint64(3), admins, admins, nil, nil), - ExpectedError: "cannot set address", - }, - "duplicate admins in config in native minter allowlist": { - Config: NewConfig(utils.NewUint64(3), append(admins, admins[0]), enableds, managers, nil), - ExpectedError: "duplicate address", - }, - "duplicate enableds in config in native minter allowlist": { - Config: NewConfig(utils.NewUint64(3), admins, append(enableds, enableds[0]), managers, nil), - ExpectedError: "duplicate address", - }, - "nil amount in native minter config": { - Config: NewConfig(utils.NewUint64(3), admins, nil, nil, - map[common.Address]*math.HexOrDecimal256{ - common.HexToAddress("0x01"): math.NewHexOrDecimal256(123), - common.HexToAddress("0x02"): nil, - }), - ExpectedError: "initial mint cannot contain nil", - }, - "negative amount in native minter config": { - Config: NewConfig(utils.NewUint64(3), admins, nil, nil, - map[common.Address]*math.HexOrDecimal256{ - common.HexToAddress("0x01"): math.NewHexOrDecimal256(123), - common.HexToAddress("0x02"): math.NewHexOrDecimal256(-1), - }), - ExpectedError: "initial mint cannot contain invalid amount", - }, - } - allowlist.VerifyPrecompileWithAllowListTests(t, Module, tests) -} - -func TestEqual(t *testing.T) { - admins := []common.Address{allowlist.TestAdminAddr} - enableds := []common.Address{allowlist.TestEnabledAddr} - managers := []common.Address{allowlist.TestManagerAddr} - tests := map[string]testutils.ConfigEqualTest{ - "non-nil config and nil other": { - Config: NewConfig(utils.NewUint64(3), admins, enableds, managers, nil), - Other: nil, - Expected: false, - }, - "different type": { - Config: NewConfig(utils.NewUint64(3), admins, enableds, managers, nil), - Other: precompileconfig.NewMockConfig(gomock.NewController(t)), - Expected: false, - }, - "different timestamp": { - Config: NewConfig(utils.NewUint64(3), admins, nil, nil, nil), - Other: NewConfig(utils.NewUint64(4), admins, nil, nil, nil), - Expected: false, - }, - "different initial mint amounts": { - Config: NewConfig(utils.NewUint64(3), admins, nil, nil, - map[common.Address]*math.HexOrDecimal256{ - common.HexToAddress("0x01"): math.NewHexOrDecimal256(1), - }), - Other: NewConfig(utils.NewUint64(3), admins, nil, nil, - map[common.Address]*math.HexOrDecimal256{ - common.HexToAddress("0x01"): math.NewHexOrDecimal256(2), - }), - Expected: false, - }, - "different initial mint addresses": { - Config: NewConfig(utils.NewUint64(3), admins, nil, nil, - map[common.Address]*math.HexOrDecimal256{ - common.HexToAddress("0x01"): math.NewHexOrDecimal256(1), - }), - Other: NewConfig(utils.NewUint64(3), admins, nil, nil, - map[common.Address]*math.HexOrDecimal256{ - common.HexToAddress("0x02"): math.NewHexOrDecimal256(1), - }), - Expected: false, - }, - "same config": { - Config: NewConfig(utils.NewUint64(3), admins, nil, nil, - map[common.Address]*math.HexOrDecimal256{ - common.HexToAddress("0x01"): math.NewHexOrDecimal256(1), - }), - Other: NewConfig(utils.NewUint64(3), admins, nil, nil, - map[common.Address]*math.HexOrDecimal256{ - common.HexToAddress("0x01"): math.NewHexOrDecimal256(1), - }), - Expected: true, - }, - } - allowlist.EqualPrecompileWithAllowListTests(t, Module, tests) -} diff --git a/precompile/contracts/nativeminter/contract.abi b/precompile/contracts/nativeminter/contract.abi deleted file mode 100644 index 49655d790a..0000000000 --- a/precompile/contracts/nativeminter/contract.abi +++ /dev/null @@ -1,116 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "NativeCoinMinted", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "mintNativeCoin", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "readAllowList", - "outputs": [ - { - "internalType": "uint256", - "name": "role", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "setAdmin", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "setEnabled", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "setManager", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "setNone", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/precompile/contracts/nativeminter/contract.go b/precompile/contracts/nativeminter/contract.go deleted file mode 100644 index 2e578fe2cc..0000000000 --- a/precompile/contracts/nativeminter/contract.go +++ /dev/null @@ -1,148 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package nativeminter - -import ( - _ "embed" - "errors" - "fmt" - "math/big" - - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ava-labs/subnet-evm/vmerrs" - "github.com/ethereum/go-ethereum/common" -) - -const ( - mintInputLen = common.HashLength + common.HashLength - - MintGasCost = 30_000 -) - -type MintNativeCoinInput struct { - Addr common.Address - Amount *big.Int -} - -var ( - // Singleton StatefulPrecompiledContract for minting native assets by permissioned callers. - ContractNativeMinterPrecompile contract.StatefulPrecompiledContract = createNativeMinterPrecompile() - - ErrCannotMint = errors.New("non-enabled cannot mint") - ErrInvalidLen = errors.New("invalid input length for minting") - - // NativeMinterRawABI contains the raw ABI of NativeMinter contract. - //go:embed contract.abi - NativeMinterRawABI string - - NativeMinterABI = contract.ParseABI(NativeMinterRawABI) -) - -// GetContractNativeMinterStatus returns the role of [address] for the minter list. -func GetContractNativeMinterStatus(stateDB contract.StateDB, address common.Address) allowlist.Role { - return allowlist.GetAllowListStatus(stateDB, ContractAddress, address) -} - -// SetContractNativeMinterStatus sets the permissions of [address] to [role] for the -// minter list. assumes [role] has already been verified as valid. -func SetContractNativeMinterStatus(stateDB contract.StateDB, address common.Address, role allowlist.Role) { - allowlist.SetAllowListRole(stateDB, ContractAddress, address, role) -} - -// PackMintNativeCoin packs [address] and [amount] into the appropriate arguments for mintNativeCoin. -func PackMintNativeCoin(address common.Address, amount *big.Int) ([]byte, error) { - return NativeMinterABI.Pack("mintNativeCoin", address, amount) -} - -// UnpackMintNativeCoinInput attempts to unpack [input] as address and amount. -// assumes that [input] does not include selector (omits first 4 func signature bytes) -// if [useStrictMode] is true, it will return an error if the length of [input] is not [mintInputLen] -func UnpackMintNativeCoinInput(input []byte, useStrictMode bool) (common.Address, *big.Int, error) { - // Initially we had this check to ensure that the input was the correct length. - // However solidity does not always pack the input to the correct length, and allows - // for extra padding bytes to be added to the end of the input. Therefore, we have removed - // this check with Durango. We still need to keep this check for backwards compatibility. - if useStrictMode && len(input) != mintInputLen { - return common.Address{}, nil, fmt.Errorf("%w: %d", ErrInvalidLen, len(input)) - } - inputStruct := MintNativeCoinInput{} - err := NativeMinterABI.UnpackInputIntoInterface(&inputStruct, "mintNativeCoin", input, useStrictMode) - - return inputStruct.Addr, inputStruct.Amount, err -} - -// mintNativeCoin checks if the caller is permissioned for minting operation. -// The execution function parses the [input] into native coin amount and receiver address. -func mintNativeCoin(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = contract.DeductGas(suppliedGas, MintGasCost); err != nil { - return nil, 0, err - } - - if readOnly { - return nil, remainingGas, vmerrs.ErrWriteProtection - } - - useStrictMode := !contract.IsDurangoActivated(accessibleState) - to, amount, err := UnpackMintNativeCoinInput(input, useStrictMode) - if err != nil { - return nil, remainingGas, err - } - - stateDB := accessibleState.GetStateDB() - // Verify that the caller is in the allow list and therefore has the right to call this function. - callerStatus := allowlist.GetAllowListStatus(stateDB, ContractAddress, caller) - if !callerStatus.IsEnabled() { - return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotMint, caller) - } - - if contract.IsDurangoActivated(accessibleState) { - if remainingGas, err = contract.DeductGas(remainingGas, NativeCoinMintedEventGasCost); err != nil { - return nil, 0, err - } - topics, data, err := PackNativeCoinMintedEvent(caller, to, amount) - if err != nil { - return nil, remainingGas, err - } - stateDB.AddLog( - ContractAddress, - topics, - data, - accessibleState.GetBlockContext().Number().Uint64(), - ) - } - // if there is no address in the state, create one. - if !stateDB.Exist(to) { - stateDB.CreateAccount(to) - } - - stateDB.AddBalance(to, amount) - // Return an empty output and the remaining gas - return []byte{}, remainingGas, nil -} - -// createNativeMinterPrecompile returns a StatefulPrecompiledContract with getters and setters for the precompile. -// Access to the getters/setters is controlled by an allow list for ContractAddress. -func createNativeMinterPrecompile() contract.StatefulPrecompiledContract { - var functions []*contract.StatefulPrecompileFunction - functions = append(functions, allowlist.CreateAllowListFunctions(ContractAddress)...) - - abiFunctionMap := map[string]contract.RunStatefulPrecompileFunc{ - "mintNativeCoin": mintNativeCoin, - } - - for name, function := range abiFunctionMap { - method, ok := NativeMinterABI.Methods[name] - if !ok { - panic(fmt.Errorf("given method (%s) does not exist in the ABI", name)) - } - functions = append(functions, contract.NewStatefulPrecompileFunction(method.ID, function)) - } - // Construct the contract with no fallback function. - statefulContract, err := contract.NewStatefulPrecompileContract(nil, functions) - if err != nil { - panic(err) - } - return statefulContract -} diff --git a/precompile/contracts/nativeminter/contract_test.go b/precompile/contracts/nativeminter/contract_test.go deleted file mode 100644 index 0881918516..0000000000 --- a/precompile/contracts/nativeminter/contract_test.go +++ /dev/null @@ -1,276 +0,0 @@ -// (c) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package nativeminter - -import ( - "math/big" - "testing" - - "github.com/ava-labs/subnet-evm/core/state" - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - "github.com/ava-labs/subnet-evm/precompile/testutils" - "github.com/ava-labs/subnet-evm/vmerrs" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/math" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" -) - -var ( - tests = map[string]testutils.PrecompileTest{ - "calling mintNativeCoin from NoRole should fail": { - Caller: allowlist.TestNoRoleAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackMintNativeCoin(allowlist.TestNoRoleAddr, common.Big1) - require.NoError(t, err) - - return input - }, - SuppliedGas: MintGasCost, - ReadOnly: false, - ExpectedErr: ErrCannotMint.Error(), - }, - "calling mintNativeCoin from Enabled should succeed": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackMintNativeCoin(allowlist.TestEnabledAddr, common.Big1) - require.NoError(t, err) - - return input - }, - SuppliedGas: MintGasCost + NativeCoinMintedEventGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, stateDB contract.StateDB) { - require.Equal(t, common.Big1, stateDB.GetBalance(allowlist.TestEnabledAddr), "expected minted funds") - - logsTopics, logsData := stateDB.GetLogData() - assertNativeCoinMintedEvent(t, logsTopics, logsData, allowlist.TestEnabledAddr, allowlist.TestEnabledAddr, common.Big1) - }, - }, - "initial mint funds": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - Config: &Config{ - InitialMint: map[common.Address]*math.HexOrDecimal256{ - allowlist.TestEnabledAddr: math.NewHexOrDecimal256(2), - }, - }, - AfterHook: func(t testing.TB, stateDB contract.StateDB) { - require.Equal(t, common.Big2, stateDB.GetBalance(allowlist.TestEnabledAddr), "expected minted funds") - }, - }, - "calling mintNativeCoin from Manager should succeed": { - Caller: allowlist.TestManagerAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackMintNativeCoin(allowlist.TestEnabledAddr, common.Big1) - require.NoError(t, err) - - return input - }, - SuppliedGas: MintGasCost + NativeCoinMintedEventGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, stateDB contract.StateDB) { - require.Equal(t, common.Big1, stateDB.GetBalance(allowlist.TestEnabledAddr), "expected minted funds") - - logsTopics, logsData := stateDB.GetLogData() - assertNativeCoinMintedEvent(t, logsTopics, logsData, allowlist.TestManagerAddr, allowlist.TestEnabledAddr, common.Big1) - }, - }, - "mint funds from admin address": { - Caller: allowlist.TestAdminAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackMintNativeCoin(allowlist.TestAdminAddr, common.Big1) - require.NoError(t, err) - - return input - }, - SuppliedGas: MintGasCost + NativeCoinMintedEventGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, stateDB contract.StateDB) { - require.Equal(t, common.Big1, stateDB.GetBalance(allowlist.TestAdminAddr), "expected minted funds") - - logsTopics, logsData := stateDB.GetLogData() - assertNativeCoinMintedEvent(t, logsTopics, logsData, allowlist.TestAdminAddr, allowlist.TestAdminAddr, common.Big1) - }, - }, - "mint max big funds": { - Caller: allowlist.TestAdminAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackMintNativeCoin(allowlist.TestAdminAddr, math.MaxBig256) - require.NoError(t, err) - - return input - }, - SuppliedGas: MintGasCost + NativeCoinMintedEventGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, stateDB contract.StateDB) { - require.Equal(t, math.MaxBig256, stateDB.GetBalance(allowlist.TestAdminAddr), "expected minted funds") - - logsTopics, logsData := stateDB.GetLogData() - assertNativeCoinMintedEvent(t, logsTopics, logsData, allowlist.TestAdminAddr, allowlist.TestAdminAddr, math.MaxBig256) - }, - }, - "readOnly mint with noRole fails": { - Caller: allowlist.TestNoRoleAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackMintNativeCoin(allowlist.TestAdminAddr, common.Big1) - require.NoError(t, err) - - return input - }, - SuppliedGas: MintGasCost, - ReadOnly: true, - ExpectedErr: vmerrs.ErrWriteProtection.Error(), - }, - "readOnly mint with allow role fails": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackMintNativeCoin(allowlist.TestEnabledAddr, common.Big1) - require.NoError(t, err) - - return input - }, - SuppliedGas: MintGasCost, - ReadOnly: true, - ExpectedErr: vmerrs.ErrWriteProtection.Error(), - }, - "readOnly mint with admin role fails": { - Caller: allowlist.TestAdminAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackMintNativeCoin(allowlist.TestAdminAddr, common.Big1) - require.NoError(t, err) - - return input - }, - SuppliedGas: MintGasCost, - ReadOnly: true, - ExpectedErr: vmerrs.ErrWriteProtection.Error(), - }, - "insufficient gas mint from admin": { - Caller: allowlist.TestAdminAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackMintNativeCoin(allowlist.TestEnabledAddr, common.Big1) - require.NoError(t, err) - - return input - }, - SuppliedGas: MintGasCost + NativeCoinMintedEventGasCost - 1, - ReadOnly: false, - ExpectedErr: vmerrs.ErrOutOfGas.Error(), - }, - "mint doesn't log pre-Durango": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig { - config := precompileconfig.NewMockChainConfig(ctrl) - config.EXPECT().IsDurango(gomock.Any()).Return(false).AnyTimes() - return config - }, - InputFn: func(t testing.TB) []byte { - input, err := PackMintNativeCoin(allowlist.TestEnabledAddr, common.Big1) - require.NoError(t, err) - return input - }, - SuppliedGas: MintGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, stateDB contract.StateDB) { - // Check no logs are stored in state - logsTopics, logsData := stateDB.GetLogData() - require.Len(t, logsTopics, 0) - require.Len(t, logsData, 0) - }, - }, - "mint with extra padded bytes should fail pre-Durango": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig { - config := precompileconfig.NewMockChainConfig(ctrl) - config.EXPECT().IsDurango(gomock.Any()).Return(false).AnyTimes() - return config - }, - InputFn: func(t testing.TB) []byte { - input, err := PackMintNativeCoin(allowlist.TestEnabledAddr, common.Big1) - require.NoError(t, err) - - // Add extra bytes to the end of the input - input = append(input, make([]byte, 32)...) - - return input - }, - SuppliedGas: MintGasCost, - ReadOnly: false, - ExpectedErr: ErrInvalidLen.Error(), - }, - "mint with extra padded bytes should succeed with Durango": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig { - config := precompileconfig.NewMockChainConfig(ctrl) - config.EXPECT().IsDurango(gomock.Any()).Return(true).AnyTimes() - return config - }, - InputFn: func(t testing.TB) []byte { - input, err := PackMintNativeCoin(allowlist.TestEnabledAddr, common.Big1) - require.NoError(t, err) - - // Add extra bytes to the end of the input - input = append(input, make([]byte, 32)...) - - return input - }, - ExpectedRes: []byte{}, - SuppliedGas: MintGasCost + NativeCoinMintedEventGasCost, - ReadOnly: false, - AfterHook: func(t testing.TB, state contract.StateDB) { - require.Equal(t, common.Big1, state.GetBalance(allowlist.TestEnabledAddr), "expected minted funds") - - logsTopics, logsData := state.GetLogData() - assertNativeCoinMintedEvent(t, logsTopics, logsData, allowlist.TestEnabledAddr, allowlist.TestEnabledAddr, common.Big1) - }, - }, - } -) - -func TestContractNativeMinterRun(t *testing.T) { - allowlist.RunPrecompileWithAllowListTests(t, Module, state.NewTestStateDB, tests) -} - -func BenchmarkContractNativeMinter(b *testing.B) { - allowlist.BenchPrecompileWithAllowList(b, Module, state.NewTestStateDB, tests) -} - -func assertNativeCoinMintedEvent(t testing.TB, - logsTopics [][]common.Hash, - logsData [][]byte, - expectedSender common.Address, - expectedRecipient common.Address, - expectedAmount *big.Int) { - require.Len(t, logsTopics, 1) - require.Len(t, logsData, 1) - topics := logsTopics[0] - require.Len(t, topics, 3) - require.Equal(t, NativeMinterABI.Events["NativeCoinMinted"].ID, topics[0]) - require.Equal(t, common.BytesToHash(expectedSender[:]), topics[1]) - require.Equal(t, common.BytesToHash(expectedRecipient[:]), topics[2]) - require.NotEmpty(t, logsData[0]) - amount, err := UnpackNativeCoinMintedEventData(logsData[0]) - require.NoError(t, err) - require.True(t, expectedAmount.Cmp(amount) == 0, "expected", expectedAmount, "got", amount) -} diff --git a/precompile/contracts/nativeminter/event.go b/precompile/contracts/nativeminter/event.go deleted file mode 100644 index b253728118..0000000000 --- a/precompile/contracts/nativeminter/event.go +++ /dev/null @@ -1,34 +0,0 @@ -// Code generated -// This file is a generated precompile contract config with stubbed abstract functions. -// The file is generated by a template. Please inspect every code and comment in this file before use. - -package nativeminter - -import ( - "math/big" - - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ethereum/go-ethereum/common" -) - -const ( - // NativeCoinMintedEventGasCost is the gas cost of the NativeCoinMinted event. - // It is the base gas cost + the gas cost of the topics (signature, sender, recipient) - // and the gas cost of the non-indexed data (32 bytes for amount). - NativeCoinMintedEventGasCost = contract.LogGas + contract.LogTopicGas*3 + contract.LogDataGas*common.HashLength -) - -// PackNativeCoinMintedEvent packs the event into the appropriate arguments for NativeCoinMinted. -// It returns topic hashes and the encoded non-indexed data. -func PackNativeCoinMintedEvent(sender common.Address, recipient common.Address, amount *big.Int) ([]common.Hash, []byte, error) { - return NativeMinterABI.PackEvent("NativeCoinMinted", sender, recipient, amount) -} - -// UnpackNativeCoinMintedEventData attempts to unpack non-indexed [dataBytes]. -func UnpackNativeCoinMintedEventData(dataBytes []byte) (*big.Int, error) { - var eventData = struct { - Amount *big.Int - }{} - err := NativeMinterABI.UnpackIntoInterface(&eventData, "NativeCoinMinted", dataBytes) - return eventData.Amount, err -} diff --git a/precompile/contracts/nativeminter/module.go b/precompile/contracts/nativeminter/module.go deleted file mode 100644 index ce62cee149..0000000000 --- a/precompile/contracts/nativeminter/module.go +++ /dev/null @@ -1,61 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package nativeminter - -import ( - "fmt" - "math/big" - - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ava-labs/subnet-evm/precompile/modules" - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - "github.com/ethereum/go-ethereum/common" -) - -var _ contract.Configurator = &configurator{} - -// ConfigKey is the key used in json config files to specify this precompile config. -// must be unique across all precompiles. -const ConfigKey = "contractNativeMinterConfig" - -var ContractAddress = common.HexToAddress("0x0200000000000000000000000000000000000001") - -// Module is the precompile module. It is used to register the precompile contract. -var Module = modules.Module{ - ConfigKey: ConfigKey, - Address: ContractAddress, - Contract: ContractNativeMinterPrecompile, - Configurator: &configurator{}, -} - -type configurator struct{} - -func init() { - if err := modules.RegisterModule(Module); err != nil { - panic(err) - } -} - -// MakeConfig returns a new precompile config instance. -// This is required to Marshal/Unmarshal the precompile config. -func (*configurator) MakeConfig() precompileconfig.Config { - return new(Config) -} - -// Configure configures [state] with the given [cfg] precompileconfig. -// This function is called by the EVM once per precompile contract activation. -func (*configurator) Configure(chainConfig precompileconfig.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, blockContext contract.ConfigurationBlockContext) error { - config, ok := cfg.(*Config) - if !ok { - return fmt.Errorf("expected config type %T, got %T: %v", &Config{}, cfg, cfg) - } - for to, amount := range config.InitialMint { - if amount != nil { - bigIntAmount := (*big.Int)(amount) - state.AddBalance(to, bigIntAmount) - } - } - - return config.AllowListConfig.Configure(chainConfig, ContractAddress, state, blockContext) -} diff --git a/precompile/contracts/nativeminter/unpack_pack_test.go b/precompile/contracts/nativeminter/unpack_pack_test.go deleted file mode 100644 index 10b2cb8a5d..0000000000 --- a/precompile/contracts/nativeminter/unpack_pack_test.go +++ /dev/null @@ -1,184 +0,0 @@ -// (c) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package nativeminter - -import ( - "fmt" - "math/big" - "testing" - - "github.com/ava-labs/subnet-evm/accounts/abi" - "github.com/ava-labs/subnet-evm/constants" - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/stretchr/testify/require" -) - -var ( - mintSignature = contract.CalculateFunctionSelector("mintNativeCoin(address,uint256)") // address, amount -) - -func FuzzPackMintNativeCoinEqualTest(f *testing.F) { - key, err := crypto.GenerateKey() - require.NoError(f, err) - addr := crypto.PubkeyToAddress(key.PublicKey) - testAddrBytes := addr.Bytes() - f.Add(testAddrBytes, common.Big0.Bytes()) - f.Add(testAddrBytes, common.Big1.Bytes()) - f.Add(testAddrBytes, abi.MaxUint256.Bytes()) - f.Add(testAddrBytes, new(big.Int).Sub(abi.MaxUint256, common.Big1).Bytes()) - f.Add(testAddrBytes, new(big.Int).Add(abi.MaxUint256, common.Big1).Bytes()) - f.Add(constants.BlackholeAddr.Bytes(), common.Big2.Bytes()) - f.Fuzz(func(t *testing.T, b []byte, bigIntBytes []byte) { - bigIntVal := new(big.Int).SetBytes(bigIntBytes) - doCheckOutputs := true - // we can only check if outputs are correct if the value is less than MaxUint256 - // otherwise the value will be truncated when packed, - // and thus unpacked output will not be equal to the value - if bigIntVal.Cmp(abi.MaxUint256) > 0 { - doCheckOutputs = false - } - testOldPackMintNativeCoinEqual(t, common.BytesToAddress(b), bigIntVal, doCheckOutputs) - }) -} - -func TestUnpackMintNativeCoinInput(t *testing.T) { - testInputBytes, err := PackMintNativeCoin(constants.BlackholeAddr, common.Big2) - require.NoError(t, err) - // exclude 4 bytes for function selector - testInputBytes = testInputBytes[4:] - tests := []struct { - name string - input []byte - strictMode bool - expectedErr string - expectedOldErr string - expectedAddr common.Address - expectedAmount *big.Int - }{ - { - name: "empty input strict mode", - input: []byte{}, - strictMode: true, - expectedErr: ErrInvalidLen.Error(), - expectedOldErr: ErrInvalidLen.Error(), - }, - { - name: "empty input", - input: []byte{}, - strictMode: false, - expectedErr: "attempting to unmarshal an empty string", - expectedOldErr: ErrInvalidLen.Error(), - }, - { - name: "input with extra bytes strict mode", - input: append(testInputBytes, make([]byte, 32)...), - strictMode: true, - expectedErr: ErrInvalidLen.Error(), - expectedOldErr: ErrInvalidLen.Error(), - }, - { - name: "input with extra bytes", - input: append(testInputBytes, make([]byte, 32)...), - strictMode: false, - expectedErr: "", - expectedOldErr: ErrInvalidLen.Error(), - expectedAddr: constants.BlackholeAddr, - expectedAmount: common.Big2, - }, - { - name: "input with extra bytes (not divisible by 32) strict mode", - input: append(testInputBytes, make([]byte, 33)...), - strictMode: true, - expectedErr: ErrInvalidLen.Error(), - expectedOldErr: ErrInvalidLen.Error(), - }, - { - name: "input with extra bytes (not divisible by 32)", - input: append(testInputBytes, make([]byte, 33)...), - strictMode: false, - expectedAddr: constants.BlackholeAddr, - expectedAmount: common.Big2, - expectedOldErr: ErrInvalidLen.Error(), - }, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - unpackedAddress, unpackedAmount, err := UnpackMintNativeCoinInput(test.input, test.strictMode) - if test.expectedErr != "" { - require.ErrorContains(t, err, test.expectedErr) - } else { - require.NoError(t, err) - require.Equal(t, test.expectedAddr, unpackedAddress) - require.True(t, test.expectedAmount.Cmp(unpackedAmount) == 0, "expected %s, got %s", test.expectedAmount.String(), unpackedAmount.String()) - } - oldUnpackedAddress, oldUnpackedAmount, oldErr := OldUnpackMintNativeCoinInput(test.input) - if test.expectedOldErr != "" { - require.ErrorContains(t, oldErr, test.expectedOldErr) - } else { - require.NoError(t, oldErr) - require.Equal(t, test.expectedAddr, oldUnpackedAddress) - require.True(t, test.expectedAmount.Cmp(oldUnpackedAmount) == 0, "expected %s, got %s", test.expectedAmount.String(), oldUnpackedAmount.String()) - } - }) - } -} - -func TestFunctionSignatures(t *testing.T) { - // Test that the mintNativeCoin signature is correct - abiMintNativeCoin := NativeMinterABI.Methods["mintNativeCoin"] - require.Equal(t, mintSignature, abiMintNativeCoin.ID) -} - -func testOldPackMintNativeCoinEqual(t *testing.T, addr common.Address, amount *big.Int, checkOutputs bool) { - t.Helper() - t.Run(fmt.Sprintf("TestUnpackAndPacks, addr: %s, amount: %s", addr.String(), amount.String()), func(t *testing.T) { - input, err := OldPackMintNativeCoinInput(addr, amount) - input2, err2 := PackMintNativeCoin(addr, amount) - if err != nil { - require.ErrorContains(t, err2, err.Error()) - return - } - require.NoError(t, err2) - require.Equal(t, input, input2) - - input = input[4:] - to, assetAmount, err := OldUnpackMintNativeCoinInput(input) - unpackedAddr, unpackedAmount, err2 := UnpackMintNativeCoinInput(input, true) - if err != nil { - require.ErrorContains(t, err2, err.Error()) - return - } - require.NoError(t, err2) - require.Equal(t, to, unpackedAddr) - require.Equal(t, assetAmount.Bytes(), unpackedAmount.Bytes()) - if checkOutputs { - require.Equal(t, addr, to) - require.Equal(t, amount.Bytes(), assetAmount.Bytes()) - } - }) -} - -func OldPackMintNativeCoinInput(address common.Address, amount *big.Int) ([]byte, error) { - // function selector (4 bytes) + input(hash for address + hash for amount) - res := make([]byte, contract.SelectorLen+mintInputLen) - err := contract.PackOrderedHashesWithSelector(res, mintSignature, []common.Hash{ - common.BytesToHash(address[:]), - common.BigToHash(amount), - }) - - return res, err -} - -func OldUnpackMintNativeCoinInput(input []byte) (common.Address, *big.Int, error) { - mintInputAddressSlot := 0 - mintInputAmountSlot := 1 - if len(input) != mintInputLen { - return common.Address{}, nil, fmt.Errorf("%w: %d", ErrInvalidLen, len(input)) - } - to := common.BytesToAddress(contract.PackedHash(input, mintInputAddressSlot)) - assetAmount := new(big.Int).SetBytes(contract.PackedHash(input, mintInputAmountSlot)) - return to, assetAmount, nil -} diff --git a/precompile/contracts/rewardmanager/config.go b/precompile/contracts/rewardmanager/config.go deleted file mode 100644 index 49949cac1a..0000000000 --- a/precompile/contracts/rewardmanager/config.go +++ /dev/null @@ -1,121 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -// Code generated -// This file is a generated precompile contract with stubbed abstract functions. - -package rewardmanager - -import ( - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - - "github.com/ethereum/go-ethereum/common" -) - -var _ precompileconfig.Config = &Config{} - -type InitialRewardConfig struct { - AllowFeeRecipients bool `json:"allowFeeRecipients"` - RewardAddress common.Address `json:"rewardAddress,omitempty"` -} - -func (i *InitialRewardConfig) Equal(other *InitialRewardConfig) bool { - if other == nil { - return false - } - - return i.AllowFeeRecipients == other.AllowFeeRecipients && i.RewardAddress == other.RewardAddress -} - -func (i *InitialRewardConfig) Verify() error { - switch { - case i.AllowFeeRecipients && i.RewardAddress != (common.Address{}): - return ErrCannotEnableBothRewards - default: - return nil - } -} - -func (i *InitialRewardConfig) Configure(state contract.StateDB) error { - // enable allow fee recipients - if i.AllowFeeRecipients { - EnableAllowFeeRecipients(state) - } else if i.RewardAddress == (common.Address{}) { - // if reward address is empty and allow fee recipients is false - // then disable rewards - DisableFeeRewards(state) - } else { - // set reward address - StoreRewardAddress(state, i.RewardAddress) - } - return nil -} - -// Config implements the StatefulPrecompileConfig interface while adding in the -// RewardManager specific precompile config. -type Config struct { - allowlist.AllowListConfig - precompileconfig.Upgrade - InitialRewardConfig *InitialRewardConfig `json:"initialRewardConfig,omitempty"` -} - -// NewConfig returns a config for a network upgrade at [blockTimestamp] that enables -// RewardManager with the given [admins], [enableds] and [managers] as members of the allowlist with [initialConfig] as initial rewards config if specified. -func NewConfig(blockTimestamp *uint64, admins []common.Address, enableds []common.Address, managers []common.Address, initialConfig *InitialRewardConfig) *Config { - return &Config{ - AllowListConfig: allowlist.AllowListConfig{ - AdminAddresses: admins, - EnabledAddresses: enableds, - ManagerAddresses: managers, - }, - Upgrade: precompileconfig.Upgrade{BlockTimestamp: blockTimestamp}, - InitialRewardConfig: initialConfig, - } -} - -// NewDisableConfig returns config for a network upgrade at [blockTimestamp] -// that disables RewardManager. -func NewDisableConfig(blockTimestamp *uint64) *Config { - return &Config{ - Upgrade: precompileconfig.Upgrade{ - BlockTimestamp: blockTimestamp, - Disable: true, - }, - } -} - -// Key returns the key for the Contract precompileconfig. -// This should be the same key as used in the precompile module. -func (*Config) Key() string { return ConfigKey } - -// Verify tries to verify Config and returns an error accordingly. -func (c *Config) Verify(chainConfig precompileconfig.ChainConfig) error { - if c.InitialRewardConfig != nil { - if err := c.InitialRewardConfig.Verify(); err != nil { - return err - } - } - return c.AllowListConfig.Verify(chainConfig, c.Upgrade) -} - -// Equal returns true if [cfg] is a [*RewardManagerConfig] and it has been configured identical to [c]. -func (c *Config) Equal(cfg precompileconfig.Config) bool { - // typecast before comparison - other, ok := (cfg).(*Config) - if !ok { - return false - } - - if c.InitialRewardConfig != nil { - if other.InitialRewardConfig == nil { - return false - } - if !c.InitialRewardConfig.Equal(other.InitialRewardConfig) { - return false - } - } - - return c.Upgrade.Equal(&other.Upgrade) && c.AllowListConfig.Equal(&other.AllowListConfig) -} diff --git a/precompile/contracts/rewardmanager/config_test.go b/precompile/contracts/rewardmanager/config_test.go deleted file mode 100644 index 958eb000d9..0000000000 --- a/precompile/contracts/rewardmanager/config_test.go +++ /dev/null @@ -1,81 +0,0 @@ -// (c) 2022 Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package rewardmanager - -import ( - "testing" - - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - "github.com/ava-labs/subnet-evm/precompile/testutils" - "github.com/ava-labs/subnet-evm/utils" - "github.com/ethereum/go-ethereum/common" - "go.uber.org/mock/gomock" -) - -func TestVerify(t *testing.T) { - admins := []common.Address{allowlist.TestAdminAddr} - enableds := []common.Address{allowlist.TestEnabledAddr} - managers := []common.Address{allowlist.TestManagerAddr} - tests := map[string]testutils.ConfigVerifyTest{ - "both reward mechanisms should not be activated at the same time in reward manager": { - Config: NewConfig(utils.NewUint64(3), admins, enableds, managers, &InitialRewardConfig{ - AllowFeeRecipients: true, - RewardAddress: common.HexToAddress("0x01"), - }), - ExpectedError: ErrCannotEnableBothRewards.Error(), - }, - } - allowlist.VerifyPrecompileWithAllowListTests(t, Module, tests) -} - -func TestEqual(t *testing.T) { - admins := []common.Address{allowlist.TestAdminAddr} - enableds := []common.Address{allowlist.TestEnabledAddr} - managers := []common.Address{allowlist.TestManagerAddr} - tests := map[string]testutils.ConfigEqualTest{ - "non-nil config and nil other": { - Config: NewConfig(utils.NewUint64(3), admins, enableds, managers, nil), - Other: nil, - Expected: false, - }, - "different type": { - Config: NewConfig(utils.NewUint64(3), admins, enableds, managers, nil), - Other: precompileconfig.NewMockConfig(gomock.NewController(t)), - Expected: false, - }, - "different timestamp": { - Config: NewConfig(utils.NewUint64(3), admins, nil, nil, nil), - Other: NewConfig(utils.NewUint64(4), admins, nil, nil, nil), - Expected: false, - }, - "non-nil initial config and nil initial config": { - Config: NewConfig(utils.NewUint64(3), admins, nil, nil, &InitialRewardConfig{ - AllowFeeRecipients: true, - }), - Other: NewConfig(utils.NewUint64(3), admins, nil, nil, nil), - Expected: false, - }, - "different initial config": { - Config: NewConfig(utils.NewUint64(3), admins, nil, nil, &InitialRewardConfig{ - RewardAddress: common.HexToAddress("0x01"), - }), - Other: NewConfig(utils.NewUint64(3), admins, nil, nil, - &InitialRewardConfig{ - RewardAddress: common.HexToAddress("0x02"), - }), - Expected: false, - }, - "same config": { - Config: NewConfig(utils.NewUint64(3), admins, nil, nil, &InitialRewardConfig{ - RewardAddress: common.HexToAddress("0x01"), - }), - Other: NewConfig(utils.NewUint64(3), admins, nil, nil, &InitialRewardConfig{ - RewardAddress: common.HexToAddress("0x01"), - }), - Expected: true, - }, - } - allowlist.EqualPrecompileWithAllowListTests(t, Module, tests) -} diff --git a/precompile/contracts/rewardmanager/contract.abi b/precompile/contracts/rewardmanager/contract.abi deleted file mode 100644 index 5b80821de3..0000000000 --- a/precompile/contracts/rewardmanager/contract.abi +++ /dev/null @@ -1,177 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - } - ], - "name": "FeeRecipientsAllowed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "oldRewardAddress", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newRewardAddress", - "type": "address" - } - ], - "name": "RewardAddressChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - } - ], - "name": "RewardsDisabled", - "type": "event" - }, - { - "inputs": [], - "name": "allowFeeRecipients", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "areFeeRecipientsAllowed", - "outputs": [ - { - "internalType": "bool", - "name": "isAllowed", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "currentRewardAddress", - "outputs": [ - { - "internalType": "address", - "name": "rewardAddress", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "disableRewards", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "readAllowList", - "outputs": [ - { - "internalType": "uint256", - "name": "role", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "setAdmin", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "setEnabled", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "setManager", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "setNone", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "addr", - "type": "address" - } - ], - "name": "setRewardAddress", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] diff --git a/precompile/contracts/rewardmanager/contract.go b/precompile/contracts/rewardmanager/contract.go deleted file mode 100644 index 4d9b6224a8..0000000000 --- a/precompile/contracts/rewardmanager/contract.go +++ /dev/null @@ -1,338 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -// Code generated -// This file is a generated precompile contract with stubbed abstract functions. - -package rewardmanager - -import ( - _ "embed" - "errors" - "fmt" - - "github.com/ava-labs/subnet-evm/accounts/abi" - "github.com/ava-labs/subnet-evm/constants" - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ava-labs/subnet-evm/vmerrs" - - "github.com/ethereum/go-ethereum/common" -) - -const ( - AllowFeeRecipientsGasCost uint64 = contract.WriteGasCostPerSlot + allowlist.ReadAllowListGasCost // write 1 slot + read allow list - AreFeeRecipientsAllowedGasCost uint64 = allowlist.ReadAllowListGasCost - CurrentRewardAddressGasCost uint64 = allowlist.ReadAllowListGasCost - DisableRewardsGasCost uint64 = contract.WriteGasCostPerSlot + allowlist.ReadAllowListGasCost // write 1 slot + read allow list - SetRewardAddressGasCost uint64 = contract.WriteGasCostPerSlot + allowlist.ReadAllowListGasCost // write 1 slot + read allow list -) - -// Singleton StatefulPrecompiledContract and signatures. -var ( - ErrCannotAllowFeeRecipients = errors.New("non-enabled cannot call allowFeeRecipients") - ErrCannotAreFeeRecipientsAllowed = errors.New("non-enabled cannot call areFeeRecipientsAllowed") - ErrCannotCurrentRewardAddress = errors.New("non-enabled cannot call currentRewardAddress") - ErrCannotDisableRewards = errors.New("non-enabled cannot call disableRewards") - ErrCannotSetRewardAddress = errors.New("non-enabled cannot call setRewardAddress") - - ErrCannotEnableBothRewards = errors.New("cannot enable both fee recipients and reward address at the same time") - ErrEmptyRewardAddress = errors.New("reward address cannot be empty") - - // RewardManagerRawABI contains the raw ABI of RewardManager contract. - //go:embed contract.abi - RewardManagerRawABI string - - RewardManagerABI = contract.ParseABI(RewardManagerRawABI) - RewardManagerPrecompile = createRewardManagerPrecompile() // will be initialized by init function - - rewardAddressStorageKey = common.Hash{'r', 'a', 's', 'k'} - allowFeeRecipientsAddressValue = common.Hash{'a', 'f', 'r', 'a', 'v'} -) - -// GetRewardManagerAllowListStatus returns the role of [address] for the RewardManager list. -func GetRewardManagerAllowListStatus(stateDB contract.StateDB, address common.Address) allowlist.Role { - return allowlist.GetAllowListStatus(stateDB, ContractAddress, address) -} - -// SetRewardManagerAllowListStatus sets the permissions of [address] to [role] for the -// RewardManager list. Assumes [role] has already been verified as valid. -func SetRewardManagerAllowListStatus(stateDB contract.StateDB, address common.Address, role allowlist.Role) { - allowlist.SetAllowListRole(stateDB, ContractAddress, address, role) -} - -// PackAllowFeeRecipients packs the function selector (first 4 func signature bytes). -// This function is mostly used for tests. -func PackAllowFeeRecipients() ([]byte, error) { - return RewardManagerABI.Pack("allowFeeRecipients") -} - -// EnableAllowFeeRecipients enables fee recipients. -func EnableAllowFeeRecipients(stateDB contract.StateDB) { - stateDB.SetState(ContractAddress, rewardAddressStorageKey, allowFeeRecipientsAddressValue) -} - -// DisableRewardAddress disables rewards and burns them by sending to Blackhole Address. -func DisableFeeRewards(stateDB contract.StateDB) { - stateDB.SetState(ContractAddress, rewardAddressStorageKey, common.BytesToHash(constants.BlackholeAddr.Bytes())) -} - -func allowFeeRecipients(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = contract.DeductGas(suppliedGas, AllowFeeRecipientsGasCost); err != nil { - return nil, 0, err - } - if readOnly { - return nil, remainingGas, vmerrs.ErrWriteProtection - } - // no input provided for this function - - // Allow list is enabled and AllowFeeRecipients is a state-changer function. - // This part of the code restricts the function to be called only by enabled/admin addresses in the allow list. - // You can modify/delete this code if you don't want this function to be restricted by the allow list. - stateDB := accessibleState.GetStateDB() - // Verify that the caller is in the allow list and therefore has the right to call this function. - callerStatus := allowlist.GetAllowListStatus(stateDB, ContractAddress, caller) - if !callerStatus.IsEnabled() { - return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotAllowFeeRecipients, caller) - } - // allow list code ends here. - - if contract.IsDurangoActivated(accessibleState) { - if remainingGas, err = contract.DeductGas(remainingGas, FeeRecipientsAllowedEventGasCost); err != nil { - return nil, 0, err - } - topics, data, err := PackFeeRecipientsAllowedEvent(caller) - if err != nil { - return nil, remainingGas, err - } - stateDB.AddLog( - ContractAddress, - topics, - data, - accessibleState.GetBlockContext().Number().Uint64(), - ) - } - EnableAllowFeeRecipients(stateDB) - // Return the packed output and the remaining gas - return []byte{}, remainingGas, nil -} - -// PackAreFeeRecipientsAllowed packs the include selector (first 4 func signature bytes). -// This function is mostly used for tests. -func PackAreFeeRecipientsAllowed() ([]byte, error) { - return RewardManagerABI.Pack("areFeeRecipientsAllowed") -} - -// PackAreFeeRecipientsAllowedOutput attempts to pack given isAllowed of type bool -// to conform the ABI outputs. -func PackAreFeeRecipientsAllowedOutput(isAllowed bool) ([]byte, error) { - return RewardManagerABI.PackOutput("areFeeRecipientsAllowed", isAllowed) -} - -func areFeeRecipientsAllowed(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = contract.DeductGas(suppliedGas, AreFeeRecipientsAllowedGasCost); err != nil { - return nil, 0, err - } - // no input provided for this function - - stateDB := accessibleState.GetStateDB() - var output bool - _, output = GetStoredRewardAddress(stateDB) - - packedOutput, err := PackAreFeeRecipientsAllowedOutput(output) - if err != nil { - return nil, remainingGas, err - } - - // Return the packed output and the remaining gas - return packedOutput, remainingGas, nil -} - -// PackCurrentRewardAddress packs the include selector (first 4 func signature bytes). -// This function is mostly used for tests. -func PackCurrentRewardAddress() ([]byte, error) { - return RewardManagerABI.Pack("currentRewardAddress") -} - -// PackCurrentRewardAddressOutput attempts to pack given rewardAddress of type common.Address -// to conform the ABI outputs. -func PackCurrentRewardAddressOutput(rewardAddress common.Address) ([]byte, error) { - return RewardManagerABI.PackOutput("currentRewardAddress", rewardAddress) -} - -// GetStoredRewardAddress returns the current value of the address stored under rewardAddressStorageKey. -// Returns an empty address and true if allow fee recipients is enabled, otherwise returns current reward address and false. -func GetStoredRewardAddress(stateDB contract.StateDB) (common.Address, bool) { - val := stateDB.GetState(ContractAddress, rewardAddressStorageKey) - return common.BytesToAddress(val.Bytes()), val == allowFeeRecipientsAddressValue -} - -// StoredRewardAddress stores the given [val] under rewardAddressStorageKey. -func StoreRewardAddress(stateDB contract.StateDB, val common.Address) { - stateDB.SetState(ContractAddress, rewardAddressStorageKey, common.BytesToHash(val.Bytes())) -} - -// PackSetRewardAddress packs [addr] of type common.Address into the appropriate arguments for setRewardAddress. -// the packed bytes include selector (first 4 func signature bytes). -// This function is mostly used for tests. -func PackSetRewardAddress(addr common.Address) ([]byte, error) { - return RewardManagerABI.Pack("setRewardAddress", addr) -} - -// UnpackSetRewardAddressInput attempts to unpack [input] into the common.Address type argument -// assumes that [input] does not include selector (omits first 4 func signature bytes) -// if [useStrictMode] is true, it will return an error if the length of [input] is not divisible by 32 -func UnpackSetRewardAddressInput(input []byte, useStrictMode bool) (common.Address, error) { - res, err := RewardManagerABI.UnpackInput("setRewardAddress", input, useStrictMode) - if err != nil { - return common.Address{}, err - } - unpacked := *abi.ConvertType(res[0], new(common.Address)).(*common.Address) - return unpacked, nil -} - -func setRewardAddress(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = contract.DeductGas(suppliedGas, SetRewardAddressGasCost); err != nil { - return nil, 0, err - } - if readOnly { - return nil, remainingGas, vmerrs.ErrWriteProtection - } - // attempts to unpack [input] into the arguments to the SetRewardAddressInput. - // Assumes that [input] does not include selector - // do not use strict mode after Durango - useStrictMode := !contract.IsDurangoActivated(accessibleState) - rewardAddress, err := UnpackSetRewardAddressInput(input, useStrictMode) - if err != nil { - return nil, remainingGas, err - } - - // Allow list is enabled and SetRewardAddress is a state-changer function. - // This part of the code restricts the function to be called only by enabled/admin addresses in the allow list. - // You can modify/delete this code if you don't want this function to be restricted by the allow list. - stateDB := accessibleState.GetStateDB() - // Verify that the caller is in the allow list and therefore has the right to call this function. - callerStatus := allowlist.GetAllowListStatus(stateDB, ContractAddress, caller) - if !callerStatus.IsEnabled() { - return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotSetRewardAddress, caller) - } - // allow list code ends here. - - // if input is empty, return an error - if rewardAddress == (common.Address{}) { - return nil, remainingGas, ErrEmptyRewardAddress - } - - // Add a log to be handled if this action is finalized. - if contract.IsDurangoActivated(accessibleState) { - if remainingGas, err = contract.DeductGas(remainingGas, RewardAddressChangedEventGasCost); err != nil { - return nil, 0, err - } - oldRewardAddress, _ := GetStoredRewardAddress(stateDB) - topics, data, err := PackRewardAddressChangedEvent(caller, oldRewardAddress, rewardAddress) - if err != nil { - return nil, remainingGas, err - } - stateDB.AddLog( - ContractAddress, - topics, - data, - accessibleState.GetBlockContext().Number().Uint64(), - ) - } - StoreRewardAddress(stateDB, rewardAddress) - // Return the packed output and the remaining gas - return []byte{}, remainingGas, nil -} - -func currentRewardAddress(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = contract.DeductGas(suppliedGas, CurrentRewardAddressGasCost); err != nil { - return nil, 0, err - } - - // no input provided for this function - stateDB := accessibleState.GetStateDB() - output, _ := GetStoredRewardAddress(stateDB) - packedOutput, err := PackCurrentRewardAddressOutput(output) - if err != nil { - return nil, remainingGas, err - } - - // Return the packed output and the remaining gas - return packedOutput, remainingGas, nil -} - -// PackDisableRewards packs the include selector (first 4 func signature bytes). -// This function is mostly used for tests. -func PackDisableRewards() ([]byte, error) { - return RewardManagerABI.Pack("disableRewards") -} - -func disableRewards(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) { - if remainingGas, err = contract.DeductGas(suppliedGas, DisableRewardsGasCost); err != nil { - return nil, 0, err - } - if readOnly { - return nil, remainingGas, vmerrs.ErrWriteProtection - } - // no input provided for this function - - // Allow list is enabled and DisableRewards is a state-changer function. - // This part of the code restricts the function to be called only by enabled/admin addresses in the allow list. - // You can modify/delete this code if you don't want this function to be restricted by the allow list. - stateDB := accessibleState.GetStateDB() - // Verify that the caller is in the allow list and therefore has the right to call this function. - callerStatus := allowlist.GetAllowListStatus(stateDB, ContractAddress, caller) - if !callerStatus.IsEnabled() { - return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotDisableRewards, caller) - } - // allow list code ends here. - - if contract.IsDurangoActivated(accessibleState) { - if remainingGas, err = contract.DeductGas(remainingGas, RewardsDisabledEventGasCost); err != nil { - return nil, 0, err - } - topics, data, err := PackRewardsDisabledEvent(caller) - if err != nil { - return nil, remainingGas, err - } - stateDB.AddLog( - ContractAddress, - topics, - data, - accessibleState.GetBlockContext().Number().Uint64(), - ) - } - DisableFeeRewards(stateDB) - // Return the packed output and the remaining gas - return []byte{}, remainingGas, nil -} - -// createRewardManagerPrecompile returns a StatefulPrecompiledContract with getters and setters for the precompile. -// Access to the getters/setters is controlled by an allow list for [precompileAddr]. -func createRewardManagerPrecompile() contract.StatefulPrecompiledContract { - var functions []*contract.StatefulPrecompileFunction - functions = append(functions, allowlist.CreateAllowListFunctions(ContractAddress)...) - abiFunctionMap := map[string]contract.RunStatefulPrecompileFunc{ - "allowFeeRecipients": allowFeeRecipients, - "areFeeRecipientsAllowed": areFeeRecipientsAllowed, - "currentRewardAddress": currentRewardAddress, - "disableRewards": disableRewards, - "setRewardAddress": setRewardAddress, - } - - for name, function := range abiFunctionMap { - method, ok := RewardManagerABI.Methods[name] - if !ok { - panic(fmt.Errorf("given method (%s) does not exist in the ABI", name)) - } - functions = append(functions, contract.NewStatefulPrecompileFunction(method.ID, function)) - } - - // Construct the contract with no fallback function. - statefulContract, err := contract.NewStatefulPrecompileContract(nil, functions) - if err != nil { - panic(err) - } - return statefulContract -} diff --git a/precompile/contracts/rewardmanager/contract_test.go b/precompile/contracts/rewardmanager/contract_test.go deleted file mode 100644 index eeecdbe795..0000000000 --- a/precompile/contracts/rewardmanager/contract_test.go +++ /dev/null @@ -1,489 +0,0 @@ -// (c) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package rewardmanager - -import ( - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" - - "github.com/ava-labs/subnet-evm/commontype" - "github.com/ava-labs/subnet-evm/constants" - "github.com/ava-labs/subnet-evm/core/state" - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - "github.com/ava-labs/subnet-evm/precompile/testutils" - "github.com/ava-labs/subnet-evm/vmerrs" -) - -var ( - rewardAddress = common.HexToAddress("0x0123") - tests = map[string]testutils.PrecompileTest{ - "set allow fee recipients from no role fails": { - Caller: allowlist.TestNoRoleAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackAllowFeeRecipients() - require.NoError(t, err) - - return input - }, - SuppliedGas: AllowFeeRecipientsGasCost, - ReadOnly: false, - ExpectedErr: ErrCannotAllowFeeRecipients.Error(), - }, - "set reward address from no role fails": { - Caller: allowlist.TestNoRoleAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackSetRewardAddress(rewardAddress) - require.NoError(t, err) - - return input - }, - SuppliedGas: SetRewardAddressGasCost, - ReadOnly: false, - ExpectedErr: ErrCannotSetRewardAddress.Error(), - }, - "disable rewards from no role fails": { - Caller: allowlist.TestNoRoleAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackDisableRewards() - require.NoError(t, err) - - return input - }, - SuppliedGas: DisableRewardsGasCost, - ReadOnly: false, - ExpectedErr: ErrCannotDisableRewards.Error(), - }, - "set allow fee recipients from enabled succeeds": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackAllowFeeRecipients() - require.NoError(t, err) - - return input - }, - SuppliedGas: AllowFeeRecipientsGasCost + FeeRecipientsAllowedEventGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, state contract.StateDB) { - _, isFeeRecipients := GetStoredRewardAddress(state) - require.True(t, isFeeRecipients) - - logsTopics, logsData := state.GetLogData() - assertFeeRecipientsAllowed(t, logsTopics, logsData, allowlist.TestEnabledAddr) - }, - }, - "set fee recipients should not emit events pre-Durango": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackAllowFeeRecipients() - require.NoError(t, err) - - return input - }, - ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig { - mockChainConfig := precompileconfig.NewMockChainConfig(ctrl) - mockChainConfig.EXPECT().GetFeeConfig().AnyTimes().Return(commontype.ValidTestFeeConfig) - mockChainConfig.EXPECT().AllowedFeeRecipients().AnyTimes().Return(false) - mockChainConfig.EXPECT().IsDurango(gomock.Any()).AnyTimes().Return(false) - return mockChainConfig - }, - SuppliedGas: AllowFeeRecipientsGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, stateDB contract.StateDB) { - // Check no logs are stored in state - logsTopics, logsData := stateDB.GetLogData() - require.Len(t, logsTopics, 0) - require.Len(t, logsData, 0) - }, - }, - "set reward address from enabled succeeds": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackSetRewardAddress(rewardAddress) - require.NoError(t, err) - - return input - }, - SuppliedGas: SetRewardAddressGasCost + RewardAddressChangedEventGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, state contract.StateDB) { - address, isFeeRecipients := GetStoredRewardAddress(state) - require.Equal(t, rewardAddress, address) - require.False(t, isFeeRecipients) - - logsTopics, logsData := state.GetLogData() - assertRewardAddressChanged(t, logsTopics, logsData, allowlist.TestEnabledAddr, common.Address{}, rewardAddress) - }, - }, - "set allow fee recipients from manager succeeds": { - Caller: allowlist.TestManagerAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackAllowFeeRecipients() - require.NoError(t, err) - - return input - }, - SuppliedGas: AllowFeeRecipientsGasCost + FeeRecipientsAllowedEventGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, state contract.StateDB) { - _, isFeeRecipients := GetStoredRewardAddress(state) - require.True(t, isFeeRecipients) - - logsTopics, logsData := state.GetLogData() - assertFeeRecipientsAllowed(t, logsTopics, logsData, allowlist.TestManagerAddr) - }, - }, - "set reward address from manager succeeds": { - Caller: allowlist.TestManagerAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackSetRewardAddress(rewardAddress) - require.NoError(t, err) - - return input - }, - SuppliedGas: SetRewardAddressGasCost + RewardAddressChangedEventGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, state contract.StateDB) { - address, isFeeRecipients := GetStoredRewardAddress(state) - require.Equal(t, rewardAddress, address) - require.False(t, isFeeRecipients) - - logsTopics, logsData := state.GetLogData() - assertRewardAddressChanged(t, logsTopics, logsData, allowlist.TestManagerAddr, common.Address{}, rewardAddress) - }, - }, - "change reward address should not emit events pre-Durango": { - Caller: allowlist.TestManagerAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackSetRewardAddress(rewardAddress) - require.NoError(t, err) - - return input - }, - ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig { - mockChainConfig := precompileconfig.NewMockChainConfig(ctrl) - mockChainConfig.EXPECT().GetFeeConfig().AnyTimes().Return(commontype.ValidTestFeeConfig) - mockChainConfig.EXPECT().AllowedFeeRecipients().AnyTimes().Return(false) - mockChainConfig.EXPECT().IsDurango(gomock.Any()).AnyTimes().Return(false) - return mockChainConfig - }, - SuppliedGas: SetRewardAddressGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, stateDB contract.StateDB) { - // Check no logs are stored in state - logsTopics, logsData := stateDB.GetLogData() - require.Len(t, logsTopics, 0) - require.Len(t, logsData, 0) - }, - }, - "disable rewards from manager succeeds": { - Caller: allowlist.TestManagerAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackDisableRewards() - require.NoError(t, err) - - return input - }, - SuppliedGas: DisableRewardsGasCost + RewardsDisabledEventGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, state contract.StateDB) { - address, isFeeRecipients := GetStoredRewardAddress(state) - require.False(t, isFeeRecipients) - require.Equal(t, constants.BlackholeAddr, address) - - logsTopics, logsData := state.GetLogData() - assertRewardsDisabled(t, logsTopics, logsData, allowlist.TestManagerAddr) - }, - }, - "disable rewards from enabled succeeds": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackDisableRewards() - require.NoError(t, err) - - return input - }, - SuppliedGas: DisableRewardsGasCost + RewardsDisabledEventGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, state contract.StateDB) { - address, isFeeRecipients := GetStoredRewardAddress(state) - require.False(t, isFeeRecipients) - require.Equal(t, constants.BlackholeAddr, address) - - logsTopics, logsData := state.GetLogData() - assertRewardsDisabled(t, logsTopics, logsData, allowlist.TestEnabledAddr) - }, - }, - "disable rewards should not emit event pre-Durango": { - Caller: allowlist.TestManagerAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackDisableRewards() - require.NoError(t, err) - - return input - }, - ChainConfigFn: func(ctrl *gomock.Controller) precompileconfig.ChainConfig { - mockChainConfig := precompileconfig.NewMockChainConfig(ctrl) - mockChainConfig.EXPECT().GetFeeConfig().AnyTimes().Return(commontype.ValidTestFeeConfig) - mockChainConfig.EXPECT().AllowedFeeRecipients().AnyTimes().Return(false) - mockChainConfig.EXPECT().IsDurango(gomock.Any()).AnyTimes().Return(false) - return mockChainConfig - }, - SuppliedGas: SetRewardAddressGasCost, - ReadOnly: false, - ExpectedRes: []byte{}, - AfterHook: func(t testing.TB, stateDB contract.StateDB) { - // Check logs are not stored in state - topics, data := stateDB.GetLogData() - require.Len(t, topics, 0) - require.Len(t, data, 0) - }, - }, - "get current reward address from no role succeeds": { - Caller: allowlist.TestNoRoleAddr, - BeforeHook: func(t testing.TB, state contract.StateDB) { - allowlist.SetDefaultRoles(Module.Address)(t, state) - StoreRewardAddress(state, rewardAddress) - }, - InputFn: func(t testing.TB) []byte { - input, err := PackCurrentRewardAddress() - require.NoError(t, err) - - return input - }, - SuppliedGas: CurrentRewardAddressGasCost, - ReadOnly: false, - ExpectedRes: func() []byte { - res, err := PackCurrentRewardAddressOutput(rewardAddress) - if err != nil { - panic(err) - } - return res - }(), - }, - "get are fee recipients allowed from no role succeeds": { - Caller: allowlist.TestNoRoleAddr, - BeforeHook: func(t testing.TB, state contract.StateDB) { - allowlist.SetDefaultRoles(Module.Address)(t, state) - EnableAllowFeeRecipients(state) - }, - InputFn: func(t testing.TB) []byte { - input, err := PackAreFeeRecipientsAllowed() - require.NoError(t, err) - return input - }, - SuppliedGas: AreFeeRecipientsAllowedGasCost, - ReadOnly: false, - ExpectedRes: func() []byte { - res, err := PackAreFeeRecipientsAllowedOutput(true) - if err != nil { - panic(err) - } - return res - }(), - }, - "get initial config with address": { - Caller: allowlist.TestNoRoleAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackCurrentRewardAddress() - require.NoError(t, err) - return input - }, - SuppliedGas: CurrentRewardAddressGasCost, - Config: &Config{ - InitialRewardConfig: &InitialRewardConfig{ - RewardAddress: rewardAddress, - }, - }, - ReadOnly: false, - ExpectedRes: func() []byte { - res, err := PackCurrentRewardAddressOutput(rewardAddress) - if err != nil { - panic(err) - } - return res - }(), - }, - "get initial config with allow fee recipients enabled": { - Caller: allowlist.TestNoRoleAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackAreFeeRecipientsAllowed() - require.NoError(t, err) - return input - }, - SuppliedGas: AreFeeRecipientsAllowedGasCost, - Config: &Config{ - InitialRewardConfig: &InitialRewardConfig{ - AllowFeeRecipients: true, - }, - }, - ReadOnly: false, - ExpectedRes: func() []byte { - res, err := PackAreFeeRecipientsAllowedOutput(true) - if err != nil { - panic(err) - } - return res - }(), - }, - "readOnly allow fee recipients with allowed role fails": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackAllowFeeRecipients() - require.NoError(t, err) - - return input - }, - SuppliedGas: AllowFeeRecipientsGasCost, - ReadOnly: true, - ExpectedErr: vmerrs.ErrWriteProtection.Error(), - }, - "readOnly set reward addresss with allowed role fails": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackSetRewardAddress(rewardAddress) - require.NoError(t, err) - - return input - }, - SuppliedGas: SetRewardAddressGasCost, - ReadOnly: true, - ExpectedErr: vmerrs.ErrWriteProtection.Error(), - }, - "insufficient gas set reward address from allowed role": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackSetRewardAddress(rewardAddress) - require.NoError(t, err) - - return input - }, - SuppliedGas: SetRewardAddressGasCost + RewardAddressChangedEventGasCost - 1, - ReadOnly: false, - ExpectedErr: vmerrs.ErrOutOfGas.Error(), - }, - "insufficient gas allow fee recipients from allowed role": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackAllowFeeRecipients() - require.NoError(t, err) - - return input - }, - SuppliedGas: AllowFeeRecipientsGasCost + FeeRecipientsAllowedEventGasCost - 1, - ReadOnly: false, - ExpectedErr: vmerrs.ErrOutOfGas.Error(), - }, - "insufficient gas read current reward address from allowed role": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackCurrentRewardAddress() - require.NoError(t, err) - - return input - }, - SuppliedGas: CurrentRewardAddressGasCost - 1, - ReadOnly: false, - ExpectedErr: vmerrs.ErrOutOfGas.Error(), - }, - "insufficient gas are fee recipients allowed from allowed role": { - Caller: allowlist.TestEnabledAddr, - BeforeHook: allowlist.SetDefaultRoles(Module.Address), - InputFn: func(t testing.TB) []byte { - input, err := PackAreFeeRecipientsAllowed() - require.NoError(t, err) - - return input - }, - SuppliedGas: AreFeeRecipientsAllowedGasCost - 1, - ReadOnly: false, - ExpectedErr: vmerrs.ErrOutOfGas.Error(), - }, - } -) - -func TestRewardManagerRun(t *testing.T) { - allowlist.RunPrecompileWithAllowListTests(t, Module, state.NewTestStateDB, tests) -} - -func BenchmarkRewardManager(b *testing.B) { - allowlist.BenchPrecompileWithAllowList(b, Module, state.NewTestStateDB, tests) -} - -func assertRewardAddressChanged( - t testing.TB, - logsTopics [][]common.Hash, - logsData [][]byte, - caller, - oldAddress, - newAddress common.Address) { - require.Len(t, logsTopics, 1) - require.Len(t, logsData, 1) - topics := logsTopics[0] - require.Len(t, topics, 4) - require.Equal(t, RewardManagerABI.Events["RewardAddressChanged"].ID, topics[0]) - require.Equal(t, common.BytesToHash(caller[:]), topics[1]) - require.Equal(t, common.BytesToHash(oldAddress[:]), topics[2]) - require.Equal(t, common.BytesToHash(newAddress[:]), topics[3]) - require.Len(t, logsData[0], 0) -} - -func assertRewardsDisabled( - t testing.TB, - logsTopics [][]common.Hash, - logsData [][]byte, - caller common.Address) { - require.Len(t, logsTopics, 1) - require.Len(t, logsData, 1) - topics := logsTopics[0] - require.Len(t, topics, 2) - require.Equal(t, RewardManagerABI.Events["RewardsDisabled"].ID, topics[0]) - require.Equal(t, common.BytesToHash(caller[:]), topics[1]) - require.Len(t, logsData[0], 0) -} - -func assertFeeRecipientsAllowed( - t testing.TB, - logsTopics [][]common.Hash, - logsData [][]byte, - caller common.Address) { - require.Len(t, logsTopics, 1) - require.Len(t, logsData, 1) - topics := logsTopics[0] - require.Len(t, topics, 2) - require.Equal(t, RewardManagerABI.Events["FeeRecipientsAllowed"].ID, topics[0]) - require.Equal(t, common.BytesToHash(caller[:]), topics[1]) - require.Len(t, logsData[0], 0) -} diff --git a/precompile/contracts/rewardmanager/event.go b/precompile/contracts/rewardmanager/event.go deleted file mode 100644 index 1bfa891682..0000000000 --- a/precompile/contracts/rewardmanager/event.go +++ /dev/null @@ -1,41 +0,0 @@ -// Code generated -// This file is a generated precompile contract config with stubbed abstract functions. -// The file is generated by a template. Please inspect every code and comment in this file before use. - -package rewardmanager - -import ( - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ethereum/go-ethereum/common" -) - -const ( - // FeeRecipientsAllowedEventGasCost is the gas cost of the FeeRecipientsAllowed event. - // It is calculated as the gas cost of the log operation + the gas cost of 2 topic hashes (signature + sender). - FeeRecipientsAllowedEventGasCost = contract.LogGas + contract.LogTopicGas*2 - // RewardAddressChangedEventGasCost is the gas cost of the RewardAddressChanged event. - // It is calculated as the gas cost of the log operation + the gas cost of 3 topic hashes (signature + sender + oldRewardAddress). - // + the gas cost of reading old reward address. - RewardAddressChangedEventGasCost = contract.LogGas + contract.LogTopicGas*4 + contract.ReadGasCostPerSlot - // RewardsDisabledEventGasCost is the gas cost of the RewardsDisabled event. - // It is calculated as the gas cost of the log operation + the gas cost of 2 topic hashes (signature + sender). - RewardsDisabledEventGasCost = contract.LogGas + contract.LogTopicGas*2 -) - -// PackFeeRecipientsAllowedEvent packs the event into the appropriate arguments for FeeRecipientsAllowed. -// It returns topic hashes and the encoded non-indexed data. -func PackFeeRecipientsAllowedEvent(sender common.Address) ([]common.Hash, []byte, error) { - return RewardManagerABI.PackEvent("FeeRecipientsAllowed", sender) -} - -// PackRewardAddressChangedEvent packs the event into the appropriate arguments for RewardAddressChanged. -// It returns topic hashes and the encoded non-indexed data. -func PackRewardAddressChangedEvent(sender common.Address, oldRewardAddress common.Address, newRewardAddress common.Address) ([]common.Hash, []byte, error) { - return RewardManagerABI.PackEvent("RewardAddressChanged", sender, oldRewardAddress, newRewardAddress) -} - -// PackRewardsDisabledEvent packs the event into the appropriate arguments for RewardsDisabled. -// It returns topic hashes and the encoded non-indexed data. -func PackRewardsDisabledEvent(sender common.Address) ([]common.Hash, []byte, error) { - return RewardManagerABI.PackEvent("RewardsDisabled", sender) -} diff --git a/precompile/contracts/rewardmanager/module.go b/precompile/contracts/rewardmanager/module.go deleted file mode 100644 index 272547c212..0000000000 --- a/precompile/contracts/rewardmanager/module.go +++ /dev/null @@ -1,70 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package rewardmanager - -import ( - "fmt" - - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ava-labs/subnet-evm/precompile/modules" - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - - "github.com/ethereum/go-ethereum/common" -) - -var _ contract.Configurator = &configurator{} - -// ConfigKey is the key used in json config files to specify this precompile config. -// must be unique across all precompiles. -const ConfigKey = "rewardManagerConfig" - -var ContractAddress = common.HexToAddress("0x0200000000000000000000000000000000000004") - -// Module is the precompile module. It is used to register the precompile contract. -var Module = modules.Module{ - ConfigKey: ConfigKey, - Address: ContractAddress, - Contract: RewardManagerPrecompile, - Configurator: &configurator{}, -} - -type configurator struct{} - -func init() { - // Register the precompile module. - // Each precompile contract registers itself through [RegisterModule] function. - if err := modules.RegisterModule(Module); err != nil { - panic(err) - } -} - -// MakeConfig returns a new precompile config instance. -// This is required for Marshal/Unmarshal the precompile config. -func (*configurator) MakeConfig() precompileconfig.Config { - return new(Config) -} - -// Configure configures [state] with the given [cfg] precompileconfig. -// This function is called by the EVM once per precompile contract activation. -// You can use this function to set up your precompile contract's initial state, -// by using the [cfg] config and [state] stateDB. -func (*configurator) Configure(chainConfig precompileconfig.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, blockContext contract.ConfigurationBlockContext) error { - config, ok := cfg.(*Config) - if !ok { - return fmt.Errorf("expected config type %T, got %T: %v", &Config{}, cfg, cfg) - } - // configure the RewardManager with the given initial configuration - if config.InitialRewardConfig != nil { - config.InitialRewardConfig.Configure(state) - } else if chainConfig.AllowedFeeRecipients() { - // configure the RewardManager according to chainConfig - EnableAllowFeeRecipients(state) - } else { - // chainConfig does not have any reward address - // if chainConfig does not enable fee recipients - // default to disabling rewards - DisableFeeRewards(state) - } - return config.AllowListConfig.Configure(chainConfig, ContractAddress, state, blockContext) -} diff --git a/precompile/contracts/txallowlist/config.go b/precompile/contracts/txallowlist/config.go deleted file mode 100644 index f5656d9c78..0000000000 --- a/precompile/contracts/txallowlist/config.go +++ /dev/null @@ -1,59 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package txallowlist - -import ( - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - "github.com/ethereum/go-ethereum/common" -) - -var _ precompileconfig.Config = &Config{} - -// Config implements the StatefulPrecompileConfig interface while adding in the -// TxAllowList specific precompile config. -type Config struct { - allowlist.AllowListConfig - precompileconfig.Upgrade -} - -// NewConfig returns a config for a network upgrade at [blockTimestamp] that enables -// TxAllowList with the given [admins], [enableds] and [managers] as members of the allowlist. -func NewConfig(blockTimestamp *uint64, admins []common.Address, enableds []common.Address, managers []common.Address) *Config { - return &Config{ - AllowListConfig: allowlist.AllowListConfig{ - AdminAddresses: admins, - EnabledAddresses: enableds, - ManagerAddresses: managers, - }, - Upgrade: precompileconfig.Upgrade{BlockTimestamp: blockTimestamp}, - } -} - -// NewDisableConfig returns config for a network upgrade at [blockTimestamp] -// that disables TxAllowList. -func NewDisableConfig(blockTimestamp *uint64) *Config { - return &Config{ - Upgrade: precompileconfig.Upgrade{ - BlockTimestamp: blockTimestamp, - Disable: true, - }, - } -} - -func (c *Config) Key() string { return ConfigKey } - -// Equal returns true if [cfg] is a [*TxAllowListConfig] and it has been configured identical to [c]. -func (c *Config) Equal(cfg precompileconfig.Config) bool { - // typecast before comparison - other, ok := (cfg).(*Config) - if !ok { - return false - } - return c.Upgrade.Equal(&other.Upgrade) && c.AllowListConfig.Equal(&other.AllowListConfig) -} - -func (c *Config) Verify(chainConfig precompileconfig.ChainConfig) error { - return c.AllowListConfig.Verify(chainConfig, c.Upgrade) -} diff --git a/precompile/contracts/txallowlist/config_test.go b/precompile/contracts/txallowlist/config_test.go deleted file mode 100644 index 29010ce8af..0000000000 --- a/precompile/contracts/txallowlist/config_test.go +++ /dev/null @@ -1,48 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package txallowlist - -import ( - "testing" - - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - "github.com/ava-labs/subnet-evm/precompile/testutils" - "github.com/ava-labs/subnet-evm/utils" - "github.com/ethereum/go-ethereum/common" - "go.uber.org/mock/gomock" -) - -func TestVerify(t *testing.T) { - allowlist.VerifyPrecompileWithAllowListTests(t, Module, nil) -} - -func TestEqual(t *testing.T) { - admins := []common.Address{allowlist.TestAdminAddr} - enableds := []common.Address{allowlist.TestEnabledAddr} - managers := []common.Address{allowlist.TestManagerAddr} - tests := map[string]testutils.ConfigEqualTest{ - "non-nil config and nil other": { - Config: NewConfig(utils.NewUint64(3), admins, enableds, managers), - Other: nil, - Expected: false, - }, - "different type": { - Config: NewConfig(nil, nil, nil, nil), - Other: precompileconfig.NewMockConfig(gomock.NewController(t)), - Expected: false, - }, - "different timestamp": { - Config: NewConfig(utils.NewUint64(3), admins, enableds, managers), - Other: NewConfig(utils.NewUint64(4), admins, enableds, managers), - Expected: false, - }, - "same config": { - Config: NewConfig(utils.NewUint64(3), admins, enableds, managers), - Other: NewConfig(utils.NewUint64(3), admins, enableds, managers), - Expected: true, - }, - } - allowlist.EqualPrecompileWithAllowListTests(t, Module, tests) -} diff --git a/precompile/contracts/txallowlist/contract.go b/precompile/contracts/txallowlist/contract.go deleted file mode 100644 index e93d53c6a1..0000000000 --- a/precompile/contracts/txallowlist/contract.go +++ /dev/null @@ -1,25 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package txallowlist - -import ( - "github.com/ava-labs/subnet-evm/precompile/allowlist" - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ethereum/go-ethereum/common" -) - -// Singleton StatefulPrecompiledContract for W/R access to the tx allow list. -var TxAllowListPrecompile contract.StatefulPrecompiledContract = allowlist.CreateAllowListPrecompile(ContractAddress) - -// GetTxAllowListStatus returns the role of [address] for the tx allow list. -func GetTxAllowListStatus(stateDB contract.StateDB, address common.Address) allowlist.Role { - return allowlist.GetAllowListStatus(stateDB, ContractAddress, address) -} - -// SetTxAllowListStatus sets the permissions of [address] to [role] for the -// tx allow list. -// assumes [role] has already been verified as valid. -func SetTxAllowListStatus(stateDB contract.StateDB, address common.Address, role allowlist.Role) { - allowlist.SetAllowListRole(stateDB, ContractAddress, address, role) -} diff --git a/precompile/contracts/txallowlist/contract_test.go b/precompile/contracts/txallowlist/contract_test.go deleted file mode 100644 index 119fec3817..0000000000 --- a/precompile/contracts/txallowlist/contract_test.go +++ /dev/null @@ -1,19 +0,0 @@ -// (c) 2019-2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package txallowlist - -import ( - "testing" - - "github.com/ava-labs/subnet-evm/core/state" - "github.com/ava-labs/subnet-evm/precompile/allowlist" -) - -func TestTxAllowListRun(t *testing.T) { - allowlist.RunPrecompileWithAllowListTests(t, Module, state.NewTestStateDB, nil) -} - -func BenchmarkTxAllowList(b *testing.B) { - allowlist.BenchPrecompileWithAllowList(b, Module, state.NewTestStateDB, nil) -} diff --git a/precompile/contracts/txallowlist/module.go b/precompile/contracts/txallowlist/module.go deleted file mode 100644 index f7333613c7..0000000000 --- a/precompile/contracts/txallowlist/module.go +++ /dev/null @@ -1,52 +0,0 @@ -// (c) 2019-2020, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package txallowlist - -import ( - "fmt" - - "github.com/ava-labs/subnet-evm/precompile/contract" - "github.com/ava-labs/subnet-evm/precompile/modules" - "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - "github.com/ethereum/go-ethereum/common" -) - -var _ contract.Configurator = &configurator{} - -// ConfigKey is the key used in json config files to specify this precompile config. -// must be unique across all precompiles. -const ConfigKey = "txAllowListConfig" - -var ContractAddress = common.HexToAddress("0x0200000000000000000000000000000000000002") - -var Module = modules.Module{ - ConfigKey: ConfigKey, - Address: ContractAddress, - Contract: TxAllowListPrecompile, - Configurator: &configurator{}, -} - -type configurator struct{} - -func init() { - if err := modules.RegisterModule(Module); err != nil { - panic(err) - } -} - -// MakeConfig returns a new precompile config instance. -// This is required to Marshal/Unmarshal the precompile config. -func (*configurator) MakeConfig() precompileconfig.Config { - return new(Config) -} - -// Configure configures [state] with the given [cfg] precompileconfig. -// This function is called by the EVM once per precompile contract activation. -func (*configurator) Configure(chainConfig precompileconfig.ChainConfig, cfg precompileconfig.Config, state contract.StateDB, blockContext contract.ConfigurationBlockContext) error { - config, ok := cfg.(*Config) - if !ok { - return fmt.Errorf("expected config type %T, got %T: %v", &Config{}, cfg, cfg) - } - return config.AllowListConfig.Configure(chainConfig, ContractAddress, state, blockContext) -} diff --git a/precompile/contracts/warp/README.md b/precompile/contracts/warp/README.md index 10e1daaa38..0e43769753 100644 --- a/precompile/contracts/warp/README.md +++ b/precompile/contracts/warp/README.md @@ -75,7 +75,7 @@ Avalanche Warp Messages are encoded as a signed Avalanche [Warp Message](https:/ Since the predicate is encoded into the [Transaction Access List](https://eips.ethereum.org/EIPS/eip-2930), it is packed into 32 byte hashes intended to declare storage slots that should be pre-warmed into the cache prior to transaction execution. -Therefore, we use the [Predicate Utils](https://github.com/ava-labs/coreth/blob/master/predicate/Predicate.md) package to encode the actual byte slice of size N into the access list. +Therefore, we use the [Predicate Utils](https://github.com/ava-labs/subnet-evm/blob/master/predicate/Predicate.md) package to encode the actual byte slice of size N into the access list. ### Performance Optimization: C-Chain to Subnet diff --git a/precompile/precompileconfig/config.go b/precompile/precompileconfig/config.go index 05d204de45..43365e3bf9 100644 --- a/precompile/precompileconfig/config.go +++ b/precompile/precompileconfig/config.go @@ -10,7 +10,6 @@ import ( "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/engine/snowman/block" "github.com/ava-labs/avalanchego/vms/platformvm/warp" - "github.com/ava-labs/subnet-evm/commontype" "github.com/ethereum/go-ethereum/common" ) @@ -73,9 +72,9 @@ type AcceptContext struct { // Accepter is an optional interface for StatefulPrecompiledContracts to implement. // If implemented, Accept will be called for every log with the address of the precompile when the block is accepted. -// WARNING: If you are implementing a custom precompile, beware that subnet-evm +// WARNING: If you are implementing a custom precompile, beware that coreth // will not maintain backwards compatibility of this interface and your code should not -// rely on this. Designed for use only by precompiles that ship with subnet-evm. +// rely on this. Designed for use only by precompiles that ship with coreth. type Accepter interface { Accept(acceptCtx *AcceptContext, blockHash common.Hash, blockNumber uint64, txHash common.Hash, logIndex int, topics []common.Hash, logData []byte) error } @@ -84,10 +83,6 @@ type Accepter interface { // about the chain configuration. The precompile can access this information to initialize // its state. type ChainConfig interface { - // GetFeeConfig returns the original FeeConfig that was set in the genesis. - GetFeeConfig() commontype.FeeConfig - // AllowedFeeRecipients returns true if fee recipients are allowed in the genesis. - AllowedFeeRecipients() bool // IsDurango returns true if the time is after Durango. IsDurango(time uint64) bool } diff --git a/precompile/precompileconfig/mocks.go b/precompile/precompileconfig/mocks.go index 614ec5a522..1443b757ad 100644 --- a/precompile/precompileconfig/mocks.go +++ b/precompile/precompileconfig/mocks.go @@ -12,7 +12,6 @@ package precompileconfig import ( reflect "reflect" - commontype "github.com/ava-labs/subnet-evm/commontype" common "github.com/ethereum/go-ethereum/common" gomock "go.uber.org/mock/gomock" ) @@ -185,34 +184,6 @@ func (m *MockChainConfig) EXPECT() *MockChainConfigMockRecorder { return m.recorder } -// AllowedFeeRecipients mocks base method. -func (m *MockChainConfig) AllowedFeeRecipients() bool { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AllowedFeeRecipients") - ret0, _ := ret[0].(bool) - return ret0 -} - -// AllowedFeeRecipients indicates an expected call of AllowedFeeRecipients. -func (mr *MockChainConfigMockRecorder) AllowedFeeRecipients() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AllowedFeeRecipients", reflect.TypeOf((*MockChainConfig)(nil).AllowedFeeRecipients)) -} - -// GetFeeConfig mocks base method. -func (m *MockChainConfig) GetFeeConfig() commontype.FeeConfig { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetFeeConfig") - ret0, _ := ret[0].(commontype.FeeConfig) - return ret0 -} - -// GetFeeConfig indicates an expected call of GetFeeConfig. -func (mr *MockChainConfigMockRecorder) GetFeeConfig() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFeeConfig", reflect.TypeOf((*MockChainConfig)(nil).GetFeeConfig)) -} - // IsDurango mocks base method. func (m *MockChainConfig) IsDurango(arg0 uint64) bool { m.ctrl.T.Helper() diff --git a/precompile/registry/registry.go b/precompile/registry/registry.go index 490694d669..87e2d7d71c 100644 --- a/precompile/registry/registry.go +++ b/precompile/registry/registry.go @@ -7,38 +7,5 @@ package registry // Force imports of each precompile to ensure each precompile's init function runs and registers itself // with the registry. import ( - _ "github.com/ava-labs/subnet-evm/precompile/contracts/deployerallowlist" - - _ "github.com/ava-labs/subnet-evm/precompile/contracts/nativeminter" - - _ "github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist" - - _ "github.com/ava-labs/subnet-evm/precompile/contracts/feemanager" - - _ "github.com/ava-labs/subnet-evm/precompile/contracts/rewardmanager" - _ "github.com/ava-labs/subnet-evm/precompile/contracts/warp" - // ADD YOUR PRECOMPILE HERE - // _ "github.com/ava-labs/subnet-evm/precompile/contracts/yourprecompile" ) - -// This list is kept just for reference. The actual addresses defined in respective packages of precompiles. -// Note: it is important that none of these addresses conflict with each other or any other precompiles -// in core/vm/contracts.go. -// The first stateful precompiles were added in coreth to support nativeAssetCall and nativeAssetBalance. New stateful precompiles -// originating in coreth will continue at this prefix, so we reserve this range in subnet-evm so that they can be migrated into -// subnet-evm without issue. -// These start at the address: 0x0100000000000000000000000000000000000000 and will increment by 1. -// Optional precompiles implemented in subnet-evm start at 0x0200000000000000000000000000000000000000 and will increment by 1 -// from here to reduce the risk of conflicts. -// For forks of subnet-evm, users should start at 0x0300000000000000000000000000000000000000 to ensure -// that their own modifications do not conflict with stateful precompiles that may be added to subnet-evm -// in the future. -// ContractDeployerAllowListAddress = common.HexToAddress("0x0200000000000000000000000000000000000000") -// ContractNativeMinterAddress = common.HexToAddress("0x0200000000000000000000000000000000000001") -// TxAllowListAddress = common.HexToAddress("0x0200000000000000000000000000000000000002") -// FeeManagerAddress = common.HexToAddress("0x0200000000000000000000000000000000000003") -// RewardManagerAddress = common.HexToAddress("0x0200000000000000000000000000000000000004") -// WarpAddress = common.HexToAddress("0x0200000000000000000000000000000000000005") -// ADD YOUR PRECOMPILE HERE -// {YourPrecompile}Address = common.HexToAddress("0x03000000000000000000000000000000000000??") diff --git a/precompile/testutils/test_config.go b/precompile/testutils/test_config.go index 101d15113d..89d36bdefc 100644 --- a/precompile/testutils/test_config.go +++ b/precompile/testutils/test_config.go @@ -6,7 +6,6 @@ package testutils import ( "testing" - "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/precompile/precompileconfig" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" @@ -36,8 +35,6 @@ func RunVerifyTests(t *testing.T, tests map[string]ConfigVerifyTest) { if chainConfig == nil { ctrl := gomock.NewController(t) mockChainConfig := precompileconfig.NewMockChainConfig(ctrl) - mockChainConfig.EXPECT().GetFeeConfig().AnyTimes().Return(commontype.ValidTestFeeConfig) - mockChainConfig.EXPECT().AllowedFeeRecipients().AnyTimes().Return(false) mockChainConfig.EXPECT().IsDurango(gomock.Any()).AnyTimes().Return(true) chainConfig = mockChainConfig } diff --git a/precompile/testutils/test_precompile.go b/precompile/testutils/test_precompile.go index c1a1eac813..5337425594 100644 --- a/precompile/testutils/test_precompile.go +++ b/precompile/testutils/test_precompile.go @@ -8,7 +8,6 @@ import ( "testing" "time" - "github.com/ava-labs/subnet-evm/commontype" "github.com/ava-labs/subnet-evm/precompile/contract" "github.com/ava-labs/subnet-evm/precompile/modules" "github.com/ava-labs/subnet-evm/precompile/precompileconfig" @@ -47,9 +46,9 @@ type PrecompileTest struct { ExpectedRes []byte // ExpectedErr is the expected error returned by the precompile ExpectedErr string - // ChainConfigFn returns the chain config to use for the precompile's block context + // ChainConfig is the chain config to use for the precompile's block context // If nil, the default chain config will be used. - ChainConfigFn func(*gomock.Controller) precompileconfig.ChainConfig + ChainConfig precompileconfig.ChainConfig } type PrecompileRunparams struct { @@ -90,16 +89,12 @@ func (test PrecompileTest) setup(t testing.TB, module modules.Module, state cont test.BeforeHook(t, state) } - if test.ChainConfigFn == nil { - test.ChainConfigFn = func(ctrl *gomock.Controller) precompileconfig.ChainConfig { - mockChainConfig := precompileconfig.NewMockChainConfig(ctrl) - mockChainConfig.EXPECT().GetFeeConfig().AnyTimes().Return(commontype.ValidTestFeeConfig) - mockChainConfig.EXPECT().AllowedFeeRecipients().AnyTimes().Return(false) - mockChainConfig.EXPECT().IsDurango(gomock.Any()).AnyTimes().Return(true) - return mockChainConfig - } + chainConfig := test.ChainConfig + if chainConfig == nil { + mockChainConfig := precompileconfig.NewMockChainConfig(ctrl) + mockChainConfig.EXPECT().IsDurango(gomock.Any()).AnyTimes().Return(true) + chainConfig = mockChainConfig } - chainConfig := test.ChainConfigFn(ctrl) blockContext := contract.NewMockBlockContext(ctrl) if test.SetupBlockContext != nil { diff --git a/rpc/client_opt_test.go b/rpc/client_opt_test.go index 64dec1a51c..cf458420da 100644 --- a/rpc/client_opt_test.go +++ b/rpc/client_opt_test.go @@ -7,7 +7,6 @@ // original code from which it is derived. // // Much love to the original authors for their work. -// ********** package rpc_test import ( diff --git a/rpc/handler.go b/rpc/handler.go index b4f54e1a91..9028a96b56 100644 --- a/rpc/handler.go +++ b/rpc/handler.go @@ -356,7 +356,7 @@ func (h *handler) addRequestOp(op *requestOp) { } } -// removeRequestOp stops waiting for the given request IDs. +// removeRequestOps stops waiting for the given request IDs. func (h *handler) removeRequestOp(op *requestOp) { for _, id := range op.ids { delete(h.respWait, string(id)) diff --git a/rpc/subscription_test.go b/rpc/subscription_test.go index 416940392e..3be7e57b60 100644 --- a/rpc/subscription_test.go +++ b/rpc/subscription_test.go @@ -287,7 +287,7 @@ func TestNotify(t *testing.T) { } notifier.Notify(id, msg) have := strings.TrimSpace(out.String()) - want := `{"jsonrpc":"2.0","method":"_subscription","params":{"subscription":"test","result":{"parentHash":"0x0000000000000000000000000000000000000000000000000000000000000001","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":null,"number":"0x64","gasLimit":"0x0","gasUsed":"0x0","timestamp":"0x0","extraData":"0x","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","baseFeePerGas":null,"blockGasCost":null,"blobGasUsed":null,"excessBlobGas":null,"parentBeaconBlockRoot":null,"hash":"0xe5fb877dde471b45b9742bb4bb4b3d74a761e2fb7cb849a3d2b687eed90fb604"}}}` + want := `{"jsonrpc":"2.0","method":"_subscription","params":{"subscription":"test","result":{"parentHash":"0x0000000000000000000000000000000000000000000000000000000000000001","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":null,"number":"0x64","gasLimit":"0x0","gasUsed":"0x0","timestamp":"0x0","extraData":"0x","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","extDataHash":"0x0000000000000000000000000000000000000000000000000000000000000000","baseFeePerGas":null,"extDataGasUsed":null,"blockGasCost":null,"blobGasUsed":null,"excessBlobGas":null,"parentBeaconBlockRoot":null,"hash":"0x9a9ca1b5790a674785245afedcee9fc1a90d3514c6faa1cbd26f696136d6fd12"}}}` if have != want { t.Errorf("have:\n%v\nwant:\n%v\n", have, want) } diff --git a/scripts/avalanche_header.txt b/scripts/avalanche_header.txt deleted file mode 100644 index c848a208bd..0000000000 --- a/scripts/avalanche_header.txt +++ /dev/null @@ -1,10 +0,0 @@ -// (c) 2024, Ava Labs, Inc. -// -// This file is a derived work, based on the go-ethereum library whose original -// notices appear below. -// -// It is distributed under a license compatible with the licensing terms of the -// original code from which it is derived. -// -// Much love to the original authors for their work. -// ********** diff --git a/scripts/build.sh b/scripts/build.sh index de9be59ed0..327dc35fdf 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -5,26 +5,26 @@ set -o nounset set -o pipefail # Root directory -SUBNET_EVM_PATH=$( - cd "$(dirname "${BASH_SOURCE[0]}")" - cd .. && pwd -) +CORETH_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd .. && pwd ) # Load the versions -source "$SUBNET_EVM_PATH"/scripts/versions.sh +source "$CORETH_PATH"/scripts/versions.sh # Load the constants -source "$SUBNET_EVM_PATH"/scripts/constants.sh +source "$CORETH_PATH"/scripts/constants.sh if [[ $# -eq 1 ]]; then - BINARY_PATH=$1 -elif [[ $# -eq 0 ]]; then - BINARY_PATH="$DEFAULT_PLUGIN_DIR/$DEFAULT_VM_ID" -else - echo "Invalid arguments to build subnet-evm. Requires zero (default binary path) or one argument to specify the binary path." + binary_path=$1 +elif [[ $# -ne 0 ]]; then + echo "Invalid arguments to build coreth. Requires either no arguments (default) or one arguments to specify binary location." exit 1 fi -# Build Subnet EVM, which is run as a subprocess -echo "Building Subnet EVM @ GitCommit: $SUBNET_EVM_COMMIT at $BINARY_PATH" -go build -ldflags "-X github.com/ava-labs/subnet-evm/plugin/evm.GitCommit=$SUBNET_EVM_COMMIT $STATIC_LD_FLAGS" -o "$BINARY_PATH" "plugin/"*.go +# Check if CORETH_COMMIT is set, if not retrieve the last commit from the repo. +# This is used in the Dockerfile to allow a commit hash to be passed in without +# including the .git/ directory within the Docker image. +CORETH_COMMIT=${CORETH_COMMIT:-$(git rev-list -1 HEAD)} + +# Build Coreth, which runs as a subprocess +echo "Building Coreth @ GitCommit: $CORETH_COMMIT" +go build -ldflags "-X github.com/ava-labs/subnet-evm/plugin/evm.GitCommit=$CORETH_COMMIT" -o "$binary_path" "plugin/"*.go diff --git a/scripts/build_antithesis_images.sh b/scripts/build_antithesis_images.sh deleted file mode 100755 index e90cfaee8e..0000000000 --- a/scripts/build_antithesis_images.sh +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# Builds docker images for antithesis testing. - -# e.g., -# ./scripts/build_antithesis_images.sh # Build local images -# IMAGE_PREFIX=/ IMAGE_TAG=latest ./scripts/build_antithesis_images.sh # Specify a prefix to enable image push and use a specific tag - -# Directory above this script -SUBNET_EVM_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd .. && pwd ) - -# Allow configuring the clone path to point to a shared and/or existing clone of the avalanchego repo -AVALANCHEGO_CLONE_PATH="${AVALANCHEGO_CLONE_PATH:-${SUBNET_EVM_PATH}/avalanchego}" - -# Assume it's necessary to build the avalanchego node image from source -# TODO(marun) Support use of a released node image if using a release version of avalanchego - -source "${SUBNET_EVM_PATH}"/scripts/versions.sh -source "${SUBNET_EVM_PATH}"/scripts/constants.sh - -echo "checking out target avalanchego version ${AVALANCHE_VERSION}" -if [[ -d "${AVALANCHEGO_CLONE_PATH}" ]]; then - echo "updating existing clone" - cd "${AVALANCHEGO_CLONE_PATH}" - git fetch -else - echo "creating new clone" - git clone https://github.com/ava-labs/avalanchego.git "${AVALANCHEGO_CLONE_PATH}" - cd "${AVALANCHEGO_CLONE_PATH}" -fi -# Branch will be reset to $AVALANCHE_VERSION if it already exists -git checkout -B "test-${AVALANCHE_VERSION}" "${AVALANCHE_VERSION}" -cd "${SUBNET_EVM_PATH}" - -AVALANCHEGO_COMMIT_HASH="$(git --git-dir="${AVALANCHEGO_CLONE_PATH}/.git" rev-parse HEAD)" -AVALANCHEGO_IMAGE_TAG="${AVALANCHEGO_COMMIT_HASH::8}" - -# Build avalanchego node image in the clone path -pushd "${AVALANCHEGO_CLONE_PATH}" > /dev/null - NODE_ONLY=1 TEST_SETUP=avalanchego IMAGE_TAG="${AVALANCHEGO_IMAGE_TAG}" bash -x "${AVALANCHEGO_CLONE_PATH}"/scripts/build_antithesis_images.sh -popd > /dev/null - -# Specifying an image prefix will ensure the image is pushed after build -IMAGE_PREFIX="${IMAGE_PREFIX:-}" - -IMAGE_TAG="${IMAGE_TAG:-}" -if [[ -z "${IMAGE_TAG}" ]]; then - # Default to tagging with the commit hash - source "${SUBNET_EVM_PATH}"/scripts/constants.sh - IMAGE_TAG="${SUBNET_EVM_COMMIT::8}" -fi - -# The dockerfiles don't specify the golang version to minimize the changes required to bump -# the version. Instead, the golang version is provided as an argument. -GO_VERSION="$(go list -m -f '{{.GoVersion}}')" - -# Import common functions used to build images for antithesis test setups -# shellcheck source=/dev/null -source "${AVALANCHEGO_CLONE_PATH}"/scripts/lib_build_antithesis_images.sh - -build_antithesis_builder_image "${GO_VERSION}" "antithesis-subnet-evm-builder:${IMAGE_TAG}" "${AVALANCHEGO_CLONE_PATH}" "${SUBNET_EVM_PATH}" - -# Ensure avalanchego and subnet-evm binaries are available to create an initial db state that includes subnets. -"${AVALANCHEGO_CLONE_PATH}"/scripts/build.sh -"${SUBNET_EVM_PATH}"/scripts/build.sh - -echo "Generating compose configuration" -gen_antithesis_compose_config "${IMAGE_TAG}" "${SUBNET_EVM_PATH}/tests/antithesis/gencomposeconfig" \ - "${SUBNET_EVM_PATH}/build/antithesis" \ - "AVALANCHEGO_PATH=${AVALANCHEGO_CLONE_PATH}/build/avalanchego \ - AVALANCHEGO_PLUGIN_DIR=${DEFAULT_PLUGIN_DIR}" - -build_antithesis_images "${GO_VERSION}" "${IMAGE_PREFIX}" "antithesis-subnet-evm" "${IMAGE_TAG}" \ - "${AVALANCHEGO_IMAGE_TAG}" "${SUBNET_EVM_PATH}/tests/antithesis/Dockerfile" \ - "${SUBNET_EVM_PATH}/Dockerfile" "${SUBNET_EVM_PATH}" diff --git a/scripts/build_antithesis_workload.sh b/scripts/build_antithesis_workload.sh deleted file mode 100755 index 942e91c0ac..0000000000 --- a/scripts/build_antithesis_workload.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# Directory above this script -SUBNET_EVM_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd .. && pwd ) -# Load the constants -source "$SUBNET_EVM_PATH"/scripts/constants.sh - -echo "Building Workload..." -go build -o "$SUBNET_EVM_PATH/build/workload" "$SUBNET_EVM_PATH/tests/antithesis/"*.go diff --git a/scripts/build_bench_precompiles.sh b/scripts/build_bench_precompiles.sh deleted file mode 100755 index 52cb39a5de..0000000000 --- a/scripts/build_bench_precompiles.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env bash - -set -o errexit -set -o nounset -set -o pipefail - -# Root directory -SUBNET_EVM_PATH=$( - cd "$(dirname "${BASH_SOURCE[0]}")" - cd .. && pwd -) - -# Load the versions -source "$SUBNET_EVM_PATH"/scripts/versions.sh - -# Load the constants -source "$SUBNET_EVM_PATH"/scripts/constants.sh - -go test ./precompile/contracts/... -bench=./... -timeout="10m" "$@" diff --git a/scripts/build_docker_image.sh b/scripts/build_docker_image.sh index e8aae432cf..9126a47a8d 100755 --- a/scripts/build_docker_image.sh +++ b/scripts/build_docker_image.sh @@ -2,31 +2,22 @@ set -euo pipefail -# Directory above this script -SUBNET_EVM_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd .. && pwd ) +# Avalanche root directory +CORETH_PATH=$( + cd "$(dirname "${BASH_SOURCE[0]}")" + cd .. && pwd +) # Load the constants -source "$SUBNET_EVM_PATH"/scripts/constants.sh +source "$CORETH_PATH"/scripts/constants.sh # Load the versions -source "$SUBNET_EVM_PATH"/scripts/versions.sh +source "$CORETH_PATH"/scripts/versions.sh # WARNING: this will use the most recent commit even if there are un-committed changes present BUILD_IMAGE_ID=${BUILD_IMAGE_ID:-"${CURRENT_BRANCH}"} -DOCKERHUB_TAG=${SUBNET_EVM_COMMIT::8} - -VM_ID=${VM_ID:-"${DEFAULT_VM_ID}"} -if [[ "${VM_ID}" != "${DEFAULT_VM_ID}" ]]; then - DOCKERHUB_TAG="${VM_ID}-${DOCKERHUB_TAG}" -fi - -# Default to the release image. Will need to be overridden when testing against unreleased versions. -AVALANCHE_NODE_IMAGE=${AVALANCHE_NODE_IMAGE:-"avaplatform/avalanchego:${AVALANCHE_VERSION}"} - echo "Building Docker Image: $DOCKERHUB_REPO:$BUILD_IMAGE_ID based of AvalancheGo@$AVALANCHE_VERSION" -docker build -t "$DOCKERHUB_REPO:$BUILD_IMAGE_ID" -t "$DOCKERHUB_REPO:${DOCKERHUB_TAG}" \ - "$SUBNET_EVM_PATH" -f "$SUBNET_EVM_PATH/Dockerfile" \ - --build-arg AVALANCHE_NODE_IMAGE="$AVALANCHE_NODE_IMAGE" \ - --build-arg SUBNET_EVM_COMMIT="$SUBNET_EVM_COMMIT" \ - --build-arg CURRENT_BRANCH="$CURRENT_BRANCH" \ - --build-arg VM_ID="$VM_ID" \ No newline at end of file +docker build -t "$DOCKERHUB_REPO:$BUILD_IMAGE_ID" "$CORETH_PATH" -f "$CORETH_PATH/Dockerfile" \ + --build-arg AVALANCHE_VERSION="$AVALANCHE_VERSION" \ + --build-arg CORETH_COMMIT="$CORETH_COMMIT" \ + --build-arg CURRENT_BRANCH="$CURRENT_BRANCH" diff --git a/scripts/build_test.sh b/scripts/build_test.sh index 6f588887e0..c62dc95789 100755 --- a/scripts/build_test.sh +++ b/scripts/build_test.sh @@ -3,22 +3,16 @@ set -o errexit set -o nounset set -o pipefail -# TODO(marun) Ensure the working directory is the repository root or a non-canonical set of tests may be executed -# Root directory -SUBNET_EVM_PATH=$( - cd "$(dirname "${BASH_SOURCE[0]}")" - cd .. && pwd +# Avalanche root directory +CORETH_PATH=$( + cd "$(dirname "${BASH_SOURCE[0]}")" + cd .. && pwd ) -# Load the versions -source "$SUBNET_EVM_PATH"/scripts/versions.sh - # Load the constants -source "$SUBNET_EVM_PATH"/scripts/constants.sh +source "$CORETH_PATH"/scripts/constants.sh # We pass in the arguments to this script directly to enable easily passing parameters such as enabling race detection, # parallelism, and test coverage. -# DO NOT RUN tests from the top level "tests" directory since they are run by ginkgo -# shellcheck disable=SC2046 -go test -shuffle=on -race -timeout="${TIMEOUT:-600s}" -coverprofile=coverage.out -covermode=atomic "$@" $(go list ./... | grep -v github.com/ava-labs/subnet-evm/tests) +go test -shuffle=on -race -timeout="${TIMEOUT:-600s}" -coverprofile=coverage.out -covermode=atomic ./... "$@" diff --git a/scripts/constants.sh b/scripts/constants.sh index 6b32d87b99..e4d0e81744 100644 --- a/scripts/constants.sh +++ b/scripts/constants.sh @@ -7,41 +7,25 @@ set -euo pipefail # Set the PATHS GOPATH="$(go env GOPATH)" -DEFAULT_PLUGIN_DIR="${HOME}/.avalanchego/plugins" -DEFAULT_VM_ID="srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy" + +# Set binary location +binary_path=${CORETH_BINARY_PATH:-"$GOPATH/src/github.com/ava-labs/avalanchego/build/plugins/evm"} # Avalabs docker hub -# avaplatform/avalanchego - defaults to local as to avoid unintentional pushes -# You should probably set it - export DOCKER_REPO='avaplatform/subnet-evm' -DOCKERHUB_REPO=${DOCKER_REPO:-"subnet-evm"} - -# if this isn't a git repository (say building from a release), don't set our git constants. -if [ ! -d .git ]; then - CURRENT_BRANCH="" - SUBNET_EVM_COMMIT="" -else - # Current branch - CURRENT_BRANCH=${CURRENT_BRANCH:-$(git describe --tags --exact-match 2>/dev/null || git symbolic-ref -q --short HEAD || git rev-parse --short HEAD || :)} - - # Image build id - # - # Use an abbreviated version of the full commit to tag the image. - # WARNING: this will use the most recent commit even if there are un-committed changes present - SUBNET_EVM_COMMIT="$(git --git-dir="$SUBNET_EVM_PATH/.git" rev-parse HEAD || :)" -fi +DOCKERHUB_REPO="avaplatform/coreth" +# Current branch +CURRENT_BRANCH=${CURRENT_BRANCH:-$(git describe --tags --exact-match 2>/dev/null || git symbolic-ref -q --short HEAD || git rev-parse --short HEAD)} echo "Using branch: ${CURRENT_BRANCH}" -# Static compilation -STATIC_LD_FLAGS='' -if [ "${STATIC_COMPILATION:-}" = 1 ]; then - export CC=musl-gcc - command -v $CC || (echo $CC must be available for static compilation && exit 1) - STATIC_LD_FLAGS=' -extldflags "-static" -linkmode external ' -fi +# Image build id +# Use an abbreviated version of the full commit to tag the image. + +# WARNING: this will use the most recent commit even if there are un-committed changes present +CORETH_COMMIT="$(git --git-dir="$CORETH_PATH/.git" rev-parse HEAD)" # Set the CGO flags to use the portable version of BLST # # We use "export" here instead of just setting a bash variable because we need # to pass this flag to all child processes spawned by the shell. -export CGO_CFLAGS="-O2 -D__BLST_PORTABLE__" +export CGO_CFLAGS="-O -D__BLST_PORTABLE__" diff --git a/scripts/diff_against.sh b/scripts/diff_against.sh deleted file mode 100755 index f165dfa279..0000000000 --- a/scripts/diff_against.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -# -# Usage: scripts/diff_against.sh {geth_commit} -# -# Per-file diffs will be written to `diffs/{geth_commit}`. -# -# Example: `scripts/diff_against.sh b20b4a71598481443d60b261d3e5dcb37f8a0d82` to compare with v1.13.8. -# -# *NOTE* Before running this script: -# 1. Run `scripts/format_as_upstream.sh` to reformat this repo as `ethereum/go-ethereum`. -# 2. Add geth as a remote: `git remote add -f geth git@github.com:ethereum/go-ethereum.git`. -# 3. Find the geth commit for the latest version merged into this repo. -# - -set -e; -set -u; - -ROOT=$(git rev-parse --show-toplevel); -cd "${ROOT}"; - -BASE="${1}"; - -# The diffs/ directory is in .gitignore to avoid recursive diffiffiffs. -mkdir -p "diffs/${BASE}"; - -git diff "${BASE}" --name-only | while read -r f -do - echo "${f}"; - DIR=$(dirname "${f}"); - mkdir -p "diffs/${BASE}/${DIR}"; - git diff "${BASE}" -- "${f}" > "diffs/${BASE}/${f}.diff"; -done diff --git a/scripts/format_add_avalanche_header.sh b/scripts/format_add_avalanche_header.sh deleted file mode 100755 index 5c58906bf8..0000000000 --- a/scripts/format_add_avalanche_header.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash - -set -o errexit -set -o nounset -set -o pipefail -set -x - -script_dir=$(dirname "$0") - -sed_command="1{/The go-ethereum Authors/{r ${script_dir}/avalanche_header.txt - N - } -}" -sed -i '' -e "${sed_command}" "$@" \ No newline at end of file diff --git a/scripts/format_as_fork.sh b/scripts/format_as_fork.sh deleted file mode 100755 index 1be9ce6248..0000000000 --- a/scripts/format_as_fork.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env bash - -set -o errexit -set -o nounset -set -o pipefail -set -x - -script_dir=$(dirname "$0") - -commit_msg_remove_header="format: remove avalanche header" -commit_msg_remove_upstream="format: remove upstream go-ethereum" -commit_msg_rename_packages_as_fork="format: rename packages as fork" - -make_commit() { - if git diff-index --cached --quiet HEAD --; then - echo "No changes to commit." - else - git commit -m "$1" - fi -} - -revert_by_message() { - hash=$(git log --grep="$1" --format="%H" -n 1) - git revert --no-edit "$hash" -} - -if git status --porcelain | grep -q '^ M'; then - echo "There are edited files in the repository. Please commit or stash them before running this script." - exit 1 -fi - -upstream_dirs=$(sed -e 's/"github.com\/ethereum\/go-ethereum\/\(.*\)"/\1/' "${script_dir}"/geth-allowed-packages.txt | xargs) -for dir in ${upstream_dirs}; do - if [ -d "${dir}" ]; then - git rm -r "${dir}" - fi -done -git clean -df -- "${upstream_dirs}" -make_commit "${commit_msg_remove_upstream}" - -sed_command='s!\([^/]\)github.com/ethereum/go-ethereum!\1github.com/ava-labs/subnet-evm!g' -find . \( -name '*.go' -o -name 'go.mod' -o -name 'build_test.sh' \) -exec sed -i '' -e "${sed_command}" {} \; -for dir in ${upstream_dirs}; do - sed_command="s!\"github.com/ava-labs/subnet-evm/${dir}\"!\"github.com/ethereum/go-ethereum/${dir}\"!g" - find . -name '*.go' -exec sed -i '' -e "${sed_command}" {} \; -done -go get github.com/ethereum/go-ethereum@"$1" -gofmt -w . -go mod tidy -git add -u . -make_commit "${commit_msg_rename_packages_as_fork}" - -revert_by_message "${commit_msg_remove_header}" \ No newline at end of file diff --git a/scripts/format_as_upstream.sh b/scripts/format_as_upstream.sh deleted file mode 100755 index c4ba234609..0000000000 --- a/scripts/format_as_upstream.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env bash - -set -o errexit -set -o nounset -set -o pipefail -set -x - -script_dir=$(dirname "$0") - -commit_msg_remove_header="format: remove avalanche header" -commit_msg_add_upstream="format: add upstream go-ethereum" -commit_msg_rename_packages_to_upstream="format: rename packages to upstream" - -make_commit() { - if git diff-index --cached --quiet HEAD --; then - echo "No changes to commit." - else - git commit -m "$1" - fi -} - -if git status --porcelain | grep -q '^ M'; then - echo "There are edited files in the repository. Please commit or stash them before running this script." - exit 1 -fi - -sed_command='/\/\/ (c) [0-9]*\(-[0-9]*\)\{0,1\}, Ava Labs, Inc.$/,+9d' -find . -name '*.go' -exec sed -i '' -e "${sed_command}" {} \; -git add -u . -make_commit "${commit_msg_remove_header}" - -upstream_tag=$(grep -o 'github.com/ethereum/go-ethereum v.*' go.mod | cut -f2 -d' ') -upstream_dirs=$(sed -e 's/"github.com\/ethereum\/go-ethereum\/\(.*\)"/\1/' "${script_dir}"/geth-allowed-packages.txt | xargs) -upstream_dirs_array=() -IFS=" " read -r -a upstream_dirs_array <<< "$upstream_dirs" - -git clean -f "${upstream_dirs_array[@]}" -git checkout "${upstream_tag}" -- "${upstream_dirs_array[@]}" -git add "${upstream_dirs_array[@]}" -make_commit "${commit_msg_add_upstream}" - -sed_command='s!\([^/]\)github.com/ava-labs/subnet-evm!\1github.com/ethereum/go-ethereum!g' -find . \( -name '*.go' -o -name 'go.mod' -o -name 'build_test.sh' \) -exec sed -i '' -e "${sed_command}" {} \; -gofmt -w . -go mod tidy -git add -u . -make_commit "${commit_msg_rename_packages_to_upstream}" \ No newline at end of file diff --git a/scripts/generate_precompile.sh b/scripts/generate_precompile.sh deleted file mode 100755 index 2150c7b6a6..0000000000 --- a/scripts/generate_precompile.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bash -set -e - -# This script generates a Stateful Precompile stub based off of a Solidity ABI file. -# It first sets the necessary CGO_FLAGs for the BLST library used in AvalancheGo and -# then runs PrecompileGen. -if ! [[ "$0" =~ scripts/generate_precompile.sh ]]; then - echo "must be run from repository root, but got $0" - exit 255 -fi - -# Load the versions -SUBNET_EVM_PATH=$( - cd "$(dirname "${BASH_SOURCE[0]}")" - cd .. && pwd -) - -# Load the constants -source "$SUBNET_EVM_PATH"/scripts/constants.sh - -go run ./cmd/precompilegen/main.go "$@" diff --git a/scripts/install_avalanchego_release.sh b/scripts/install_avalanchego_release.sh deleted file mode 100755 index 83426e7036..0000000000 --- a/scripts/install_avalanchego_release.sh +++ /dev/null @@ -1,127 +0,0 @@ -#!/usr/bin/env bash -set -e - -# Load the versions -SUBNET_EVM_PATH=$( - cd "$(dirname "${BASH_SOURCE[0]}")" - cd .. && pwd -) -source "$SUBNET_EVM_PATH"/scripts/versions.sh - -# Load the constants -source "$SUBNET_EVM_PATH"/scripts/constants.sh - -############################ -# download avalanchego -# https://github.com/ava-labs/avalanchego/releases -GOARCH=$(go env GOARCH) -GOOS=$(go env GOOS) -BASEDIR=${BASEDIR:-"/tmp/avalanchego-release"} -AVALANCHEGO_BUILD_PATH=${AVALANCHEGO_BUILD_PATH:-${BASEDIR}/avalanchego} - -mkdir -p "${BASEDIR}" - -AVAGO_DOWNLOAD_URL=https://github.com/ava-labs/avalanchego/releases/download/${AVALANCHE_VERSION}/avalanchego-linux-${GOARCH}-${AVALANCHE_VERSION}.tar.gz -AVAGO_DOWNLOAD_PATH=${BASEDIR}/avalanchego-linux-${GOARCH}-${AVALANCHE_VERSION}.tar.gz - -if [[ ${GOOS} == "darwin" ]]; then - AVAGO_DOWNLOAD_URL=https://github.com/ava-labs/avalanchego/releases/download/${AVALANCHE_VERSION}/avalanchego-macos-${AVALANCHE_VERSION}.zip - AVAGO_DOWNLOAD_PATH=${BASEDIR}/avalanchego-macos-${AVALANCHE_VERSION}.zip -fi - -BUILD_DIR=${AVALANCHEGO_BUILD_PATH}-${AVALANCHE_VERSION} - -extract_archive() { - mkdir -p "${BUILD_DIR}" - - if [[ ${AVAGO_DOWNLOAD_PATH} == *.tar.gz ]]; then - tar xzvf "${AVAGO_DOWNLOAD_PATH}" --directory "${BUILD_DIR}" --strip-components 1 - elif [[ ${AVAGO_DOWNLOAD_PATH} == *.zip ]]; then - unzip "${AVAGO_DOWNLOAD_PATH}" -d "${BUILD_DIR}" - mv "${BUILD_DIR}"/build/* "${BUILD_DIR}" - rm -rf "${BUILD_DIR}"/build/ - fi -} - -# first check if we already have the archive -if [[ -f ${AVAGO_DOWNLOAD_PATH} ]]; then - # if the download path already exists, extract and exit - echo "found avalanchego ${AVALANCHE_VERSION} at ${AVAGO_DOWNLOAD_PATH}" - - extract_archive -else - # try to download the archive if it exists - if curl -s --head --request GET "${AVAGO_DOWNLOAD_URL}" | grep "302" > /dev/null; then - echo "${AVAGO_DOWNLOAD_URL} found" - echo "downloading to ${AVAGO_DOWNLOAD_PATH}" - curl -L "${AVAGO_DOWNLOAD_URL}" -o "${AVAGO_DOWNLOAD_PATH}" - - extract_archive - else - # else the version is a git commitish (or it's invalid) - GIT_CLONE_URL=https://github.com/ava-labs/avalanchego.git - GIT_CLONE_PATH=${BASEDIR}/avalanchego-repo/ - - # check to see if the repo already exists, if not clone it - if [[ ! -d ${GIT_CLONE_PATH} ]]; then - echo "cloning ${GIT_CLONE_URL} to ${GIT_CLONE_PATH}" - git clone --no-checkout ${GIT_CLONE_URL} "${GIT_CLONE_PATH}" - fi - - # check to see if the commitish exists in the repo - WORKDIR=$(pwd) - - cd "${GIT_CLONE_PATH}" - - git fetch - - echo "checking out ${AVALANCHE_VERSION}" - - set +e - # try to checkout the branch - git checkout origin/"${AVALANCHE_VERSION}" > /dev/null 2>&1 - CHECKOUT_STATUS=$? - set -e - - # if it's not a branch, try to checkout the commit - if [[ $CHECKOUT_STATUS -ne 0 ]]; then - set +e - git checkout "${AVALANCHE_VERSION}" > /dev/null 2>&1 - CHECKOUT_STATUS=$? - set -e - - if [[ $CHECKOUT_STATUS -ne 0 ]]; then - echo - echo "'${AVALANCHE_VERSION}' is not a valid release tag, commit hash, or branch name" - exit 1 - fi - fi - - COMMIT=$(git rev-parse HEAD) - - # use the commit hash instead of the branch name or tag - BUILD_DIR=${AVALANCHEGO_BUILD_PATH}-${COMMIT} - - # if the build-directory doesn't exist, build avalanchego - if [[ ! -d ${BUILD_DIR} ]]; then - echo "building avalanchego ${COMMIT} to ${BUILD_DIR}" - ./scripts/build.sh - mkdir -p "${BUILD_DIR}" - - mv "${GIT_CLONE_PATH}"/build/* "${BUILD_DIR}"/ - fi - - cd "$WORKDIR" - fi -fi - -AVALANCHEGO_PATH=${AVALANCHEGO_BUILD_PATH}/avalanchego - -mkdir -p "${AVALANCHEGO_BUILD_PATH}" - -cp "${BUILD_DIR}"/avalanchego "${AVALANCHEGO_PATH}" - - -echo "Installed AvalancheGo release ${AVALANCHE_VERSION}" -echo "AvalancheGo Path: ${AVALANCHEGO_PATH}" -echo "Plugin Dir: ${DEFAULT_PLUGIN_DIR}" diff --git a/scripts/lint_allowed_geth_imports.sh b/scripts/lint_allowed_geth_imports.sh index 6603c86da6..e428113613 100755 --- a/scripts/lint_allowed_geth_imports.sh +++ b/scripts/lint_allowed_geth_imports.sh @@ -8,16 +8,9 @@ set -o pipefail # 1. Recursively search through all go files for any lines that include a direct import from go-ethereum # 2. Sort the unique results # #. Print out the difference between the search results and the list of specified allowed package imports from geth. -extra_imports=$(grep -r --include='*.go' --exclude-dir='simulator' '"github.com/ethereum/go-ethereum/.*"' -o -h | sort -u | comm -23 - ./scripts/geth-allowed-packages.txt) +extra_imports=$(grep -r --include='*.go' '"github.com/ethereum/go-ethereum/.*"' -o -h | sort -u | comm -23 - ./scripts/geth-allowed-packages.txt) if [ -n "${extra_imports}" ]; then echo "new go-ethereum imports should be added to ./scripts/geth-allowed-packages.txt to prevent accidental imports:" echo "${extra_imports}" exit 1 fi - -extra_imports=$(grep -r --include='*.go' '"github.com/ava-labs/coreth/.*"' -o -h || true | sort -u) -if [ -n "${extra_imports}" ]; then - echo "subnet-evm should not import packages from coreth:" - echo "${extra_imports}" - exit 1 -fi diff --git a/scripts/mock.gen.sh b/scripts/mock.gen.sh index 3550cba16f..87465d43a9 100755 --- a/scripts/mock.gen.sh +++ b/scripts/mock.gen.sh @@ -3,7 +3,7 @@ set -euo pipefail # Root directory -SUBNET_EVM_PATH=$( +CORETH_PATH=$( cd "$(dirname "${BASH_SOURCE[0]}")" cd .. && pwd ) @@ -22,8 +22,11 @@ if ! command -v go-license &>/dev/null; then go install -v github.com/palantir/go-license@v1.25.0 fi +# Load the versions +source "$CORETH_PATH"/scripts/versions.sh + # Load the constants -source "$SUBNET_EVM_PATH"/scripts/constants.sh +source "$CORETH_PATH"/scripts/constants.sh # tuples of (source interface import path, comma-separated interface names, output file path) input="scripts/mocks.mockgen.txt" diff --git a/scripts/run.sh b/scripts/run.sh deleted file mode 100755 index 6d01094daf..0000000000 --- a/scripts/run.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bash -set -e - -# This script starts N nodes (TODO N instead of 5) and waits for ctrl-c to shutdown the process group of AvalancheGo processes -# Uses data directory to store all AvalancheGo data neatly in one location with minimal config overhead -if ! [[ "$0" =~ scripts/run.sh ]]; then - echo "must be run from repository root, but got $0" - exit 255 -fi - -# Load the versions -SUBNET_EVM_PATH=$( - cd "$(dirname "${BASH_SOURCE[0]}")" - cd .. && pwd -) -source "$SUBNET_EVM_PATH"/scripts/versions.sh - -# Load the constants -source "$SUBNET_EVM_PATH"/scripts/constants.sh - -# Set up avalanche binary path and assume build directory is set -AVALANCHEGO_BUILD_PATH=${AVALANCHEGO_BUILD_PATH:-"$GOPATH/src/github.com/ava-labs/avalanchego/build"} -AVALANCHEGO_PATH=${AVALANCHEGO_PATH:-"$AVALANCHEGO_BUILD_PATH/avalanchego"} -AVALANCHEGO_PLUGIN_DIR=${AVALANCHEGO_PLUGIN_DIR:-"$DEFAULT_PLUGIN_DIR"} -DATA_DIR=${DATA_DIR:-/tmp/subnet-evm-start-node/$(date "+%Y-%m-%d%:%H:%M:%S")} - -mkdir -p "$DATA_DIR" - -# Set the config file contents for the path passed in as the first argument -function _set_config(){ - cat <"$1" - { - "network-id": "local", - "sybil-protection-enabled": false, - "health-check-frequency": "5s", - "plugin-dir": "$AVALANCHEGO_PLUGIN_DIR" - } -EOF -} - -NODE_NAME="node1" -NODE_DATA_DIR="$DATA_DIR/$NODE_NAME" -echo "Creating data directory: $NODE_DATA_DIR" -mkdir -p "$NODE_DATA_DIR" -NODE_CONFIG_FILE_PATH="$NODE_DATA_DIR/config.json" -_set_config "$NODE_CONFIG_FILE_PATH" - -(set -x; $AVALANCHEGO_PATH --data-dir="$NODE_DATA_DIR" --config-file="$NODE_CONFIG_FILE_PATH") diff --git a/scripts/run_ginkgo_load.sh b/scripts/run_ginkgo_load.sh deleted file mode 100755 index 6dd6c58757..0000000000 --- a/scripts/run_ginkgo_load.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash -set -e - -# This script assumes that an AvalancheGo and Subnet-EVM binaries are available in the standard location -# within the $GOPATH -# The AvalancheGo and PluginDir paths can be specified via the environment variables used in ./scripts/run.sh. - -# Load the versions -SUBNET_EVM_PATH=$( - cd "$(dirname "${BASH_SOURCE[0]}")" - cd .. && pwd -) - -source "$SUBNET_EVM_PATH"/scripts/constants.sh - -source "$SUBNET_EVM_PATH"/scripts/versions.sh - -# Build ginkgo -# to install the ginkgo binary (required for test build and run) -go install -v github.com/onsi/ginkgo/v2/ginkgo@"${GINKGO_VERSION}" - -EXTRA_ARGS=() -AVALANCHEGO_BUILD_PATH="${AVALANCHEGO_BUILD_PATH:-}" -if [[ -n "${AVALANCHEGO_BUILD_PATH}" ]]; then - EXTRA_ARGS=("--avalanchego-path=${AVALANCHEGO_BUILD_PATH}/avalanchego") - echo "Running with extra args:" "${EXTRA_ARGS[@]}" -fi - -ginkgo -vv --label-filter="${GINKGO_LABEL_FILTER:-}" ./tests/load -- "${EXTRA_ARGS[@]}" diff --git a/scripts/run_ginkgo_precompile.sh b/scripts/run_ginkgo_precompile.sh deleted file mode 100755 index 9e626445c3..0000000000 --- a/scripts/run_ginkgo_precompile.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash -set -e - -# This script assumes that an AvalancheGo and Subnet-EVM binaries are available in the standard location -# within the $GOPATH -# The AvalancheGo and PluginDir paths can be specified via the environment variables used in ./scripts/run.sh. - -# Load the versions -SUBNET_EVM_PATH=$( - cd "$(dirname "${BASH_SOURCE[0]}")" - cd .. && pwd -) - -source "$SUBNET_EVM_PATH"/scripts/constants.sh - -source "$SUBNET_EVM_PATH"/scripts/versions.sh - -# Build ginkgo -# to install the ginkgo binary (required for test build and run) -go install -v github.com/onsi/ginkgo/v2/ginkgo@"${GINKGO_VERSION}" - -TEST_SOURCE_ROOT=$(pwd) - -# By default, it runs all e2e test cases! -# Use "--ginkgo.skip" to skip tests. -# Use "--ginkgo.focus" to select tests. -TEST_SOURCE_ROOT="$TEST_SOURCE_ROOT" ginkgo run -procs=5 tests/precompile \ - --ginkgo.vv \ - --ginkgo.label-filter="${GINKGO_LABEL_FILTER:-""}" diff --git a/scripts/run_ginkgo_warp.sh b/scripts/run_ginkgo_warp.sh deleted file mode 100755 index 0d7c3abd3a..0000000000 --- a/scripts/run_ginkgo_warp.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# This script assumes that an AvalancheGo and Subnet-EVM binaries are available in the standard location -# within the $GOPATH -# The AvalancheGo and PluginDir paths can be specified via the environment variables used in ./scripts/run.sh. - -# Load the versions -SUBNET_EVM_PATH=$( - cd "$(dirname "${BASH_SOURCE[0]}")" - cd .. && pwd -) - -source "$SUBNET_EVM_PATH"/scripts/constants.sh - -source "$SUBNET_EVM_PATH"/scripts/versions.sh - -# Build ginkgo -# to install the ginkgo binary (required for test build and run) -go install -v github.com/onsi/ginkgo/v2/ginkgo@"${GINKGO_VERSION}" - -EXTRA_ARGS=() -AVALANCHEGO_BUILD_PATH="${AVALANCHEGO_BUILD_PATH:-}" -if [[ -n "${AVALANCHEGO_BUILD_PATH}" ]]; then - EXTRA_ARGS=("--avalanchego-path=${AVALANCHEGO_BUILD_PATH}/avalanchego") - echo "Running with extra args:" "${EXTRA_ARGS[@]}" -fi - -ginkgo -vv --label-filter="${GINKGO_LABEL_FILTER:-}" ./tests/warp -- "${EXTRA_ARGS[@]}" diff --git a/scripts/run_prometheus.sh b/scripts/run_prometheus.sh deleted file mode 100755 index 1952227231..0000000000 --- a/scripts/run_prometheus.sh +++ /dev/null @@ -1,120 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# Starts a prometheus instance in agent-mode, forwarding to a central -# instance. Intended to enable metrics collection from temporary networks running -# locally and in CI. -# -# The prometheus instance will remain running in the background and will forward -# metrics to the central instance for all tmpnet networks. -# -# To stop it: -# -# $ kill -9 `cat ~/.tmpnet/prometheus/run.pid` && rm ~/.tmpnet/prometheus/run.pid -# - -# e.g., -# PROMETHEUS_ID= PROMETHEUS_PASSWORD= ./scripts/run_prometheus.sh -if ! [[ "$0" =~ scripts/run_prometheus.sh ]]; then - echo "must be run from repository root" - exit 255 -fi - -PROMETHEUS_WORKING_DIR="${HOME}/.tmpnet/prometheus" -PIDFILE="${PROMETHEUS_WORKING_DIR}"/run.pid - -# First check if an agent-mode prometheus is already running. A single instance can collect -# metrics from all local temporary networks. -if pgrep --pidfile="${PIDFILE}" -f 'prometheus.*enable-feature=agent' &> /dev/null; then - echo "prometheus is already running locally with --enable-feature=agent" - exit 0 -fi - -PROMETHEUS_URL="${PROMETHEUS_URL:-https://prometheus-experimental.avax-dev.network}" -if [[ -z "${PROMETHEUS_URL}" ]]; then - echo "Please provide a value for PROMETHEUS_URL" - exit 1 -fi - -PROMETHEUS_ID="${PROMETHEUS_ID:-}" -if [[ -z "${PROMETHEUS_ID}" ]]; then - echo "Please provide a value for PROMETHEUS_ID" - exit 1 -fi - -PROMETHEUS_PASSWORD="${PROMETHEUS_PASSWORD:-}" -if [[ -z "${PROMETHEUS_PASSWORD}" ]]; then - echo "Plase provide a value for PROMETHEUS_PASSWORD" - exit 1 -fi - -# This was the LTS version when this script was written. Probably not -# much reason to update it unless something breaks since the usage -# here is only to collect metrics from temporary networks. -VERSION="2.45.3" - -# Ensure the prometheus command is locally available -CMD=prometheus -if ! command -v "${CMD}" &> /dev/null; then - # Try to use a local version - CMD="${PWD}/bin/prometheus" - if ! command -v "${CMD}" &> /dev/null; then - echo "prometheus not found, attempting to install..." - - # Determine the arch - if which sw_vers &> /dev/null; then - echo "on macos, only amd64 binaries are available so rosetta is required on apple silicon machines." - echo "to avoid using rosetta, install via homebrew: brew install prometheus" - DIST=darwin - else - ARCH="$(uname -i)" - if [[ "${ARCH}" != "x86_64" ]]; then - echo "on linux, only amd64 binaries are available. manual installation of prometheus is required." - exit 1 - else - DIST="linux" - fi - fi - - # Install the specified release - PROMETHEUS_FILE="prometheus-${VERSION}.${DIST}-amd64" - URL="https://github.com/prometheus/prometheus/releases/download/v${VERSION}/${PROMETHEUS_FILE}.tar.gz" - curl -s -L "${URL}" | tar zxv -C /tmp > /dev/null - mkdir -p "$(dirname "${CMD}")" - cp /tmp/"${PROMETHEUS_FILE}/prometheus" "${CMD}" - fi -fi - -# Configure prometheus -FILE_SD_PATH="${PROMETHEUS_WORKING_DIR}/file_sd_configs" -mkdir -p "${FILE_SD_PATH}" - -echo "writing configuration..." -cat >"${PROMETHEUS_WORKING_DIR}"/prometheus.yaml < prometheus.log 2>&1 & -echo $! > "${PIDFILE}" -echo "running with pid $(cat "${PIDFILE}")" diff --git a/scripts/run_promtail.sh b/scripts/run_promtail.sh deleted file mode 100755 index 9b386d3d55..0000000000 --- a/scripts/run_promtail.sh +++ /dev/null @@ -1,115 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# Starts a promtail instance to collect logs from temporary networks -# running locally and in CI. -# -# The promtail instance will remain running in the background and will forward -# logs to the central instance for all tmpnet networks. -# -# To stop it: -# -# $ kill -9 `cat ~/.tmpnet/promtail/run.pid` && rm ~/.tmpnet/promtail/run.pid -# - -# e.g., -# LOKI_ID= LOKI_PASSWORD= ./scripts/run_promtail.sh -if ! [[ "$0" =~ scripts/run_promtail.sh ]]; then - echo "must be run from repository root" - exit 255 -fi - -PROMTAIL_WORKING_DIR="${HOME}/.tmpnet/promtail" -PIDFILE="${PROMTAIL_WORKING_DIR}"/run.pid - -# First check if promtail is already running. A single instance can -# collect logs from all local temporary networks. -if pgrep --pidfile="${PIDFILE}" &> /dev/null; then - echo "promtail is already running" - exit 0 -fi - -LOKI_URL="${LOKI_URL:-https://loki-experimental.avax-dev.network}" -if [[ -z "${LOKI_URL}" ]]; then - echo "Please provide a value for LOKI_URL" - exit 1 -fi - -LOKI_ID="${LOKI_ID:-}" -if [[ -z "${LOKI_ID}" ]]; then - echo "Please provide a value for LOKI_ID" - exit 1 -fi - -LOKI_PASSWORD="${LOKI_PASSWORD:-}" -if [[ -z "${LOKI_PASSWORD}" ]]; then - echo "Plase provide a value for LOKI_PASSWORD" - exit 1 -fi - -# Version as of this writing -VERSION="v2.9.5" - -# Ensure the promtail command is locally available -CMD=promtail -if ! command -v "${CMD}" &> /dev/null; then - # Try to use a local version - CMD="${PWD}/bin/promtail" - if ! command -v "${CMD}" &> /dev/null; then - echo "promtail not found, attempting to install..." - # Determine the arch - if which sw_vers &> /dev/null; then - DIST="darwin-$(uname -m)" - else - ARCH="$(uname -i)" - if [[ "${ARCH}" == "aarch64" ]]; then - ARCH="arm64" - elif [[ "${ARCH}" == "x86_64" ]]; then - ARCH="amd64" - fi - DIST="linux-${ARCH}" - fi - - # Install the specified release - PROMTAIL_FILE="promtail-${DIST}" - ZIP_PATH="/tmp/${PROMTAIL_FILE}.zip" - BIN_DIR="$(dirname "${CMD}")" - URL="https://github.com/grafana/loki/releases/download/${VERSION}/promtail-${DIST}.zip" - curl -L -o "${ZIP_PATH}" "${URL}" - unzip "${ZIP_PATH}" -d "${BIN_DIR}" - mv "${BIN_DIR}/${PROMTAIL_FILE}" "${CMD}" - fi -fi - -# Configure promtail -FILE_SD_PATH="${PROMTAIL_WORKING_DIR}/file_sd_configs" -mkdir -p "${FILE_SD_PATH}" - -echo "writing configuration..." -cat >"${PROMTAIL_WORKING_DIR}"/promtail.yaml < promtail.log 2>&1 & -echo $! > "${PIDFILE}" -echo "running with pid $(cat "${PIDFILE}")" diff --git a/scripts/run_simulator.sh b/scripts/run_simulator.sh deleted file mode 100755 index 0b723a15aa..0000000000 --- a/scripts/run_simulator.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env bash -# This script runs a 30s load simulation using RPC_ENDPOINTS environment variable to specify -# which RPC endpoints to hit. - -set -e - -echo "Beginning simulator script" - -if ! [[ "$0" =~ scripts/run_simulator.sh ]]; then - echo "must be run from repository root, but got $0" - exit 255 -fi - -# Load the versions -SUBNET_EVM_PATH=$( - cd "$(dirname "${BASH_SOURCE[0]}")" - cd .. && pwd -) -source "$SUBNET_EVM_PATH"/scripts/versions.sh - -# Load the constants -source "$SUBNET_EVM_PATH"/scripts/constants.sh - -run_simulator() { - ################################# - echo "building simulator" - pushd ./cmd/simulator - go build -o ./simulator main/*.go - echo - - popd - echo "running simulator from $PWD" - ./cmd/simulator/simulator \ - --endpoints="$RPC_ENDPOINTS" \ - --key-dir=./cmd/simulator/.simulator/keys \ - --timeout=300s \ - --workers=1 \ - --txs-per-worker=50000 \ - --batch-size=50000 \ - --max-fee-cap=1000000 \ - --max-tip-cap=10000 -} - -run_simulator diff --git a/scripts/shellcheck.sh b/scripts/shellcheck.sh index f57b853362..61fc09f90b 100755 --- a/scripts/shellcheck.sh +++ b/scripts/shellcheck.sh @@ -4,14 +4,6 @@ set -euo pipefail VERSION="v0.9.0" -# Scripts that are sourced from upstream and not maintained in this repo will not be shellchecked. -# Also ignore the local avalanchego clone. -IGNORED_FILES=" - cmd/evm/transition-test.sh - metrics/validate.sh - avalanchego/* -" - function get_version { local target_path=$1 if command -v "${target_path}" > /dev/null; then @@ -44,12 +36,4 @@ else fi fi -IGNORED_CONDITIONS=() -for file in ${IGNORED_FILES}; do - if [[ -n "${IGNORED_CONDITIONS-}" ]]; then - IGNORED_CONDITIONS+=(-o) - fi - IGNORED_CONDITIONS+=(-path "${REPO_ROOT}/${file}" -prune) -done - -find "${REPO_ROOT}" \( "${IGNORED_CONDITIONS[@]}" \) -o -type f -name "*.sh" -print0 | xargs -0 "${SHELLCHECK}" "${@}" +find "${REPO_ROOT}" -name "*.sh" -type f -print0 | xargs -0 "${SHELLCHECK}" "${@}" diff --git a/scripts/tests.build_antithesis_images.sh b/scripts/tests.build_antithesis_images.sh deleted file mode 100755 index 658806524d..0000000000 --- a/scripts/tests.build_antithesis_images.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# Validates the construction of the antithesis images by: -# -# 1. Building the antithesis test images -# 2. Extracting the docker compose configuration from the config image -# 3. Running the workload and its target network without error for a minute -# 4. Stopping the workload and its target network -# - -# e.g., -# ./scripts/tests.build_antithesis_images.sh # Test build of antithesis images -# DEBUG=1 ./scripts/tests.build_antithesis_images.sh # Retain the temporary compose path for troubleshooting - -SUBNET_EVM_PATH=$( cd "$( dirname "${BASH_SOURCE[0]}" )"; cd .. && pwd ) - -# Discover the default tag that will be used for the image -source "${SUBNET_EVM_PATH}"/scripts/constants.sh -export IMAGE_TAG="${SUBNET_EVM_COMMIT::8}" - -# Build the images -bash -x "${SUBNET_EVM_PATH}"/scripts/build_antithesis_images.sh - -# Test the images -AVALANCHEGO_CLONE_PATH="${AVALANCHEGO_CLONE_PATH:-${SUBNET_EVM_PATH}/avalanchego}" -export IMAGE_NAME="antithesis-subnet-evm-config" -export DEBUG="${DEBUG:-}" -set -x -# shellcheck source=/dev/null -. "${AVALANCHEGO_CLONE_PATH}"/scripts/lib_test_antithesis_images.sh diff --git a/scripts/versions.sh b/scripts/versions.sh index d6f28fb862..5bed52626d 100644 --- a/scripts/versions.sh +++ b/scripts/versions.sh @@ -3,9 +3,7 @@ # Ignore warnings about variables appearing unused since this file is not the consumer of the variables it defines. # shellcheck disable=SC2034 -# Don't export them as they're used in the context of other calls -AVALANCHE_VERSION=${AVALANCHE_VERSION:-'df91c2f4a'} -GINKGO_VERSION=${GINKGO_VERSION:-'v2.2.0'} +set -euo pipefail -# This won't be used, but it's here to make code syncs easier -LATEST_CORETH_VERSION='v0.13.7' +# Don't export them as they're used in the context of other calls +AVALANCHE_VERSION=${AVALANCHE_VERSION:-'1ac532af76df'} diff --git a/stateupgrade/interfaces.go b/stateupgrade/interfaces.go deleted file mode 100644 index f667980487..0000000000 --- a/stateupgrade/interfaces.go +++ /dev/null @@ -1,35 +0,0 @@ -// (c) 2023 Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package stateupgrade - -import ( - "math/big" - - "github.com/ethereum/go-ethereum/common" -) - -// StateDB is the interface for accessing EVM state in state upgrades -type StateDB interface { - SetState(common.Address, common.Hash, common.Hash) - SetCode(common.Address, []byte) - AddBalance(common.Address, *big.Int) - - GetNonce(common.Address) uint64 - SetNonce(common.Address, uint64) - - CreateAccount(common.Address) - Exist(common.Address) bool -} - -// ChainContext defines an interface that provides information to a state upgrade -// about the chain configuration. -type ChainContext interface { - IsEIP158(num *big.Int) bool -} - -// BlockContext defines an interface that provides information to a state upgrade -// about the block that activates the upgrade. -type BlockContext interface { - Number() *big.Int -} diff --git a/stateupgrade/state_upgrade.go b/stateupgrade/state_upgrade.go deleted file mode 100644 index 682e7cd493..0000000000 --- a/stateupgrade/state_upgrade.go +++ /dev/null @@ -1,46 +0,0 @@ -// (c) 2023 Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package stateupgrade - -import ( - "math/big" - - "github.com/ava-labs/subnet-evm/params" - "github.com/ethereum/go-ethereum/common" -) - -// Configure applies the state upgrade to the state. -func Configure(stateUpgrade *params.StateUpgrade, chainConfig ChainContext, state StateDB, blockContext BlockContext) error { - isEIP158 := chainConfig.IsEIP158(blockContext.Number()) - for account, upgrade := range stateUpgrade.StateUpgradeAccounts { - if err := upgradeAccount(account, upgrade, state, isEIP158); err != nil { - return err - } - } - return nil -} - -// upgradeAccount applies the state upgrade to the given account. -func upgradeAccount(account common.Address, upgrade params.StateUpgradeAccount, state StateDB, isEIP158 bool) error { - // Create the account if it does not exist - if !state.Exist(account) { - state.CreateAccount(account) - } - - if upgrade.BalanceChange != nil { - state.AddBalance(account, (*big.Int)(upgrade.BalanceChange)) - } - if len(upgrade.Code) != 0 { - // if the nonce is 0, set the nonce to 1 as we would when deploying a contract at - // the address. - if isEIP158 && state.GetNonce(account) == 0 { - state.SetNonce(account, 1) - } - state.SetCode(account, upgrade.Code) - } - for key, value := range upgrade.Storage { - state.SetState(account, key, value) - } - return nil -} diff --git a/sync/README.md b/sync/README.md index ad991ada53..2e6437a71f 100644 --- a/sync/README.md +++ b/sync/README.md @@ -16,6 +16,7 @@ _Note: nodes joining the network through state sync will not have historical sta ## What is the chain state? The node needs the following data from its peers to continue processing blocks from a syncable block: - Accounts trie & storage tries for all accounts (at the state root corresponding to the syncable block), +- State of the cross-chain shared memory (this data is fetched from peers as a merkelized trie containing add/remove operations from Import and Export Txs, known as the _atomic trie_), - Contract code referenced in the account trie, - 256 parents of the syncable block (required for the BLOCKHASH opcode) @@ -29,10 +30,11 @@ State sync code is structured as follows: - _Note: There are response size and time limits in place so peers joining the network do not overload peers providing data. Additionally, the engine tracks the CPU usage of each peer for such messages and throttles inbound requests accordingly._ - `sync/client`: Validates responses from peers and provides support for syncing tries. - `sync/statesync`: Uses `sync/client` to sync EVM related state: Accounts, storage tries, and contract code. +- `plugin/evm/atomicSyncer`: Uses `sync/client` to sync the atomic trie. - `plugin/evm/`: The engine expects the VM to implement `StateSyncableVM` interface, - `StateSyncServer`: Contains methods executed on nodes _serving_ state sync requests. - `StateSyncClient`: Contains methods executed on nodes joining the network via state sync, and orchestrates the top level steps of the sync. -- `peer`: Contains abstractions used by `sync/statesync` to send requests to peers (`AppRequest`) and receive responses from peers (`AppResponse`). +- `peer`: Contains abstractions used by `sync/statesync` to send requests to peers (`AppRequest`) and receive responses from peers (`AppResponse`). - `message`: Contains structs that are serialized and sent over the network during state sync. @@ -41,14 +43,15 @@ When a new node wants to join the network via state sync, it will need a few pie - Number (height) and hash of the latest available syncable block, - Root of the account trie, +- Root of the atomic trie The above information is called a _state summary_, and each syncable block corresponds to one such summary (see `message.SyncSummary`). The engine and VM interact as follows to find a syncable state summary: -1. The engine calls `StateSyncEnabled`. The VM returns `true` to initiate state sync, or `false` to start bootstrapping. In `subnet-evm`, this is controlled by the `state-sync-enabled` flag. -1. The engine calls `GetOngoingSyncStateSummary`. If the VM has a previously interrupted sync to resume it returns that summary. Otherwise, it returns `ErrNotFound`. By default, `subnet-evm` will resume an interrupted sync. +1. The engine calls `StateSyncEnabled`. The VM returns `true` to initiate state sync, or `false` to start bootstrapping. In `coreth`, this is controlled by the `state-sync-enabled` flag. +1. The engine calls `GetOngoingSyncStateSummary`. If the VM has a previously interrupted sync to resume it returns that summary. Otherwise, it returns `ErrNotFound`. By default, `coreth` will resume an interrupted sync. 1. The engine samples peers for their latest available summaries, then verifies the correctness and availability of each sampled summary with validators. The messaging flow is documented [here](https://github.com/ava-labs/avalanchego/blob/master/snow/engine/snowman/block/README.md). -1. The engine calls `Accept` on the chosen summary. The VM may return `false` to skip syncing to this summary (`subnet-evm` skips state sync for less than `defaultStateSyncMinBlocks = 300_000` blocks). If the VM decides to perform the sync, it must return `true` without blocking and fetch the state from its peers asynchronously. +1. The engine calls `Accept` on the chosen summary. The VM may return `false` to skip syncing to this summary (`coreth` skips state sync for less than `defaultStateSyncMinBlocks = 300_000` blocks). If the VM decides to perform the sync, it must return `true` without blocking and fetch the state from its peers asynchronously. 1. The VM sends `common.StateSyncDone` on the `toEngine` channel on completion. 1. The engine calls `VM.SetState(Bootstrapping)`. Then, blocks after the syncable block are processed one by one. @@ -56,10 +59,14 @@ The above information is called a _state summary_, and each syncable block corre The following steps are executed by the VM to sync its state from peers (see `stateSyncClient.StateSync`): 1. Wipe snapshot data 1. Sync 256 parents of the syncable block (see `BlockRequest`), +1. Sync the atomic trie, 1. Sync the EVM state: account trie, code, and storage tries, 1. Update in-memory and on-disk pointers. Steps 3 and 4 involve syncing tries. To sync trie data, the VM will send a series of `LeafRequests` to its peers. Each request specifies: +- Type of trie (`NodeType`): + - `message.StateTrieNode` (account trie and storage tries share the same database) + - `message.AtomicTrieNode` (atomic trie has an independent database) - `Root` of the trie to sync, - `Start` and `End` specify a range of keys. @@ -85,6 +92,13 @@ When the trie is complete, an `OnFinish` callback is called and we hash any rema When a storage trie leaf is received, it is stored in the account's storage snapshot. A `StackTrie` is used here to reconstruct intermediary trie nodes & root as well. +### Atomic trie +`plugin/evm.atomicSyncer` uses `CallbackLeafSyncer` to sync the atomic trie. In this trie, each leaf represents a set of put or remove shared memory operations and is structured as follows: +- Key: block height + peer blockchain ID +- Value: codec serialized `atomic.Requests` (includes `PutRequests` and `RemoveRequests`) + +For each 4096 blocks (`commitHeightInterval`) inserted in the atomic trie, a root is constructed and the trie is persisted. There is no concurrency in sycing this trie. + ### Updating in-memory and on-disk pointers `plugin/evm.stateSyncClient.StateSyncSetLastSummaryBlock` is the last step in state sync. Once the tries have been synced, this method: @@ -93,6 +107,7 @@ Once the tries have been synced, this method: - Adds a checkpoint to the `core.ChainIndexer` (to avoid indexing missing blocks) - Resets in-memory and on disk pointers on the `core.BlockChain` struct. - Updates VM's last accepted block. +- Applies the atomic operations from the atomic trie to shared memory. (Note: the VM will resume applying these operations even if the VM is shutdown prior to completing this step) ## Resuming a partial sync operation diff --git a/sync/client/client_test.go b/sync/client/client_test.go index 0ec9cafb77..5eab2e8900 100644 --- a/sync/client/client_test.go +++ b/sync/client/client_test.go @@ -432,10 +432,11 @@ func TestGetLeafs(t *testing.T) { }{ "full response for small (single request) trie": { request: message.LeafsRequest{ - Root: smallTrieRoot, - Start: bytes.Repeat([]byte{0x00}, common.HashLength), - End: bytes.Repeat([]byte{0xff}, common.HashLength), - Limit: leafsLimit, + Root: smallTrieRoot, + Start: bytes.Repeat([]byte{0x00}, common.HashLength), + End: bytes.Repeat([]byte{0xff}, common.HashLength), + Limit: leafsLimit, + NodeType: message.StateTrieNode, }, getResponse: func(t *testing.T, request message.LeafsRequest) []byte { response, err := handler.OnLeafsRequest(context.Background(), ids.GenerateTestNodeID(), 1, request) @@ -456,10 +457,11 @@ func TestGetLeafs(t *testing.T) { }, "too many leaves in response": { request: message.LeafsRequest{ - Root: smallTrieRoot, - Start: bytes.Repeat([]byte{0x00}, common.HashLength), - End: bytes.Repeat([]byte{0xff}, common.HashLength), - Limit: leafsLimit / 2, + Root: smallTrieRoot, + Start: bytes.Repeat([]byte{0x00}, common.HashLength), + End: bytes.Repeat([]byte{0xff}, common.HashLength), + Limit: leafsLimit / 2, + NodeType: message.StateTrieNode, }, getResponse: func(t *testing.T, request message.LeafsRequest) []byte { modifiedRequest := request @@ -478,10 +480,11 @@ func TestGetLeafs(t *testing.T) { }, "partial response to request for entire trie (full leaf limit)": { request: message.LeafsRequest{ - Root: largeTrieRoot, - Start: bytes.Repeat([]byte{0x00}, common.HashLength), - End: bytes.Repeat([]byte{0xff}, common.HashLength), - Limit: leafsLimit, + Root: largeTrieRoot, + Start: bytes.Repeat([]byte{0x00}, common.HashLength), + End: bytes.Repeat([]byte{0xff}, common.HashLength), + Limit: leafsLimit, + NodeType: message.StateTrieNode, }, getResponse: func(t *testing.T, request message.LeafsRequest) []byte { response, err := handler.OnLeafsRequest(context.Background(), ids.GenerateTestNodeID(), 1, request) @@ -502,10 +505,11 @@ func TestGetLeafs(t *testing.T) { }, "partial response to request for middle range of trie (full leaf limit)": { request: message.LeafsRequest{ - Root: largeTrieRoot, - Start: largeTrieKeys[1000], - End: largeTrieKeys[99000], - Limit: leafsLimit, + Root: largeTrieRoot, + Start: largeTrieKeys[1000], + End: largeTrieKeys[99000], + Limit: leafsLimit, + NodeType: message.StateTrieNode, }, getResponse: func(t *testing.T, request message.LeafsRequest) []byte { response, err := handler.OnLeafsRequest(context.Background(), ids.GenerateTestNodeID(), 1, request) @@ -526,10 +530,11 @@ func TestGetLeafs(t *testing.T) { }, "full response from near end of trie to end of trie (less than leaf limit)": { request: message.LeafsRequest{ - Root: largeTrieRoot, - Start: largeTrieKeys[len(largeTrieKeys)-30], // Set start 30 keys from the end of the large trie - End: bytes.Repeat([]byte{0xff}, common.HashLength), - Limit: leafsLimit, + Root: largeTrieRoot, + Start: largeTrieKeys[len(largeTrieKeys)-30], // Set start 30 keys from the end of the large trie + End: bytes.Repeat([]byte{0xff}, common.HashLength), + Limit: leafsLimit, + NodeType: message.StateTrieNode, }, getResponse: func(t *testing.T, request message.LeafsRequest) []byte { response, err := handler.OnLeafsRequest(context.Background(), ids.GenerateTestNodeID(), 1, request) @@ -549,10 +554,11 @@ func TestGetLeafs(t *testing.T) { }, "full response for intermediate range of trie (less than leaf limit)": { request: message.LeafsRequest{ - Root: largeTrieRoot, - Start: largeTrieKeys[1000], // Set the range for 1000 leafs in an intermediate range of the trie - End: largeTrieKeys[1099], // (inclusive range) - Limit: leafsLimit, + Root: largeTrieRoot, + Start: largeTrieKeys[1000], // Set the range for 1000 leafs in an intermediate range of the trie + End: largeTrieKeys[1099], // (inclusive range) + Limit: leafsLimit, + NodeType: message.StateTrieNode, }, getResponse: func(t *testing.T, request message.LeafsRequest) []byte { response, err := handler.OnLeafsRequest(context.Background(), ids.GenerateTestNodeID(), 1, request) @@ -573,10 +579,11 @@ func TestGetLeafs(t *testing.T) { }, "removed first key in response": { request: message.LeafsRequest{ - Root: largeTrieRoot, - Start: bytes.Repeat([]byte{0x00}, common.HashLength), - End: bytes.Repeat([]byte{0xff}, common.HashLength), - Limit: leafsLimit, + Root: largeTrieRoot, + Start: bytes.Repeat([]byte{0x00}, common.HashLength), + End: bytes.Repeat([]byte{0xff}, common.HashLength), + Limit: leafsLimit, + NodeType: message.StateTrieNode, }, getResponse: func(t *testing.T, request message.LeafsRequest) []byte { response, err := handler.OnLeafsRequest(context.Background(), ids.GenerateTestNodeID(), 1, request) @@ -603,10 +610,11 @@ func TestGetLeafs(t *testing.T) { }, "removed first key in response and replaced proof": { request: message.LeafsRequest{ - Root: largeTrieRoot, - Start: bytes.Repeat([]byte{0x00}, common.HashLength), - End: bytes.Repeat([]byte{0xff}, common.HashLength), - Limit: leafsLimit, + Root: largeTrieRoot, + Start: bytes.Repeat([]byte{0x00}, common.HashLength), + End: bytes.Repeat([]byte{0xff}, common.HashLength), + Limit: leafsLimit, + NodeType: message.StateTrieNode, }, getResponse: func(t *testing.T, request message.LeafsRequest) []byte { response, err := handler.OnLeafsRequest(context.Background(), ids.GenerateTestNodeID(), 1, request) @@ -632,10 +640,11 @@ func TestGetLeafs(t *testing.T) { }, "removed last key in response": { request: message.LeafsRequest{ - Root: largeTrieRoot, - Start: bytes.Repeat([]byte{0x00}, common.HashLength), - End: bytes.Repeat([]byte{0xff}, common.HashLength), - Limit: leafsLimit, + Root: largeTrieRoot, + Start: bytes.Repeat([]byte{0x00}, common.HashLength), + End: bytes.Repeat([]byte{0xff}, common.HashLength), + Limit: leafsLimit, + NodeType: message.StateTrieNode, }, getResponse: func(t *testing.T, request message.LeafsRequest) []byte { response, err := handler.OnLeafsRequest(context.Background(), ids.GenerateTestNodeID(), 1, request) @@ -662,10 +671,11 @@ func TestGetLeafs(t *testing.T) { }, "removed key from middle of response": { request: message.LeafsRequest{ - Root: largeTrieRoot, - Start: bytes.Repeat([]byte{0x00}, common.HashLength), - End: bytes.Repeat([]byte{0xff}, common.HashLength), - Limit: leafsLimit, + Root: largeTrieRoot, + Start: bytes.Repeat([]byte{0x00}, common.HashLength), + End: bytes.Repeat([]byte{0xff}, common.HashLength), + Limit: leafsLimit, + NodeType: message.StateTrieNode, }, getResponse: func(t *testing.T, request message.LeafsRequest) []byte { response, err := handler.OnLeafsRequest(context.Background(), ids.GenerateTestNodeID(), 1, request) @@ -693,10 +703,11 @@ func TestGetLeafs(t *testing.T) { }, "corrupted value in middle of response": { request: message.LeafsRequest{ - Root: largeTrieRoot, - Start: bytes.Repeat([]byte{0x00}, common.HashLength), - End: bytes.Repeat([]byte{0xff}, common.HashLength), - Limit: leafsLimit, + Root: largeTrieRoot, + Start: bytes.Repeat([]byte{0x00}, common.HashLength), + End: bytes.Repeat([]byte{0xff}, common.HashLength), + Limit: leafsLimit, + NodeType: message.StateTrieNode, }, getResponse: func(t *testing.T, request message.LeafsRequest) []byte { response, err := handler.OnLeafsRequest(context.Background(), ids.GenerateTestNodeID(), 1, request) @@ -723,10 +734,11 @@ func TestGetLeafs(t *testing.T) { }, "all proof keys removed from response": { request: message.LeafsRequest{ - Root: largeTrieRoot, - Start: bytes.Repeat([]byte{0x00}, common.HashLength), - End: bytes.Repeat([]byte{0xff}, common.HashLength), - Limit: leafsLimit, + Root: largeTrieRoot, + Start: bytes.Repeat([]byte{0x00}, common.HashLength), + End: bytes.Repeat([]byte{0xff}, common.HashLength), + Limit: leafsLimit, + NodeType: message.StateTrieNode, }, getResponse: func(t *testing.T, request message.LeafsRequest) []byte { response, err := handler.OnLeafsRequest(context.Background(), ids.GenerateTestNodeID(), 1, request) @@ -798,10 +810,11 @@ func TestGetLeafsRetries(t *testing.T) { }) request := message.LeafsRequest{ - Root: root, - Start: bytes.Repeat([]byte{0x00}, common.HashLength), - End: bytes.Repeat([]byte{0xff}, common.HashLength), - Limit: 1024, + Root: root, + Start: bytes.Repeat([]byte{0x00}, common.HashLength), + End: bytes.Repeat([]byte{0xff}, common.HashLength), + Limit: 1024, + NodeType: message.StateTrieNode, } ctx, cancel := context.WithCancel(context.Background()) diff --git a/sync/client/leaf_syncer.go b/sync/client/leaf_syncer.go index 8ad9ef27c6..20902da6d9 100644 --- a/sync/client/leaf_syncer.go +++ b/sync/client/leaf_syncer.go @@ -30,6 +30,7 @@ type LeafSyncTask interface { Account() common.Hash // Account hash of the trie to sync (only applicable to storage tries) Start() []byte // Starting key to request new leaves End() []byte // End key to request new leaves + NodeType() message.NodeType // Specifies the message type (atomic/state trie) for the leaf syncer to send OnStart() (bool, error) // Callback when tasks begins, returns true if work can be skipped OnLeafs(keys, vals [][]byte) error // Callback when new leaves are received from the network OnFinish(ctx context.Context) error // Callback when there are no more leaves in the trie to sync or when we reach End() @@ -97,10 +98,11 @@ func (c *CallbackLeafSyncer) syncTask(ctx context.Context, task LeafSyncTask) er } leafsResponse, err := c.client.GetLeafs(ctx, message.LeafsRequest{ - Root: root, - Account: task.Account(), - Start: start, - Limit: c.requestSize, + Root: root, + Account: task.Account(), + Start: start, + Limit: c.requestSize, + NodeType: task.NodeType(), }) if err != nil { return fmt.Errorf("%s: %w", errFailedToFetchLeafs, err) diff --git a/sync/client/stats/stats.go b/sync/client/stats/stats.go index 92519e5da4..aaf97b0e06 100644 --- a/sync/client/stats/stats.go +++ b/sync/client/stats/stats.go @@ -75,6 +75,7 @@ func (m *messageMetric) UpdateRequestLatency(duration time.Duration) { } type clientSyncerStats struct { + atomicTrieLeavesMetric, stateTrieLeavesMetric, codeRequestMetric, blockRequestMetric MessageMetric @@ -83,9 +84,10 @@ type clientSyncerStats struct { // NewClientSyncerStats returns stats for the client syncer func NewClientSyncerStats() ClientSyncerStats { return &clientSyncerStats{ - stateTrieLeavesMetric: NewMessageMetric("sync_state_trie_leaves"), - codeRequestMetric: NewMessageMetric("sync_code"), - blockRequestMetric: NewMessageMetric("sync_blocks"), + atomicTrieLeavesMetric: NewMessageMetric("sync_atomic_trie_leaves"), + stateTrieLeavesMetric: NewMessageMetric("sync_state_trie_leaves"), + codeRequestMetric: NewMessageMetric("sync_code"), + blockRequestMetric: NewMessageMetric("sync_blocks"), } } @@ -97,7 +99,14 @@ func (c *clientSyncerStats) GetMetric(msgIntf message.Request) (MessageMetric, e case message.CodeRequest: return c.codeRequestMetric, nil case message.LeafsRequest: - return c.stateTrieLeavesMetric, nil + switch msg.NodeType { + case message.StateTrieNode: + return c.stateTrieLeavesMetric, nil + case message.AtomicTrieNode: + return c.atomicTrieLeavesMetric, nil + default: + return nil, fmt.Errorf("invalid leafs request for node type: %T", msg.NodeType) + } default: return nil, fmt.Errorf("attempted to get metric for invalid request with type %T", msg) } diff --git a/sync/handlers/leafs_request.go b/sync/handlers/leafs_request.go index bc4abc809d..41ce1f22bc 100644 --- a/sync/handlers/leafs_request.go +++ b/sync/handlers/leafs_request.go @@ -6,11 +6,13 @@ package handlers import ( "bytes" "context" + "fmt" "sync" "time" "github.com/ava-labs/avalanchego/codec" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/wrappers" "github.com/ava-labs/subnet-evm/core/state/snapshot" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/plugin/evm/message" @@ -34,8 +36,7 @@ const ( // reading the snapshot to find the response maxSnapshotReadTimePercent = 75 - segmentLen = 64 // divide data from snapshot to segments of this size - keyLength = common.HashLength // length of the keys of the trie to sync + segmentLen = 64 // divide data from snapshot to segments of this size ) // LeafsRequestHandler is a peer.RequestHandler for types.LeafsRequest @@ -68,7 +69,8 @@ func NewLeafsRequestHandler(trieDB *trie.Database, snapshotProvider SnapshotProv // Specified Limit in message.LeafsRequest is overridden to maxLeavesLimit if it is greater than maxLeavesLimit // Expects returned errors to be treated as FATAL // Never returns errors -// Returns nothing if the requested trie root is not found +// Expects NodeType to be one of message.AtomicTrieNode or message.StateTrieNode +// Returns nothing if NodeType is invalid or requested trie root is not found // Assumes ctx is active func (lrh *LeafsRequestHandler) OnLeafsRequest(ctx context.Context, nodeID ids.NodeID, requestID uint32, leafsRequest message.LeafsRequest) ([]byte, error) { startTime := time.Now() @@ -82,6 +84,13 @@ func (lrh *LeafsRequestHandler) OnLeafsRequest(ctx context.Context, nodeID ids.N lrh.stats.IncInvalidLeafsRequest() return nil, nil } + keyLength, err := getKeyLength(leafsRequest.NodeType) + if err != nil { + // Note: LeafsRequest.Handle checks NodeType's validity so clients cannot cause the server to spam this error + log.Error("Failed to get key length for leafs request", "err", err) + lrh.stats.IncInvalidLeafsRequest() + return nil, nil + } if len(leafsRequest.Start) != 0 && len(leafsRequest.Start) != keyLength || len(leafsRequest.End) != 0 && len(leafsRequest.End) != keyLength { log.Debug("invalid length for leafs request range, dropping request", "startLen", len(leafsRequest.Start), "endLen", len(leafsRequest.End), "expected", keyLength) @@ -445,6 +454,18 @@ func (rb *responseBuilder) fillFromTrie(ctx context.Context, end []byte) (bool, return more, it.Err } +// getKeyLength returns trie key length for given nodeType +// expects nodeType to be one of message.AtomicTrieNode or message.StateTrieNode +func getKeyLength(nodeType message.NodeType) (int, error) { + switch nodeType { + case message.AtomicTrieNode: + return wrappers.LongLen + common.HashLength, nil + case message.StateTrieNode: + return common.HashLength, nil + } + return 0, fmt.Errorf("cannot get key length for unknown node type: %s", nodeType) +} + // readLeafsFromSnapshot iterates the storage snapshot of the requested account // (or the main account trie if account is empty). Returns up to [rb.limit] key/value // pairs for keys that are in the request's range (inclusive). diff --git a/sync/handlers/leafs_request_test.go b/sync/handlers/leafs_request_test.go index d73aa31ad8..5c3055edc6 100644 --- a/sync/handlers/leafs_request_test.go +++ b/sync/handlers/leafs_request_test.go @@ -88,10 +88,11 @@ func TestLeafsRequestHandler_OnLeafsRequest(t *testing.T) { "zero limit dropped": { prepareTestFn: func() (context.Context, message.LeafsRequest) { return context.Background(), message.LeafsRequest{ - Root: largeTrieRoot, - Start: bytes.Repeat([]byte{0x00}, common.HashLength), - End: bytes.Repeat([]byte{0xff}, common.HashLength), - Limit: 0, + Root: largeTrieRoot, + Start: bytes.Repeat([]byte{0x00}, common.HashLength), + End: bytes.Repeat([]byte{0xff}, common.HashLength), + Limit: 0, + NodeType: message.StateTrieNode, } }, assertResponseFn: func(t *testing.T, _ message.LeafsRequest, response []byte, err error) { @@ -103,10 +104,11 @@ func TestLeafsRequestHandler_OnLeafsRequest(t *testing.T) { "empty root dropped": { prepareTestFn: func() (context.Context, message.LeafsRequest) { return context.Background(), message.LeafsRequest{ - Root: common.Hash{}, - Start: bytes.Repeat([]byte{0x00}, common.HashLength), - End: bytes.Repeat([]byte{0xff}, common.HashLength), - Limit: maxLeavesLimit, + Root: common.Hash{}, + Start: bytes.Repeat([]byte{0x00}, common.HashLength), + End: bytes.Repeat([]byte{0xff}, common.HashLength), + Limit: maxLeavesLimit, + NodeType: message.StateTrieNode, } }, assertResponseFn: func(t *testing.T, _ message.LeafsRequest, response []byte, err error) { @@ -118,10 +120,11 @@ func TestLeafsRequestHandler_OnLeafsRequest(t *testing.T) { "bad start len dropped": { prepareTestFn: func() (context.Context, message.LeafsRequest) { return context.Background(), message.LeafsRequest{ - Root: common.Hash{}, - Start: bytes.Repeat([]byte{0x00}, common.HashLength+2), - End: bytes.Repeat([]byte{0xff}, common.HashLength), - Limit: maxLeavesLimit, + Root: common.Hash{}, + Start: bytes.Repeat([]byte{0x00}, common.HashLength+2), + End: bytes.Repeat([]byte{0xff}, common.HashLength), + Limit: maxLeavesLimit, + NodeType: message.StateTrieNode, } }, assertResponseFn: func(t *testing.T, _ message.LeafsRequest, response []byte, err error) { @@ -133,10 +136,11 @@ func TestLeafsRequestHandler_OnLeafsRequest(t *testing.T) { "bad end len dropped": { prepareTestFn: func() (context.Context, message.LeafsRequest) { return context.Background(), message.LeafsRequest{ - Root: common.Hash{}, - Start: bytes.Repeat([]byte{0x00}, common.HashLength), - End: bytes.Repeat([]byte{0xff}, common.HashLength-1), - Limit: maxLeavesLimit, + Root: common.Hash{}, + Start: bytes.Repeat([]byte{0x00}, common.HashLength), + End: bytes.Repeat([]byte{0xff}, common.HashLength-1), + Limit: maxLeavesLimit, + NodeType: message.StateTrieNode, } }, assertResponseFn: func(t *testing.T, _ message.LeafsRequest, response []byte, err error) { @@ -148,10 +152,11 @@ func TestLeafsRequestHandler_OnLeafsRequest(t *testing.T) { "empty storage root dropped": { prepareTestFn: func() (context.Context, message.LeafsRequest) { return context.Background(), message.LeafsRequest{ - Root: types.EmptyRootHash, - Start: bytes.Repeat([]byte{0x00}, common.HashLength), - End: bytes.Repeat([]byte{0xff}, common.HashLength), - Limit: maxLeavesLimit, + Root: types.EmptyRootHash, + Start: bytes.Repeat([]byte{0x00}, common.HashLength), + End: bytes.Repeat([]byte{0xff}, common.HashLength), + Limit: maxLeavesLimit, + NodeType: message.StateTrieNode, } }, assertResponseFn: func(t *testing.T, _ message.LeafsRequest, response []byte, err error) { @@ -163,10 +168,11 @@ func TestLeafsRequestHandler_OnLeafsRequest(t *testing.T) { "missing root dropped": { prepareTestFn: func() (context.Context, message.LeafsRequest) { return context.Background(), message.LeafsRequest{ - Root: common.BytesToHash([]byte("something is missing here...")), - Start: bytes.Repeat([]byte{0x00}, common.HashLength), - End: bytes.Repeat([]byte{0xff}, common.HashLength), - Limit: maxLeavesLimit, + Root: common.BytesToHash([]byte("something is missing here...")), + Start: bytes.Repeat([]byte{0x00}, common.HashLength), + End: bytes.Repeat([]byte{0xff}, common.HashLength), + Limit: maxLeavesLimit, + NodeType: message.StateTrieNode, } }, assertResponseFn: func(t *testing.T, _ message.LeafsRequest, response []byte, err error) { @@ -178,10 +184,11 @@ func TestLeafsRequestHandler_OnLeafsRequest(t *testing.T) { "corrupted trie drops request": { prepareTestFn: func() (context.Context, message.LeafsRequest) { return context.Background(), message.LeafsRequest{ - Root: corruptedTrieRoot, - Start: bytes.Repeat([]byte{0x00}, common.HashLength), - End: bytes.Repeat([]byte{0xff}, common.HashLength), - Limit: maxLeavesLimit, + Root: corruptedTrieRoot, + Start: bytes.Repeat([]byte{0x00}, common.HashLength), + End: bytes.Repeat([]byte{0xff}, common.HashLength), + Limit: maxLeavesLimit, + NodeType: message.StateTrieNode, } }, assertResponseFn: func(t *testing.T, _ message.LeafsRequest, response []byte, err error) { @@ -195,10 +202,11 @@ func TestLeafsRequestHandler_OnLeafsRequest(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() return ctx, message.LeafsRequest{ - Root: largeTrieRoot, - Start: bytes.Repeat([]byte{0x00}, common.HashLength), - End: bytes.Repeat([]byte{0xff}, common.HashLength), - Limit: maxLeavesLimit, + Root: largeTrieRoot, + Start: bytes.Repeat([]byte{0x00}, common.HashLength), + End: bytes.Repeat([]byte{0xff}, common.HashLength), + Limit: maxLeavesLimit, + NodeType: message.StateTrieNode, } }, assertResponseFn: func(t *testing.T, _ message.LeafsRequest, response []byte, err error) { @@ -209,10 +217,11 @@ func TestLeafsRequestHandler_OnLeafsRequest(t *testing.T) { "nil start and end range returns entire trie": { prepareTestFn: func() (context.Context, message.LeafsRequest) { return context.Background(), message.LeafsRequest{ - Root: smallTrieRoot, - Start: nil, - End: nil, - Limit: maxLeavesLimit, + Root: smallTrieRoot, + Start: nil, + End: nil, + Limit: maxLeavesLimit, + NodeType: message.StateTrieNode, } }, assertResponseFn: func(t *testing.T, _ message.LeafsRequest, response []byte, err error) { @@ -228,10 +237,11 @@ func TestLeafsRequestHandler_OnLeafsRequest(t *testing.T) { "nil end range treated like greatest possible value": { prepareTestFn: func() (context.Context, message.LeafsRequest) { return context.Background(), message.LeafsRequest{ - Root: smallTrieRoot, - Start: bytes.Repeat([]byte{0x00}, common.HashLength), - End: nil, - Limit: maxLeavesLimit, + Root: smallTrieRoot, + Start: bytes.Repeat([]byte{0x00}, common.HashLength), + End: nil, + Limit: maxLeavesLimit, + NodeType: message.StateTrieNode, } }, assertResponseFn: func(t *testing.T, _ message.LeafsRequest, response []byte, err error) { @@ -248,10 +258,11 @@ func TestLeafsRequestHandler_OnLeafsRequest(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() return ctx, message.LeafsRequest{ - Root: largeTrieRoot, - Start: bytes.Repeat([]byte{0xbb}, common.HashLength), - End: bytes.Repeat([]byte{0xaa}, common.HashLength), - Limit: maxLeavesLimit, + Root: largeTrieRoot, + Start: bytes.Repeat([]byte{0xbb}, common.HashLength), + End: bytes.Repeat([]byte{0xaa}, common.HashLength), + Limit: maxLeavesLimit, + NodeType: message.StateTrieNode, } }, assertResponseFn: func(t *testing.T, _ message.LeafsRequest, response []byte, err error) { @@ -265,10 +276,11 @@ func TestLeafsRequestHandler_OnLeafsRequest(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() return ctx, message.LeafsRequest{ - Root: largeTrieRoot, - Start: bytes.Repeat([]byte{0xbb}, common.HashLength), - End: bytes.Repeat([]byte{0xaa}, common.HashLength), - Limit: maxLeavesLimit, + Root: largeTrieRoot, + Start: bytes.Repeat([]byte{0xbb}, common.HashLength), + End: bytes.Repeat([]byte{0xaa}, common.HashLength), + Limit: maxLeavesLimit, + NodeType: message.NodeType(11), } }, assertResponseFn: func(t *testing.T, _ message.LeafsRequest, response []byte, err error) { @@ -279,10 +291,11 @@ func TestLeafsRequestHandler_OnLeafsRequest(t *testing.T) { "max leaves overridden": { prepareTestFn: func() (context.Context, message.LeafsRequest) { return context.Background(), message.LeafsRequest{ - Root: largeTrieRoot, - Start: bytes.Repeat([]byte{0x00}, common.HashLength), - End: bytes.Repeat([]byte{0xff}, common.HashLength), - Limit: maxLeavesLimit * 10, + Root: largeTrieRoot, + Start: bytes.Repeat([]byte{0x00}, common.HashLength), + End: bytes.Repeat([]byte{0xff}, common.HashLength), + Limit: maxLeavesLimit * 10, + NodeType: message.StateTrieNode, } }, assertResponseFn: func(t *testing.T, _ message.LeafsRequest, response []byte, err error) { @@ -299,10 +312,11 @@ func TestLeafsRequestHandler_OnLeafsRequest(t *testing.T) { "full range with nil start": { prepareTestFn: func() (context.Context, message.LeafsRequest) { return context.Background(), message.LeafsRequest{ - Root: largeTrieRoot, - Start: nil, - End: bytes.Repeat([]byte{0xff}, common.HashLength), - Limit: maxLeavesLimit, + Root: largeTrieRoot, + Start: nil, + End: bytes.Repeat([]byte{0xff}, common.HashLength), + Limit: maxLeavesLimit, + NodeType: message.StateTrieNode, } }, assertResponseFn: func(t *testing.T, request message.LeafsRequest, response []byte, err error) { @@ -320,10 +334,11 @@ func TestLeafsRequestHandler_OnLeafsRequest(t *testing.T) { "full range with 0x00 start": { prepareTestFn: func() (context.Context, message.LeafsRequest) { return context.Background(), message.LeafsRequest{ - Root: largeTrieRoot, - Start: bytes.Repeat([]byte{0x00}, common.HashLength), - End: bytes.Repeat([]byte{0xff}, common.HashLength), - Limit: maxLeavesLimit, + Root: largeTrieRoot, + Start: bytes.Repeat([]byte{0x00}, common.HashLength), + End: bytes.Repeat([]byte{0xff}, common.HashLength), + Limit: maxLeavesLimit, + NodeType: message.StateTrieNode, } }, assertResponseFn: func(t *testing.T, request message.LeafsRequest, response []byte, err error) { @@ -344,10 +359,11 @@ func TestLeafsRequestHandler_OnLeafsRequest(t *testing.T) { startKey[31] = startKey[31] + 1 // exclude start key from response endKey := largeTrieKeys[1_040] // include end key in response return context.Background(), message.LeafsRequest{ - Root: largeTrieRoot, - Start: startKey, - End: endKey, - Limit: maxLeavesLimit, + Root: largeTrieRoot, + Start: startKey, + End: endKey, + Limit: maxLeavesLimit, + NodeType: message.StateTrieNode, } }, assertResponseFn: func(t *testing.T, request message.LeafsRequest, response []byte, err error) { @@ -365,10 +381,11 @@ func TestLeafsRequestHandler_OnLeafsRequest(t *testing.T) { "partial end range": { prepareTestFn: func() (context.Context, message.LeafsRequest) { return context.Background(), message.LeafsRequest{ - Root: largeTrieRoot, - Start: largeTrieKeys[9_400], - End: bytes.Repeat([]byte{0xff}, common.HashLength), - Limit: maxLeavesLimit, + Root: largeTrieRoot, + Start: largeTrieKeys[9_400], + End: bytes.Repeat([]byte{0xff}, common.HashLength), + Limit: maxLeavesLimit, + NodeType: message.StateTrieNode, } }, assertResponseFn: func(t *testing.T, request message.LeafsRequest, response []byte, err error) { @@ -386,10 +403,11 @@ func TestLeafsRequestHandler_OnLeafsRequest(t *testing.T) { "final end range": { prepareTestFn: func() (context.Context, message.LeafsRequest) { return context.Background(), message.LeafsRequest{ - Root: largeTrieRoot, - Start: bytes.Repeat([]byte{0xff}, common.HashLength), - End: bytes.Repeat([]byte{0xff}, common.HashLength), - Limit: maxLeavesLimit, + Root: largeTrieRoot, + Start: bytes.Repeat([]byte{0xff}, common.HashLength), + End: bytes.Repeat([]byte{0xff}, common.HashLength), + Limit: maxLeavesLimit, + NodeType: message.StateTrieNode, } }, assertResponseFn: func(t *testing.T, request message.LeafsRequest, response []byte, err error) { @@ -407,10 +425,11 @@ func TestLeafsRequestHandler_OnLeafsRequest(t *testing.T) { "small trie root": { prepareTestFn: func() (context.Context, message.LeafsRequest) { return context.Background(), message.LeafsRequest{ - Root: smallTrieRoot, - Start: nil, - End: bytes.Repeat([]byte{0xff}, common.HashLength), - Limit: maxLeavesLimit, + Root: smallTrieRoot, + Start: nil, + End: bytes.Repeat([]byte{0xff}, common.HashLength), + Limit: maxLeavesLimit, + NodeType: message.StateTrieNode, } }, assertResponseFn: func(t *testing.T, request message.LeafsRequest, response []byte, err error) { @@ -437,8 +456,9 @@ func TestLeafsRequestHandler_OnLeafsRequest(t *testing.T) { } snapshotProvider.Snapshot = snap return context.Background(), message.LeafsRequest{ - Root: accountTrieRoot, - Limit: maxLeavesLimit, + Root: accountTrieRoot, + Limit: maxLeavesLimit, + NodeType: message.StateTrieNode, } }, assertResponseFn: func(t *testing.T, request message.LeafsRequest, response []byte, err error) { @@ -484,8 +504,9 @@ func TestLeafsRequestHandler_OnLeafsRequest(t *testing.T) { } return context.Background(), message.LeafsRequest{ - Root: accountTrieRoot, - Limit: maxLeavesLimit, + Root: accountTrieRoot, + Limit: maxLeavesLimit, + NodeType: message.StateTrieNode, } }, assertResponseFn: func(t *testing.T, request message.LeafsRequest, response []byte, err error) { @@ -515,9 +536,10 @@ func TestLeafsRequestHandler_OnLeafsRequest(t *testing.T) { } snapshotProvider.Snapshot = snap return context.Background(), message.LeafsRequest{ - Root: largeTrieRoot, - Account: largeStorageAccount, - Limit: maxLeavesLimit, + Root: largeTrieRoot, + Account: largeStorageAccount, + Limit: maxLeavesLimit, + NodeType: message.StateTrieNode, } }, assertResponseFn: func(t *testing.T, request message.LeafsRequest, response []byte, err error) { @@ -560,9 +582,10 @@ func TestLeafsRequestHandler_OnLeafsRequest(t *testing.T) { } return context.Background(), message.LeafsRequest{ - Root: largeTrieRoot, - Account: largeStorageAccount, - Limit: maxLeavesLimit, + Root: largeTrieRoot, + Account: largeStorageAccount, + Limit: maxLeavesLimit, + NodeType: message.StateTrieNode, } }, assertResponseFn: func(t *testing.T, request message.LeafsRequest, response []byte, err error) { @@ -600,9 +623,10 @@ func TestLeafsRequestHandler_OnLeafsRequest(t *testing.T) { rawdb.DeleteStorageSnapshot(memdb, smallStorageAccount, lastKey) return context.Background(), message.LeafsRequest{ - Root: smallTrieRoot, - Account: smallStorageAccount, - Limit: maxLeavesLimit, + Root: smallTrieRoot, + Account: smallStorageAccount, + Limit: maxLeavesLimit, + NodeType: message.StateTrieNode, } }, assertResponseFn: func(t *testing.T, request message.LeafsRequest, response []byte, err error) { @@ -635,10 +659,11 @@ func TestLeafsRequestHandler_OnLeafsRequest(t *testing.T) { rawdb.DeleteStorageSnapshot(memdb, smallStorageAccount, lastKey) return context.Background(), message.LeafsRequest{ - Root: smallTrieRoot, - Account: smallStorageAccount, - Start: lastKey[:], - Limit: maxLeavesLimit, + Root: smallTrieRoot, + Account: smallStorageAccount, + Start: lastKey[:], + Limit: maxLeavesLimit, + NodeType: message.StateTrieNode, } }, assertResponseFn: func(t *testing.T, request message.LeafsRequest, response []byte, err error) { diff --git a/sync/statesync/state_syncer.go b/sync/statesync/state_syncer.go index 0cb7c33b54..5b16574402 100644 --- a/sync/statesync/state_syncer.go +++ b/sync/statesync/state_syncer.go @@ -132,7 +132,7 @@ func (t *stateSync) onStorageTrieFinished(root common.Hash) error { return nil } -// onMainTrieFinished is called after the main trie finishes syncing. +// onMainTrieFinishes is called after the main trie finishes syncing. func (t *stateSync) onMainTrieFinished() error { t.codeSyncer.notifyAccountTrieCompleted() diff --git a/sync/statesync/trie_segments.go b/sync/statesync/trie_segments.go index 4ba43a2bc4..67e42e2ca8 100644 --- a/sync/statesync/trie_segments.go +++ b/sync/statesync/trie_segments.go @@ -12,6 +12,7 @@ import ( "github.com/ava-labs/avalanchego/utils/wrappers" "github.com/ava-labs/subnet-evm/core/rawdb" + "github.com/ava-labs/subnet-evm/plugin/evm/message" syncclient "github.com/ava-labs/subnet-evm/sync/client" "github.com/ava-labs/subnet-evm/trie" "github.com/ava-labs/subnet-evm/utils" @@ -344,6 +345,7 @@ func (t *trieSegment) String() string { func (t *trieSegment) Root() common.Hash { return t.trie.root } func (t *trieSegment) Account() common.Hash { return t.trie.account } func (t *trieSegment) End() []byte { return t.end } +func (t *trieSegment) NodeType() message.NodeType { return message.StateTrieNode } func (t *trieSegment) OnStart() (bool, error) { return t.trie.task.OnStart() } func (t *trieSegment) OnFinish(ctx context.Context) error { return t.trie.segmentFinished(ctx, t.idx) } diff --git a/tests/README.md b/tests/README.md deleted file mode 100644 index f434f45592..0000000000 --- a/tests/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# Developing with tmpnet - -The `load/` and `warp/` paths contain end-to-end (e2e) tests that use -the [tmpnet -fixture](https://github.com/ava-labs/avalanchego/blob/master/tests/fixture/tmpnet/README.md). By -default both test suites use the tmpnet fixture to create a temporary -network that exists for only the duration of their execution. - -It is possible to create a temporary network that can be reused across -test runs to minimize the setup cost involved: - -```bash -# From the root of a clone of avalanchego, build the tmpnetctl cli -$ ./scripts/build_tmpnetctl.sh - -# Start a new temporary network configured with subnet-evm's default plugin path -$ ./build/tmpnetctl start-network --avalanche-path=./build/avalanchego - -# From the root of a clone of subnet-evm, execute the warp test suite against the existing network -$ ginkgo -vv ./tests/warp -- --use-existing-network --network-dir=$HOME/.tmpnet/networks/latest - -# To stop the temporary network when no longer needed, execute the following from the root of the clone of avalanchego -$ ./build/tmpnetctl stop-network --network-dir=$HOME/.tmpnet/networks/latest -``` - -The network started by `tmpnetctl` won't come with subnets configured, -so the test suite will add them to the network the first time it -runs. Subsequent test runs will be able to reuse those subnets without -having to set them up. - -## Collection of logs and metrics - -Logs and metrics can be optionally collected for tmpnet networks and -viewed in grafana. The details of configuration and usage for -subnet-evm mirror those of avalanchego and the same -[documentation](https://github.com/ava-labs/avalanchego/blob/master/tests/fixture/tmpnet/README.md#Monitoring) -applies. diff --git a/tests/antithesis/Dockerfile.config b/tests/antithesis/Dockerfile.config deleted file mode 100644 index 6cce3bb374..0000000000 --- a/tests/antithesis/Dockerfile.config +++ /dev/null @@ -1,6 +0,0 @@ -FROM scratch AS execution - -# Copy config artifacts from the build path. For simplicity, artifacts -# are built outside of the docker image. -COPY ./build/antithesis/docker-compose.yml / -COPY ./build/antithesis/volumes /volumes diff --git a/tests/antithesis/Dockerfile.node b/tests/antithesis/Dockerfile.node deleted file mode 100644 index 67538fefad..0000000000 --- a/tests/antithesis/Dockerfile.node +++ /dev/null @@ -1,32 +0,0 @@ -# BUILDER_IMAGE_TAG should identify the builder image -ARG BUILDER_IMAGE_TAG - -# AVALANCHEGO_NODE_IMAGE needs to identify an existing avalanchego node image and should include the tag -ARG AVALANCHEGO_NODE_IMAGE - -# ============= Compilation Stage ================ -FROM antithesis-subnet-evm-builder:$BUILDER_IMAGE_TAG AS builder - -# The builder workdir will vary between instrumented and non-instrumented builders -ARG BUILDER_WORKDIR - -WORKDIR $BUILDER_WORKDIR - -# Build the VM -RUN ./scripts/build.sh /build/srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy - -# ============= Cleanup Stage ================ -FROM $AVALANCHEGO_NODE_IMAGE AS execution - -# Copy identifying information into the container. This will replace -# the avalanchego commit hash in the base image. -COPY --from=builder /build/commit_hash.txt /avalanchego/build/commit_hash.txt - -# Copy the antithesis dependencies into the container -COPY --from=builder /instrumented/symbols /symbols - -# Copy the executable into the container -COPY --from=builder /build/srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy\ - /avalanchego/build/plugins/srEXiWaHuhNyGwPUi444Tu47ZEDwxTWrbQiuD7FmgSAQ6X7Dy - -# The node image's entrypoint will be reused. diff --git a/tests/antithesis/Dockerfile.workload b/tests/antithesis/Dockerfile.workload deleted file mode 100644 index 3b6a9a2e11..0000000000 --- a/tests/antithesis/Dockerfile.workload +++ /dev/null @@ -1,28 +0,0 @@ -# BUILDER_IMAGE_TAG should identify the builder image -ARG BUILDER_IMAGE_TAG - -# AVALANCHEGO_NODE_IMAGE needs to identify an existing avalanchego node image and should include the tag -ARG AVALANCHEGO_NODE_IMAGE - -# ============= Compilation Stage ================ -FROM antithesis-subnet-evm-builder:$BUILDER_IMAGE_TAG AS builder - -# The builder workdir will vary between instrumented and non-instrumented builders -ARG BUILDER_WORKDIR - -WORKDIR $BUILDER_WORKDIR - -# Build the workload -RUN ./scripts/build_antithesis_workload.sh - -# ============= Cleanup Stage ================ -# Base the workflow on the node image to support bootstrap testing -FROM $AVALANCHEGO_NODE_IMAGE AS execution - -# The builder workdir will vary between instrumented and non-instrumented builders -ARG BUILDER_WORKDIR - -# Copy the executable into the container -COPY --from=builder $BUILDER_WORKDIR/build/workload ./workload - -CMD [ "./workload" ] diff --git a/tests/antithesis/README.md b/tests/antithesis/README.md deleted file mode 100644 index 0924b9f63d..0000000000 --- a/tests/antithesis/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Antithesis Testing - -This package supports testing with -[Antithesis](https://antithesis.com/docs/introduction/introduction.html), -a SaaS offering that enables deployment of distributed systems (such -as Avalanche) to a deterministic and simulated environment that -enables discovery and reproduction of anomalous behavior. - -See avalanchego's -[documentation](https://github.com/ava-labs/avalanchego/blob/master/tests/antithesis/README.md) -for more details. diff --git a/tests/antithesis/gencomposeconfig/main.go b/tests/antithesis/gencomposeconfig/main.go deleted file mode 100644 index 40cbf0037d..0000000000 --- a/tests/antithesis/gencomposeconfig/main.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package main - -import ( - "log" - "os" - "path/filepath" - - "github.com/ava-labs/avalanchego/tests/antithesis" - "github.com/ava-labs/avalanchego/tests/fixture/tmpnet" - - "github.com/ava-labs/subnet-evm/tests/utils" -) - -const baseImageName = "antithesis-subnet-evm" - -// Creates docker-compose.yml and its associated volumes in the target path. -func main() { - // Assume the working directory is the root of the repository - cwd, err := os.Getwd() - if err != nil { - log.Fatalf("failed to get current working directory: %s", err) - } - - genesisPath := filepath.Join(cwd, "tests/load/genesis/genesis.json") - - // Create a network with a subnet-evm subnet - network := tmpnet.LocalNetworkOrPanic() - network.Subnets = []*tmpnet.Subnet{ - utils.NewTmpnetSubnet("subnet-evm", genesisPath, utils.DefaultChainConfig, network.Nodes...), - } - - // Path to the plugin dir on subnet-evm node images that will be run by docker compose. - runtimePluginDir := "/avalanchego/build/plugins" - - if err := antithesis.GenerateComposeConfig(network, baseImageName, runtimePluginDir); err != nil { - log.Fatalf("failed to generate compose config: %v", err) - } -} diff --git a/tests/antithesis/main.go b/tests/antithesis/main.go deleted file mode 100644 index a58893a1be..0000000000 --- a/tests/antithesis/main.go +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package main - -import ( - "context" - "crypto/ecdsa" - "crypto/rand" - "fmt" - "log" - "math/big" - "path/filepath" - "time" - - "github.com/antithesishq/antithesis-sdk-go/assert" - "github.com/antithesishq/antithesis-sdk-go/lifecycle" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/stretchr/testify/require" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/tests/antithesis" - "github.com/ava-labs/avalanchego/tests/fixture/tmpnet" - - "github.com/ava-labs/subnet-evm/accounts/abi/bind" - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/ethclient" - "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/tests" - "github.com/ava-labs/subnet-evm/tests/utils" - - ago_tests "github.com/ava-labs/avalanchego/tests" - timerpkg "github.com/ava-labs/avalanchego/utils/timer" -) - -const NumKeys = 5 - -func main() { - tc := ago_tests.NewTestContext() - defer tc.Cleanup() - require := require.New(tc) - - c := antithesis.NewConfigWithSubnets( - tc, - // TODO(marun) Centralize network configuration for all test types - utils.NewTmpnetNetwork( - "antithesis-subnet-evm", - nil, - tmpnet.FlagsMap{}, - ), - func(nodes ...*tmpnet.Node) []*tmpnet.Subnet { - repoRootPath := tests.GetRepoRootPath("tests/antithesis") - genesisPath := filepath.Join(repoRootPath, "tests/load/genesis/genesis.json") - return []*tmpnet.Subnet{ - utils.NewTmpnetSubnet("subnet-evm", genesisPath, utils.DefaultChainConfig, nodes...), - } - }, - ) - ctx := ago_tests.DefaultNotifyContext(c.Duration, tc.DeferCleanup) - - require.Len(c.ChainIDs, 1) - log.Printf("CHAIN IDS: %v", c.ChainIDs) - chainID, err := ids.FromString(c.ChainIDs[0]) - require.NoError(err, "failed to parse chainID") - - genesisClient, err := ethclient.Dial(getChainURI(c.URIs[0], chainID.String())) - require.NoError(err, "failed to dial chain") - genesisKey := tmpnet.HardhatKey.ToECDSA() - genesisWorkload := &workload{ - id: 0, - client: genesisClient, - key: genesisKey, - uris: c.URIs, - } - - workloads := make([]*workload, NumKeys) - workloads[0] = genesisWorkload - - initialAmount := uint64(1_000_000_000_000_000) - for i := 1; i < NumKeys; i++ { - key, err := crypto.ToECDSA(crypto.Keccak256([]byte{uint8(i)})) - require.NoError(err, "failed to generate key") - - require.NoError(transferFunds(ctx, genesisClient, genesisKey, crypto.PubkeyToAddress(key.PublicKey), initialAmount)) - - client, err := ethclient.Dial(getChainURI(c.URIs[i%len(c.URIs)], chainID.String())) - require.NoError(err, "failed to dial chain") - - workloads[i] = &workload{ - id: i, - client: client, - key: key, - uris: c.URIs, - } - } - - lifecycle.SetupComplete(map[string]any{ - "msg": "initialized workers", - "numWorkers": NumKeys, - }) - - for _, w := range workloads[1:] { - go w.run(ctx) - } - genesisWorkload.run(ctx) -} - -type workload struct { - id int - client ethclient.Client - key *ecdsa.PrivateKey - uris []string -} - -func (w *workload) run(ctx context.Context) { - timer := timerpkg.StoppedTimer() - - tc := ago_tests.NewTestContext() - defer tc.Cleanup() - require := require.New(tc) - - balance, err := w.client.BalanceAt(ctx, crypto.PubkeyToAddress(w.key.PublicKey), nil) - require.NoError(err, "failed to fetch balance") - assert.Reachable("worker starting", map[string]any{ - "worker": w.id, - "balance": balance, - }) - - // TODO(marun) What should this value be? - txAmount := uint64(10000) - for { - // TODO(marun) Exercise a wider variety of transactions - recipientEthAddress := crypto.PubkeyToAddress(w.key.PublicKey) - err := transferFunds(ctx, w.client, w.key, recipientEthAddress, txAmount) - if err != nil { - // Log the error and continue since the problem may be - // transient. require.NoError is only for errors that should stop - // execution. - log.Printf("failed to transfer funds: %s", err) - } - - val, err := rand.Int(rand.Reader, big.NewInt(int64(time.Second))) - require.NoError(err, "failed to read randomness") - - timer.Reset(time.Duration(val.Int64())) - select { - case <-ctx.Done(): - return - case <-timer.C: - } - } -} - -func getChainURI(nodeURI string, blockchainID string) string { - return fmt.Sprintf("%s/ext/bc/%s/rpc", nodeURI, blockchainID) -} - -func transferFunds(ctx context.Context, client ethclient.Client, key *ecdsa.PrivateKey, recipientAddress common.Address, txAmount uint64) error { - chainID, err := client.ChainID(ctx) - if err != nil { - return fmt.Errorf("failed to fetch chainID: %w", err) - } - acceptedNonce, err := client.AcceptedNonceAt(ctx, crypto.PubkeyToAddress(key.PublicKey)) - if err != nil { - return fmt.Errorf("failed to fetch accepted nonce: %w", err) - } - gasTipCap, err := client.SuggestGasTipCap(ctx) - if err != nil { - return fmt.Errorf("failed to fetch suggested gas tip: %w", err) - } - gasFeeCap, err := client.EstimateBaseFee(ctx) - if err != nil { - return fmt.Errorf("failed to fetch estimated base fee: %w", err) - } - signer := types.LatestSignerForChainID(chainID) - - tx, err := types.SignNewTx(key, signer, &types.DynamicFeeTx{ - ChainID: chainID, - Nonce: acceptedNonce, - GasTipCap: gasTipCap, - GasFeeCap: gasFeeCap, - Gas: params.TxGas, - To: &recipientAddress, - Value: big.NewInt(int64(txAmount)), - }) - if err != nil { - return fmt.Errorf("failed to format transaction: %w", err) - } - - log.Printf("sending transaction with ID %s and nonce %d\n", tx.Hash(), acceptedNonce) - err = client.SendTransaction(ctx, tx) - if err != nil { - return fmt.Errorf("failed to send transaction: %w", err) - } - - log.Printf("waiting for acceptance of transaction with ID %s\n", tx.Hash()) - if _, err := bind.WaitMined(ctx, client, tx); err != nil { - return fmt.Errorf("failed to wait for receipt: %v", err) - } - log.Printf("confirmed acceptance of transaction with ID %s\n", tx.Hash()) - - return nil -} diff --git a/tests/init.go b/tests/init.go index aba9cc8372..e8c274fd20 100644 --- a/tests/init.go +++ b/tests/init.go @@ -29,9 +29,7 @@ package tests import ( "fmt" "math/big" - "os" "sort" - "strings" "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/utils" @@ -64,6 +62,7 @@ var Forks = map[string]*params.ChainConfig{ EIP150Block: big.NewInt(0), EIP155Block: big.NewInt(0), EIP158Block: big.NewInt(0), + DAOForkBlock: big.NewInt(0), ByzantiumBlock: big.NewInt(0), }, "Constantinople": { @@ -72,6 +71,7 @@ var Forks = map[string]*params.ChainConfig{ EIP150Block: big.NewInt(0), EIP155Block: big.NewInt(0), EIP158Block: big.NewInt(0), + DAOForkBlock: big.NewInt(0), ByzantiumBlock: big.NewInt(0), ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(10000000), @@ -82,6 +82,7 @@ var Forks = map[string]*params.ChainConfig{ EIP150Block: big.NewInt(0), EIP155Block: big.NewInt(0), EIP158Block: big.NewInt(0), + DAOForkBlock: big.NewInt(0), ByzantiumBlock: big.NewInt(0), ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0), @@ -92,6 +93,7 @@ var Forks = map[string]*params.ChainConfig{ EIP150Block: big.NewInt(0), EIP155Block: big.NewInt(0), EIP158Block: big.NewInt(0), + DAOForkBlock: big.NewInt(0), ByzantiumBlock: big.NewInt(0), ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0), @@ -103,6 +105,7 @@ var Forks = map[string]*params.ChainConfig{ EIP150Block: big.NewInt(0), EIP155Block: big.NewInt(0), EIP158Block: big.NewInt(0), + DAOForkBlock: big.NewInt(0), ByzantiumBlock: big.NewInt(0), ConstantinopleBlock: big.NewInt(0), PetersburgBlock: big.NewInt(0), @@ -118,6 +121,12 @@ var Forks = map[string]*params.ChainConfig{ HomesteadBlock: big.NewInt(0), EIP150Block: big.NewInt(5), }, + "HomesteadToDaoAt5": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + DAOForkBlock: big.NewInt(5), + DAOForkSupport: true, + }, "EIP158ToByzantiumAt5": { ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), @@ -156,7 +165,54 @@ var Forks = map[string]*params.ChainConfig{ PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(5), }, - "Pre-SubnetEVM": { + "ApricotPhase1": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + NetworkUpgrades: params.NetworkUpgrades{ + ApricotPhase1BlockTimestamp: utils.NewUint64(0), + }, + }, + "ApricotPhase2": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + NetworkUpgrades: params.NetworkUpgrades{ + ApricotPhase1BlockTimestamp: utils.NewUint64(0), + ApricotPhase2BlockTimestamp: utils.NewUint64(0), + }, + }, + "ApricotPhase3": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + NetworkUpgrades: params.NetworkUpgrades{ + ApricotPhase1BlockTimestamp: utils.NewUint64(0), + ApricotPhase2BlockTimestamp: utils.NewUint64(0), + ApricotPhase3BlockTimestamp: utils.NewUint64(0), + }, + }, + "ApricotPhase4": { ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), EIP150Block: big.NewInt(0), @@ -167,8 +223,14 @@ var Forks = map[string]*params.ChainConfig{ PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(0), MuirGlacierBlock: big.NewInt(0), + NetworkUpgrades: params.NetworkUpgrades{ + ApricotPhase1BlockTimestamp: utils.NewUint64(0), + ApricotPhase2BlockTimestamp: utils.NewUint64(0), + ApricotPhase3BlockTimestamp: utils.NewUint64(0), + ApricotPhase4BlockTimestamp: utils.NewUint64(0), + }, }, - "SubnetEVM": { + "ApricotPhase5": { ChainID: big.NewInt(1), HomesteadBlock: big.NewInt(0), EIP150Block: big.NewInt(0), @@ -179,7 +241,50 @@ var Forks = map[string]*params.ChainConfig{ PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(0), NetworkUpgrades: params.NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(0), + ApricotPhase1BlockTimestamp: utils.NewUint64(0), + ApricotPhase2BlockTimestamp: utils.NewUint64(0), + ApricotPhase3BlockTimestamp: utils.NewUint64(0), + ApricotPhase4BlockTimestamp: utils.NewUint64(0), + ApricotPhase5BlockTimestamp: utils.NewUint64(0), + }, + }, + "Banff": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + NetworkUpgrades: params.NetworkUpgrades{ + ApricotPhase1BlockTimestamp: utils.NewUint64(0), + ApricotPhase2BlockTimestamp: utils.NewUint64(0), + ApricotPhase3BlockTimestamp: utils.NewUint64(0), + ApricotPhase4BlockTimestamp: utils.NewUint64(0), + ApricotPhase5BlockTimestamp: utils.NewUint64(0), + BanffBlockTimestamp: utils.NewUint64(0), + }, + }, + "Cortina": { + ChainID: big.NewInt(1), + HomesteadBlock: big.NewInt(0), + EIP150Block: big.NewInt(0), + EIP155Block: big.NewInt(0), + EIP158Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + NetworkUpgrades: params.NetworkUpgrades{ + ApricotPhase1BlockTimestamp: utils.NewUint64(0), + ApricotPhase2BlockTimestamp: utils.NewUint64(0), + ApricotPhase3BlockTimestamp: utils.NewUint64(0), + ApricotPhase4BlockTimestamp: utils.NewUint64(0), + ApricotPhase5BlockTimestamp: utils.NewUint64(0), + BanffBlockTimestamp: utils.NewUint64(0), + CortinaBlockTimestamp: utils.NewUint64(0), }, }, "Durango": { @@ -193,8 +298,14 @@ var Forks = map[string]*params.ChainConfig{ PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(0), NetworkUpgrades: params.NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(0), - DurangoTimestamp: utils.NewUint64(0), + ApricotPhase1BlockTimestamp: utils.NewUint64(0), + ApricotPhase2BlockTimestamp: utils.NewUint64(0), + ApricotPhase3BlockTimestamp: utils.NewUint64(0), + ApricotPhase4BlockTimestamp: utils.NewUint64(0), + ApricotPhase5BlockTimestamp: utils.NewUint64(0), + BanffBlockTimestamp: utils.NewUint64(0), + CortinaBlockTimestamp: utils.NewUint64(0), + DurangoBlockTimestamp: utils.NewUint64(0), }, }, "Cancun": { @@ -209,8 +320,14 @@ var Forks = map[string]*params.ChainConfig{ IstanbulBlock: big.NewInt(0), CancunTime: utils.NewUint64(0), NetworkUpgrades: params.NetworkUpgrades{ - SubnetEVMTimestamp: utils.NewUint64(0), - DurangoTimestamp: utils.NewUint64(0), + ApricotPhase1BlockTimestamp: utils.NewUint64(0), + ApricotPhase2BlockTimestamp: utils.NewUint64(0), + ApricotPhase3BlockTimestamp: utils.NewUint64(0), + ApricotPhase4BlockTimestamp: utils.NewUint64(0), + ApricotPhase5BlockTimestamp: utils.NewUint64(0), + BanffBlockTimestamp: utils.NewUint64(0), + CortinaBlockTimestamp: utils.NewUint64(0), + DurangoBlockTimestamp: utils.NewUint64(0), }, }, } @@ -233,23 +350,3 @@ type UnsupportedForkError struct { func (e UnsupportedForkError) Error() string { return fmt.Sprintf("unsupported fork %q", e.Name) } - -func GetRepoRootPath(suffix string) string { - // - When executed via a test binary, the working directory will be wherever - // the binary is executed from, but scripts should require execution from - // the repo root. - // - // - When executed via ginkgo (nicer for development + supports - // parallel execution) the working directory will always be the - // target path (e.g. [repo root]./tests/warp) and getting the repo - // root will require stripping the target path suffix. - // - // TODO(marun) Avoid relying on the current working directory to find test - // dependencies by embedding data where possible (e.g. for genesis) and - // explicitly configuring paths for execution. - cwd, err := os.Getwd() - if err != nil { - panic(err) - } - return strings.TrimSuffix(cwd, suffix) -} diff --git a/tests/load/genesis/genesis.json b/tests/load/genesis/genesis.json deleted file mode 100644 index a6602d0a6a..0000000000 --- a/tests/load/genesis/genesis.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "config": { - "chainId": 99999, - "feeConfig": { - "gasLimit": 200000000, - "minBaseFee": 1000000000, - "targetGas": 400000000, - "baseFeeChangeDenominator": 48, - "minBlockGasCost": 0, - "maxBlockGasCost": 10000000, - "targetBlockRate": 2, - "blockGasCostStep": 500000 - } - }, - "alloc": { - "8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC": { - "balance": "0x52B7D2DCC80CD2E4000000" - }, - "0x0Fa8EA536Be85F32724D57A37758761B86416123": { - "balance": "0x52B7D2DCC80CD2E4000000" - } - }, - "nonce": "0x0", - "timestamp": "0x5FCB13D0", - "extraData": "0x00", - "gasLimit": "0xBEBC200", - "difficulty": "0x0", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "coinbase": "0x0000000000000000000000000000000000000000", - "number": "0x0", - "gasUsed": "0x0", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" -} diff --git a/tests/load/load_test.go b/tests/load/load_test.go deleted file mode 100644 index aa5a14783b..0000000000 --- a/tests/load/load_test.go +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package load - -import ( - "fmt" - "os" - "os/exec" - "path/filepath" - "strings" - "testing" - - ginkgo "github.com/onsi/ginkgo/v2" - - "github.com/stretchr/testify/require" - - "github.com/ethereum/go-ethereum/log" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/tests/fixture/e2e" - "github.com/ava-labs/avalanchego/tests/fixture/tmpnet" - "github.com/ava-labs/avalanchego/utils/set" - - "github.com/ava-labs/subnet-evm/tests" - "github.com/ava-labs/subnet-evm/tests/utils" -) - -const ( - // The load test requires 5 nodes - nodeCount = 5 - - subnetAName = "load-subnet-a" -) - -var ( - flagVars *e2e.FlagVars - repoRootPath = tests.GetRepoRootPath("tests/load") -) - -func init() { - // Configures flags used to configure tmpnet - flagVars = e2e.RegisterFlags() -} - -func TestE2E(t *testing.T) { - ginkgo.RunSpecs(t, "subnet-evm small load simulator test suite") -} - -var _ = ginkgo.Describe("[Load Simulator]", ginkgo.Ordered, func() { - require := require.New(ginkgo.GinkgoT()) - - var env *e2e.TestEnvironment - - ginkgo.BeforeAll(func() { - tc := e2e.NewTestContext() - genesisPath := filepath.Join(repoRootPath, "tests/load/genesis/genesis.json") - - nodes := utils.NewTmpnetNodes(nodeCount) - env = e2e.NewTestEnvironment( - tc, - flagVars, - utils.NewTmpnetNetwork( - "subnet-evm-small-load", - nodes, - tmpnet.FlagsMap{}, - utils.NewTmpnetSubnet(subnetAName, genesisPath, utils.DefaultChainConfig, nodes...), - ), - ) - }) - - ginkgo.It("basic subnet load test", ginkgo.Label("load"), func() { - network := env.GetNetwork() - - subnet := network.GetSubnet(subnetAName) - require.NotNil(subnet) - blockchainID := subnet.Chains[0].ChainID - - nodeURIs := tmpnet.GetNodeURIs(network.Nodes) - validatorIDs := set.NewSet[ids.NodeID](len(subnet.ValidatorIDs)) - validatorIDs.Add(subnet.ValidatorIDs...) - rpcEndpoints := make([]string, 0, len(nodeURIs)) - for _, nodeURI := range nodeURIs { - if !validatorIDs.Contains(nodeURI.NodeID) { - continue - } - rpcEndpoints = append(rpcEndpoints, fmt.Sprintf("%s/ext/bc/%s/rpc", nodeURI.URI, blockchainID)) - } - commaSeparatedRPCEndpoints := strings.Join(rpcEndpoints, ",") - err := os.Setenv("RPC_ENDPOINTS", commaSeparatedRPCEndpoints) - require.NoError(err) - - log.Info("Running load simulator...", "rpcEndpoints", commaSeparatedRPCEndpoints) - cmd := exec.Command("./scripts/run_simulator.sh") - cmd.Dir = repoRootPath - log.Info("Running load simulator script", "cmd", cmd.String()) - - out, err := cmd.CombinedOutput() - fmt.Printf("\nCombined output:\n\n%s\n", string(out)) - require.NoError(err) - }) -}) diff --git a/tests/precompile/genesis/contract_deployer_allow_list.json b/tests/precompile/genesis/contract_deployer_allow_list.json deleted file mode 100644 index 63ed69fec1..0000000000 --- a/tests/precompile/genesis/contract_deployer_allow_list.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "config": { - "chainId": 99999, - "homesteadBlock": 0, - "eip150Block": 0, - "eip155Block": 0, - "eip158Block": 0, - "byzantiumBlock": 0, - "constantinopleBlock": 0, - "petersburgBlock": 0, - "istanbulBlock": 0, - "muirGlacierBlock": 0, - "feeConfig": { - "gasLimit": 20000000, - "minBaseFee": 1000000000, - "targetGas": 100000000, - "baseFeeChangeDenominator": 48, - "minBlockGasCost": 0, - "maxBlockGasCost": 10000000, - "targetBlockRate": 2, - "blockGasCostStep": 500000 - }, - "contractDeployerAllowListConfig": { - "blockTimestamp": 0, - "adminAddresses": [ - "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" - ] - } - }, - "alloc": { - "8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC": { - "balance": "0x52B7D2DCC80CD2E4000000" - }, - "0x0Fa8EA536Be85F32724D57A37758761B86416123": { - "balance": "0x52B7D2DCC80CD2E4000000" - } - }, - "nonce": "0x0", - "timestamp": "0x5FCB13D0", - "extraData": "0x00", - "gasLimit": "0x1312D00", - "difficulty": "0x0", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "coinbase": "0x0000000000000000000000000000000000000000", - "number": "0x0", - "gasUsed": "0x0", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" -} diff --git a/tests/precompile/genesis/contract_native_minter.json b/tests/precompile/genesis/contract_native_minter.json deleted file mode 100644 index c44be05d97..0000000000 --- a/tests/precompile/genesis/contract_native_minter.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "config": { - "chainId": 99999, - "homesteadBlock": 0, - "eip150Block": 0, - "eip155Block": 0, - "eip158Block": 0, - "byzantiumBlock": 0, - "constantinopleBlock": 0, - "petersburgBlock": 0, - "istanbulBlock": 0, - "muirGlacierBlock": 0, - "feeConfig": { - "gasLimit": 20000000, - "minBaseFee": 1000000000, - "targetGas": 100000000, - "baseFeeChangeDenominator": 48, - "minBlockGasCost": 0, - "maxBlockGasCost": 10000000, - "targetBlockRate": 1, - "blockGasCostStep": 500000 - }, - "contractNativeMinterConfig": { - "blockTimestamp": 0, - "adminAddresses": [ - "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" - ] - } - }, - "alloc": { - "8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC": { - "balance": "0x52B7D2DCC80CD2E4000000" - }, - "0x0Fa8EA536Be85F32724D57A37758761B86416123": { - "balance": "0x52B7D2DCC80CD2E4000000" - } - }, - "nonce": "0x0", - "timestamp": "0x5FCB13D0", - "extraData": "0x00", - "gasLimit": "0x1312D00", - "difficulty": "0x0", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "coinbase": "0x0000000000000000000000000000000000000000", - "number": "0x0", - "gasUsed": "0x0", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" -} diff --git a/tests/precompile/genesis/fee_manager.json b/tests/precompile/genesis/fee_manager.json deleted file mode 100644 index e5e6b68722..0000000000 --- a/tests/precompile/genesis/fee_manager.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "config": { - "chainId": 99999, - "homesteadBlock": 0, - "eip150Block": 0, - "eip155Block": 0, - "eip158Block": 0, - "byzantiumBlock": 0, - "constantinopleBlock": 0, - "petersburgBlock": 0, - "istanbulBlock": 0, - "muirGlacierBlock": 0, - "feeConfig": { - "gasLimit": 20000000, - "minBaseFee": 1000000000, - "targetGas": 100000000, - "baseFeeChangeDenominator": 48, - "minBlockGasCost": 0, - "maxBlockGasCost": 10000000, - "targetBlockRate": 1, - "blockGasCostStep": 500000 - }, - "feeManagerConfig": { - "blockTimestamp": 0, - "adminAddresses": [ - "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" - ] - } - }, - "alloc": { - "8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC": { - "balance": "0x52B7D2DCC80CD2E4000000" - }, - "0x0Fa8EA536Be85F32724D57A37758761B86416123": { - "balance": "0x52B7D2DCC80CD2E4000000" - } - }, - "nonce": "0x0", - "timestamp": "0x5FCB13D0", - "extraData": "0x00", - "gasLimit": "0x1312D00", - "difficulty": "0x0", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "coinbase": "0x0000000000000000000000000000000000000000", - "number": "0x0", - "gasUsed": "0x0", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" -} diff --git a/tests/precompile/genesis/reward_manager.json b/tests/precompile/genesis/reward_manager.json deleted file mode 100644 index ac6c314b06..0000000000 --- a/tests/precompile/genesis/reward_manager.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "config": { - "chainId": 99999, - "homesteadBlock": 0, - "eip150Block": 0, - "eip155Block": 0, - "eip158Block": 0, - "byzantiumBlock": 0, - "constantinopleBlock": 0, - "petersburgBlock": 0, - "istanbulBlock": 0, - "muirGlacierBlock": 0, - "feeConfig": { - "gasLimit": 20000000, - "minBaseFee": 1000000000, - "targetGas": 100000000, - "baseFeeChangeDenominator": 48, - "minBlockGasCost": 0, - "maxBlockGasCost": 10000000, - "targetBlockRate": 1, - "blockGasCostStep": 500000 - }, - "rewardManagerConfig": { - "blockTimestamp": 0, - "adminAddresses": [ - "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" - ] - } - }, - "alloc": { - "8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC": { - "balance": "0x52B7D2DCC80CD2E4000000" - }, - "0x0Fa8EA536Be85F32724D57A37758761B86416123": { - "balance": "0x52B7D2DCC80CD2E4000000" - } - }, - "nonce": "0x0", - "timestamp": "0x5FCB13D0", - "extraData": "0x00", - "gasLimit": "0x1312D00", - "difficulty": "0x0", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "coinbase": "0x0000000000000000000000000000000000000000", - "number": "0x0", - "gasUsed": "0x0", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" -} diff --git a/tests/precompile/genesis/tx_allow_list.json b/tests/precompile/genesis/tx_allow_list.json deleted file mode 100644 index a2a1aaeb18..0000000000 --- a/tests/precompile/genesis/tx_allow_list.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "config": { - "chainId": 99999, - "homesteadBlock": 0, - "eip150Block": 0, - "eip155Block": 0, - "eip158Block": 0, - "byzantiumBlock": 0, - "constantinopleBlock": 0, - "petersburgBlock": 0, - "istanbulBlock": 0, - "muirGlacierBlock": 0, - "feeConfig": { - "gasLimit": 20000000, - "minBaseFee": 1000000000, - "targetGas": 100000000, - "baseFeeChangeDenominator": 48, - "minBlockGasCost": 0, - "maxBlockGasCost": 10000000, - "targetBlockRate": 1, - "blockGasCostStep": 500000 - }, - "txAllowListConfig": { - "blockTimestamp": 0, - "adminAddresses": [ - "0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC" - ] - } - }, - "alloc": { - "8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC": { - "balance": "0x52B7D2DCC80CD2E4000000" - }, - "0Fa8EA536Be85F32724D57A37758761B86416123": { - "balance": "0x52B7D2DCC80CD2E4000000" - } - }, - "nonce": "0x0", - "timestamp": "0x5FCB13D0", - "extraData": "0x00", - "gasLimit": "0x1312D00", - "difficulty": "0x0", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "coinbase": "0x0000000000000000000000000000000000000000", - "number": "0x0", - "gasUsed": "0x0", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" -} diff --git a/tests/precompile/genesis/warp.json b/tests/precompile/genesis/warp.json deleted file mode 100644 index e4c17d05f0..0000000000 --- a/tests/precompile/genesis/warp.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "config": { - "chainId": 99999, - "homesteadBlock": 0, - "eip150Block": 0, - "eip155Block": 0, - "eip158Block": 0, - "byzantiumBlock": 0, - "constantinopleBlock": 0, - "petersburgBlock": 0, - "istanbulBlock": 0, - "muirGlacierBlock": 0, - "feeConfig": { - "gasLimit": 20000000, - "minBaseFee": 1000000000, - "targetGas": 100000000, - "baseFeeChangeDenominator": 48, - "minBlockGasCost": 0, - "maxBlockGasCost": 10000000, - "targetBlockRate": 2, - "blockGasCostStep": 500000 - }, - "warpConfig": { - "blockTimestamp": 1607144400 - } - }, - "alloc": { - "8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC": { - "balance": "0x52B7D2DCC80CD2E4000000" - }, - "0x0Fa8EA536Be85F32724D57A37758761B86416123": { - "balance": "0x52B7D2DCC80CD2E4000000" - } - }, - "nonce": "0x0", - "timestamp": "0x5FCB13D0", - "extraData": "0x00", - "gasLimit": "0x1312D00", - "difficulty": "0x0", - "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", - "coinbase": "0x0000000000000000000000000000000000000000", - "number": "0x0", - "gasUsed": "0x0", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" -} diff --git a/tests/precompile/precompile_test.go b/tests/precompile/precompile_test.go deleted file mode 100644 index fbfb1c9644..0000000000 --- a/tests/precompile/precompile_test.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package precompile - -import ( - "os" - "testing" - - ginkgo "github.com/onsi/ginkgo/v2" - - // Import the solidity package, so that ginkgo maps out the tests declared within the package - "github.com/ava-labs/subnet-evm/tests/precompile/solidity" -) - -func TestE2E(t *testing.T) { - if basePath := os.Getenv("TEST_SOURCE_ROOT"); basePath != "" { - os.Chdir(basePath) - } - solidity.RegisterAsyncTests() - ginkgo.RunSpecs(t, "subnet-evm precompile ginkgo test suite") -} diff --git a/tests/precompile/solidity/suites.go b/tests/precompile/solidity/suites.go deleted file mode 100644 index 4aacb83c4b..0000000000 --- a/tests/precompile/solidity/suites.go +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (C) 2019-2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -// Implements solidity tests. -package solidity - -import ( - "context" - "fmt" - "time" - - "github.com/ava-labs/subnet-evm/tests/utils" - ginkgo "github.com/onsi/ginkgo/v2" -) - -// Registers the Asynchronized Precompile Tests -// Before running the tests, this function creates all subnets given in the genesis files -// and then runs the hardhat tests for each one asynchronously if called with `ginkgo run -procs=`. -func RegisterAsyncTests() { - // Tests here assumes that the genesis files are in ./tests/precompile/genesis/ - // with the name {precompile_name}.json - genesisFiles, err := utils.GetFilesAndAliases("./tests/precompile/genesis/*.json") - if err != nil { - ginkgo.AbortSuite("Failed to get genesis files: " + err.Error()) - } - if len(genesisFiles) == 0 { - ginkgo.AbortSuite("No genesis files found") - } - subnetsSuite := utils.CreateSubnetsSuite(genesisFiles) - - var _ = ginkgo.Describe("[Asynchronized Precompile Tests]", func() { - // Register the ping test first - utils.RegisterPingTest() - - // Each ginkgo It node specifies the name of the genesis file (in ./tests/precompile/genesis/) - // to use to launch the subnet and the name of the TS test file to run on the subnet (in ./contracts/tests/) - ginkgo.It("contract native minter", ginkgo.Label("Precompile"), ginkgo.Label("ContractNativeMinter"), func() { - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - - blockchainID := subnetsSuite.GetBlockchainID("contract_native_minter") - runDefaultHardhatTests(ctx, blockchainID, "contract_native_minter") - }) - - ginkgo.It("tx allow list", ginkgo.Label("Precompile"), ginkgo.Label("TxAllowList"), func() { - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - - blockchainID := subnetsSuite.GetBlockchainID("tx_allow_list") - runDefaultHardhatTests(ctx, blockchainID, "tx_allow_list") - }) - - ginkgo.It("contract deployer allow list", ginkgo.Label("Precompile"), ginkgo.Label("ContractDeployerAllowList"), func() { - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - - blockchainID := subnetsSuite.GetBlockchainID("contract_deployer_allow_list") - runDefaultHardhatTests(ctx, blockchainID, "contract_deployer_allow_list") - }) - - ginkgo.It("fee manager", ginkgo.Label("Precompile"), ginkgo.Label("FeeManager"), func() { - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - - blockchainID := subnetsSuite.GetBlockchainID("fee_manager") - runDefaultHardhatTests(ctx, blockchainID, "fee_manager") - }) - - ginkgo.It("reward manager", ginkgo.Label("Precompile"), ginkgo.Label("RewardManager"), func() { - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - - blockchainID := subnetsSuite.GetBlockchainID("reward_manager") - runDefaultHardhatTests(ctx, blockchainID, "reward_manager") - }) - - // ADD YOUR PRECOMPILE HERE - /* - ginkgo.It("your precompile", ginkgo.Label("Precompile"), ginkgo.Label("YourPrecompile"), func() { - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - - // Specify the name shared by the genesis file in ./tests/precompile/genesis/{your_precompile}.json - // and the test file in ./contracts/tests/{your_precompile}.ts - // If you want to use a different test command and genesis path than the defaults, you can - // use the utils.RunTestCMD. See utils.RunDefaultHardhatTests for an example. - subnetsSuite.RunHardhatTests(ctx, "your_precompile") - }) - */ - }) -} - -// Default parameters are: -// -// 1. Hardhat contract environment is located at ./contracts -// 2. Hardhat test file is located at ./contracts/test/.ts -// 3. npx is available in the ./contracts directory -func runDefaultHardhatTests(ctx context.Context, blockchainID, testName string) { - cmdPath := "./contracts" - // test path is relative to the cmd path - testPath := fmt.Sprintf("./test/%s.ts", testName) - utils.RunHardhatTests(ctx, blockchainID, cmdPath, testPath) -} diff --git a/tests/state_test_util.go b/tests/state_test_util.go index a31ea88a95..6d52c9cb40 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -27,290 +27,18 @@ package tests import ( - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "math/big" - "strconv" - "strings" - "github.com/ava-labs/subnet-evm/core" "github.com/ava-labs/subnet-evm/core/rawdb" "github.com/ava-labs/subnet-evm/core/state" "github.com/ava-labs/subnet-evm/core/state/snapshot" "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/core/vm" - "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/trie" "github.com/ava-labs/subnet-evm/trie/triedb/hashdb" "github.com/ava-labs/subnet-evm/trie/triedb/pathdb" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/common/math" - "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/rlp" - "golang.org/x/crypto/sha3" ) -// StateTest checks transaction processing without block context. -// See https://github.com/ethereum/EIPs/issues/176 for the test format specification. -type StateTest struct { - json stJSON -} - -// StateSubtest selects a specific configuration of a General State Test. -type StateSubtest struct { - Fork string - Index int -} - -func (t *StateTest) UnmarshalJSON(in []byte) error { - return json.Unmarshal(in, &t.json) -} - -type stJSON struct { - Env stEnv `json:"env"` - Pre core.GenesisAlloc `json:"pre"` - Tx stTransaction `json:"transaction"` - Out hexutil.Bytes `json:"out"` - Post map[string][]stPostState `json:"post"` -} - -type stPostState struct { - Root common.UnprefixedHash `json:"hash"` - Logs common.UnprefixedHash `json:"logs"` - TxBytes hexutil.Bytes `json:"txbytes"` - ExpectException string `json:"expectException"` - Indexes struct { - Data int `json:"data"` - Gas int `json:"gas"` - Value int `json:"value"` - } -} - -//go:generate go run github.com/fjl/gencodec -type stEnv -field-override stEnvMarshaling -out gen_stenv.go -type stEnv struct { - Coinbase common.Address `json:"currentCoinbase" gencodec:"required"` - Difficulty *big.Int `json:"currentDifficulty" gencodec:"required"` - Random *big.Int `json:"currentRandom" gencodec:"optional"` - GasLimit uint64 `json:"currentGasLimit" gencodec:"required"` - Number uint64 `json:"currentNumber" gencodec:"required"` - Timestamp uint64 `json:"currentTimestamp" gencodec:"required"` - BaseFee *big.Int `json:"currentBaseFee" gencodec:"optional"` -} - -//go:generate go run github.com/fjl/gencodec -type stTransaction -field-override stTransactionMarshaling -out gen_sttransaction.go -type stTransaction struct { - GasPrice *big.Int `json:"gasPrice"` - MaxFeePerGas *big.Int `json:"maxFeePerGas"` - MaxPriorityFeePerGas *big.Int `json:"maxPriorityFeePerGas"` - Nonce uint64 `json:"nonce"` - To string `json:"to"` - Data []string `json:"data"` - AccessLists []*types.AccessList `json:"accessLists,omitempty"` - GasLimit []uint64 `json:"gasLimit"` - Value []string `json:"value"` - PrivateKey []byte `json:"secretKey"` - Sender *common.Address `json:"sender"` - BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"` - BlobGasFeeCap *big.Int `json:"maxFeePerBlobGas,omitempty"` -} - -// nolint: unused -type stTransactionMarshaling struct { - GasPrice *math.HexOrDecimal256 - MaxFeePerGas *math.HexOrDecimal256 - MaxPriorityFeePerGas *math.HexOrDecimal256 - Nonce math.HexOrDecimal64 - GasLimit []math.HexOrDecimal64 - PrivateKey hexutil.Bytes - BlobGasFeeCap *math.HexOrDecimal256 -} - -// GetChainConfig takes a fork definition and returns a chain config. -// The fork definition can be -// - a plain forkname, e.g. `Byzantium`, -// - a fork basename, and a list of EIPs to enable; e.g. `Byzantium+1884+1283`. -func GetChainConfig(forkString string) (baseConfig *params.ChainConfig, eips []int, err error) { - var ( - splitForks = strings.Split(forkString, "+") - ok bool - baseName, eipsStrings = splitForks[0], splitForks[1:] - ) - - // NOTE: this is added to support mapping geth fork names to - // subnet-evm fork names. - forkAliases := map[string]string{ - "Berlin": "Pre-SubnetEVM", - "London": "SubnetEVM", - "ArrowGlacier": "SubnetEVM", - "GrayGlacier": "SubnetEVM", - "Merge": "SubnetEVM", - } - if alias, ok := forkAliases[baseName]; ok { - baseName = alias - } - - if baseConfig, ok = Forks[baseName]; !ok { - return nil, nil, UnsupportedForkError{baseName} - } - for _, eip := range eipsStrings { - if eipNum, err := strconv.Atoi(eip); err != nil { - return nil, nil, fmt.Errorf("syntax error, invalid eip number %v", eipNum) - } else { - if !vm.ValidEip(eipNum) { - return nil, nil, fmt.Errorf("syntax error, invalid eip number %v", eipNum) - } - eips = append(eips, eipNum) - } - } - return baseConfig, eips, nil -} - -// Subtests returns all valid subtests of the test. -func (t *StateTest) Subtests() []StateSubtest { - var sub []StateSubtest - for fork, pss := range t.json.Post { - for i := range pss { - sub = append(sub, StateSubtest{fork, i}) - } - } - return sub -} - -// checkError checks if the error returned by the state transition matches any expected error. -// A failing expectation returns a wrapped version of the original error, if any, -// or a new error detailing the failing expectation. -// This function does not return or modify the original error, it only evaluates and returns expectations for the error. -func (t *StateTest) checkError(subtest StateSubtest, err error) error { - expectedError := t.json.Post[subtest.Fork][subtest.Index].ExpectException - if err == nil && expectedError == "" { - return nil - } - if err == nil && expectedError != "" { - return fmt.Errorf("expected error %q, got no error", expectedError) - } - if err != nil && expectedError == "" { - return fmt.Errorf("unexpected error: %w", err) - } - if err != nil && expectedError != "" { - // Ignore expected errors (TODO MariusVanDerWijden check error string) - return nil - } - return nil -} - -// Run executes a specific subtest and verifies the post-state and logs -func (t *StateTest) Run(subtest StateSubtest, vmconfig vm.Config, snapshotter bool, scheme string, postCheck func(err error, snaps *snapshot.Tree, state *state.StateDB)) (result error) { - triedb, snaps, statedb, root, err := t.RunNoVerify(subtest, vmconfig, snapshotter, scheme) - - // Invoke the callback at the end of function for further analysis. - defer func() { - postCheck(result, snaps, statedb) - - if triedb != nil { - triedb.Close() - } - if snaps != nil { - snaps.Release() - } - }() - checkedErr := t.checkError(subtest, err) - if checkedErr != nil { - return checkedErr - } - // The error has been checked; if it was unexpected, it's already returned. - if err != nil { - // Here, an error exists but it was expected. - // We do not check the post state or logs. - return nil - } - post := t.json.Post[subtest.Fork][subtest.Index] - // N.B: We need to do this in a two-step process, because the first Commit takes care - // of self-destructs, and we need to touch the coinbase _after_ it has potentially self-destructed. - if root != common.Hash(post.Root) { - return fmt.Errorf("post state root mismatch: got %x, want %x", root, post.Root) - } - if logs := rlpHash(statedb.Logs()); logs != common.Hash(post.Logs) { - return fmt.Errorf("post state logs hash mismatch: got %x, want %x", logs, post.Logs) - } - statedb, _ = state.New(root, statedb.Database(), snaps) - return nil -} - -// RunNoVerify runs a specific subtest and returns the statedb and post-state root -func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapshotter bool, scheme string) (*trie.Database, *snapshot.Tree, *state.StateDB, common.Hash, error) { - config, eips, err := GetChainConfig(subtest.Fork) - if err != nil { - return nil, nil, nil, common.Hash{}, UnsupportedForkError{subtest.Fork} - } - vmconfig.ExtraEips = eips - - block := t.genesis(config).ToBlock() - triedb, snaps, statedb := MakePreState(rawdb.NewMemoryDatabase(), t.json.Pre, snapshotter, scheme) - - var baseFee *big.Int - if config.IsSubnetEVM(0) { - baseFee = t.json.Env.BaseFee - if baseFee == nil { - // Retesteth uses `0x10` for genesis baseFee. Therefore, it defaults to - // parent - 2 : 0xa as the basefee for 'this' context. - baseFee = big.NewInt(0x0a) - } - } - post := t.json.Post[subtest.Fork][subtest.Index] - msg, err := t.json.Tx.toMessage(post, baseFee) - if err != nil { - triedb.Close() - return nil, nil, nil, common.Hash{}, err - } - - // Try to recover tx with current signer - if len(post.TxBytes) != 0 { - var ttx types.Transaction - err := ttx.UnmarshalBinary(post.TxBytes) - if err != nil { - triedb.Close() - return nil, nil, nil, common.Hash{}, err - } - - if _, err := types.Sender(types.LatestSigner(config), &ttx); err != nil { - triedb.Close() - return nil, nil, nil, common.Hash{}, err - } - } - - // Prepare the EVM. - txContext := core.NewEVMTxContext(msg) - context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase) - context.GetHash = vmTestBlockHash - context.BaseFee = baseFee - if config.IsSubnetEVM(0) && t.json.Env.Random != nil { - context.Difficulty = big.NewInt(0) - } - evm := vm.NewEVM(context, txContext, statedb, config, vmconfig) - - // Execute the message. - snapshot := statedb.Snapshot() - gaspool := new(core.GasPool) - gaspool.AddGas(block.GasLimit()) - _, err = core.ApplyMessage(evm, msg, gaspool) - if err != nil { - statedb.RevertToSnapshot(snapshot) - } - // Add 0-value mining reward. This only makes a difference in the cases - // where - // - the coinbase self-destructed, or - // - there are only 'bad' transactions, which aren't executed. In those cases, - // the coinbase gets no txfee, so isn't created, and thus needs to be touched - statedb.AddBalance(block.Coinbase(), new(big.Int)) - // Commit block - root, _ := statedb.Commit(block.NumberU64(), config.IsEIP158(block.Number()), false) - return triedb, snaps, statedb, root, err -} - func MakePreState(db ethdb.Database, accounts core.GenesisAlloc, snapshotter bool, scheme string) (*trie.Database, *snapshot.Tree, *state.StateDB) { tconf := &trie.Config{Preimages: true} if scheme == rawdb.HashScheme { @@ -345,120 +73,3 @@ func MakePreState(db ethdb.Database, accounts core.GenesisAlloc, snapshotter boo statedb, _ = state.New(root, sdb, snaps) return triedb, snaps, statedb } - -func (t *StateTest) genesis(config *params.ChainConfig) *core.Genesis { - genesis := &core.Genesis{ - Config: config, - Coinbase: t.json.Env.Coinbase, - Difficulty: t.json.Env.Difficulty, - GasLimit: t.json.Env.GasLimit, - Number: t.json.Env.Number, - Timestamp: t.json.Env.Timestamp, - Alloc: t.json.Pre, - } - if t.json.Env.Random != nil { - // Post-Merge - genesis.Mixhash = common.BigToHash(t.json.Env.Random) - genesis.Difficulty = big.NewInt(0) - } - return genesis -} - -func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (*core.Message, error) { - var from common.Address - // If 'sender' field is present, use that - if tx.Sender != nil { - from = *tx.Sender - } else if len(tx.PrivateKey) > 0 { - // Derive sender from private key if needed. - key, err := crypto.ToECDSA(tx.PrivateKey) - if err != nil { - return nil, fmt.Errorf("invalid private key: %v", err) - } - from = crypto.PubkeyToAddress(key.PublicKey) - } - // Parse recipient if present. - var to *common.Address - if tx.To != "" { - to = new(common.Address) - if err := to.UnmarshalText([]byte(tx.To)); err != nil { - return nil, fmt.Errorf("invalid to address: %v", err) - } - } - - // Get values specific to this post state. - if ps.Indexes.Data > len(tx.Data) { - return nil, fmt.Errorf("tx data index %d out of bounds", ps.Indexes.Data) - } - if ps.Indexes.Value > len(tx.Value) { - return nil, fmt.Errorf("tx value index %d out of bounds", ps.Indexes.Value) - } - if ps.Indexes.Gas > len(tx.GasLimit) { - return nil, fmt.Errorf("tx gas limit index %d out of bounds", ps.Indexes.Gas) - } - dataHex := tx.Data[ps.Indexes.Data] - valueHex := tx.Value[ps.Indexes.Value] - gasLimit := tx.GasLimit[ps.Indexes.Gas] - // Value, Data hex encoding is messy: https://github.com/ethereum/tests/issues/203 - value := new(big.Int) - if valueHex != "0x" { - v, ok := math.ParseBig256(valueHex) - if !ok { - return nil, fmt.Errorf("invalid tx value %q", valueHex) - } - value = v - } - data, err := hex.DecodeString(strings.TrimPrefix(dataHex, "0x")) - if err != nil { - return nil, fmt.Errorf("invalid tx data %q", dataHex) - } - var accessList types.AccessList - if tx.AccessLists != nil && tx.AccessLists[ps.Indexes.Data] != nil { - accessList = *tx.AccessLists[ps.Indexes.Data] - } - // If baseFee provided, set gasPrice to effectiveGasPrice. - gasPrice := tx.GasPrice - if baseFee != nil { - if tx.MaxFeePerGas == nil { - tx.MaxFeePerGas = gasPrice - } - if tx.MaxFeePerGas == nil { - tx.MaxFeePerGas = new(big.Int) - } - if tx.MaxPriorityFeePerGas == nil { - tx.MaxPriorityFeePerGas = tx.MaxFeePerGas - } - gasPrice = math.BigMin(new(big.Int).Add(tx.MaxPriorityFeePerGas, baseFee), - tx.MaxFeePerGas) - } - if gasPrice == nil { - return nil, errors.New("no gas price provided") - } - - msg := &core.Message{ - From: from, - To: to, - Nonce: tx.Nonce, - Value: value, - GasLimit: gasLimit, - GasPrice: gasPrice, - GasFeeCap: tx.MaxFeePerGas, - GasTipCap: tx.MaxPriorityFeePerGas, - Data: data, - AccessList: accessList, - BlobHashes: tx.BlobVersionedHashes, - BlobGasFeeCap: tx.BlobGasFeeCap, - } - return msg, nil -} - -func rlpHash(x interface{}) (h common.Hash) { - hw := sha3.NewLegacyKeccak256() - rlp.Encode(hw, x) - hw.Sum(h[:0]) - return h -} - -func vmTestBlockHash(n uint64) common.Hash { - return common.BytesToHash(crypto.Keccak256([]byte(big.NewInt(int64(n)).String()))) -} diff --git a/tests/utils/command.go b/tests/utils/command.go deleted file mode 100644 index 33e674e8ae..0000000000 --- a/tests/utils/command.go +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (C) 2019-2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package utils - -import ( - "context" - "fmt" - "os" - "os/exec" - "strings" - "time" - - "github.com/ava-labs/avalanchego/api/health" - "github.com/ethereum/go-ethereum/log" - "github.com/go-cmd/cmd" - "github.com/onsi/ginkgo/v2" - "github.com/stretchr/testify/require" -) - -// RunCommand starts the command [bin] with the given [args] and returns the command to the caller -// TODO cmd package mentions we can do this more efficiently with cmd.NewCmdOptions rather than looping -// and calling Status(). -func RunCommand(bin string, args ...string) (*cmd.Cmd, error) { - log.Info("Executing", "cmd", fmt.Sprintf("%s %s", bin, strings.Join(args, " "))) - - curCmd := cmd.NewCmd(bin, args...) - _ = curCmd.Start() - - // to stream outputs - ticker := time.NewTicker(10 * time.Millisecond) - go func() { - prevLine := "" - for range ticker.C { - status := curCmd.Status() - n := len(status.Stdout) - if n == 0 { - continue - } - - line := status.Stdout[n-1] - if prevLine != line && line != "" { - fmt.Println("[streaming output]", line) - } - - prevLine = line - } - }() - - return curCmd, nil -} - -func RegisterPingTest() { - require := require.New(ginkgo.GinkgoT()) - - ginkgo.It("ping the network", ginkgo.Label("ping"), func() { - client := health.NewClient(DefaultLocalNodeURI) - healthy, err := client.Readiness(context.Background(), nil) - require.NoError(err) - require.True(healthy.Healthy) - }) -} - -// RegisterNodeRun registers a before suite that starts an AvalancheGo process to use for the e2e tests -// and an after suite that stops the AvalancheGo process -func RegisterNodeRun() { - require := require.New(ginkgo.GinkgoT()) - - // BeforeSuite starts an AvalancheGo process to use for the e2e tests - var startCmd *cmd.Cmd - _ = ginkgo.BeforeSuite(func() { - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) - defer cancel() - - wd, err := os.Getwd() - require.NoError(err) - log.Info("Starting AvalancheGo node", "wd", wd) - cmd, err := RunCommand("./scripts/run.sh") - startCmd = cmd - require.NoError(err) - - // Assumes that startCmd will launch a node with HTTP Port at [utils.DefaultLocalNodeURI] - healthClient := health.NewClient(DefaultLocalNodeURI) - healthy, err := health.AwaitReady(ctx, healthClient, HealthCheckTimeout, nil) - require.NoError(err) - require.True(healthy) - log.Info("AvalancheGo node is healthy") - }) - - ginkgo.AfterSuite(func() { - require.NotNil(startCmd) - require.NoError(startCmd.Stop()) - // TODO add a new node to bootstrap off of the existing node and ensure it can bootstrap all subnets - // created during the test - }) -} - -// RunHardhatTests runs the hardhat tests in the given [testPath] on the blockchain with [blockchainID] -// [execPath] is the path where the test command is executed -func RunHardhatTests(ctx context.Context, blockchainID string, execPath string, testPath string) { - chainURI := GetDefaultChainURI(blockchainID) - RunHardhatTestsCustomURI(ctx, chainURI, execPath, testPath) -} - -func RunHardhatTestsCustomURI(ctx context.Context, chainURI string, execPath string, testPath string) { - require := require.New(ginkgo.GinkgoT()) - - log.Info( - "Executing HardHat tests on blockchain", - "testPath", testPath, - "ChainURI", chainURI, - ) - - cmd := exec.Command("npx", "hardhat", "test", testPath, "--network", "local") - cmd.Dir = execPath - - log.Info("Sleeping to wait for test ping", "rpcURI", chainURI) - err := os.Setenv("RPC_URI", chainURI) - require.NoError(err) - log.Info("Running test command", "cmd", cmd.String()) - - out, err := cmd.CombinedOutput() - fmt.Printf("\nCombined output:\n\n%s\n", string(out)) - require.NoError(err) -} diff --git a/tests/utils/constants.go b/tests/utils/constants.go deleted file mode 100644 index cd507eca1b..0000000000 --- a/tests/utils/constants.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (C) 2019-2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package utils - -import "time" - -const ( - // Timeout to boot the AvalancheGo node - BootAvalancheNodeTimeout = 5 * time.Minute - - // Timeout for the health API to check the AvalancheGo is ready - HealthCheckTimeout = 5 * time.Second - - DefaultLocalNodeURI = "http://127.0.0.1:9650" -) diff --git a/tests/utils/proposervm.go b/tests/utils/proposervm.go deleted file mode 100644 index d7f2a37530..0000000000 --- a/tests/utils/proposervm.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package utils - -import ( - "context" - "crypto/ecdsa" - "math/big" - - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/ethclient" - "github.com/ava-labs/subnet-evm/params" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" -) - -const numTriggerTxs = 2 // Number of txs needed to activate the proposer VM fork - -// IssueTxsToActivateProposerVMFork issues transactions at the current -// timestamp, which should be after the ProposerVM activation time (aka -// ApricotPhase4). This should generate a PostForkBlock because its parent block -// (genesis) has a timestamp (0) that is greater than or equal to the fork -// activation time of 0. Therefore, subsequent blocks should be built with -// BuildBlockWithContext. -func IssueTxsToActivateProposerVMFork( - ctx context.Context, chainID *big.Int, fundedKey *ecdsa.PrivateKey, - client ethclient.Client, -) error { - addr := crypto.PubkeyToAddress(fundedKey.PublicKey) - nonce, err := client.NonceAt(ctx, addr, nil) - if err != nil { - return err - } - - newHeads := make(chan *types.Header, 1) - sub, err := client.SubscribeNewHead(ctx, newHeads) - if err != nil { - return err - } - defer sub.Unsubscribe() - - gasPrice := big.NewInt(params.MinGasPrice) - txSigner := types.LatestSignerForChainID(chainID) - for i := 0; i < numTriggerTxs; i++ { - tx := types.NewTransaction( - nonce, addr, common.Big1, params.TxGas, gasPrice, nil) - triggerTx, err := types.SignTx(tx, txSigner, fundedKey) - if err != nil { - return err - } - if err := client.SendTransaction(ctx, triggerTx); err != nil { - return err - } - <-newHeads // wait for block to be accepted - nonce++ - } - log.Info( - "Built sufficient blocks to activate proposerVM fork", - "txCount", numTriggerTxs, - ) - return nil -} diff --git a/tests/utils/subnet.go b/tests/utils/subnet.go deleted file mode 100644 index 6f7aea3500..0000000000 --- a/tests/utils/subnet.go +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright (C) 2019-2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package utils - -import ( - "context" - "encoding/json" - "fmt" - "os" - "path/filepath" - "strings" - "sync" - "time" - - "github.com/ava-labs/avalanchego/api/health" - "github.com/ava-labs/avalanchego/api/info" - "github.com/ava-labs/avalanchego/genesis" - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/vms/secp256k1fx" - wallet "github.com/ava-labs/avalanchego/wallet/subnet/primary" - "github.com/ava-labs/subnet-evm/core" - "github.com/ava-labs/subnet-evm/plugin/evm" - "github.com/ethereum/go-ethereum/log" - "github.com/go-cmd/cmd" - "github.com/onsi/ginkgo/v2" - "github.com/stretchr/testify/require" -) - -type SubnetSuite struct { - blockchainIDs map[string]string - lock sync.RWMutex -} - -func (s *SubnetSuite) GetBlockchainID(alias string) string { - s.lock.RLock() - defer s.lock.RUnlock() - return s.blockchainIDs[alias] -} - -func (s *SubnetSuite) SetBlockchainIDs(blockchainIDs map[string]string) { - s.lock.Lock() - defer s.lock.Unlock() - s.blockchainIDs = blockchainIDs -} - -// CreateSubnetsSuite creates subnets for given [genesisFiles], and registers a before suite that starts an AvalancheGo process to use for the e2e tests. -// genesisFiles is a map of test aliases to genesis file paths. -func CreateSubnetsSuite(genesisFiles map[string]string) *SubnetSuite { - require := require.New(ginkgo.GinkgoT()) - - // Keep track of the AvalancheGo external bash script, it is null for most - // processes except the first process that starts AvalancheGo - var startCmd *cmd.Cmd - - // This is used to pass the blockchain IDs from the SynchronizedBeforeSuite() to the tests - var globalSuite SubnetSuite - - // Our test suite runs in separate processes, ginkgo has - // SynchronizedBeforeSuite() which runs once, and its return value is passed - // over to each worker. - // - // Here an AvalancheGo node instance is started, and subnets are created for - // each test case. Each test case has its own subnet, therefore all tests - // can run in parallel without any issue. - // - var _ = ginkgo.SynchronizedBeforeSuite(func() []byte { - ctx, cancel := context.WithTimeout(context.Background(), BootAvalancheNodeTimeout) - defer cancel() - - wd, err := os.Getwd() - require.NoError(err) - log.Info("Starting AvalancheGo node", "wd", wd) - cmd, err := RunCommand("./scripts/run.sh") - require.NoError(err) - startCmd = cmd - - // Assumes that startCmd will launch a node with HTTP Port at [utils.DefaultLocalNodeURI] - healthClient := health.NewClient(DefaultLocalNodeURI) - healthy, err := health.AwaitReady(ctx, healthClient, HealthCheckTimeout, nil) - require.NoError(err) - require.True(healthy) - log.Info("AvalancheGo node is healthy") - - blockchainIDs := make(map[string]string) - for alias, file := range genesisFiles { - blockchainIDs[alias] = CreateNewSubnet(ctx, file) - } - - blockchainIDsBytes, err := json.Marshal(blockchainIDs) - require.NoError(err) - return blockchainIDsBytes - }, func(ctx ginkgo.SpecContext, data []byte) { - blockchainIDs := make(map[string]string) - err := json.Unmarshal(data, &blockchainIDs) - require.NoError(err) - - globalSuite.SetBlockchainIDs(blockchainIDs) - }) - - // SynchronizedAfterSuite() takes two functions, the first runs after each test suite is done and the second - // function is executed once when all the tests are done. This function is used - // to gracefully shutdown the AvalancheGo node. - var _ = ginkgo.SynchronizedAfterSuite(func() {}, func() { - require.NotNil(startCmd) - require.NoError(startCmd.Stop()) - }) - - return &globalSuite -} - -// CreateNewSubnet creates a new subnet and Subnet-EVM blockchain with the given genesis file. -// returns the ID of the new created blockchain. -func CreateNewSubnet(ctx context.Context, genesisFilePath string) string { - require := require.New(ginkgo.GinkgoT()) - - kc := secp256k1fx.NewKeychain(genesis.EWOQKey) - - // MakeWallet fetches the available UTXOs owned by [kc] on the network - // that [LocalAPIURI] is hosting. - wallet, err := wallet.MakeWallet(ctx, &wallet.WalletConfig{ - URI: DefaultLocalNodeURI, - AVAXKeychain: kc, - EthKeychain: kc, - }) - require.NoError(err) - - pWallet := wallet.P() - - owner := &secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{ - genesis.EWOQKey.PublicKey().Address(), - }, - } - - wd, err := os.Getwd() - require.NoError(err) - log.Info("Reading genesis file", "filePath", genesisFilePath, "wd", wd) - genesisBytes, err := os.ReadFile(genesisFilePath) - require.NoError(err) - - log.Info("Creating new subnet") - createSubnetTx, err := pWallet.IssueCreateSubnetTx(owner) - require.NoError(err) - - genesis := &core.Genesis{} - err = json.Unmarshal(genesisBytes, genesis) - require.NoError(err) - - log.Info("Creating new Subnet-EVM blockchain", "genesis", genesis) - createChainTx, err := pWallet.IssueCreateChainTx( - createSubnetTx.ID(), - genesisBytes, - evm.ID, - nil, - "testChain", - ) - require.NoError(err) - createChainTxID := createChainTx.ID() - - // Confirm the new blockchain is ready by waiting for the readiness endpoint - infoClient := info.NewClient(DefaultLocalNodeURI) - bootstrapped, err := info.AwaitBootstrapped(ctx, infoClient, createChainTxID.String(), 2*time.Second) - require.NoError(err) - require.True(bootstrapped) - - // Return the blockchainID of the newly created blockchain - return createChainTxID.String() -} - -// GetDefaultChainURI returns the default chain URI for a given blockchainID -func GetDefaultChainURI(blockchainID string) string { - return fmt.Sprintf("%s/ext/bc/%s/rpc", DefaultLocalNodeURI, blockchainID) -} - -// GetFilesAndAliases returns a map of aliases to file paths in given [dir]. -func GetFilesAndAliases(dir string) (map[string]string, error) { - files, err := filepath.Glob(dir) - if err != nil { - return nil, err - } - aliasesToFiles := make(map[string]string) - for _, file := range files { - alias := strings.TrimSuffix(filepath.Base(file), filepath.Ext(file)) - aliasesToFiles[alias] = file - } - return aliasesToFiles, nil -} diff --git a/tests/utils/tmpnet.go b/tests/utils/tmpnet.go deleted file mode 100644 index d7c17d1c95..0000000000 --- a/tests/utils/tmpnet.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package utils - -import ( - "encoding/json" - "os" - - "github.com/ava-labs/avalanchego/config" - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/tests/fixture/tmpnet" - - "github.com/ava-labs/subnet-evm/plugin/evm" -) - -var DefaultChainConfig = tmpnet.FlagsMap{ - "log-level": "debug", - "warp-api-enabled": true, - "local-txs-enabled": true, -} - -func NewTmpnetNodes(count int) []*tmpnet.Node { - nodes := make([]*tmpnet.Node, count) - for i := range nodes { - node := tmpnet.NewNode("") - node.EnsureKeys() - nodes[i] = node - } - return nodes -} - -func NewTmpnetNetwork(owner string, nodes []*tmpnet.Node, flags tmpnet.FlagsMap, subnets ...*tmpnet.Subnet) *tmpnet.Network { - defaultFlags := tmpnet.FlagsMap{} - defaultFlags.SetDefaults(flags) - defaultFlags.SetDefaults(tmpnet.FlagsMap{ - config.ProposerVMUseCurrentHeightKey: true, - }) - return &tmpnet.Network{ - Owner: owner, - DefaultFlags: defaultFlags, - Nodes: nodes, - Subnets: subnets, - } -} - -// Create the configuration that will enable creation and access to a -// subnet created on a temporary network. -func NewTmpnetSubnet(name string, genesisPath string, chainConfig tmpnet.FlagsMap, nodes ...*tmpnet.Node) *tmpnet.Subnet { - if len(nodes) == 0 { - panic("a subnet must be validated by at least one node") - } - - validatorIDs := make([]ids.NodeID, len(nodes)) - for i, node := range nodes { - validatorIDs[i] = node.NodeID - } - - genesisBytes, err := os.ReadFile(genesisPath) - if err != nil { - panic(err) - } - - chainConfigBytes, err := json.Marshal(chainConfig) - if err != nil { - panic(err) - } - - return &tmpnet.Subnet{ - Name: name, - Chains: []*tmpnet.Chain{ - { - VMID: evm.ID, - Genesis: genesisBytes, - Config: string(chainConfigBytes), - PreFundedKey: tmpnet.HardhatKey, - }, - }, - ValidatorIDs: validatorIDs, - } -} diff --git a/tests/warp/warp_test.go b/tests/warp/warp_test.go deleted file mode 100644 index 57567ae451..0000000000 --- a/tests/warp/warp_test.go +++ /dev/null @@ -1,749 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -// Implements solidity tests. -package warp - -import ( - "context" - "crypto/ecdsa" - "encoding/hex" - "fmt" - "math/big" - "os" - "path/filepath" - "strings" - "testing" - "time" - - ginkgo "github.com/onsi/ginkgo/v2" - - "github.com/stretchr/testify/require" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/log" - - "github.com/ava-labs/avalanchego/api/info" - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/snow/validators" - "github.com/ava-labs/avalanchego/tests/fixture/e2e" - "github.com/ava-labs/avalanchego/tests/fixture/tmpnet" - "github.com/ava-labs/avalanchego/utils/constants" - "github.com/ava-labs/avalanchego/vms/platformvm" - avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" - "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" - - "github.com/ava-labs/subnet-evm/cmd/simulator/key" - "github.com/ava-labs/subnet-evm/cmd/simulator/load" - "github.com/ava-labs/subnet-evm/cmd/simulator/metrics" - "github.com/ava-labs/subnet-evm/cmd/simulator/txs" - "github.com/ava-labs/subnet-evm/core/types" - "github.com/ava-labs/subnet-evm/ethclient" - "github.com/ava-labs/subnet-evm/interfaces" - "github.com/ava-labs/subnet-evm/params" - "github.com/ava-labs/subnet-evm/precompile/contracts/warp" - "github.com/ava-labs/subnet-evm/predicate" - "github.com/ava-labs/subnet-evm/tests" - "github.com/ava-labs/subnet-evm/tests/utils" - warpBackend "github.com/ava-labs/subnet-evm/warp" - "github.com/ava-labs/subnet-evm/warp/aggregator" -) - -const ( - subnetAName = "warp-subnet-a" - subnetBName = "warp-subnet-b" -) - -var ( - flagVars *e2e.FlagVars - - repoRootPath = tests.GetRepoRootPath("tests/warp") - - genesisPath = filepath.Join(repoRootPath, "tests/precompile/genesis/warp.json") - - subnetA, subnetB, cChainSubnetDetails *Subnet - - testPayload = []byte{1, 2, 3} -) - -func init() { - // Configures flags used to configure tmpnet (via SynchronizedBeforeSuite) - flagVars = e2e.RegisterFlags() -} - -// Subnet provides the basic details of a created subnet -type Subnet struct { - // SubnetID is the txID of the transaction that created the subnet - SubnetID ids.ID - // For simplicity assume a single blockchain per subnet - BlockchainID ids.ID - // Key funded in the genesis of the blockchain - PreFundedKey *ecdsa.PrivateKey - // ValidatorURIs are the base URIs for each participant of the Subnet - ValidatorURIs []string -} - -func TestE2E(t *testing.T) { - ginkgo.RunSpecs(t, "subnet-evm warp e2e test") -} - -var _ = ginkgo.SynchronizedBeforeSuite(func() []byte { - // Run only once in the first ginkgo process - - tc := e2e.NewTestContext() - nodes := utils.NewTmpnetNodes(tmpnet.DefaultNodeCount) - - env := e2e.NewTestEnvironment( - tc, - flagVars, - utils.NewTmpnetNetwork( - "subnet-evm-warp-e2e", - nodes, - tmpnet.FlagsMap{}, - utils.NewTmpnetSubnet(subnetAName, genesisPath, utils.DefaultChainConfig, nodes...), - utils.NewTmpnetSubnet(subnetBName, genesisPath, utils.DefaultChainConfig, nodes...), - ), - ) - - return env.Marshal() -}, func(envBytes []byte) { - // Run in every ginkgo process - - require := require.New(ginkgo.GinkgoT()) - tc := e2e.NewTestContext() - - // Initialize the local test environment from the global state - if len(envBytes) > 0 { - e2e.InitSharedTestEnvironment(ginkgo.GinkgoT(), envBytes) - } - - network := e2e.GetEnv(tc).GetNetwork() - - // By default all nodes are validating all subnets - validatorURIs := make([]string, len(network.Nodes)) - for i, node := range network.Nodes { - validatorURIs[i] = node.URI - } - - tmpnetSubnetA := network.GetSubnet(subnetAName) - require.NotNil(tmpnetSubnetA) - subnetA = &Subnet{ - SubnetID: tmpnetSubnetA.SubnetID, - BlockchainID: tmpnetSubnetA.Chains[0].ChainID, - PreFundedKey: tmpnetSubnetA.Chains[0].PreFundedKey.ToECDSA(), - ValidatorURIs: validatorURIs, - } - - tmpnetSubnetB := network.GetSubnet(subnetBName) - require.NotNil(tmpnetSubnetB) - subnetB = &Subnet{ - SubnetID: tmpnetSubnetB.SubnetID, - BlockchainID: tmpnetSubnetB.Chains[0].ChainID, - PreFundedKey: tmpnetSubnetB.Chains[0].PreFundedKey.ToECDSA(), - ValidatorURIs: validatorURIs, - } - - infoClient := info.NewClient(network.Nodes[0].URI) - cChainBlockchainID, err := infoClient.GetBlockchainID(tc.DefaultContext(), "C") - require.NoError(err) - - cChainSubnetDetails = &Subnet{ - SubnetID: constants.PrimaryNetworkID, - BlockchainID: cChainBlockchainID, - PreFundedKey: tmpnet.HardhatKey.ToECDSA(), - ValidatorURIs: validatorURIs, - } -}) - -var _ = ginkgo.Describe("[Warp]", func() { - testFunc := func(sendingSubnet *Subnet, receivingSubnet *Subnet) { - tc := e2e.NewTestContext() - w := newWarpTest(tc.DefaultContext(), sendingSubnet, receivingSubnet) - - log.Info("Sending message from A to B") - w.sendMessageFromSendingSubnet() - - log.Info("Aggregating signatures via API") - w.aggregateSignaturesViaAPI() - - log.Info("Aggregating signatures via p2p aggregator") - w.aggregateSignatures() - - log.Info("Delivering addressed call payload to receiving subnet") - w.deliverAddressedCallToReceivingSubnet() - - log.Info("Delivering block hash payload to receiving subnet") - w.deliverBlockHashPayload() - - log.Info("Executing HardHat test") - w.executeHardHatTest() - - log.Info("Executing warp load test") - w.warpLoad() - } - ginkgo.It("SubnetA -> SubnetB", func() { testFunc(subnetA, subnetB) }) - ginkgo.It("SubnetA -> SubnetA", func() { testFunc(subnetA, subnetA) }) - ginkgo.It("SubnetA -> C-Chain", func() { testFunc(subnetA, cChainSubnetDetails) }) - ginkgo.It("C-Chain -> SubnetA", func() { testFunc(cChainSubnetDetails, subnetA) }) - ginkgo.It("C-Chain -> C-Chain", func() { testFunc(cChainSubnetDetails, cChainSubnetDetails) }) -}) - -type warpTest struct { - // network-wide fields set in the constructor - networkID uint32 - - // sendingSubnet fields set in the constructor - sendingSubnet *Subnet - sendingSubnetURIs []string - sendingSubnetClients []ethclient.Client - sendingSubnetFundedKey *ecdsa.PrivateKey - sendingSubnetFundedAddress common.Address - sendingSubnetChainID *big.Int - sendingSubnetSigner types.Signer - - // receivingSubnet fields set in the constructor - receivingSubnet *Subnet - receivingSubnetURIs []string - receivingSubnetClients []ethclient.Client - receivingSubnetFundedKey *ecdsa.PrivateKey - receivingSubnetFundedAddress common.Address - receivingSubnetChainID *big.Int - receivingSubnetSigner types.Signer - - // Fields set throughout test execution - blockID ids.ID - blockPayload *payload.Hash - blockPayloadUnsignedMessage *avalancheWarp.UnsignedMessage - blockPayloadSignedMessage *avalancheWarp.Message - - addressedCallUnsignedMessage *avalancheWarp.UnsignedMessage - addressedCallSignedMessage *avalancheWarp.Message -} - -func newWarpTest(ctx context.Context, sendingSubnet *Subnet, receivingSubnet *Subnet) *warpTest { - require := require.New(ginkgo.GinkgoT()) - - sendingSubnetFundedKey := sendingSubnet.PreFundedKey - receivingSubnetFundedKey := receivingSubnet.PreFundedKey - - warpTest := &warpTest{ - sendingSubnet: sendingSubnet, - sendingSubnetURIs: sendingSubnet.ValidatorURIs, - receivingSubnet: receivingSubnet, - receivingSubnetURIs: receivingSubnet.ValidatorURIs, - sendingSubnetFundedKey: sendingSubnetFundedKey, - sendingSubnetFundedAddress: crypto.PubkeyToAddress(sendingSubnetFundedKey.PublicKey), - receivingSubnetFundedKey: receivingSubnetFundedKey, - receivingSubnetFundedAddress: crypto.PubkeyToAddress(receivingSubnetFundedKey.PublicKey), - } - infoClient := info.NewClient(sendingSubnet.ValidatorURIs[0]) - networkID, err := infoClient.GetNetworkID(ctx) - require.NoError(err) - warpTest.networkID = networkID - - warpTest.initClients() - - sendingClient := warpTest.sendingSubnetClients[0] - sendingSubnetChainID, err := sendingClient.ChainID(ctx) - require.NoError(err) - warpTest.sendingSubnetChainID = sendingSubnetChainID - warpTest.sendingSubnetSigner = types.LatestSignerForChainID(sendingSubnetChainID) - - receivingClient := warpTest.receivingSubnetClients[0] - receivingChainID, err := receivingClient.ChainID(ctx) - require.NoError(err) - // Issue transactions to activate ProposerVM on the receiving chain - require.NoError(utils.IssueTxsToActivateProposerVMFork(ctx, receivingChainID, receivingSubnetFundedKey, receivingClient)) - warpTest.receivingSubnetChainID = receivingChainID - warpTest.receivingSubnetSigner = types.LatestSignerForChainID(receivingChainID) - - return warpTest -} - -func (w *warpTest) initClients() { - require := require.New(ginkgo.GinkgoT()) - - w.sendingSubnetClients = make([]ethclient.Client, 0, len(w.sendingSubnetClients)) - for _, uri := range w.sendingSubnet.ValidatorURIs { - wsURI := toWebsocketURI(uri, w.sendingSubnet.BlockchainID.String()) - log.Info("Creating ethclient for blockchain A", "blockchainID", w.sendingSubnet.BlockchainID) - client, err := ethclient.Dial(wsURI) - require.NoError(err) - w.sendingSubnetClients = append(w.sendingSubnetClients, client) - } - - w.receivingSubnetClients = make([]ethclient.Client, 0, len(w.receivingSubnetClients)) - for _, uri := range w.receivingSubnet.ValidatorURIs { - wsURI := toWebsocketURI(uri, w.receivingSubnet.BlockchainID.String()) - log.Info("Creating ethclient for blockchain B", "blockchainID", w.receivingSubnet.BlockchainID) - client, err := ethclient.Dial(wsURI) - require.NoError(err) - w.receivingSubnetClients = append(w.receivingSubnetClients, client) - } -} - -func (w *warpTest) getBlockHashAndNumberFromTxReceipt(ctx context.Context, client ethclient.Client, tx *types.Transaction) (common.Hash, uint64) { - // This uses the Subnet-EVM client to fetch a block from Coreth (when testing the C-Chain), so we use this - // workaround to get the correct block hash. Note the client recalculates the block hash locally, which results - // in a different block hash due to small differences in the block format. - require := require.New(ginkgo.GinkgoT()) - for { - require.NoError(ctx.Err()) - receipt, err := client.TransactionReceipt(ctx, tx.Hash()) - if err == nil { - return receipt.BlockHash, receipt.BlockNumber.Uint64() - } - } -} - -func (w *warpTest) sendMessageFromSendingSubnet() { - tc := e2e.NewTestContext() - ctx := tc.DefaultContext() - require := require.New(ginkgo.GinkgoT()) - - client := w.sendingSubnetClients[0] - log.Info("Subscribing to new heads") - newHeads := make(chan *types.Header, 10) - sub, err := client.SubscribeNewHead(ctx, newHeads) - require.NoError(err) - defer sub.Unsubscribe() - - startingNonce, err := client.NonceAt(ctx, w.sendingSubnetFundedAddress, nil) - require.NoError(err) - - packedInput, err := warp.PackSendWarpMessage(testPayload) - require.NoError(err) - tx := types.NewTx(&types.DynamicFeeTx{ - ChainID: w.sendingSubnetChainID, - Nonce: startingNonce, - To: &warp.Module.Address, - Gas: 200_000, - GasFeeCap: big.NewInt(225 * params.GWei), - GasTipCap: big.NewInt(params.GWei), - Value: common.Big0, - Data: packedInput, - }) - signedTx, err := types.SignTx(tx, w.sendingSubnetSigner, w.sendingSubnetFundedKey) - require.NoError(err) - log.Info("Sending sendWarpMessage transaction", "txHash", signedTx.Hash()) - err = client.SendTransaction(ctx, signedTx) - require.NoError(err) - - log.Info("Waiting for new block confirmation") - <-newHeads - receiptCtx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - blockHash, blockNumber := w.getBlockHashAndNumberFromTxReceipt(receiptCtx, client, signedTx) - - log.Info("Constructing warp block hash unsigned message", "blockHash", blockHash) - w.blockID = ids.ID(blockHash) // Set blockID to construct a warp message containing a block hash payload later - w.blockPayload, err = payload.NewHash(w.blockID) - require.NoError(err) - w.blockPayloadUnsignedMessage, err = avalancheWarp.NewUnsignedMessage(w.networkID, w.sendingSubnet.BlockchainID, w.blockPayload.Bytes()) - require.NoError(err) - - log.Info("Fetching relevant warp logs from the newly produced block") - logs, err := client.FilterLogs(ctx, interfaces.FilterQuery{ - BlockHash: &blockHash, - Addresses: []common.Address{warp.Module.Address}, - }) - require.NoError(err) - require.Len(logs, 1) - - // Check for relevant warp log from subscription and ensure that it matches - // the log extracted from the last block. - txLog := logs[0] - log.Info("Parsing logData as unsigned warp message") - unsignedMsg, err := warp.UnpackSendWarpEventDataToMessage(txLog.Data) - require.NoError(err) - - // Set local variables for the duration of the test - w.addressedCallUnsignedMessage = unsignedMsg - log.Info("Parsed unsignedWarpMsg", "unsignedWarpMessageID", w.addressedCallUnsignedMessage.ID(), "unsignedWarpMessage", w.addressedCallUnsignedMessage) - - // Loop over each client on chain A to ensure they all have time to accept the block. - // Note: if we did not confirm this here, the next stage could be racy since it assumes every node - // has accepted the block. - for i, client := range w.sendingSubnetClients { - // Loop until each node has advanced to >= the height of the block that emitted the warp log - for { - block, err := client.BlockByNumber(ctx, nil) - require.NoError(err) - if block.NumberU64() >= blockNumber { - log.Info("client accepted the block containing SendWarpMessage", "client", i, "height", block.NumberU64()) - break - } - } - } -} - -func (w *warpTest) aggregateSignaturesViaAPI() { - require := require.New(ginkgo.GinkgoT()) - tc := e2e.NewTestContext() - ctx := tc.DefaultContext() - - warpAPIs := make(map[ids.NodeID]warpBackend.Client, len(w.sendingSubnetURIs)) - for _, uri := range w.sendingSubnetURIs { - client, err := warpBackend.NewClient(uri, w.sendingSubnet.BlockchainID.String()) - require.NoError(err) - - infoClient := info.NewClient(uri) - nodeID, _, err := infoClient.GetNodeID(ctx) - require.NoError(err) - warpAPIs[nodeID] = client - } - - pChainClient := platformvm.NewClient(w.sendingSubnetURIs[0]) - pChainHeight, err := pChainClient.GetHeight(ctx) - require.NoError(err) - // If the source subnet is the Primary Network, then we only need to aggregate signatures from the receiving - // subnet's validator set instead of the entire Primary Network. - // If the destination turns out to be the Primary Network as well, then this is a no-op. - var validators map[ids.NodeID]*validators.GetValidatorOutput - if w.sendingSubnet.SubnetID == constants.PrimaryNetworkID { - validators, err = pChainClient.GetValidatorsAt(ctx, w.receivingSubnet.SubnetID, pChainHeight) - } else { - validators, err = pChainClient.GetValidatorsAt(ctx, w.sendingSubnet.SubnetID, pChainHeight) - } - require.NoError(err) - require.NotZero(len(validators)) - - totalWeight := uint64(0) - warpValidators := make([]*avalancheWarp.Validator, 0, len(validators)) - for nodeID, validator := range validators { - warpValidators = append(warpValidators, &avalancheWarp.Validator{ - PublicKey: validator.PublicKey, - Weight: validator.Weight, - NodeIDs: []ids.NodeID{nodeID}, - }) - totalWeight += validator.Weight - } - - log.Info("Aggregating signatures from validator set", "numValidators", len(warpValidators), "totalWeight", totalWeight) - apiSignatureGetter := warpBackend.NewAPIFetcher(warpAPIs) - signatureResult, err := aggregator.New(apiSignatureGetter, warpValidators, totalWeight).AggregateSignatures(ctx, w.addressedCallUnsignedMessage, 100) - require.NoError(err) - require.Equal(signatureResult.SignatureWeight, signatureResult.TotalWeight) - require.Equal(signatureResult.SignatureWeight, totalWeight) - - w.addressedCallSignedMessage = signatureResult.Message - - signatureResult, err = aggregator.New(apiSignatureGetter, warpValidators, totalWeight).AggregateSignatures(ctx, w.blockPayloadUnsignedMessage, 100) - require.NoError(err) - require.Equal(signatureResult.SignatureWeight, signatureResult.TotalWeight) - require.Equal(signatureResult.SignatureWeight, totalWeight) - w.blockPayloadSignedMessage = signatureResult.Message - - log.Info("Aggregated signatures for warp messages", "addressedCallMessage", common.Bytes2Hex(w.addressedCallSignedMessage.Bytes()), "blockPayloadMessage", common.Bytes2Hex(w.blockPayloadSignedMessage.Bytes())) -} - -func (w *warpTest) aggregateSignatures() { - require := require.New(ginkgo.GinkgoT()) - tc := e2e.NewTestContext() - ctx := tc.DefaultContext() - - // Verify that the signature aggregation matches the results of manually constructing the warp message - client, err := warpBackend.NewClient(w.sendingSubnetURIs[0], w.sendingSubnet.BlockchainID.String()) - require.NoError(err) - - log.Info("Fetching addressed call aggregate signature via p2p API") - subnetIDStr := "" - if w.sendingSubnet.SubnetID == constants.PrimaryNetworkID { - subnetIDStr = w.receivingSubnet.SubnetID.String() - } - signedWarpMessageBytes, err := client.GetMessageAggregateSignature(ctx, w.addressedCallSignedMessage.ID(), warp.WarpQuorumDenominator, subnetIDStr) - require.NoError(err) - require.Equal(w.addressedCallSignedMessage.Bytes(), signedWarpMessageBytes) - - log.Info("Fetching block payload aggregate signature via p2p API") - signedWarpBlockBytes, err := client.GetBlockAggregateSignature(ctx, w.blockID, warp.WarpQuorumDenominator, subnetIDStr) - require.NoError(err) - require.Equal(w.blockPayloadSignedMessage.Bytes(), signedWarpBlockBytes) -} - -func (w *warpTest) deliverAddressedCallToReceivingSubnet() { - require := require.New(ginkgo.GinkgoT()) - tc := e2e.NewTestContext() - ctx := tc.DefaultContext() - - client := w.receivingSubnetClients[0] - log.Info("Subscribing to new heads") - newHeads := make(chan *types.Header, 10) - sub, err := client.SubscribeNewHead(ctx, newHeads) - require.NoError(err) - defer sub.Unsubscribe() - - nonce, err := client.NonceAt(ctx, w.receivingSubnetFundedAddress, nil) - require.NoError(err) - - packedInput, err := warp.PackGetVerifiedWarpMessage(0) - require.NoError(err) - tx := predicate.NewPredicateTx( - w.receivingSubnetChainID, - nonce, - &warp.Module.Address, - 5_000_000, - big.NewInt(225*params.GWei), - big.NewInt(params.GWei), - common.Big0, - packedInput, - types.AccessList{}, - warp.ContractAddress, - w.addressedCallSignedMessage.Bytes(), - ) - signedTx, err := types.SignTx(tx, w.receivingSubnetSigner, w.receivingSubnetFundedKey) - require.NoError(err) - txBytes, err := signedTx.MarshalBinary() - require.NoError(err) - log.Info("Sending getVerifiedWarpMessage transaction", "txHash", signedTx.Hash(), "txBytes", common.Bytes2Hex(txBytes)) - require.NoError(client.SendTransaction(ctx, signedTx)) - - log.Info("Waiting for new block confirmation") - <-newHeads - receiptCtx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - blockHash, _ := w.getBlockHashAndNumberFromTxReceipt(receiptCtx, client, signedTx) - - log.Info("Fetching relevant warp logs and receipts from new block") - logs, err := client.FilterLogs(ctx, interfaces.FilterQuery{ - BlockHash: &blockHash, - Addresses: []common.Address{warp.Module.Address}, - }) - require.NoError(err) - require.Len(logs, 0) - receipt, err := client.TransactionReceipt(ctx, signedTx.Hash()) - require.NoError(err) - require.Equal(receipt.Status, types.ReceiptStatusSuccessful) -} - -func (w *warpTest) deliverBlockHashPayload() { - require := require.New(ginkgo.GinkgoT()) - tc := e2e.NewTestContext() - ctx := tc.DefaultContext() - - client := w.receivingSubnetClients[0] - log.Info("Subscribing to new heads") - newHeads := make(chan *types.Header, 10) - sub, err := client.SubscribeNewHead(ctx, newHeads) - require.NoError(err) - defer sub.Unsubscribe() - - nonce, err := client.NonceAt(ctx, w.receivingSubnetFundedAddress, nil) - require.NoError(err) - - packedInput, err := warp.PackGetVerifiedWarpBlockHash(0) - require.NoError(err) - tx := predicate.NewPredicateTx( - w.receivingSubnetChainID, - nonce, - &warp.Module.Address, - 5_000_000, - big.NewInt(225*params.GWei), - big.NewInt(params.GWei), - common.Big0, - packedInput, - types.AccessList{}, - warp.ContractAddress, - w.blockPayloadSignedMessage.Bytes(), - ) - signedTx, err := types.SignTx(tx, w.receivingSubnetSigner, w.receivingSubnetFundedKey) - require.NoError(err) - txBytes, err := signedTx.MarshalBinary() - require.NoError(err) - log.Info("Sending getVerifiedWarpBlockHash transaction", "txHash", signedTx.Hash(), "txBytes", common.Bytes2Hex(txBytes)) - err = client.SendTransaction(ctx, signedTx) - require.NoError(err) - - log.Info("Waiting for new block confirmation") - <-newHeads - receiptCtx, cancel := context.WithTimeout(ctx, 10*time.Second) - defer cancel() - blockHash, _ := w.getBlockHashAndNumberFromTxReceipt(receiptCtx, client, signedTx) - log.Info("Fetching relevant warp logs and receipts from new block") - logs, err := client.FilterLogs(ctx, interfaces.FilterQuery{ - BlockHash: &blockHash, - Addresses: []common.Address{warp.Module.Address}, - }) - require.NoError(err) - require.Len(logs, 0) - receipt, err := client.TransactionReceipt(ctx, signedTx.Hash()) - require.NoError(err) - require.Equal(receipt.Status, types.ReceiptStatusSuccessful) -} - -func (w *warpTest) executeHardHatTest() { - require := require.New(ginkgo.GinkgoT()) - tc := e2e.NewTestContext() - ctx := tc.DefaultContext() - - client := w.sendingSubnetClients[0] - log.Info("Subscribing to new heads") - newHeads := make(chan *types.Header, 10) - sub, err := client.SubscribeNewHead(ctx, newHeads) - require.NoError(err) - defer sub.Unsubscribe() - - chainID, err := client.ChainID(ctx) - require.NoError(err) - - rpcURI := toRPCURI(w.sendingSubnetURIs[0], w.sendingSubnet.BlockchainID.String()) - - os.Setenv("SENDER_ADDRESS", crypto.PubkeyToAddress(w.sendingSubnetFundedKey.PublicKey).Hex()) - os.Setenv("SOURCE_CHAIN_ID", "0x"+w.sendingSubnet.BlockchainID.Hex()) - os.Setenv("PAYLOAD", "0x"+common.Bytes2Hex(testPayload)) - os.Setenv("EXPECTED_UNSIGNED_MESSAGE", "0x"+hex.EncodeToString(w.addressedCallUnsignedMessage.Bytes())) - os.Setenv("CHAIN_ID", fmt.Sprintf("%d", chainID.Uint64())) - - cmdPath := filepath.Join(repoRootPath, "contracts") - // test path is relative to the cmd path - testPath := "./test/warp.ts" - utils.RunHardhatTestsCustomURI(ctx, rpcURI, cmdPath, testPath) -} - -func (w *warpTest) warpLoad() { - require := require.New(ginkgo.GinkgoT()) - tc := e2e.NewTestContext() - ctx := tc.DefaultContext() - - var ( - numWorkers = len(w.sendingSubnetClients) - txsPerWorker uint64 = 10 - batchSize uint64 = 10 - sendingClient = w.sendingSubnetClients[0] - ) - - chainAKeys, chainAPrivateKeys := generateKeys(w.sendingSubnetFundedKey, numWorkers) - chainBKeys, chainBPrivateKeys := generateKeys(w.receivingSubnetFundedKey, numWorkers) - - loadMetrics := metrics.NewDefaultMetrics() - - log.Info("Distributing funds on sending subnet", "numKeys", len(chainAKeys)) - chainAKeys, err := load.DistributeFunds(ctx, sendingClient, chainAKeys, len(chainAKeys), new(big.Int).Mul(big.NewInt(100), big.NewInt(params.Ether)), loadMetrics) - require.NoError(err) - - log.Info("Distributing funds on receiving subnet", "numKeys", len(chainBKeys)) - _, err = load.DistributeFunds(ctx, w.receivingSubnetClients[0], chainBKeys, len(chainBKeys), new(big.Int).Mul(big.NewInt(100), big.NewInt(params.Ether)), loadMetrics) - require.NoError(err) - - log.Info("Creating workers for each subnet...") - chainAWorkers := make([]txs.Worker[*types.Transaction], 0, len(chainAKeys)) - for i := range chainAKeys { - chainAWorkers = append(chainAWorkers, load.NewTxReceiptWorker(ctx, w.sendingSubnetClients[i])) - } - chainBWorkers := make([]txs.Worker[*types.Transaction], 0, len(chainBKeys)) - for i := range chainBKeys { - chainBWorkers = append(chainBWorkers, load.NewTxReceiptWorker(ctx, w.receivingSubnetClients[i])) - } - - log.Info("Subscribing to warp send events on sending subnet") - logs := make(chan types.Log, numWorkers*int(txsPerWorker)) - sub, err := sendingClient.SubscribeFilterLogs(ctx, interfaces.FilterQuery{ - Addresses: []common.Address{warp.Module.Address}, - }, logs) - require.NoError(err) - defer func() { - sub.Unsubscribe() - err := <-sub.Err() - require.NoError(err) - }() - - log.Info("Generating tx sequence to send warp messages...") - warpSendSequences, err := txs.GenerateTxSequences(ctx, func(key *ecdsa.PrivateKey, nonce uint64) (*types.Transaction, error) { - data, err := warp.PackSendWarpMessage([]byte(fmt.Sprintf("Jets %d-%d Dolphins", key.X.Int64(), nonce))) - if err != nil { - return nil, err - } - tx := types.NewTx(&types.DynamicFeeTx{ - ChainID: w.sendingSubnetChainID, - Nonce: nonce, - To: &warp.Module.Address, - Gas: 200_000, - GasFeeCap: big.NewInt(225 * params.GWei), - GasTipCap: big.NewInt(params.GWei), - Value: common.Big0, - Data: data, - }) - return types.SignTx(tx, w.sendingSubnetSigner, key) - }, w.sendingSubnetClients[0], chainAPrivateKeys, txsPerWorker, false) - require.NoError(err) - log.Info("Executing warp send loader...") - warpSendLoader := load.New(chainAWorkers, warpSendSequences, batchSize, loadMetrics) - // TODO: execute send and receive loaders concurrently. - require.NoError(warpSendLoader.Execute(ctx)) - require.NoError(warpSendLoader.ConfirmReachedTip(ctx)) - - warpClient, err := warpBackend.NewClient(w.sendingSubnetURIs[0], w.sendingSubnet.BlockchainID.String()) - require.NoError(err) - subnetIDStr := "" - if w.sendingSubnet.SubnetID == constants.PrimaryNetworkID { - subnetIDStr = w.receivingSubnet.SubnetID.String() - } - - log.Info("Executing warp delivery sequences...") - warpDeliverSequences, err := txs.GenerateTxSequences(ctx, func(key *ecdsa.PrivateKey, nonce uint64) (*types.Transaction, error) { - // Wait for the next warp send log - warpLog := <-logs - - unsignedMessage, err := warp.UnpackSendWarpEventDataToMessage(warpLog.Data) - if err != nil { - return nil, err - } - log.Info("Fetching addressed call aggregate signature via p2p API") - - signedWarpMessageBytes, err := warpClient.GetMessageAggregateSignature(ctx, unsignedMessage.ID(), warp.WarpDefaultQuorumNumerator, subnetIDStr) - if err != nil { - return nil, err - } - - packedInput, err := warp.PackGetVerifiedWarpMessage(0) - if err != nil { - return nil, err - } - tx := predicate.NewPredicateTx( - w.receivingSubnetChainID, - nonce, - &warp.Module.Address, - 5_000_000, - big.NewInt(225*params.GWei), - big.NewInt(params.GWei), - common.Big0, - packedInput, - types.AccessList{}, - warp.ContractAddress, - signedWarpMessageBytes, - ) - return types.SignTx(tx, w.receivingSubnetSigner, key) - }, w.receivingSubnetClients[0], chainBPrivateKeys, txsPerWorker, true) - require.NoError(err) - - log.Info("Executing warp delivery...") - warpDeliverLoader := load.New(chainBWorkers, warpDeliverSequences, batchSize, loadMetrics) - require.NoError(warpDeliverLoader.Execute(ctx)) - require.NoError(warpSendLoader.ConfirmReachedTip(ctx)) - log.Info("Completed warp delivery successfully.") -} - -func generateKeys(preFundedKey *ecdsa.PrivateKey, numWorkers int) ([]*key.Key, []*ecdsa.PrivateKey) { - keys := []*key.Key{ - key.CreateKey(preFundedKey), - } - privateKeys := []*ecdsa.PrivateKey{ - preFundedKey, - } - for i := 1; i < numWorkers; i++ { - newKey, err := key.Generate() - require.NoError(ginkgo.GinkgoT(), err) - keys = append(keys, newKey) - privateKeys = append(privateKeys, newKey.PrivKey) - } - return keys, privateKeys -} - -func toWebsocketURI(uri string, blockchainID string) string { - return fmt.Sprintf("ws://%s/ext/bc/%s/ws", strings.TrimPrefix(uri, "http://"), blockchainID) -} - -func toRPCURI(uri string, blockchainID string) string { - return fmt.Sprintf("%s/ext/bc/%s/rpc", uri, blockchainID) -} diff --git a/utils/numbers.go b/utils/numbers.go index d665b2f8e8..a6be2341fb 100644 --- a/utils/numbers.go +++ b/utils/numbers.go @@ -4,7 +4,6 @@ package utils import ( - "math/big" "time" ) @@ -20,15 +19,6 @@ func Uint64ToTime(val *uint64) time.Time { return time.Unix(timestamp, 0) } -// BigNumEqual returns true if x and y are equivalent ie. both nil or both -// contain the same value. -func BigNumEqual(x, y *big.Int) bool { - if x == nil || y == nil { - return x == y - } - return x.Cmp(y) == 0 -} - // Uint64PtrEqual returns true if x and y pointers are equivalent ie. both nil or both // contain the same value. func Uint64PtrEqual(x, y *uint64) bool { diff --git a/utils/snow.go b/utils/snow.go index 2042c9ff2d..92a1d236c1 100644 --- a/utils/snow.go +++ b/utils/snow.go @@ -8,7 +8,6 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/snow" "github.com/ava-labs/avalanchego/snow/validators/validatorstest" - "github.com/ava-labs/avalanchego/upgrade" "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/logging" ) @@ -20,16 +19,15 @@ func TestSnowContext() *snow.Context { } pk := bls.PublicFromSecretKey(sk) return &snow.Context{ - NetworkID: 0, - SubnetID: ids.Empty, - ChainID: ids.Empty, - NodeID: ids.EmptyNodeID, - NetworkUpgrades: upgrade.Default, - PublicKey: pk, - Log: logging.NoLog{}, - BCLookup: ids.NewAliaser(), - Metrics: metrics.NewPrefixGatherer(), - ChainDataDir: "", - ValidatorState: &validatorstest.State{}, + NetworkID: 0, + SubnetID: ids.Empty, + ChainID: ids.Empty, + NodeID: ids.EmptyNodeID, + PublicKey: pk, + Log: logging.NoLog{}, + BCLookup: ids.NewAliaser(), + Metrics: metrics.NewMultiGatherer(), + ChainDataDir: "", + ValidatorState: &validatorstest.State{}, } } diff --git a/vmerrs/vmerrs.go b/vmerrs/vmerrs.go index 8e3bb07bc9..4a7afcfd1f 100644 --- a/vmerrs/vmerrs.go +++ b/vmerrs/vmerrs.go @@ -32,21 +32,19 @@ import ( // List evm execution errors var ( - ErrOutOfGas = errors.New("out of gas") - ErrCodeStoreOutOfGas = errors.New("contract creation code storage out of gas") - ErrDepth = errors.New("max call depth exceeded") - ErrInsufficientBalance = errors.New("insufficient balance for transfer") - ErrContractAddressCollision = errors.New("contract address collision") - ErrExecutionReverted = errors.New("execution reverted") - ErrMaxInitCodeSizeExceeded = errors.New("max initcode size exceeded") - ErrMaxCodeSizeExceeded = errors.New("max code size exceeded") - ErrInvalidJump = errors.New("invalid jump destination") - ErrWriteProtection = errors.New("write protection") - ErrReturnDataOutOfBounds = errors.New("return data out of bounds") - ErrGasUintOverflow = errors.New("gas uint64 overflow") - ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") - ErrNonceUintOverflow = errors.New("nonce uint64 overflow") - ErrAddrProhibited = errors.New("prohibited address cannot be sender or created contract address") - ErrInvalidCoinbase = errors.New("invalid coinbase") - ErrSenderAddressNotAllowListed = errors.New("cannot issue transaction from non-allow listed address") + ErrOutOfGas = errors.New("out of gas") + ErrCodeStoreOutOfGas = errors.New("contract creation code storage out of gas") + ErrDepth = errors.New("max call depth exceeded") + ErrInsufficientBalance = errors.New("insufficient balance for transfer") + ErrContractAddressCollision = errors.New("contract address collision") + ErrExecutionReverted = errors.New("execution reverted") + ErrMaxInitCodeSizeExceeded = errors.New("max initcode size exceeded") + ErrMaxCodeSizeExceeded = errors.New("max code size exceeded") + ErrInvalidJump = errors.New("invalid jump destination") + ErrWriteProtection = errors.New("write protection") + ErrReturnDataOutOfBounds = errors.New("return data out of bounds") + ErrGasUintOverflow = errors.New("gas uint64 overflow") + ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") + ErrNonceUintOverflow = errors.New("nonce uint64 overflow") + ErrAddrProhibited = errors.New("prohibited address cannot be sender or created contract address") )