From 205c9f2ef05b809e8fbf0026fae990075b122043 Mon Sep 17 00:00:00 2001 From: circle-github-action-bot Date: Thu, 21 May 2026 12:24:38 +0000 Subject: [PATCH] chore: sync to arc-node - 1cb496d15b50ee5588035feb619186459c14b39a chore: sync from main@76a051f0f537f1f075d57c74f42305620a1... by circle-github-action-bot <[internal]> - 20fd6b17772b93832e573ad4b1fe65e11a714f92 chore: sync from main@60160bb1732fe95238383bf6a5d08698059... by circle-github-action-bot <[internal]> - 1d567cad660b2ce9bc7306bbff94af79a3515a3c chore: sync from main@44eed3cf693b01951df1662671c7f18e090... by circle-github-action-bot <[internal]> - e638b34e5ad8bcac1a5eecfd4eb0193f286786db chore: sync from main@01074a905e69604533e259464438a658291... by circle-github-action-bot <[internal]> - e96b8dd9df9ad5808a61724d973903e1012c2631 chore: sync from main@250c5e7ded6a2a275806f43c5abc470d851... by circle-github-action-bot <[internal]> - e68abf2c2e9311226b7204ba8968bd682bc4f927 chore: sync from main@a664dc6d97a8897513640141548d6b01add... by circle-github-action-bot <[internal]> - 2ac9f659c2d81e8565dab7c3f45277da3aeccc32 chore: sync from main@8ad6031f2c04dc12f1224f9637008a9394b... by circle-github-action-bot <[internal]> - 177c0f903f7334d18abd2b894b5579553be72779 chore: sync from main@9813b75209ca3d90ce9da3fb3ac52acaa89... by circle-github-action-bot <[internal]> - adcede9e30dea3bd1ae8da4dad6a743609d188c8 chore: sync from main@502b4c070d69ee21f464adfab2a01d12502... by circle-github-action-bot <[internal]> - bd7c9ec7fb264cf829fcc5e4f382d3f92488a2a9 chore: sync from main@a157d435fbf247a86e0a56c2a8a1a5a3cf7... by circle-github-action-bot <[internal]> - d11d041e827014f53d8d76063711bcf1e82ec028 chore: sync from main@7227417bf29513efb3b90938db16fea4296... by circle-github-action-bot <[internal]> - e12702fed5a03ba400d147e8697327e7a771ae79 chore: sync from main@88dd1065b89aadd293b24fbcc80116e349b... by circle-github-action-bot <[internal]> - 291432bae06e1b38e6b068ec168ecc71cab043dc chore: sync from main@fdf4bc1f9f9a5bfd43c27c8247b61e65754... by circle-github-action-bot <[internal]> - 62cebc5daa9144b349974c6bc0a6c7495e2116b8 chore: sync from main@a3e67b1240ae41a1a9e9939ee4f7d443089... by circle-github-action-bot <[internal]> - 3479354a9d8216045530f1963c5d9d6aabbf503a fix(ci): resolve attestation failures in Docker build wor... by circle-github-action-bot <[internal]> - adeda17f91b862c90379e0e79302f8215a30422c chore: sync from main@8aa29d4674aeafc387286bcbe351e52a3c6... by circle-github-action-bot <[internal]> - 3bf944219512fc6527b262a75ea04d4d75cead0b chore: sync from main@940bc43c399e5e484a79c1fe02dea697264... by circle-github-action-bot <[internal]> - 4e0649f4e07f1fdcc84ff1a785ab1776d57d6ac2 chore: sync from main@cb3d9264088e95c846b690bd1423e9d7c08... by circle-github-action-bot <[internal]> - e5d026d1418a47a9bb37223199f1b59b70d1bf8e chore: sync from main@d86cb94fb29ba5bcefa3225884a75bc20a8... by circle-github-action-bot <[internal]> - ee4bfcf84332ca175886a2932ad65f395be35ac5 chore: sync from main@34b9b7a6fc281846a2ee13bd541ec119c56... by circle-github-action-bot <[internal]> - 399961fc1cdda863dd689899d04f8699588804be chore: sync from main@5791a7cd77b7ecfab1d92f7431d35b6c6ce... by circle-github-action-bot <[internal]> - 5ae30a8833eee29d32e9b40d90b3ce5b8530c990 chore: sync from main@3588ea94a31f211a133ceb7720e31d94502... by circle-github-action-bot <[internal]> - 0cbce309f786c0c477486ab8f851993ed952fc3c chore: sync from main@8a35d4a10f6fad51901cfa8929435b0b90d... by circle-github-action-bot <[internal]> - 0a096464090acd37013d08f6fe818c67710cdbb0 chore: sync from main@1b549b00755f7242b0af57c0f19aedf838d... by circle-github-action-bot <[internal]> - 4c10725e0885855717090300b3ea92322adda960 chore: sync from main@54fc12f37aba4a94a0b2485bae4a76fe2c9... by circle-github-action-bot <[internal]> - 17ec0c8a569346a0969a1ebf5600d71a445ba114 chore: sync from main@074e3961a38ae6518c89e1f61dc5cc250ef... by circle-github-action-bot <[internal]> - 2c55947fa19ef83b44f8cab0b92c0a07d4157787 chore: sync from main@6e5a9d4e4ad949551be29a587f15a4c759c... by circle-github-action-bot <[internal]> - 023762613a3a4f6a06279fb68e2908faa76b4351 chore: sync from main@2117d033739fb0a7103a058665b4c9e3857... by circle-github-action-bot <[internal]> - c057bbffde2a2ac504e78e88f9bd6269bbb424dc chore: sync from main@fb98e5c6501934235797c2d13fa74b332ba... by circle-github-action-bot <[internal]> - fa4b8ec512d9cef8a3625ff85c0518519388899f chore: sync from main@64f09104a53d23904e0ad13de73271db1a4... by circle-github-action-bot <[internal]> - 7019f027442ade248fb34f48a769fa3965a8a910 chore: sync from main@4f9bdfdbfd03aad21665985b6a5e6629696... by circle-github-action-bot <[internal]> - 6f6a75d159faf336ef5446bdcc93f6c911ff9f76 ci(NoStory): add public binary release workflow by circle-github-action-bot <[internal]> - 75f7dd685afd6b4d61991b7eb33379b27cb355e6 chore: sync from main@149bd046971bfa66643b3353629d7366ce0... by circle-github-action-bot <[internal]> - 5b708c96360d631c46ee2d09877f40007b73c91a chore: sync from main@849d6d00107e395b9bf17674a1b35ea2323... by circle-github-action-bot <[internal]> - 8304489d61f55f470c181cc3d7221e7671d80055 chore: sync from main@a913a6e673b17e93539cc943d9226b19fc3... by circle-github-action-bot <[internal]> - f0bfbbdd5dc861b8e72bbb1bb51343fc09620094 chore: sync from main@29bd97d1e653be8f210fe52f90bfe3577c9... by circle-github-action-bot <[internal]> - f9d5b087a7877e44b344340c3507e71fe4c12d31 chore: sync from main@c0340e06532ed3b1ea0bcd0a2b9b1f16581... by circle-github-action-bot <[internal]> - d47fad7827a7e730b355476ef8b047fb7f248e2e chore: sync from main@ae53603086f31148cd1b76a38ffaef54e67... by circle-github-action-bot <[internal]> - 6c2c2e21b653ceaf099ad9302ebf5064fbc1ae1d chore: sync from main@f6a449878ed4770fd4d5a56cd30559bff0b... by circle-github-action-bot <[internal]> - dd32a81c04a9971ee173c40c0b598e58aa645341 chore: sync from main@a95fc9fa8639af94b104f67a007c0d361de... by circle-github-action-bot <[internal]> - 9d404a2e656358f44d519dd80701318ebc759b2e chore: sync from main@fd124ceeb9a90df5849827a595bba92608a... by circle-github-action-bot <[internal]> - c6aad7a1ac8d0437488ad57984219e46e49d0134 chore: sync from main@84d6ad948545bdfd2de33cdd53d4d0bd59d... by circle-github-action-bot <[internal]> - 3de0d0bcfef984e38ef0577311f76e880627e417 chore: sync from main@c189e2b04f91abc2ee33d84f1d2184e2379... by circle-github-action-bot <[internal]> - 8ec70a350c0000520302553494ced11ae7c92245 chore: sync from main@1a94334dd6dfa403e281a89da83373c5e19... by circle-github-action-bot <[internal]> - 20361bb8b9295d1087a24a50d7ec1b1c5f91a30e chore: sync from main@b57e385ce79c8cdf3eeff69a7a863fee864... by circle-github-action-bot <[internal]> - c6f5dd63b4e4f6bf162d702f7b0280ac2e40af16 chore: sync from main@3f03201132d08debb983d9133bd073deacd... by circle-github-action-bot <[internal]> - 36c08ec2903d8745fb68d26cc273432cff68f7f7 chore: sync from main@bf67fcf737d87796327ab1b761063a3163f... by circle-github-action-bot <[internal]> - b91443a3218c2fb6dfa337191a5921e8f3a07ccb chore: sync from main@fe0af2f37f2b51a65da8233e63bd14b81f5... by circle-github-action-bot <[internal]> - 2442c1d72eeb8804c168352811b4d85a7762f7c7 chore: sync from main@ea7b852d520247f23a2c0c15748c78e9a43... by circle-github-action-bot <[internal]> - 6a50919a170153c84c19ee8f8d024867a4efd02e chore: sync from main@5486ae04e83d2b72274602ff535eccfe6a1... by circle-github-action-bot <[internal]> - bea14eccaa6e02cee562d8a63084c6da7a301527 chore: sync from main@cbbfc83bcc04ab6ec6f1a41cf3358a38e84... by circle-github-action-bot <[internal]> - ad2d754ffcfff9a44ba4ac556d5348234b933aae chore: sync from main@00abcf2eb3983f3c0dbb3c59e871e7d8338... by circle-github-action-bot <[internal]> - ba11409b8c4f526cc44ac6c3edee251d0483cd97 chore: sync from main@0fe5e21e332a0f8378c6bfca3b1558fe96f... by circle-github-action-bot <[internal]> - 21ca94a2322b590db23b13ef9d5578c382b56c5b chore: sync from main@5c97f1fcc67c60aa24a817bbed144ab52b0... by circle-github-action-bot <[internal]> - d46d77ee93f1a69628b515d187b742cf5bd024e9 chore: sync from main@688206ae733be82d8c9d573672d43c7913a... by circle-github-action-bot <[internal]> - 4fea2d94517018f3c7e161f681e2394a2a60f4ed chore: sync from main@63ec0aa0b651ec39ad55c5d11ca28cf3879... by circle-github-action-bot <[internal]> - 061094972a545bf145c3fd1eb1e77ae5d42daef3 chore: sync from main@4d424867d985f9272030bf38512fc4daa34... by circle-github-action-bot <[internal]> - db9c32b831c972f2e69e49d910ed8a2c93032e3f chore: sync from main@3990dd2ef942b6bd0002d03b3f113dfd68b... by circle-github-action-bot <[internal]> - 25b1056332146a0b5ae88457fd512856b364d837 chore: sync from main@5319bc14c65192cb798dde24402520cab03... by circle-github-action-bot <[internal]> - 7957427a55e8f2df2c08c2d6ed55aac742f861f6 chore: sync from main@df8a0712979d217b7403c5e0f59532e1b2b... by circle-github-action-bot <[internal]> - 151d70301d934dc88fe53ed1c751eff4268aad70 chore: sync from main@81445b37dcf83e486942dcd21753a3884c4... by circle-github-action-bot <[internal]> - 95863b95d5e54716a5a0f8d6a846c210ae61c0e8 chore: sync from main@070d94218b243008f28e1cde64bf5bf84a2... by circle-github-action-bot <[internal]> - b5ac5db32d1be5bfbc0f397e1a13627bef4e86d7 chore: sync from main@a72d625f94cb1168239f6be15c5bf149d42... by circle-github-action-bot <[internal]> - 704fd687efe6a91968955dd93fb5b1ecc43361be chore: sync from main@845e0178122511e9192645497d10c647817... by circle-github-action-bot <[internal]> - 55282dc903aa7d5e318628a2e3ea234f840230e1 chore: sync from main@d8ae67e23b40a0764996913bce3fe04c541... by circle-github-action-bot <[internal]> - 331c0868e870f44edcb966942e0e4c0231b26ec3 chore: sync from main@92ed1fb989c63c53137a9946af45e914391... by circle-github-action-bot <[internal]> - 557ee696cc3c32bc1b09bc85de5c596b8e14ea89 chore: sync from main@7a33dd2d6285ad607ff0ec30b2b25c8ca3b... by circle-github-action-bot <[internal]> - 235999d06e2fa3e1a13c06febf3d75f68ecff802 chore: sync from main@df7da4bff7812ca47d1eafd484a19937c0e... by circle-github-action-bot <[internal]> - b773eb9665f0fe16f85b25ae2caaefad3ccde8a0 chore: sync from main@169bdd4a8db7d9798f9c7a004e16889123a... by circle-github-action-bot <[internal]> - 4dfbd43aae378f003205ae0fc09ed729ccd6bdc9 chore: sync from main@3038861d5a7edc12d17665ceee3c605eeaa... by circle-github-action-bot <[internal]> - f0729c2c2245012a97deff734e0fa3f6ce0c1107 chore: sync from main@01bd5f07efff7b20eb6d76fbaf3077187ef... by circle-github-action-bot <[internal]> - 1dc64fbbd9095f74c6374b95d025575079b28672 chore: sync from main@571a4e15276ad44f946706b669bf9d082ec... by circle-github-action-bot <[internal]> - 454542491a573c8a84b363ca3abcf495dd17d5f0 chore: sync from main@2cae6619985f678e55fde8736c4d26149cf... by circle-github-action-bot <[internal]> - c54db9ec758e75d040c57dc2465e486d5d650fcf chore: sync from main@14c1f64fc734cb97e2b687f575384eedb31... by circle-github-action-bot <[internal]> - a8c8a4ef213998acc53bb6b1203b16590b83c5b0 chore: sync from main@b3e8c8fca4ea48d68588373698e4c43afcb... by circle-github-action-bot <[internal]> - 864a3a0df929fafa83ae481a8470208d783c7ca6 chore: sync from main@353d747ab0ebd319e498eff052e96a11b53... by circle-github-action-bot <[internal]> - 0c8cef867c4a319d1ba26b6bb350454b6be4e537 chore: sync from main@52d91ec54a43995dca98233ebb1d6ae922f... by circle-github-action-bot <[internal]> - 3a21177918e5029c0c92290327eec1dbbaa1c13b chore: sync from main@6578fbbc5cc9c7cf7b7ea67d3506db9f382... by circle-github-action-bot <[internal]> - 9324da19c6f5ba016442ea9d65668300ea574477 chore: sync from main@c1f67cdd868b8597a45823bbd0578546173... by circle-github-action-bot <[internal]> - e8d14c332410742731cc2d623d9bfdac5a897661 chore: sync from main@db199c3cc594ede0606f99ce74649e963a0... by circle-github-action-bot <[internal]> - a5504ef69069b6239a16eea3537e2dc26ef7b754 chore: sync from main@6fc528897bfbf01f23740f5a6054324a425... by circle-github-action-bot <[internal]> - 31f0cd12b666622449d7f84ed1982d3d641f4e99 chore: sync from main@fa1384e96631935cbd6c54ff44a7c95dac1... by circle-github-action-bot <[internal]> - 1c1c13521b8acce34a2436c97870ee8489977cfe chore: sync from main@75873fa88c15fe661ce43a5854b41cd695a... by circle-github-action-bot <[internal]> - 02cb46b93f679c493a6b6b1657bbaad55b5a294f chore: sync from main@600be094d415652ab05560b4e2ad47c37bc... by circle-github-action-bot <[internal]> GitOrigin-RevId: 1cb496d15b50ee5588035feb619186459c14b39a --- .dockerignore | 1 + .github/workflows/build-docker.yaml | 19 +- .gitignore | 4 - BREAKING_CHANGES.md | 80 + CHANGELOG.md | 143 + Cargo.lock | 162 +- Cargo.toml | 42 +- Makefile | 5 +- arcup/arcup | 504 +- arcup/install | 21 +- arcup/test_arcup.sh | 829 +++ assets/localdev/genesis.config.ts | 15 +- assets/localdev/genesis.json | 541 +- assets/mainnet/.gitignore | 4 + assets/mainnet/config.json | 230 + assets/mainnet/genesis.config.ts | 177 + assets/mainnet/genesis.json | 1583 ++++++ contracts/scripts/Addresses.sol | 2 +- .../scripts/ProtocolConfigManagement.s.sol | 8 + contracts/scripts/ValidatorManagement.s.sol | 28 +- contracts/src/batch/IMulticall3From.sol | 13 +- contracts/src/batch/Multicall3From.sol | 53 +- contracts/src/common/roles/Pausable.sol | 15 +- contracts/src/pq/IPQ.sol | 12 +- .../validator-manager/ValidatorRegistry.sol | 14 +- .../validator-manager/roles/Controller.sol | 13 +- contracts/test/batch/Multicall3From.t.sol | 124 + .../test/protocol-config/ProtocolConfig.t.sol | 27 + .../test/scripts/ValidatorManagement.t.sol | 23 + .../validator-manager/ValidatorRegistry.t.sol | 12 +- .../validator-manager/roles/Controller.t.sol | 29 +- .../src/repositories/decided_blocks.rs | 1 - crates/eth-engine/Cargo.toml | 1 + crates/eth-engine/src/abi_utils.rs | 217 +- crates/eth-engine/src/engine.rs | 53 +- crates/eth-engine/tests/integration.rs | 1 + crates/evm-node/src/node.rs | 19 +- crates/evm-node/src/rebroadcast.rs | 2 +- crates/evm-node/src/rpc_middleware.rs | 334 +- crates/evm-specs-tests/src/adapter.rs | 28 +- crates/evm-specs-tests/src/roots.rs | 82 +- crates/evm/src/evm.rs | 4623 ++++++++++------- crates/evm/src/executor.rs | 50 +- crates/evm/src/handler.rs | 223 +- crates/evm/src/subcall.rs | 4 + crates/evm/src/subcall_test.rs | 124 +- crates/execution-config/Cargo.toml | 2 + .../src/addresses_denylist.rs | 2 +- crates/execution-config/src/call_from.rs | 2 +- crates/execution-config/src/chainspec.rs | 585 ++- crates/execution-config/src/defaults.rs | 102 +- crates/execution-config/src/hardforks.rs | 282 +- .../execution-config/src/protocol_config.rs | 5 +- .../execution-e2e/src/actions/assertions.rs | 97 +- .../src/actions/send_transaction.rs | 2 +- crates/execution-e2e/src/chainspec.rs | 26 +- crates/execution-e2e/tests/base_fee.rs | 7 +- .../execution-e2e/tests/block_hash_history.rs | 9 +- crates/execution-e2e/tests/blocklist_gas.rs | 102 - crates/execution-e2e/tests/eip7708_gas.rs | 250 - .../tests/eip7708_hardfork_transition.rs | 17 +- .../tests/eip7708_native_transfer.rs | 30 +- .../tests/eip7708_zero_address.rs | 9 +- .../tests/hardfork_transition.rs | 19 +- .../tests/native_transfer_balance.rs | 4 +- crates/execution-e2e/tests/pq_precompile.rs | 16 +- crates/execution-e2e/tests/transaction.rs | 3 +- crates/execution-payload/src/payload.rs | 124 +- crates/execution-txpool/src/pool.rs | 2 +- crates/execution-txpool/src/validator.rs | 18 +- crates/execution-validation/src/consensus.rs | 41 +- crates/malachite-app/src/app.rs | 13 +- crates/malachite-app/src/handlers/decided.rs | 127 +- .../malachite-app/src/handlers/get_value.rs | 2 +- crates/malachite-app/src/handlers/mod.rs | 3 + .../src/handlers/process_synced_value.rs | 160 +- .../src/handlers/received_proposal_part.rs | 67 +- .../src/handlers/started_round.rs | 175 +- .../malachite-app/src/handlers/test_utils.rs | 52 + crates/malachite-app/src/hardcoded_config.rs | 109 +- crates/malachite-app/src/metrics/app.rs | 74 + crates/malachite-app/src/metrics/mod.rs | 4 +- .../src/metrics/validator_set.rs | 73 + crates/malachite-app/src/node.rs | 58 +- crates/malachite-app/src/payload.rs | 66 +- crates/malachite-app/src/proposal_parts.rs | 1 + crates/malachite-app/src/rpc/routes.rs | 2 +- crates/malachite-app/src/streaming.rs | 96 +- crates/malachite-app/src/validator_proof.rs | 3 +- crates/node/Cargo.toml | 1 + crates/node/README.md | 3 +- crates/node/src/main.rs | 15 +- crates/node/tests/native_transfer.rs | 94 +- crates/precompiles/Cargo.toml | 2 +- crates/precompiles/src/call_from.rs | 34 +- crates/precompiles/src/helpers.rs | 214 +- crates/precompiles/src/lib.rs | 22 +- crates/precompiles/src/macros.rs | 12 +- .../precompiles/src/native_coin_authority.rs | 259 +- crates/precompiles/src/native_coin_control.rs | 265 +- crates/precompiles/src/pq.rs | 33 +- crates/precompiles/src/subcall.rs | 17 +- crates/precompiles/src/system_accounting.rs | 313 +- crates/quake/Cargo.toml | 2 +- crates/quake/README.md | 161 +- crates/quake/docs/web-architecture.md | 312 ++ crates/quake/files/web_index.html | 2145 ++++++++ crates/quake/scenarios/examples/14nodes.toml | 81 + .../scenarios/nightly-tx-propagation.toml | 97 + crates/quake/scripts/aws-resources.sh | 1077 ++++ crates/quake/src/clean.rs | 294 +- crates/quake/src/genesis.rs | 138 +- crates/quake/src/infra/local.rs | 57 +- crates/quake/src/infra/mod.rs | 9 + crates/quake/src/infra/remote.rs | 46 +- crates/quake/src/infra/terraform.rs | 80 +- crates/quake/src/latency.rs | 13 +- crates/quake/src/load.rs | 483 ++ crates/quake/src/main.rs | 251 +- crates/quake/src/manifest.rs | 384 +- crates/quake/src/manifest/generate.rs | 82 +- crates/quake/src/manifest/raw.rs | 62 + crates/quake/src/mcp.rs | 40 +- crates/quake/src/node.rs | 2 +- crates/quake/src/nodes.rs | 216 +- crates/quake/src/report.rs | 2 +- crates/quake/src/rpc/valset_manager.rs | 2 +- crates/quake/src/setup.rs | 198 +- crates/quake/src/testnet.rs | 618 +-- crates/quake/src/tests/arc_node.rs | 3 +- crates/quake/src/tests/malformed_validator.rs | 282 + crates/quake/src/tests/mod.rs | 14 +- crates/quake/src/tests/sanity.rs | 2 +- crates/quake/src/tests/tx.rs | 202 +- crates/quake/src/web.rs | 1793 +++++++ crates/quake/templates/local/compose.yaml.hbs | 28 + .../templates/remote/compose-node.yaml.hbs | 22 +- crates/quake/terraform/cc.tf | 7 + crates/quake/terraform/nodes.tf | 38 +- crates/quake/terraform/project.tf | 8 +- crates/quake/terraform/variables.tf | 40 +- crates/remote-signer/src/lib.rs | 2 +- crates/remote-signer/src/provider.rs | 146 +- crates/shared/src/metrics/mod.rs | 1 + crates/shared/src/metrics/validator_set.rs | 81 + crates/signer/Cargo.toml | 1 + crates/signer/src/lib.rs | 120 +- crates/signer/src/local.rs | 94 +- crates/signer/tests/cross.rs | 2 +- crates/spammer/Dockerfile | 16 +- crates/spammer/src/accounts.rs | 2 +- crates/spammer/src/cli.rs | 307 +- crates/spammer/src/config.rs | 8 +- crates/spammer/src/lib.rs | 2 +- crates/spammer/src/result_tracker.rs | 89 +- crates/spammer/src/sender.rs | 42 +- crates/spammer/src/spammer.rs | 54 +- crates/spammer/src/ws.rs | 32 +- crates/test/checks/src/lib.rs | 2 + crates/test/checks/src/metric.rs | 75 + crates/test/framework/README.md | 71 +- crates/test/framework/src/lib.rs | 5 + crates/test/framework/src/scenarios.rs | 29 +- crates/test/framework/tests/basic.rs | 2 +- crates/test/integration/Cargo.toml | 35 + crates/test/integration/README.md | 76 + crates/test/integration/src/bridge.rs | 170 + crates/test/integration/src/lib.rs | 36 + crates/test/integration/src/runner.rs | 787 +++ crates/test/integration/tests/basic.rs | 94 + crates/types/Cargo.toml | 1 + crates/types/src/evidence.rs | 72 +- crates/types/src/signing.rs | 31 +- .../types/tests/unit/certificates/commit.rs | 28 +- crates/types/tests/unit/certificates/mod.rs | 25 +- crates/types/tests/unit/certificates/polka.rs | 20 +- crates/types/tests/unit/certificates/round.rs | 59 +- docs/ARCHITECTURE.md | 2 +- ...governance-configuration-and-validation.md | 3 +- docs/adr/0004-base-fee-validation.md | 22 +- docs/installation.md | 78 +- docs/running-an-arc-node.md | 32 +- hardhat.config.ts | 4 + scripts/engine-bench-report.py | 698 ++- scripts/genesis/AccountCreator.ts | 13 +- scripts/genesis/Denylist.ts | 11 +- scripts/genesis/NativeFiatToken.ts | 15 +- scripts/genesis/ProtocolConfig.ts | 34 +- scripts/genesis/ValidatorManager.ts | 194 +- scripts/genesis/addresses.ts | 20 +- scripts/genesis/genesis.ts | 27 +- scripts/genesis/types.ts | 19 + scripts/hardhat/tasks/genesis.ts | 33 +- scripts/hardhat/tasks/query-state.ts | 103 +- scripts/register-validator.sh | 16 +- scripts/scenarios/README.md | 13 + scripts/scenarios/nightly-tx-propagation.sh | 396 ++ scripts/scenarios/nightly-upgrade.sh | 79 +- tests/helpers/Denylist.ts | 14 +- tests/helpers/NativeCoinControl.ts | 23 +- tests/localdev/7702.test.ts | 2 +- tests/localdev/NativeCoinAuthority.test.ts | 24 +- tests/localdev/NativeCoinControl.test.ts | 29 +- tests/localdev/NativeFiatToken.test.ts | 253 +- tests/localdev/PrecompileCode.test.ts | 161 + tests/localdev/SystemAccounting.test.ts | 11 +- tests/localdev/evm_compatibility.test.ts | 22 +- tests/localdev/genesis.test.ts | 78 +- tests/localdev/native_transfer.test.ts | 41 +- tests/localdev/per_validator_fees.test.ts | 78 +- tests/localdev/subcall.test.ts | 36 +- tests/simulation/DelegateCall.test.ts | 6 +- tests/simulation/NativeFiatToken.test.ts | 135 +- tests/simulation/ProtocolConfig.test.ts | 7 +- tests/simulation/ValidatorManager.test.ts | 13 + 215 files changed, 23785 insertions(+), 5488 deletions(-) create mode 100644 BREAKING_CHANGES.md create mode 100644 CHANGELOG.md create mode 100755 arcup/test_arcup.sh create mode 100644 assets/mainnet/.gitignore create mode 100644 assets/mainnet/config.json create mode 100644 assets/mainnet/genesis.config.ts create mode 100644 assets/mainnet/genesis.json delete mode 100644 crates/execution-e2e/tests/blocklist_gas.rs delete mode 100644 crates/execution-e2e/tests/eip7708_gas.rs create mode 100644 crates/malachite-app/src/handlers/test_utils.rs create mode 100644 crates/malachite-app/src/metrics/validator_set.rs create mode 100644 crates/quake/docs/web-architecture.md create mode 100644 crates/quake/files/web_index.html create mode 100644 crates/quake/scenarios/examples/14nodes.toml create mode 100644 crates/quake/scenarios/nightly-tx-propagation.toml create mode 100755 crates/quake/scripts/aws-resources.sh create mode 100644 crates/quake/src/load.rs create mode 100644 crates/quake/src/tests/malformed_validator.rs create mode 100644 crates/quake/src/web.rs create mode 100644 crates/shared/src/metrics/validator_set.rs create mode 100644 crates/test/checks/src/metric.rs create mode 100644 crates/test/integration/Cargo.toml create mode 100644 crates/test/integration/README.md create mode 100644 crates/test/integration/src/bridge.rs create mode 100644 crates/test/integration/src/lib.rs create mode 100644 crates/test/integration/src/runner.rs create mode 100644 crates/test/integration/tests/basic.rs create mode 100755 scripts/scenarios/nightly-tx-propagation.sh create mode 100644 tests/localdev/PrecompileCode.test.ts diff --git a/.dockerignore b/.dockerignore index 926f969..36aaf45 100644 --- a/.dockerignore +++ b/.dockerignore @@ -59,6 +59,7 @@ docker-bake.hcl # CI/CD and tooling .github/ +.agents/ .claude/ .gitlab-ci.yml .travis.yml diff --git a/.github/workflows/build-docker.yaml b/.github/workflows/build-docker.yaml index d53126d..ec92ba9 100644 --- a/.github/workflows/build-docker.yaml +++ b/.github/workflows/build-docker.yaml @@ -71,6 +71,7 @@ jobs: ${{ matrix.image }}.tags= ${{ matrix.image }}.output=type=oci,tar=false,dest=/tmp/image env: + BUILDX_NO_DEFAULT_ATTESTATIONS: 1 GITHUB_TOKEN: ${{ github.token }} GIT_COMMIT_HASH: ${{ github.sha }} GIT_VERSION: ${{ github.ref_name }} @@ -104,17 +105,23 @@ jobs: env: IMAGE: ${{ env.REGISTRY }}/${{ env.REGISTRY_NAMESPACE }}/${{ matrix.image }} BAKE_METADATA: ${{ steps.build.outputs.metadata }} + MATRIX_IMAGE: ${{ matrix.image }} + MATRIX_PLATFORM: ${{ matrix.platform }} run: | - DIGEST=$(echo "${BAKE_METADATA}" | jq -r '."${{ matrix.image }}"."containerimage.digest"') + DIGEST=$(echo "${BAKE_METADATA}" | jq -r --arg img "${MATRIX_IMAGE}" '.[$img]."containerimage.digest"') if [ -z "${DIGEST}" ] || [ "${DIGEST}" = "null" ]; then echo "::error::Failed to extract digest from build metadata" exit 1 fi - skopeo copy "oci:/tmp/image" "docker://${IMAGE}@${DIGEST}" + skopeo copy "oci:/tmp/image" "docker://${IMAGE}@${DIGEST}" --digestfile /tmp/push-digest rm -rf /tmp/image - PLATFORM_SLUG=$(echo "${{ matrix.platform }}" | tr '/' '-') - mkdir -p "/tmp/digests/${{ matrix.image }}" - echo "${DIGEST}" > "/tmp/digests/${{ matrix.image }}/${PLATFORM_SLUG}" + REGISTRY_DIGEST=$(cat /tmp/push-digest) + if [ "${DIGEST}" != "${REGISTRY_DIGEST}" ]; then + echo "::warning::Bake metadata digest ${DIGEST} differs from registry digest ${REGISTRY_DIGEST}" + fi + PLATFORM_SLUG=$(echo "${MATRIX_PLATFORM}" | tr '/' '-') + mkdir -p "/tmp/digests/${MATRIX_IMAGE}" + echo "${REGISTRY_DIGEST}" > "/tmp/digests/${MATRIX_IMAGE}/${PLATFORM_SLUG}" - name: Upload digest if: ${{ github.event_name == 'push' }} @@ -188,11 +195,13 @@ jobs: echo "image-with-digest=${IMAGE}@${MANIFEST_DIGEST}" >> "$GITHUB_OUTPUT" - name: Generate SBOM + continue-on-error: true uses: anchore/sbom-action@17ae1740179002c89186b61233e0f892c3118b11 # v0.23.0 with: image: ${{ steps.manifest.outputs.image-with-digest }} artifact-name: sbom-${{ matrix.image }}.spdx.json output-file: sbom-${{ matrix.image }}.spdx.json + upload-release-assets: false - name: Attest build provenance uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0 diff --git a/.gitignore b/.gitignore index e94b104..52df018 100644 --- a/.gitignore +++ b/.gitignore @@ -117,10 +117,6 @@ terraform.tfvars* .claude/memory/ .claude/worktrees/ .claude/todo.md -.claude/skills/** -!.claude/skills/ -!.claude/skills/*/ -!.claude/skills/*/** .planning deployments/certs/* diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md new file mode 100644 index 0000000..069f4bb --- /dev/null +++ b/BREAKING_CHANGES.md @@ -0,0 +1,80 @@ +# Breaking Changes + +Records breaking changes between tagged public releases of [`arc-node`](https://github.com/circlefin/arc-node). + +Each bullet is prefixed with a flag identifying the kind of breaking change: + +- `[CLI]` -- CLI flag added, renamed, removed, or made required. +- `[Config]` -- default value, environment variable, or manifest field change. +- `[Format]` -- log, metric label, or serialized output format change that breaks parsers. + +Entries are split by audience. A change appears under `### For Validators` when validator-mode operation must change; otherwise it appears under `### For Node Operators`. A change requiring both audiences to act appears in both sections (rare). + +Compare and release-notes links resolve once the corresponding tag is published at [`circlefin/arc-node`](https://github.com/circlefin/arc-node). + +## [v0.7.1] + +**Changes:** [v0.7.0...v0.7.1](https://github.com/circlefin/arc-node/compare/v0.7.0...v0.7.1) -- [release notes](https://github.com/circlefin/arc-node/releases/tag/v0.7.1) + +*Note: testnet node operators must use v0.7.1 before timestamp `1779894517` (2026-05-27 15:08:37 UTC), when Zero5/Zero6 activate on testnet. Earlier versions are not supported.* + +### For Node Operators + +- **[Config] `arc-node-execution`: EL RPC connection defaults tightened.** + - `--rpc.max-connections` default: `500` -> `250`. + - `--rpc.max-subscriptions-per-connection` default: `1024` -> `32`. + - Both flags remain accepted on `arc-node-execution`; operators that need the previous behavior must pass them explicitly. The new defaults bound a WebSocket subscription fan-out memory pressure path; real-world clients typically multiplex around five subscriptions per socket and are unaffected. + +## [v0.7.0] + +**Changes:** [v0.6.0...v0.7.0](https://github.com/circlefin/arc-node/compare/v0.6.0...v0.7.0) -- [release notes](https://github.com/circlefin/arc-node/releases/tag/v0.7.0) + +*Note: mainnet node operators must use v0.7.0. Earlier versions are not supported.* + +### For Node Operators + +- **[CLI] `arc-node-execution`: pending-tx flag rename and default flip.** + - Old (`v0.6.0`): `--arc.hide-pending-txs` (opt-in to hide, default exposed). + - New (`v0.7.0`): `--arc.expose-pending-txs` (opt-in to expose, default hidden). + - Adds `--public-api`, a convenience flag for externally-exposed nodes that forces hiding and warns if `--http.api` / `--ws.api` expose namespaces outside `{eth, net, web3, rpc}`. + - Nodes that relied on the default exposure must now pass `--arc.expose-pending-txs` or adopt the new secure-by-default behavior. + +- **[Config] `arc-node-consensus`: `--execution-persistence-backpressure-threshold=0` is rejected at startup.** + - Old (`v0.6.0`): `0` was accepted and caused indefinite stalling. + - New (`v0.7.0`): the value must be `> 0`; the CL refuses to start otherwise. + - Backpressure trigger semantics also changed: the gap now triggers when it *reaches* the threshold (previously *exceeds*). + - The default (`16`) is unchanged. Only operators who set this flag explicitly to `0` (now rejected) or who monitor the exact threshold value need to act. + +- **[Config] CL default `--log-level` changed from `debug` to `info`.** + Not a config syntax change, but a behavior change that affects log volume and content. Pass `--log-level debug` explicitly if your tooling depends on debug-level output. + +- **[Format] libp2p protocol identifiers on mainnet are Arc-branded.** + - The CL on mainnet (chain id `5042`) advertises Arc-branded libp2p protocol IDs from v0.7.0. A pre-v0.7.0 CL **cannot** peer with a v0.7.0 CL on mainnet. + - Operators must upgrade all mainnet nodes before or simultaneously with the v0.7.0 rollout; staged rollouts that leave a subset of nodes on `v0.6.x` will fragment the mainnet mesh. + - Testnet (`5042002`) protocol IDs are unchanged in this release. + +- **[Format] Address and public-key rendering uniformly switched to `0x`-prefixed lowercase hex.** + - Logs, metrics, and JSON-RPC responses now use a single canonical format (signatures continue to use Base64). EIP-55 checksums are not used; Prometheus labels are case-sensitive. + - Log parsers, alerting rules, and dashboards built against the previous mixed formats (EIP-55 checksummed, non-prefixed hex, etc.) must be updated. + +### For Validators + +- **[CLI] `arc-node-consensus`: `--validator` is required for block signing and voting.** + - The CL now runs as a non-voting full node unless `--validator` is explicitly set. + - The flag did not exist in `v0.6.0`. Validator operators upgrading from `v0.6.0` must add `--validator` to their startup command or they will stop participating in consensus. + +- **[CLI] `arc-node-consensus`: `--suggested-fee-recipient` is required when `--validator` is set.** + - Enforced at startup. Omitting the recipient with `--validator` set causes the binary to refuse to start. + - **Important**: this address is where block rewards (tx fees, in USDC) collect after successful proposals are made. + - Example: + + ``` + arc-node-consensus start \ + --validator \ + --suggested-fee-recipient 0xYOUR_ADDRESS \ + ... + ``` + +## [v0.6.0] + +Baseline -- initial public open-source release. Treat the [`v0.6.0`](https://github.com/circlefin/arc-node/releases/tag/v0.6.0) tag as the reference point for subsequent breaking-change notes. No breaking-change entries are recorded for this release. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..eb607ae --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,143 @@ +# Changelog + +All notable changes to arc-node are documented in this file. + +## [v0.7.1] + +**Changes:** [v0.7.0...v0.7.1](https://github.com/circlefin/arc-node/compare/v0.7.0...v0.7.1) -- [release notes](https://github.com/circlefin/arc-node/releases/tag/v0.7.1) + +*Note: testnet node operators must use v0.7.1 before timestamp `1779894517` (2026-05-27 15:08:37 UTC), when Zero5/Zero6 activate on testnet. Earlier versions are not supported.* + +### For Node Operators + +- **[Config] EL RPC connection defaults tightened.** `--rpc.max-connections` default lowered from `500` to `250`; `--rpc.max-subscriptions-per-connection` default lowered from `1024` to `32`. Operators running tooling that opens many concurrent WebSocket connections, or that subscribes more than 32 times on a single connection, must raise these explicitly on the `arc-node-execution` command line. See [BREAKING_CHANGES.md](./BREAKING_CHANGES.md#v071) for migration details. + +### Features + +- [Shared] Enable global keccak cache and asm-backed keccak +- [Spammer] Expose per-run telemetry from the spammer + +### Fixes + +- [EL] Avoid double-hashing initCode on CREATE2 with non-zero value +- [EL] Activate testnet Zero5/Zero6 by timestamp instead of block height to preserve fork-id compatibility across mixed-version peers + +## [v0.7.0] + +**Changes:** [v0.6.0...v0.7.0](https://github.com/circlefin/arc-node/compare/v0.6.0...v0.7.0) -- [release notes](https://github.com/circlefin/arc-node/releases/tag/v0.7.0) + +### For Node Operators + +*Note: mainnet node operators must use v0.7.0. Earlier versions are not supported.* + +- **[Config] Pending transactions are hidden from RPC by default.** Renamed `--arc.hide-pending-txs` (opt-in to hide) to `--arc.expose-pending-txs` (opt-in to expose) and flipped the default. Added `--public-api`, a convenience flag for externally-exposed nodes that forces hiding and warns if `--http.api` / `--ws.api` expose namespaces outside `{eth, net, web3, rpc}`. +- **[Config] CL default log level changed from `debug` to `info`.** Pass `--log-level debug` explicitly if your monitoring depends on debug-level output. +- **[Config] `--follow` no longer requires `--follow.endpoint` for standard chains.** The CL resolves a default RPC endpoint from the chain id at startup; run `arc-node-consensus start --help` for the per-chain defaults. Explicit `--follow.endpoint` still takes precedence. +- **[CLI] New `--txpool.rebroadcast-interval` flag (EL).** Periodic re-announcement of pending transactions to peers (default `60` seconds, `0` to disable). Recovers from missed gossip announcements. +- **[CLI] New `--pprof.heap-prof` flag (EL and CL).** Enables jemalloc heap profiling on demand when built with `--features pprof`. Heap profiling is now inactive by default. +- **[Config] `--execution-persistence-backpressure-threshold` must be greater than zero** and triggers when the gap *reaches* the threshold (previously *exceeds*). Default is `16` and is unchanged; operators who never set this flag explicitly are unaffected. See [BREAKING_CHANGES.md](./BREAKING_CHANGES.md#v070) for migration details. +- **[API] New `/ready` readiness probe** and `sync_state` field on the CL `/status` endpoint. +- **[CLI] New `arc-node-consensus db rollback` command** (alias: `unwind`) for operator-driven rollback. Dry-run by default; pass `--execute` to commit. `--num-heights` and `--to-height` are mutually exclusive. +- **[Config] Arc mainnet is a named chainspec** (`--chain arc-mainnet`, chain id `5042`). + +### For Validators + +- **[CLI] `--validator` flag is required** for a CL to participate in block signing and voting. Without it, the node runs as a non-voting full node. This flag did not exist in `v0.6.0`. See [BREAKING_CHANGES.md](./BREAKING_CHANGES.md#v070). +- **[CLI] `--suggested-fee-recipient` is required when `--validator` is set.** It is required that this address be set / non-zero. + + ``` + arc-node-consensus start \ + --validator \ + --suggested-fee-recipient 0xYOUR_ADDRESS \ + ... + ``` + +- **[Format] Equivocation evidence log levels raised**: persistence failures promoted from `warn` to `error`, successful persistence from `info` to `warn`. Both include validator addresses for forensics. +- **[API] Validator public key exposed in the CL `/status` endpoint.** +- **[Format] Address and public-key rendering uniformly switched to `0x`-prefixed lowercase hex.** Logs, metrics, and JSON-RPC responses use this single canonical format (signatures continue to use Base64). Tooling that parsed EIP-55 checksummed addresses or non-prefixed hex must be updated. + +### Features + +- [CL] Add `--validator` configuration flag +- [CL] Require `--suggested-fee-recipient` when `--validator` is set +- [CL] Resolve default follow endpoint from chain id +- [CL] Add `/ready` readiness probe and `sync_state` to `/status` +- [CL] Add `db rollback` command (alias: `unwind`) for operator-driven rollback +- [CL] Raise equivocation evidence log levels +- [CL] Add versioned wire encoding for consensus network messages +- [CL] Harden validator-set decoding against malformed public keys +- [CL] Count and log invalid payloads across all storage paths +- [CL] Model consensus fork history; narrow `ForkCondition` to height-only +- [CL] Use Arc-branded libp2p protocol names on mainnet; see [BREAKING_CHANGES.md](./BREAKING_CHANGES.md#v070) for cross-version peering implications +- [CL] Detect EL crashes over IPC and log a diagnostic instead of silently stalling +- [EL] Implement **Zero7** hardfork: `CallFrom` subcall precompile, `Multicall3From`, `Memo` +- [EL] Apply EIP-2929 warm/cold pricing to precompile account loads; see [BREAKING_CHANGES.md](./BREAKING_CHANGES.md#v070) for the gas-estimation impact +- [EL] Add periodic transaction rebroadcast to recover from missed gossip +- [EL] Unconditionally use validator-provided beneficiary addresses +- [EL] Apply `0xef` non-deployable prefix (EIP-3541) to Arc precompile addresses in genesis, preventing EOAs or contracts from being deployed at those addresses +- [EL] EEST fixture runner for EVM spec test validation +- [EL] Register `arc-mainnet` as a named chainspec (Zero3-Zero6 active at block `0`) +- [EL] Finalize mainnet genesis with USDC admin roles, denylist, prefunded ops wallet +- [Shared] Add `--pprof.heap-prof` flag for on-demand heap profiling +- [Shared] Uniformize address and key rendering to `0x`-prefixed lowercase hex +- [Contracts] ProtocolConfig upgrade scripts; remove `rewardBeneficiary` field (proposer-provided fee recipient is authoritative) +- [Contracts] Deploy denylist contract on testnet (mainnet ships it pre-deployed in genesis) +- [Quake] Testnet orchestrator improvements: web topology viewer, node-group support in `load` / `spam`, mesh/health/performance/sanity test runner with report generation, manifest fields for EL/CL CPU and memory limits and `block_gas_limit` +- [Bench] `arc-engine-bench` with IPC and RPC engine transports +- [Bench] Nightly engine bench workflow +- [Spammer] Cache gas estimates for ERC-20 and Guzzler transactions +- [Spammer] Reuse with parallel nonce resync and reduced request timeout +- [Spammer] Improved send-stall visibility + +### Fixes + +- [CL] Prevent stream eviction by colluding validators +- [CL] Propagate `pol_round` as `valid_round` in assembled blocks +- [CL] On restream, look up block by hash and preserve `round` / `valid_round` +- [CL] Fetch validator set at `certificate_height - 1` in `get_certificate_info` +- [CL] Align `RemoteSigningProvider` Ed25519 verification with Malachite +- [CL] Bound repeated proto fields to prevent unbounded allocation +- [CL] Use checked arithmetic in `total_voting_power()` +- [CL] Account for EL earliest block in `GetHistoryMinHeight` +- [CL] Skip persistence wait during sync when block is already present or height decided +- [CL] Acknowledge `AppMsg::Decided` so sync advertises a new tip +- [CL] Mark undecided block `Invalid` on engine validation errors; persist verdict +- [CL] Surface duplicate Init/Fin proposal parts as `InsertResult::Invalid` +- [CL] Improve EL/CL height-mismatch error with actionable guidance +- [CL] Update backpressure semantics +- [EL] Suppress pool-based pending-tx leaks in RPC middleware +- [EL] Charge EIP-2929 cold account access cost in `CallFrom` subcalls +- [EL] Blocklist SLOADs are unmetered on native value transfers +- [EL] Consume all gas for subcall in static context +- [EL] Charge gas for subcall completion phase +- [EL] Strictly decode ABI parameters in precompiles +- [EL] Implement EIP-2200 sentry for `SSTORE` +- [EL] Apply new-account surcharge via precompiles +- [EL] Extend early-revert penalty to auth reverts in Zero6 +- [EL] Drop redundant `SLOAD` charge in `storeGasValuesCall` under Zero6 +- [EL] Align `totalSupply` input validation with other precompiles +- [EL] Revert child state when subcall precompile rejects +- [EL] Resolve EIP-7702 delegation when loading subcall target bytecode +- [EL] Check EIP-7702 authorization-list authorities against the denylist +- [EL] `DenylistedAddressError` should not penalize peers +- [EL] Include base fee in payload builder fee totals +- [EL] Use checked arithmetic for cumulative gas accounting in payload builder +- [EL] Panic on missing subcall continuation instead of reverting +- [Shared] Remediate cargo audit advisories +- [Contracts] Capture `Multicall3From` precompile reverts instead of propagating +- [Quake] Validate manifest flags against consensus binary CLI struct; decouple monitoring lifecycle from `clean` and `restart` +- [Spammer] Lift gas-fee caps above testnet base-fee ceiling +- [Spammer] Fix raw tx encoding, TCP backpressure drain, zero-latency warning + +### Docs + +Full documentation tree at this release: [`arc-node` v0.7.0 docs](https://github.com/circlefin/arc-node/tree/v0.7.0/docs). New or updated topics in this release: + +- Add Docker instructions for running an Arc node +- Add single-host monitoring guide for Arc EL + CL + +## [v0.6.0] + +**Released:** 2026-04-08 -- [release notes](https://github.com/circlefin/arc-node/releases/tag/v0.6.0) + +Initial public open-source release of `arc-node`. Baseline for subsequent changelog entries. diff --git a/Cargo.lock b/Cargo.lock index ab29b8b..57a0dc2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -403,6 +403,7 @@ dependencies = [ "cfg-if", "const-hex", "derive_more", + "fixed-cache", "foldhash 0.2.0", "getrandom 0.4.1", "hashbrown 0.16.1", @@ -652,7 +653,7 @@ dependencies = [ "alloy-serde", "alloy-sol-types", "arbitrary", - "itertools 0.14.0", + "itertools 0.13.0", "serde", "serde_json", "serde_with", @@ -847,7 +848,7 @@ checksum = "aa501ad58dd20acddbfebc65b52e60f05ebf97c52fa40d1b35e91f5e2da0ad0e" dependencies = [ "alloy-json-rpc", "alloy-transport", - "itertools 0.14.0", + "itertools 0.13.0", "reqwest", "serde_json", "tower 0.5.3", @@ -1081,6 +1082,7 @@ dependencies = [ "arc-malachitebft-sync", "arc-shared", "arc-signer", + "async-trait", "bytes", "bytesize", "config", @@ -1147,6 +1149,7 @@ dependencies = [ "arc-execution-config", "arc-execution-txpool", "arc-malachitebft-core-types", + "arc-shared", "async-trait", "backon", "ethereum_serde_utils", @@ -1292,6 +1295,7 @@ dependencies = [ "alloy-serde", "alloy-sol-types", "arc-shared", + "clap", "eyre", "itertools 0.14.0", "once_cell", @@ -1303,6 +1307,7 @@ dependencies = [ "reth-ethereum-primitives", "reth-evm", "reth-network-peers", + "reth-node-core", "reth-primitives-traits", "revm", "revm-primitives", @@ -1442,7 +1447,7 @@ dependencies = [ [[package]] name = "arc-malachitebft-app" version = "0.7.0-pre" -source = "git+https://github.com/circlefin/malachite.git?rev=6d95e673#6d95e673f6ade62762ab5ee896b625d17d25cb63" +source = "git+https://github.com/circlefin/malachite.git?rev=0422137f#0422137fb7fc60673c3769d6a5bd0a4c8f57317d" dependencies = [ "arc-malachitebft-codec", "arc-malachitebft-config", @@ -1470,7 +1475,7 @@ dependencies = [ [[package]] name = "arc-malachitebft-app-channel" version = "0.7.0-pre" -source = "git+https://github.com/circlefin/malachite.git?rev=6d95e673#6d95e673f6ade62762ab5ee896b625d17d25cb63" +source = "git+https://github.com/circlefin/malachite.git?rev=0422137f#0422137fb7fc60673c3769d6a5bd0a4c8f57317d" dependencies = [ "arc-malachitebft-app", "arc-malachitebft-config", @@ -1488,7 +1493,7 @@ dependencies = [ [[package]] name = "arc-malachitebft-codec" version = "0.7.0-pre" -source = "git+https://github.com/circlefin/malachite.git?rev=6d95e673#6d95e673f6ade62762ab5ee896b625d17d25cb63" +source = "git+https://github.com/circlefin/malachite.git?rev=0422137f#0422137fb7fc60673c3769d6a5bd0a4c8f57317d" dependencies = [ "bytes", ] @@ -1496,7 +1501,7 @@ dependencies = [ [[package]] name = "arc-malachitebft-config" version = "0.7.0-pre" -source = "git+https://github.com/circlefin/malachite.git?rev=6d95e673#6d95e673f6ade62762ab5ee896b625d17d25cb63" +source = "git+https://github.com/circlefin/malachite.git?rev=0422137f#0422137fb7fc60673c3769d6a5bd0a4c8f57317d" dependencies = [ "arc-malachitebft-core-types", "bytesize", @@ -1510,7 +1515,7 @@ dependencies = [ [[package]] name = "arc-malachitebft-core-consensus" version = "0.7.0-pre" -source = "git+https://github.com/circlefin/malachite.git?rev=6d95e673#6d95e673f6ade62762ab5ee896b625d17d25cb63" +source = "git+https://github.com/circlefin/malachite.git?rev=0422137f#0422137fb7fc60673c3769d6a5bd0a4c8f57317d" dependencies = [ "arc-malachitebft-core-driver", "arc-malachitebft-core-types", @@ -1530,7 +1535,7 @@ dependencies = [ [[package]] name = "arc-malachitebft-core-driver" version = "0.7.0-pre" -source = "git+https://github.com/circlefin/malachite.git?rev=6d95e673#6d95e673f6ade62762ab5ee896b625d17d25cb63" +source = "git+https://github.com/circlefin/malachite.git?rev=0422137f#0422137fb7fc60673c3769d6a5bd0a4c8f57317d" dependencies = [ "arc-malachitebft-core-state-machine", "arc-malachitebft-core-types", @@ -1543,7 +1548,7 @@ dependencies = [ [[package]] name = "arc-malachitebft-core-state-machine" version = "0.7.0-pre" -source = "git+https://github.com/circlefin/malachite.git?rev=6d95e673#6d95e673f6ade62762ab5ee896b625d17d25cb63" +source = "git+https://github.com/circlefin/malachite.git?rev=0422137f#0422137fb7fc60673c3769d6a5bd0a4c8f57317d" dependencies = [ "arc-malachitebft-core-types", "derive-where", @@ -1553,7 +1558,7 @@ dependencies = [ [[package]] name = "arc-malachitebft-core-types" version = "0.7.0-pre" -source = "git+https://github.com/circlefin/malachite.git?rev=6d95e673#6d95e673f6ade62762ab5ee896b625d17d25cb63" +source = "git+https://github.com/circlefin/malachite.git?rev=0422137f#0422137fb7fc60673c3769d6a5bd0a4c8f57317d" dependencies = [ "arc-malachitebft-peer", "async-trait", @@ -1566,7 +1571,7 @@ dependencies = [ [[package]] name = "arc-malachitebft-core-votekeeper" version = "0.7.0-pre" -source = "git+https://github.com/circlefin/malachite.git?rev=6d95e673#6d95e673f6ade62762ab5ee896b625d17d25cb63" +source = "git+https://github.com/circlefin/malachite.git?rev=0422137f#0422137fb7fc60673c3769d6a5bd0a4c8f57317d" dependencies = [ "arc-malachitebft-core-types", "derive-where", @@ -1577,7 +1582,7 @@ dependencies = [ [[package]] name = "arc-malachitebft-discovery" version = "0.7.0-pre" -source = "git+https://github.com/circlefin/malachite.git?rev=6d95e673#6d95e673f6ade62762ab5ee896b625d17d25cb63" +source = "git+https://github.com/circlefin/malachite.git?rev=0422137f#0422137fb7fc60673c3769d6a5bd0a4c8f57317d" dependencies = [ "arc-malachitebft-metrics", "either", @@ -1592,7 +1597,7 @@ dependencies = [ [[package]] name = "arc-malachitebft-engine" version = "0.7.0-pre" -source = "git+https://github.com/circlefin/malachite.git?rev=6d95e673#6d95e673f6ade62762ab5ee896b625d17d25cb63" +source = "git+https://github.com/circlefin/malachite.git?rev=0422137f#0422137fb7fc60673c3769d6a5bd0a4c8f57317d" dependencies = [ "arc-malachitebft-codec", "arc-malachitebft-config", @@ -1614,6 +1619,7 @@ dependencies = [ "derive-where", "eyre", "hex", + "itertools 0.14.0", "libp2p", "ractor", "rand 0.8.5", @@ -1624,7 +1630,7 @@ dependencies = [ [[package]] name = "arc-malachitebft-metrics" version = "0.7.0-pre" -source = "git+https://github.com/circlefin/malachite.git?rev=6d95e673#6d95e673f6ade62762ab5ee896b625d17d25cb63" +source = "git+https://github.com/circlefin/malachite.git?rev=0422137f#0422137fb7fc60673c3769d6a5bd0a4c8f57317d" dependencies = [ "arc-malachitebft-core-state-machine", "prometheus-client", @@ -1633,7 +1639,7 @@ dependencies = [ [[package]] name = "arc-malachitebft-network" version = "0.7.0-pre" -source = "git+https://github.com/circlefin/malachite.git?rev=6d95e673#6d95e673f6ade62762ab5ee896b625d17d25cb63" +source = "git+https://github.com/circlefin/malachite.git?rev=0422137f#0422137fb7fc60673c3769d6a5bd0a4c8f57317d" dependencies = [ "arc-malachitebft-discovery", "arc-malachitebft-metrics", @@ -1662,7 +1668,7 @@ dependencies = [ [[package]] name = "arc-malachitebft-peer" version = "0.7.0-pre" -source = "git+https://github.com/circlefin/malachite.git?rev=6d95e673#6d95e673f6ade62762ab5ee896b625d17d25cb63" +source = "git+https://github.com/circlefin/malachite.git?rev=0422137f#0422137fb7fc60673c3769d6a5bd0a4c8f57317d" dependencies = [ "bs58", "multihash", @@ -1673,7 +1679,7 @@ dependencies = [ [[package]] name = "arc-malachitebft-proto" version = "0.7.0-pre" -source = "git+https://github.com/circlefin/malachite.git?rev=6d95e673#6d95e673f6ade62762ab5ee896b625d17d25cb63" +source = "git+https://github.com/circlefin/malachite.git?rev=0422137f#0422137fb7fc60673c3769d6a5bd0a4c8f57317d" dependencies = [ "prost 0.13.5", "prost-types 0.13.5", @@ -1683,7 +1689,7 @@ dependencies = [ [[package]] name = "arc-malachitebft-signing" version = "0.7.0-pre" -source = "git+https://github.com/circlefin/malachite.git?rev=6d95e673#6d95e673f6ade62762ab5ee896b625d17d25cb63" +source = "git+https://github.com/circlefin/malachite.git?rev=0422137f#0422137fb7fc60673c3769d6a5bd0a4c8f57317d" dependencies = [ "arc-malachitebft-core-types", "async-trait", @@ -1693,7 +1699,7 @@ dependencies = [ [[package]] name = "arc-malachitebft-signing-ed25519" version = "0.7.0-pre" -source = "git+https://github.com/circlefin/malachite.git?rev=6d95e673#6d95e673f6ade62762ab5ee896b625d17d25cb63" +source = "git+https://github.com/circlefin/malachite.git?rev=0422137f#0422137fb7fc60673c3769d6a5bd0a4c8f57317d" dependencies = [ "arc-malachitebft-core-types", "base64 0.22.1", @@ -1706,7 +1712,7 @@ dependencies = [ [[package]] name = "arc-malachitebft-sync" version = "0.7.0-pre" -source = "git+https://github.com/circlefin/malachite.git?rev=6d95e673#6d95e673f6ade62762ab5ee896b625d17d25cb63" +source = "git+https://github.com/circlefin/malachite.git?rev=0422137f#0422137fb7fc60673c3769d6a5bd0a4c8f57317d" dependencies = [ "arc-malachitebft-core-types", "arc-malachitebft-metrics", @@ -1728,7 +1734,7 @@ dependencies = [ [[package]] name = "arc-malachitebft-wal" version = "0.7.0-pre" -source = "git+https://github.com/circlefin/malachite.git?rev=6d95e673#6d95e673f6ade62762ab5ee896b625d17d25cb63" +source = "git+https://github.com/circlefin/malachite.git?rev=0422137f#0422137fb7fc60673c3769d6a5bd0a4c8f57317d" dependencies = [ "advisory-lock", "bytes", @@ -1885,6 +1891,7 @@ dependencies = [ "reth-node-ethereum", "reth-node-metrics", "reth-prune-types", + "reth-rpc-builder", "reth-rpc-eth-types", "reth-rpc-server-types", "revm", @@ -1962,6 +1969,7 @@ name = "arc-signer" version = "0.0.1" dependencies = [ "arc-consensus-types", + "arc-malachitebft-core-types", "arc-malachitebft-signing-ed25519", "arc-remote-signer", "async-trait", @@ -2009,6 +2017,31 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "arc-test-integration" +version = "0.0.1" +dependencies = [ + "alloy-primitives", + "arc-consensus-types", + "arc-evm-node", + "arc-execution-config", + "arc-execution-txpool", + "arc-malachitebft-app-channel", + "arc-node-consensus", + "arc-node-consensus-cli", + "arc-signer", + "arc-test-framework", + "async-trait", + "eyre", + "reth-node-builder", + "reth-tasks", + "rstest", + "tempfile", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "arc-version" version = "0.0.1" @@ -2917,9 +2950,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96eb4cdd6cf1b31d671e9efe75c5d1ec614776856cefbe109ca373554a6d514f" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" dependencies = [ "hybrid-array", ] @@ -3232,11 +3265,11 @@ dependencies = [ [[package]] name = "bytesize" -version = "1.3.3" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e93abca9e28e0a1b9877922aacb20576e05d4679ffa78c3d6dc22a26a216659" +checksum = "6bd91ee7b2422bcb158d90ef4d14f75ef67f340943fc4149891dcce8f8b972a3" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -3957,9 +3990,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "211f05e03c7d03754740fd9e585de910a095d6b99f8bcfffdef8319fa02a8331" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" dependencies = [ "hybrid-array", ] @@ -4187,7 +4220,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ab67060fc6b8ef687992d439ca0fa36e7ed17e9a0b16b25b601e8757df720de" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 2.0.117", ] [[package]] @@ -4401,12 +4434,12 @@ dependencies = [ [[package]] name = "digest" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bf3682cdec91817be507e4aa104314898b95b84d74f3d43882210101a545b6" +checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" dependencies = [ - "block-buffer 0.11.0", - "crypto-common 0.2.0", + "block-buffer 0.12.0", + "crypto-common 0.2.1", "ctutils", ] @@ -5675,11 +5708,11 @@ dependencies = [ [[package]] name = "hmac" -version = "0.13.0-rc.5" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef451d73f36d8a3f93ad32c332ea01146c9650e1ec821a9b0e46c01277d544f8" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" dependencies = [ - "digest 0.11.0", + "digest 0.11.2", ] [[package]] @@ -6581,11 +6614,12 @@ dependencies = [ [[package]] name = "keccak" -version = "0.2.0-rc.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a412fe37705d515cba9dbf1448291a717e187e2351df908cfc0137cbec3d480" +checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" dependencies = [ - "cpufeatures 0.2.17", + "cfg-if", + "cpufeatures 0.3.0", ] [[package]] @@ -8591,12 +8625,12 @@ dependencies = [ [[package]] name = "pkcs8" -version = "0.11.0-rc.11" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12922b6296c06eb741b02d7b5161e3aaa22864af38dfa025a1a3ba3f68c84577" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" dependencies = [ "der 0.8.0", - "spki 0.8.0-rc.4", + "spki 0.8.0", ] [[package]] @@ -9060,7 +9094,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" dependencies = [ "heck", - "itertools 0.14.0", + "itertools 0.12.1", "log", "multimap", "once_cell", @@ -9093,7 +9127,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.14.0", + "itertools 0.12.1", "proc-macro2", "quote", "syn 2.0.117", @@ -9106,7 +9140,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools 0.14.0", + "itertools 0.12.1", "proc-macro2", "quote", "syn 2.0.117", @@ -13531,13 +13565,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.11.0-rc.5" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f3b1e2dc8aad28310d8410bd4d7e180eca65fca176c52ab00d364475d0024" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", - "cpufeatures 0.2.17", - "digest 0.11.0", + "cpufeatures 0.3.0", + "digest 0.11.2", ] [[package]] @@ -13552,12 +13586,12 @@ dependencies = [ [[package]] name = "sha3" -version = "0.11.0-rc.7" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5bfe7820113e633d8886e839aae78c1184b8d7011000db6bc7eb61e34f28350" +checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" dependencies = [ - "digest 0.11.0", - "keccak 0.2.0-rc.1", + "digest 0.11.2", + "keccak 0.2.0", ] [[package]] @@ -13682,18 +13716,18 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "slh-dsa" -version = "0.2.0-rc.4" +version = "0.2.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85f6f9b5317f06189671584c283b3f26339b89c97f21b5c50ae24aec397304a7" +checksum = "371c02fe34044d8866ddf7cb0e8204a87ef31a39f0408bed41c4253ea9dd61ed" dependencies = [ "const-oid 0.10.2", - "digest 0.11.0", - "hmac 0.13.0-rc.5", + "digest 0.11.2", + "hmac 0.13.0", "hybrid-array", - "pkcs8 0.11.0-rc.11", + "pkcs8 0.11.0", "rand_core 0.10.0", - "sha2 0.11.0-rc.5", - "sha3 0.11.0-rc.7", + "sha2 0.11.0", + "sha3 0.11.0", "signature 3.0.0-rc.10", "typenum", "zerocopy", @@ -13852,9 +13886,9 @@ dependencies = [ [[package]] name = "spki" -version = "0.8.0-rc.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8baeff88f34ed0691978ec34440140e1572b68c7dd4a495fd14a3dc1944daa80" +checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" dependencies = [ "base64ct", "der 0.8.0", @@ -14927,9 +14961,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "ucd-trie" diff --git a/Cargo.toml b/Cargo.toml index 7246ef7..d6232cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,12 @@ [workspace] resolver = "2" -members = ["crates/*", "crates/test/checks", "crates/test/framework"] +members = [ + "crates/*", + "crates/test/checks", + "crates/test/framework", + "crates/test/integration", +] exclude = ["crates/test"] [workspace.package] @@ -34,7 +39,7 @@ alloy-genesis = { version = "1.6.3", default-features = false } alloy-network = { version = "1.6.3", default-features = false } # op -alloy-primitives = { version = "1.5.6", default-features = false, features = ["map-foldhash"] } +alloy-primitives = { version = "1.5.6", default-features = false, features = ["map-foldhash", "keccak-cache-global", "asm-keccak"] } alloy-provider = { version = "1.6.3", features = ["reqwest"], default-features = false } alloy-rlp = { version = "0.3.13", default-features = false, features = ["core-net"] } alloy-rpc-types = { version = "1.6.3", features = ["eth"], default-features = false } @@ -74,6 +79,7 @@ arc-shared = { version = "0.0.1", path = "crates/shared" } arc-signer = { version = "0.0.1", path = "crates/signer" } arc-snapshots = { version = "0.0.1", path = "crates/snapshots" } arc-test-framework = { version = "0.0.1", path = "crates/test/framework" } +arc-test-integration = { version = "0.0.1", path = "crates/test/integration" } arc-version = { version = "0.0.1", path = "crates/version" } # async @@ -88,7 +94,7 @@ bip39 = { version = "2.2.0", default-features = false, features = ["std"] } # arc bon = "2.3.0" bytes = { version = "1.5", default-features = false } -bytesize = "1.3" +bytesize = "2.3" # for eip-4844 cc = "=1.2.15" @@ -229,6 +235,7 @@ sha3 = "0.10.5" signature = "2.2.0" # Security fix for RUSTSEC-2025-0047 slab = "0.4.11" +slh-dsa = "0.2.0-rc.5" # spammer spammer = { version = "0.0.1", path = "crates/spammer" } @@ -262,67 +269,72 @@ wiremock = "0.6" [workspace.dependencies.malachitebft-app] package = "arc-malachitebft-app" git = "https://github.com/circlefin/malachite.git" -rev = "6d95e673" +rev = "0422137f" [workspace.dependencies.malachitebft-app-channel] package = "arc-malachitebft-app-channel" git = "https://github.com/circlefin/malachite.git" -rev = "6d95e673" +rev = "0422137f" [workspace.dependencies.malachitebft-codec] package = "arc-malachitebft-codec" git = "https://github.com/circlefin/malachite.git" -rev = "6d95e673" +rev = "0422137f" [workspace.dependencies.malachitebft-config] package = "arc-malachitebft-config" git = "https://github.com/circlefin/malachite.git" -rev = "6d95e673" +rev = "0422137f" [workspace.dependencies.malachitebft-core-consensus] package = "arc-malachitebft-core-consensus" git = "https://github.com/circlefin/malachite.git" -rev = "6d95e673" +rev = "0422137f" [workspace.dependencies.malachitebft-core-state-machine] package = "arc-malachitebft-core-state-machine" git = "https://github.com/circlefin/malachite.git" -rev = "6d95e673" +rev = "0422137f" [workspace.dependencies.malachitebft-core-types] package = "arc-malachitebft-core-types" git = "https://github.com/circlefin/malachite.git" +rev = "0422137f" + +[workspace.dependencies.malachitebft-engine] +package = "arc-malachitebft-engine" +git = "https://github.com/circlefin/malachite.git" rev = "6d95e673" [workspace.dependencies.malachitebft-network] package = "arc-malachitebft-network" git = "https://github.com/circlefin/malachite.git" -rev = "6d95e673" +rev = "0422137f" [workspace.dependencies.malachitebft-peer] package = "arc-malachitebft-peer" git = "https://github.com/circlefin/malachite.git" -rev = "6d95e673" +rev = "0422137f" [workspace.dependencies.malachitebft-proto] package = "arc-malachitebft-proto" git = "https://github.com/circlefin/malachite.git" -rev = "6d95e673" +rev = "0422137f" [workspace.dependencies.malachitebft-signing] package = "arc-malachitebft-signing" git = "https://github.com/circlefin/malachite.git" -rev = "6d95e673" +rev = "0422137f" [workspace.dependencies.malachitebft-signing-ed25519] package = "arc-malachitebft-signing-ed25519" git = "https://github.com/circlefin/malachite.git" -rev = "6d95e673" +rev = "0422137f" [workspace.dependencies.malachitebft-sync] package = "arc-malachitebft-sync" git = "https://github.com/circlefin/malachite.git" -rev = "6d95e673" +rev = "0422137f" [profile.dev] # enable basic optimizations (otherwise reth can't process txs) diff --git a/Makefile b/Makefile index 7231ce2..f7c4892 100644 --- a/Makefile +++ b/Makefile @@ -87,9 +87,12 @@ build-contract: check-foundry ## Build the contracts and bindings npm install $(HARDHAT) compile -genesis: build-contract ## Generate the genesis file idempotently +genesis: build-contract ## Generate the localdev genesis file idempotently $(HARDHAT) genesis --network localdev --num-validators $(NUM_VALIDATORS) +genesis-mainnet: build-contract ## Generate the mainnet genesis file + $(HARDHAT) genesis --network mainnet + .PHONY: mine-denylist-salt mine-denylist-salt: check-foundry ## Mine a CREATE2 salt for the Denylist proxy with a 0x360 address prefix (usage: make mine-denylist-salt INIT_CODE_HASH=0x...) @test -n "$$INIT_CODE_HASH" || { echo "Error: INIT_CODE_HASH is required"; exit 1; } diff --git a/arcup/arcup b/arcup/arcup index a740a06..3590cd3 100755 --- a/arcup/arcup +++ b/arcup/arcup @@ -1,12 +1,12 @@ #!/usr/bin/env bash -set -e +set -euo pipefail # Arcup - Arc node installer # Downloads and installs arc-node binaries from GitHub releases # NOTE: if you make modifications to this script, please increment the version number. # WARNING: the SemVer pattern: major.minor.patch must be followed as we use it to determine if the script is up to date. -ARCUP_INSTALLER_VERSION="0.0.1" +ARCUP_INSTALLER_VERSION="0.2.0" REPO="${ARC_REPO:-circlefin/arc-node}" if [[ -n "${ARC_REPO:-}" ]] && [[ ! "$ARC_REPO" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; then @@ -16,14 +16,25 @@ fi # TODO: set GPG key fingerprint once release signing key is published GPG_KEY_FINGERPRINT="" GPG_KEYSERVER="keyserver.ubuntu.com" -BIN_DIR="${ARC_BIN_DIR:-$HOME/.arc/bin}" +ARC_DIR="${ARC_DIR:-${ARC_HOME:-$HOME/.arc}}" +BIN_DIR="${ARC_BIN_DIR:-$ARC_DIR/bin}" # Self-update always uses the canonical repo to prevent hijack via ARC_REPO ARCUP_BIN_URL="https://raw.githubusercontent.com/circlefin/arc-node/main/arcup/arcup" ARCUP_BIN_PATH="$BIN_DIR/arcup" +GITHUB_API_URL="${GITHUB_API_URL:-https://api.github.com}" +case "$GITHUB_API_URL" in + https://?*) GITHUB_API_URL="${GITHUB_API_URL%/}" ;; + *) + echo "error: invalid GITHUB_API_URL: '$GITHUB_API_URL' (expected https:// URL)" >&2 + exit 1 + ;; +esac +CURL_RETRY_ARGS=(--retry 3 --retry-delay 2 --connect-timeout 15) CURL_HEADERS=() if [ "${CURL_HEADER:-}" ]; then CURL_HEADERS=(-H "$CURL_HEADER") fi +GITHUB_AUTH_TOKEN="${ARC_GITHUB_TOKEN:-${GH_TOKEN:-${GITHUB_TOKEN:-}}}" # Binaries included in each release archive BINARIES=("arc-node-execution" "arc-node-consensus" "arc-snapshots") @@ -56,6 +67,195 @@ error() { exit 1 } +require_command() { + local command_name="$1" + local message="$2" + + if ! command -v "$command_name" >/dev/null 2>&1; then + error "$message" + fi +} + +curl_with_headers() { + if ((${#CURL_HEADERS[@]} > 0)); then + curl "${CURL_RETRY_ARGS[@]}" "${CURL_HEADERS[@]}" "$@" + else + curl "${CURL_RETRY_ARGS[@]}" "$@" + fi +} + +curl_github_api_json() { + local args=("${CURL_RETRY_ARGS[@]}" -H "Accept: application/vnd.github+json") + if [[ -n "$GITHUB_AUTH_TOKEN" ]]; then + args+=(-H "Authorization: Bearer $GITHUB_AUTH_TOKEN") + fi + if ((${#CURL_HEADERS[@]} > 0)); then + args+=("${CURL_HEADERS[@]}") + fi + curl "${args[@]}" "$@" +} + +curl_github_api_json_anonymous() { + local args=("${CURL_RETRY_ARGS[@]}" -H "Accept: application/vnd.github+json") + if ((${#CURL_HEADERS[@]} > 0)); then + args+=("${CURL_HEADERS[@]}") + fi + curl "${args[@]}" "$@" +} + +safe_curl_error() { + local message="$1" + + if [[ -n "$GITHUB_AUTH_TOKEN" ]] || ((${#CURL_HEADERS[@]} > 0)); then + echo "request failed" + else + echo "$message" + fi +} + +curl_github_asset_headers() { + if [[ -z "$GITHUB_AUTH_TOKEN" ]] && ((${#CURL_HEADERS[@]} == 0)); then + return 1 + fi + + local args=("${CURL_RETRY_ARGS[@]}" -H "Accept: application/octet-stream") + if [[ -n "$GITHUB_AUTH_TOKEN" ]]; then + args+=(-H "Authorization: Bearer $GITHUB_AUTH_TOKEN") + fi + if ((${#CURL_HEADERS[@]} > 0)); then + args+=("${CURL_HEADERS[@]}") + fi + curl "${args[@]}" "$@" +} + +resolve_github_asset_download_url() { + local asset_api_url="$1" + local headers location + + headers=$(curl_github_asset_headers -fsS -D - -o /dev/null "$asset_api_url") || return 1 + location=$(printf '%s\n' "$headers" | awk ' + /^[Ll][Oo][Cc][Aa][Tt][Ii][Oo][Nn]:[[:space:]]*/ { + sub(/\r$/, "") + sub(/^[^:]+:[[:space:]]*/, "") + print + exit + } + ') + + [[ -n "$location" ]] || return 1 + printf '%s\n' "$location" +} + +require_install_prerequisites() { + require_command curl "curl not found. Please install curl." + require_command tar "tar not found. Please install tar." + + if ! command -v sha256sum >/dev/null 2>&1 && ! command -v shasum >/dev/null 2>&1; then + error "No SHA-256 tool found. Please install sha256sum or shasum." + fi +} + +extract_json_string_field() { + local field="$1" + + awk -v field="$field" ' + { + pattern = "\"" field "\"[[:space:]]*:[[:space:]]*\"[^\"]+\"" + if (match($0, pattern)) { + value = substr($0, RSTART, RLENGTH) + prefix = "\"" field "\"[[:space:]]*:[[:space:]]*\"" + sub("^" prefix, "", value) + sub("\"$", "", value) + print value + exit + } + } + ' +} + +extract_arc_asset_names() { + awk ' + { + while (match($0, /"name"[[:space:]]*:[[:space:]]*"[^"]+"/)) { + value = substr($0, RSTART, RLENGTH) + sub(/^"name"[[:space:]]*:[[:space:]]*"/, "", value) + sub(/"$/, "", value) + if (value ~ /^arc-node-/) { + print value + } + $0 = substr($0, RSTART + RLENGTH) + } + } + ' +} + +extract_asset_api_url() { + local asset_name="$1" + + awk -v asset_name="$asset_name" ' + BEGIN { RS = "\\{" } + { + compact = $0 + gsub(/[[:space:]]+/, "", compact) + if (index(compact, "\"name\":\"" asset_name "\"") > 0 && + match(compact, /"url":"[^"]+\/releases\/assets\/[0-9]+"/)) { + value = substr(compact, RSTART, RLENGTH) + sub(/^"url":"/, "", value) + sub(/"$/, "", value) + print value + exit + } + } + ' +} + +fetch_release_json() { + local tag="$1" + local api_url="$GITHUB_API_URL/repos/$REPO/releases/tags/$tag" + + curl_github_api_json -fsSL "$api_url" +} + +fetch_latest_release_json() { + local api_url="$GITHUB_API_URL/repos/$REPO/releases/latest" + + curl_github_api_json -fsSL "$api_url" +} + +fetch_latest_release_json_anonymous() { + local api_url="$GITHUB_API_URL/repos/$REPO/releases/latest" + + curl_github_api_json_anonymous -fsSL "$api_url" +} + +list_release_assets() { + local tag="$1" + + fetch_release_json "$tag" 2>/dev/null | extract_arc_asset_names || true +} + +download_error() { + local tag="$1" + local filename="$2" + local url="$3" + local assets + + echo -e "${RED}error${NC}: Failed to download $filename from $url" >&2 + echo " repository: $REPO" >&2 + echo " tag: $tag" >&2 + echo " target: ${TARGET:-unknown}" >&2 + assets="$(list_release_assets "$tag")" + if [[ -n "$assets" ]]; then + echo "Available arc-node release assets for $tag:" >&2 + while IFS= read -r asset; do + echo " $asset" >&2 + done <<< "$assets" + else + echo "Could not list release assets for $tag. Confirm the tag exists and contains binary assets." >&2 + fi + exit 1 +} + # Compare SemVer versions # Returns 0 if $1 > $2, 1 otherwise version_gt() { @@ -64,6 +264,8 @@ version_gt() { # Remove 'v' prefix if present local ver1="${1#v}" local ver2="${2#v}" + ver1="${ver1%%-*}" + ver2="${ver2%%-*}" IFS=. read -r major1 minor1 patch1 </dev/null 2>&1; then - error "curl not found. Please install curl." - fi + require_command curl "curl not found. Please install curl." info "Downloading $filename..." - if ! curl -#fL "${CURL_HEADERS[@]}" -o "$output_path" "$url"; then - error "Failed to download $filename from $url" + if download_file_with_github_api "$tag" "$filename" "$output_dir"; then + return + fi + + if download_file_with_gh "$tag" "$filename" "$output_dir"; then + return + fi + + if ! curl_with_headers -#fL --max-time 900 -o "$output_path" "$url"; then + rm -f "$output_path" + download_error "$tag" "$filename" "$url" fi } +download_file_with_github_api() { + local tag="$1" + local filename="$2" + local output_dir="$3" + local output_path="$output_dir/$filename" + local release_json asset_api_url download_url + + if [[ -z "$GITHUB_AUTH_TOKEN" ]] && ((${#CURL_HEADERS[@]} == 0)); then + return 1 + fi + + release_json=$(fetch_release_json "$tag" 2>/dev/null) || return 1 + asset_api_url=$(printf '%s\n' "$release_json" | extract_asset_api_url "$filename") + if [[ -z "$asset_api_url" ]]; then + return 1 + fi + + download_url=$(resolve_github_asset_download_url "$asset_api_url") || return 1 + + if curl "${CURL_RETRY_ARGS[@]}" -fL --max-time 900 -o "$output_path" "$download_url" >/dev/null 2>&1; then + [[ -f "$output_path" ]] && return 0 + fi + + rm -f "$output_path" + return 1 +} + +download_file_with_gh() { + local tag="$1" + local filename="$2" + local output_dir="$3" + local output_path="$output_dir/$filename" + + if ! command -v gh >/dev/null 2>&1; then + return 1 + fi + + if GH_PROMPT_DISABLED=1 GH_NO_UPDATE_NOTIFIER=1 \ + gh release download "$tag" \ + --repo "$REPO" \ + --pattern "$filename" \ + --dir "$output_dir" \ + --clobber \ + >/dev/null 2>&1; then + [[ -f "$output_path" ]] && return 0 + fi + + rm -f "$output_path" + return 1 +} + # Compute SHA256 hash of a file compute_sha256() { if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | cut -d' ' -f1 else + require_command shasum "No SHA-256 tool found. Please install sha256sum or shasum." shasum -a 256 "$1" | awk '{print $1}' fi } +# Check available disk space meets minimum requirement +check_disk_space() { + local dir="$1" + local required_mb="${2:-500}" + + local available_kb + available_kb=$(df -Pk "$dir" 2>/dev/null | awk 'NR==2 {print $4}') + + if [[ -n "$available_kb" ]]; then + local available_mb=$((available_kb / 1024)) + if [[ "$available_mb" -lt "$required_mb" ]]; then + error "Insufficient disk space: ${available_mb}MB available, ${required_mb}MB required in $dir" + fi + fi +} + # Check if arcup installer is up to date check_installer_up_to_date() { - if ! command -v curl >/dev/null 2>&1; then - error "curl not found. Please install curl." - fi + require_command curl "curl not found. Please install curl." + local response local remote_version - remote_version=$(curl -fsSL "${CURL_HEADERS[@]}" "$ARCUP_BIN_URL" 2>/dev/null | \ - grep '^ARCUP_INSTALLER_VERSION=' | sed -E 's/ARCUP_INSTALLER_VERSION="(.+)"/\1/') + response=$(curl_with_headers -fsSL "$ARCUP_BIN_URL" 2>/dev/null || true) + remote_version=$(printf '%s\n' "$response" | awk -F'"' '/^ARCUP_INSTALLER_VERSION=/ {print $2; exit}') if [[ -z "$remote_version" ]]; then return @@ -143,14 +433,12 @@ check_installer_up_to_date() { update_arcup() { info "Updating arcup..." - if ! command -v curl >/dev/null 2>&1; then - error "curl not found. Please install curl." - fi + require_command curl "curl not found. Please install curl." _ARCUP_TMP_FILE=$(mktemp) info "Downloading latest arcup..." - if ! curl -fsSL "${CURL_HEADERS[@]}" -o "$_ARCUP_TMP_FILE" "$ARCUP_BIN_URL"; then + if ! curl_with_headers -fsSL -o "$_ARCUP_TMP_FILE" "$ARCUP_BIN_URL"; then error "Failed to download arcup update" fi @@ -174,7 +462,52 @@ update_arcup() { fi echo "" - info "✓ Arcup updated successfully to version $remote_version" + info "Arcup updated successfully to version $remote_version" + exit 0 +} + +# Remove arc binaries and env files +uninstall() { + local found=0 + local items=() + + for binary in "${BINARIES[@]}"; do + [[ -f "$BIN_DIR/$binary" ]] && items+=(" $BIN_DIR/$binary") && found=1 + done + [[ -f "$ARCUP_BIN_PATH" ]] && items+=(" $ARCUP_BIN_PATH") && found=1 + [[ -f "$ARC_DIR/env" ]] && items+=(" $ARC_DIR/env") && found=1 + [[ -f "$ARC_DIR/env.fish" ]] && items+=(" $ARC_DIR/env.fish") && found=1 + + if [[ "$found" -eq 0 ]]; then + info "Nothing to uninstall — no arc binaries found in $BIN_DIR" + exit 0 + fi + + echo "The following will be removed:" + for item in "${items[@]}"; do + echo "$item" + done + echo "" + + printf "Proceed? [y/N] " + read -r confirm + if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then + info "Uninstall cancelled." + exit 0 + fi + + for binary in "${BINARIES[@]}"; do + rm -f "$BIN_DIR/$binary" + done + rm -f "$ARCUP_BIN_PATH" + rm -f "$ARC_DIR/env" "$ARC_DIR/env.fish" + + # Remove BIN_DIR only if empty + rmdir "$BIN_DIR" 2>/dev/null || true + + echo "" + info "Arc binaries removed." + info "You may also want to remove the PATH entries from your shell config (~/.zshenv, ~/.bashrc, etc.)." exit 0 } @@ -195,20 +528,25 @@ while [[ $# -gt 0 ]]; do -U|--self-update) update_arcup ;; + --uninstall) + uninstall + ;; -h|--help) echo "Usage: arcup [OPTIONS]" echo "" echo "Options:" echo " -v, --version Print arcup installer version" echo " -i, --install Install specific arc-node version (e.g., v1.0.0)" - echo " -U, --self-update Update arcup to the latest version" + echo " -U, --self-update Update arcup to the latest version" + echo " --uninstall Remove arc binaries and env files" echo " -h, --help Show this help message" echo "" echo "Examples:" echo " arcup # Install latest release" echo " arcup -i v1.0.0 # Install specific version" echo " arcup -v # Print installer version" - echo " arcup --self-update # Update arcup itself" + echo " arcup --self-update # Update arcup itself" + echo " arcup --uninstall # Remove arc installation" exit 0 ;; *) @@ -217,8 +555,8 @@ while [[ $# -gt 0 ]]; do esac done -if [[ -n "$VERSION" ]] && [[ ! "$VERSION" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then - error "Invalid version format: '$VERSION' (expected e.g. v1.2.3)" +if [[ -n "$VERSION" ]]; then + VERSION="$(normalize_version "$VERSION")" fi # Detect platform @@ -257,8 +595,7 @@ detect_arch() { echo "arm64" ;; *) - warn "Unknown architecture: $arch, defaulting to amd64" - echo "amd64" + error "Unsupported architecture: $arch" ;; esac } @@ -272,7 +609,7 @@ detect_target() { darwin) case "$arch" in arm64) echo "aarch64-apple-darwin" ;; - amd64) echo "x86_64-apple-darwin" ;; + amd64) error "macOS Intel release builds are not supported" ;; *) error "Unsupported Darwin architecture: $arch" ;; esac ;; @@ -291,24 +628,87 @@ detect_target() { # Get latest release tag from GitHub get_latest_version() { - if ! command -v curl >/dev/null 2>&1; then - error "curl not found. Please install curl." - fi + require_command curl "curl not found. Please install curl." - local api_url="https://api.github.com/repos/$REPO/releases/latest" - local response - if ! response=$(curl -fsSL "${CURL_HEADERS[@]}" "$api_url" 2>&1); then - error "Failed to fetch latest version from '$api_url': $response" + local response token_error + if ! response=$(fetch_latest_release_json 2>&1); then + token_error="$(safe_curl_error "$response")" + if [[ -n "$GITHUB_AUTH_TOKEN" ]] && response=$(fetch_latest_release_json_anonymous 2>&1); then + : + else + error "Failed to fetch latest version from '$GITHUB_API_URL/repos/$REPO/releases/latest': $token_error" + fi fi local version - version=$(echo "$response" | grep '"tag_name":' | head -n 1 | sed 's/.*"tag_name": *"\([^"]*\)".*/\1/') + version=$(printf '%s\n' "$response" | extract_json_string_field "tag_name") if [[ -z "$version" ]]; then error "Failed to parse version from GitHub API response" fi - echo "$version" + normalize_version "$version" +} + +verify_checksum_file() { + local archive_path="$1" + local checksum_path="$2" + local archive_name="$3" + local expected_checksum expected_name actual_checksum + + if ! read -r expected_checksum expected_name < "$checksum_path"; then + error "Checksum file is empty: $checksum_path" + fi + + if [[ ! "$expected_checksum" =~ ^[0-9A-Fa-f]{64}$ ]]; then + error "Checksum file has invalid SHA-256 hash: $checksum_path" + fi + + if [[ -n "$expected_name" ]]; then + expected_name="${expected_name#\*}" + expected_name="${expected_name##*/}" + if [[ "$expected_name" != "$archive_name" ]]; then + error "Checksum file is for '$expected_name', expected '$archive_name'" + fi + fi + + actual_checksum=$(compute_sha256 "$archive_path") + + if [[ "$expected_checksum" != "$actual_checksum" ]]; then + error "Checksum verification failed + Expected: $expected_checksum + Actual: $actual_checksum" + fi +} + +validate_archive_contents() { + local archive_path="$1" + local listing metadata entry type + + if ! listing=$(tar -tzf "$archive_path"); then + error "Failed to read archive: $archive_path" + fi + + while IFS= read -r entry; do + [[ -z "$entry" ]] && continue + case "$entry" in + /* | .. | ../* | */../* | */.. ) + error "Archive contains unsafe path: $entry" ;; + esac + done <<< "$listing" + + if ! metadata=$(tar -tvzf "$archive_path"); then + error "Failed to inspect archive metadata: $archive_path" + fi + + while IFS= read -r entry; do + [[ -z "$entry" ]] && continue + type="${entry:0:1}" + case "$type" in + l | h ) + error "Archive contains unsupported link entry: $entry" ;; + esac + done <<< "$metadata" } # Install a single binary from TMP_DIR to BIN_DIR @@ -316,8 +716,8 @@ install_binary() { local name="$1" local src="$TMP_DIR/$name" - if [[ ! -f "$src" ]]; then - error "Binary not found in archive: $name" + if [[ -L "$src" || ! -f "$src" ]]; then + error "Binary not found as a regular file in archive: $name" fi local dst="$BIN_DIR/$name" @@ -338,6 +738,7 @@ install_binary() { # Main installation logic main() { + require_install_prerequisites check_installer_up_to_date info "Installing arc-node binaries..." @@ -349,10 +750,12 @@ main() { mkdir -p "$BIN_DIR" + check_disk_space "$BIN_DIR" + PLATFORM=$(detect_platform) ARCH=$(detect_arch) TARGET=$(detect_target "$PLATFORM" "$ARCH") - info "Detected platform: $PLATFORM ($ARCH)" + info "Detected platform: $PLATFORM ($ARCH); release target: $TARGET" if [[ -z "$VERSION" ]]; then info "Fetching latest release version..." @@ -377,16 +780,9 @@ main() { info "Verifying checksum..." download_file "$VERSION_TAG" "${ARCHIVE_NAME}.sha256" "$TMP_DIR" - EXPECTED_CHECKSUM=$(head -n1 "$TMP_DIR/${ARCHIVE_NAME}.sha256" | awk '{print $1}') - ACTUAL_CHECKSUM=$(compute_sha256 "$ARCHIVE_PATH") + verify_checksum_file "$ARCHIVE_PATH" "$TMP_DIR/${ARCHIVE_NAME}.sha256" "$ARCHIVE_NAME" - if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ]; then - error "Checksum verification failed - Expected: $EXPECTED_CHECKSUM - Actual: $ACTUAL_CHECKSUM" - fi - - info "Checksum verified ✓" + info "Checksum verified" # GPG signature verification if [[ -n "$GPG_KEY_FINGERPRINT" ]]; then @@ -404,7 +800,7 @@ main() { if gpg --list-keys "$GPG_KEY_FINGERPRINT" >/dev/null 2>&1; then if gpg --batch --verify "$TMP_DIR/${ARCHIVE_NAME}.asc" "$ARCHIVE_PATH" 2>/dev/null; then - info "GPG signature verified ✓" + info "GPG signature verified" else error "GPG signature verification failed. The binary may have been tampered with." fi @@ -415,13 +811,7 @@ main() { fi fi - # Validate archive contents before extracting (guard against path traversal) - while IFS= read -r entry; do - case "$entry" in - /* | .. | ../* | */../* | */.. ) - error "Archive contains unsafe path: $entry" ;; - esac - done < <(tar -tzf "$ARCHIVE_PATH") + validate_archive_contents "$ARCHIVE_PATH" # Extract archive info "Extracting archive..." @@ -433,7 +823,7 @@ main() { done echo "" - info "✓ Arc node $VERSION_TAG installed successfully!" + info "Arc node $VERSION_TAG installed successfully!" echo "" if [[ ":$PATH:" != *":$BIN_DIR:"* ]]; then @@ -446,4 +836,6 @@ main() { fi } -main +if [[ "${ARCUP_SKIP_MAIN:-}" != "1" ]]; then + main +fi diff --git a/arcup/install b/arcup/install index 9570901..b9a8d2b 100755 --- a/arcup/install +++ b/arcup/install @@ -1,13 +1,14 @@ #!/usr/bin/env bash -set -e +set -euo pipefail # Arcup bootstrap installer # Downloads arcup and arc-node binaries to ~/.arc/bin -ARC_DIR="${ARC_DIR:-$HOME/.arc}" +ARC_DIR="${ARC_DIR:-${ARC_HOME:-$HOME/.arc}}" BIN_DIR="${ARC_BIN_DIR:-$ARC_DIR/bin}" ENV_FILE="$ARC_DIR/env" ARCUP_URL="https://raw.githubusercontent.com/circlefin/arc-node/main/arcup/arcup" +CURL_RETRY_ARGS=(--retry 3 --retry-delay 2 --connect-timeout 15) CURL_HEADERS=() if [ "${CURL_HEADER:-}" ]; then CURL_HEADERS=(-H "$CURL_HEADER") @@ -36,6 +37,14 @@ error() { exit 1 } +curl_with_headers() { + if ((${#CURL_HEADERS[@]} > 0)); then + curl "${CURL_RETRY_ARGS[@]}" "${CURL_HEADERS[@]}" "$@" + else + curl "${CURL_RETRY_ARGS[@]}" "$@" + fi +} + install_bin() { local src="$1" local dst="$2" @@ -63,7 +72,7 @@ main() { error "curl not found. Please install curl." fi - if ! curl -#fL "${CURL_HEADERS[@]}" -o "$_INSTALL_TMP_FILE" "$ARCUP_URL" ; then + if ! curl_with_headers -#fL --max-time 300 -o "$_INSTALL_TMP_FILE" "$ARCUP_URL" ; then error "failed to download arcup from '$ARCUP_URL'" fi @@ -94,7 +103,7 @@ ENVEOF echo "" local user_shell - user_shell="$(basename "$SHELL")" + user_shell="$(basename "${SHELL:-sh}")" info "Arc binaries are in your PATH for this session." info "To make that permanent, add to your shell config file:" echo "" @@ -114,4 +123,6 @@ EOF echo "" } -main +if [[ "${ARCUP_INSTALL_SKIP_MAIN:-}" != "1" ]]; then + main +fi diff --git a/arcup/test_arcup.sh b/arcup/test_arcup.sh new file mode 100755 index 0000000..4902119 --- /dev/null +++ b/arcup/test_arcup.sh @@ -0,0 +1,829 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +set -- +ARCUP_SKIP_MAIN=1 +source "$ROOT_DIR/arcup/arcup" +GITHUB_AUTH_TOKEN="" +CURL_HEADERS=() + +TEST_TMP="$(mktemp -d)" +cleanup() { + rm -rf "$TEST_TMP" +} +trap cleanup EXIT + +pass() { + printf 'ok - %s\n' "$1" +} + +fail() { + printf 'not ok - %s\n' "$1" >&2 + exit 1 +} + +assert_eq() { + local expected="$1" + local actual="$2" + local name="$3" + + if [[ "$expected" != "$actual" ]]; then + printf 'expected: %s\nactual: %s\n' "$expected" "$actual" >&2 + fail "$name" + fi + pass "$name" +} + +expect_fail() { + local name="$1" + shift + + if ( "$@" ) >"$TEST_TMP/expect_fail.out" 2>&1; then + cat "$TEST_TMP/expect_fail.out" >&2 + fail "$name" + fi + pass "$name" +} + +test_version_normalization() { + assert_eq "v1.2.3" "$(normalize_version "1.2.3")" "normalizes missing v prefix" + assert_eq "v1.2.3" "$(normalize_version "v1.2.3")" "keeps v-prefixed version" + assert_eq "v1.2.3-rc.1" "$(normalize_version "1.2.3-rc.1")" "normalizes prerelease version" + expect_fail "rejects invalid version" normalize_version "latest" +} + +test_version_comparison() { + if ! version_gt "0.3.1-rc.1" "0.3.0"; then + fail "compares prerelease installer versions" + fi + pass "compares prerelease installer versions" + + if version_gt "0.3.0-rc.1" "0.3.0"; then + fail "same prerelease base is not newer" + fi + pass "same prerelease base is not newer" +} + +test_target_mapping() { + assert_eq "x86_64-unknown-linux-gnu" "$(detect_target "linux" "amd64")" "maps linux amd64 target" + assert_eq "aarch64-unknown-linux-gnu" "$(detect_target "linux" "arm64")" "maps linux arm64 target" + assert_eq "aarch64-apple-darwin" "$(detect_target "darwin" "arm64")" "maps macos arm64 target" + expect_fail "rejects macos amd64 target" detect_target "darwin" "amd64" +} + +test_unknown_architecture_fails() { + if ( + uname() { + case "${1:-}" in + -m) echo "sparc64" ;; + -s) echo "Linux" ;; + *) command uname "$@" ;; + esac + } + detect_arch + ) >"$TEST_TMP/unknown_arch.out" 2>&1; then + cat "$TEST_TMP/unknown_arch.out" >&2 + fail "unsupported architecture fails" + fi + pass "unsupported architecture fails" +} + +test_github_api_url_rejects_non_https() { + local out="$TEST_TMP/github-api-url.out" + + if GITHUB_API_URL="http://api.github.com" ARCUP_SKIP_MAIN=1 bash "$ROOT_DIR/arcup/arcup" >"$out" 2>&1; then + cat "$out" >&2 + fail "rejects non-https GitHub API URL" + fi + + if ! grep -q "invalid GITHUB_API_URL" "$out"; then + cat "$out" >&2 + fail "rejects non-https GitHub API URL" + fi + pass "rejects non-https GitHub API URL" +} + +test_checksum_validation() { + local archive="$TEST_TMP/archive.tar.gz" + local checksum_file="$TEST_TMP/archive.tar.gz.sha256" + local archive_name="arc-node-v1.2.3-x86_64-unknown-linux-gnu.tar.gz" + local checksum + + printf 'archive bytes' > "$archive" + checksum="$(compute_sha256 "$archive")" + printf '%s %s\n' "$checksum" "$archive_name" > "$checksum_file" + verify_checksum_file "$archive" "$checksum_file" "$archive_name" + pass "valid checksum file passes" + + printf '%s other-asset.tar.gz\n' "$checksum" > "$checksum_file" + expect_fail "checksum filename mismatch fails" verify_checksum_file "$archive" "$checksum_file" "$archive_name" +} + +test_download_error_lists_assets() { + local out="$TEST_TMP/download_error.out" + + if ( + GITHUB_AUTH_TOKEN="" + CURL_HEADERS=() + TARGET="x86_64-unknown-linux-gnu" + gh() { return 1; } + curl_with_headers() { return 22; } + list_release_assets() { + printf '%s\n' \ + "arc-node-v1.2.3-x86_64-unknown-linux-gnu.tar.gz" \ + "arc-node-v1.2.3-aarch64-apple-darwin.tar.gz" + } + download_file "v1.2.3" "arc-node-v1.2.3-missing.tar.gz" "$TEST_TMP" + ) >"$out" 2>&1; then + cat "$out" >&2 + fail "download error fails" + fi + + if ! grep -q "Available arc-node release assets" "$out"; then + cat "$out" >&2 + fail "download error lists available assets" + fi + pass "download error lists available assets" +} + +test_download_file_uses_github_token_api() { + local fakebin="$TEST_TMP/token-api-fakebin" + local release_dir="$TEST_TMP/token-api-release" + local output_dir="$TEST_TMP/token-api-output" + local asset_name="arc-node-v1.2.3-aarch64-apple-darwin.tar.gz" + + mkdir -p "$fakebin" "$release_dir" "$output_dir" + printf 'token asset\n' > "$release_dir/$asset_name" + + cat > "$fakebin/gh" <<'EOF' +#!/usr/bin/env bash +echo "gh should not be called when token API succeeds" >&2 +exit 99 +EOF + chmod 755 "$fakebin/gh" + + cat > "$fakebin/curl" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail + +out="" +dump="" +url="" +while [[ $# -gt 0 ]]; do + case "$1" in + -o) + out="$2" + shift 2 + ;; + -D) + dump="$2" + shift 2 + ;; + -H | --retry | --retry-delay | --connect-timeout | --max-time) + shift 2 + ;; + -*) + shift + ;; + *) + url="$1" + shift + ;; + esac +done + +case "$url" in + *api.github.com/repos/test/arc-node/releases/tags/v1.2.3) + data='{"assets":[{"url":"https://api.github.com/repos/test/arc-node/releases/assets/123","name":"arc-node-v1.2.3-aarch64-apple-darwin.tar.gz"}]}' + ;; + *api.github.com/repos/test/arc-node/releases/assets/123) + if [[ "$dump" == "-" ]]; then + printf 'HTTP/1.1 302 Found\r\nLocation: https://objects.example/arc-node-v1.2.3-aarch64-apple-darwin.tar.gz\r\n\r\n' + exit 0 + fi + printf 'asset API request should only resolve redirect location\n' >&2 + exit 22 + ;; + *objects.example/arc-node-v1.2.3-aarch64-apple-darwin.tar.gz) + cp "$TOKEN_RELEASE_DIR/arc-node-v1.2.3-aarch64-apple-darwin.tar.gz" "$out" + exit 0 + ;; + *releases/download*) + echo "direct release URL should not be called when token API succeeds" >&2 + exit 99 + ;; + *) + printf 'unexpected curl URL: %s\n' "$url" >&2 + exit 22 + ;; +esac + +if [[ -n "$out" ]]; then + printf '%s\n' "$data" > "$out" +else + printf '%s\n' "$data" +fi +EOF + chmod 755 "$fakebin/curl" + + ( + PATH="$fakebin:$PATH" + export TOKEN_RELEASE_DIR="$release_dir" + GITHUB_AUTH_TOKEN="test-token" + CURL_HEADERS=() + REPO="test/arc-node" + download_file "v1.2.3" "$asset_name" "$output_dir" + ) >"$TEST_TMP/token-api.out" 2>&1 + + if [[ "$(cat "$output_dir/$asset_name")" != "token asset" ]]; then + cat "$TEST_TMP/token-api.out" >&2 + fail "download_file uses token API when available" + fi + pass "download_file uses token API when available" +} + +test_token_api_preserves_custom_header() { + local fakebin="$TEST_TMP/token-header-fakebin" + local release_dir="$TEST_TMP/token-header-release" + local output_dir="$TEST_TMP/token-header-output" + local asset_name="arc-node-v1.2.3-aarch64-apple-darwin.tar.gz" + + mkdir -p "$fakebin" "$release_dir" "$output_dir" + printf 'token header asset\n' > "$release_dir/$asset_name" + + cat > "$fakebin/gh" <<'EOF' +#!/usr/bin/env bash +echo "gh should not be called when token API succeeds" >&2 +exit 99 +EOF + chmod 755 "$fakebin/gh" + + cat > "$fakebin/curl" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail + +out="" +dump="" +url="" +headers="" +while [[ $# -gt 0 ]]; do + case "$1" in + -o) + out="$2" + shift 2 + ;; + -D) + dump="$2" + shift 2 + ;; + -H) + headers="${headers}${2} +" + shift 2 + ;; + --retry | --retry-delay | --connect-timeout | --max-time) + shift 2 + ;; + -*) + shift + ;; + *) + url="$1" + shift + ;; + esac +done + +require_header() { + local expected="$1" + if [[ "$headers" != *"$expected"* ]]; then + printf 'missing header %s for %s\nheaders:\n%s\n' "$expected" "$url" "$headers" >&2 + exit 22 + fi +} + +case "$url" in + *api.github.com/repos/test/arc-node/releases/tags/v1.2.3) + require_header "Authorization: Bearer test-token" + require_header "X-Arc-Test: custom" + require_header "Accept: application/vnd.github+json" + data='{"assets":[{"url":"https://api.github.com/repos/test/arc-node/releases/assets/123","name":"arc-node-v1.2.3-aarch64-apple-darwin.tar.gz"}]}' + ;; + *api.github.com/repos/test/arc-node/releases/assets/123) + require_header "Authorization: Bearer test-token" + require_header "X-Arc-Test: custom" + require_header "Accept: application/octet-stream" + if [[ "$dump" == "-" ]]; then + printf 'HTTP/2 302\r\nlocation: https://objects.example/arc-node-v1.2.3-aarch64-apple-darwin.tar.gz\r\n\r\n' + exit 0 + fi + printf 'asset API request should only resolve redirect location\n' >&2 + exit 22 + ;; + *objects.example/arc-node-v1.2.3-aarch64-apple-darwin.tar.gz) + if [[ "$headers" == *"Authorization:"* || "$headers" == *"X-Arc-Test:"* ]]; then + printf 'signed asset URL received leaked headers:\n%s\n' "$headers" >&2 + exit 22 + fi + cp "$TOKEN_HEADER_RELEASE_DIR/arc-node-v1.2.3-aarch64-apple-darwin.tar.gz" "$out" + exit 0 + ;; + *releases/download*) + echo "direct release URL should not be called when token API succeeds" >&2 + exit 99 + ;; + *) + printf 'unexpected curl URL: %s\n' "$url" >&2 + exit 22 + ;; +esac + +if [[ -n "$out" ]]; then + printf '%s\n' "$data" > "$out" +else + printf '%s\n' "$data" +fi +EOF + chmod 755 "$fakebin/curl" + + ( + PATH="$fakebin:$PATH" + export TOKEN_HEADER_RELEASE_DIR="$release_dir" + GITHUB_AUTH_TOKEN="test-token" + CURL_HEADERS=(-H "X-Arc-Test: custom") + REPO="test/arc-node" + download_file "v1.2.3" "$asset_name" "$output_dir" + ) >"$TEST_TMP/token-header.out" 2>&1 + + if [[ "$(cat "$output_dir/$asset_name")" != "token header asset" ]]; then + cat "$TEST_TMP/token-header.out" >&2 + fail "token API preserves custom header" + fi + pass "token API preserves custom header" +} + +test_latest_version_retries_anonymous_after_token_failure() { + local fakebin="$TEST_TMP/latest-fallback-fakebin" + local out="$TEST_TMP/latest-fallback.out" + + mkdir -p "$fakebin" + cat > "$fakebin/curl" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail + +headers="" +url="" +while [[ $# -gt 0 ]]; do + case "$1" in + -H) + headers="${headers}${2} +" + shift 2 + ;; + --retry | --retry-delay | --connect-timeout | --max-time) + shift 2 + ;; + -*) + shift + ;; + *) + url="$1" + shift + ;; + esac +done + +case "$url" in + *api.github.com/repos/test/arc-node/releases/latest) + if [[ "$headers" == *"Authorization:"* ]]; then + printf 'bad token\n' >&2 + exit 22 + fi + printf '{"tag_name":"v1.2.3"}\n' + ;; + *) + printf 'unexpected curl URL: %s\n' "$url" >&2 + exit 22 + ;; +esac +EOF + chmod 755 "$fakebin/curl" + + ( + PATH="$fakebin:$PATH" + GITHUB_AUTH_TOKEN="stale-token" + CURL_HEADERS=() + REPO="test/arc-node" + GITHUB_API_URL="https://api.github.com" + version="$(get_latest_version)" + [[ "$version" == "v1.2.3" ]] + ) >"$out" 2>&1 || { + cat "$out" >&2 + fail "latest version retries anonymously after token failure" + } + + pass "latest version retries anonymously after token failure" +} + +test_latest_version_redacts_authenticated_failure() { + local fakebin="$TEST_TMP/latest-redaction-fakebin" + local out="$TEST_TMP/latest-redaction.out" + + mkdir -p "$fakebin" + cat > "$fakebin/curl" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail + +headers="" +url="" +while [[ $# -gt 0 ]]; do + case "$1" in + -H) + headers="${headers}${2} +" + shift 2 + ;; + --retry | --retry-delay | --connect-timeout | --max-time) + shift 2 + ;; + -*) + shift + ;; + *) + url="$1" + shift + ;; + esac +done + +case "$url" in + *api.github.com/repos/test/arc-node/releases/latest) + if [[ "$headers" == *"Authorization:"* ]]; then + printf 'curl verbose Authorization: Bearer stale-token\n' >&2 + else + printf 'anonymous failed\n' >&2 + fi + exit 22 + ;; + *) + printf 'unexpected curl URL: %s\n' "$url" >&2 + exit 22 + ;; +esac +EOF + chmod 755 "$fakebin/curl" + + if ( + PATH="$fakebin:$PATH" + GITHUB_AUTH_TOKEN="stale-token" + CURL_HEADERS=() + REPO="test/arc-node" + GITHUB_API_URL="https://api.github.com" + get_latest_version + ) >"$out" 2>&1; then + cat "$out" >&2 + fail "latest version redacts authenticated failure" + fi + + if grep -q "stale-token" "$out" || grep -q "Authorization: Bearer" "$out"; then + cat "$out" >&2 + fail "latest version redacts authenticated failure" + fi + if ! grep -q "request failed" "$out"; then + cat "$out" >&2 + fail "latest version redacts authenticated failure" + fi + pass "latest version redacts authenticated failure" +} + +test_download_file_uses_gh_when_available() { + local fakebin="$TEST_TMP/gh-download-fakebin" + local release_dir="$TEST_TMP/gh-download-release" + local output_dir="$TEST_TMP/gh-download-output" + local asset_name="arc-node-v1.2.3-aarch64-apple-darwin.tar.gz" + + mkdir -p "$fakebin" "$release_dir" "$output_dir" + printf 'release asset\n' > "$release_dir/$asset_name" + + cat > "$fakebin/gh" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail + +if [[ "${1:-}" != "release" || "${2:-}" != "download" ]]; then + exit 1 +fi +shift 2 + +tag="$1" +shift +repo="" +pattern="" +dir="" +while [[ $# -gt 0 ]]; do + case "$1" in + --repo) + repo="$2" + shift 2 + ;; + --pattern) + pattern="$2" + shift 2 + ;; + --dir) + dir="$2" + shift 2 + ;; + --clobber) + shift + ;; + *) + exit 1 + ;; + esac +done + +[[ "$tag" == "v1.2.3" ]] +[[ "$repo" == "test/arc-node" ]] +cp "$GH_RELEASE_DIR/$pattern" "$dir/$pattern" +EOF + chmod 755 "$fakebin/gh" + + cat > "$fakebin/curl" <<'EOF' +#!/usr/bin/env bash +echo "curl should not be called when gh succeeds" >&2 +exit 99 +EOF + chmod 755 "$fakebin/curl" + + ( + PATH="$fakebin:$PATH" + export GH_RELEASE_DIR="$release_dir" + GITHUB_AUTH_TOKEN="" + CURL_HEADERS=() + REPO="test/arc-node" + download_file "v1.2.3" "$asset_name" "$output_dir" + ) >"$TEST_TMP/gh-download.out" 2>&1 + + if [[ "$(cat "$output_dir/$asset_name")" != "release asset" ]]; then + cat "$TEST_TMP/gh-download.out" >&2 + fail "download_file uses gh when available" + fi + pass "download_file uses gh when available" +} + +test_download_file_falls_back_to_curl() { + local fakebin="$TEST_TMP/curl-fallback-fakebin" + local release_dir="$TEST_TMP/curl-fallback-release" + local output_dir="$TEST_TMP/curl-fallback-output" + local asset_name="arc-node-v1.2.3-aarch64-apple-darwin.tar.gz" + + mkdir -p "$fakebin" "$release_dir" "$output_dir" + printf 'fallback asset\n' > "$release_dir/$asset_name" + + cat > "$fakebin/gh" <<'EOF' +#!/usr/bin/env bash +exit 1 +EOF + chmod 755 "$fakebin/gh" + + cat > "$fakebin/curl" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail + +out="" +url="" +while [[ $# -gt 0 ]]; do + case "$1" in + -o) + out="$2" + shift 2 + ;; + --retry | --retry-delay | --connect-timeout | --max-time) + shift 2 + ;; + -*) + shift + ;; + *) + url="$1" + shift + ;; + esac +done + +case "$url" in + *releases/download/v1.2.3/arc-node-v1.2.3-aarch64-apple-darwin.tar.gz) + cp "$FALLBACK_RELEASE_DIR/${url##*/}" "$out" + ;; + *) + printf 'unexpected curl URL: %s\n' "$url" >&2 + exit 22 + ;; +esac +EOF + chmod 755 "$fakebin/curl" + + ( + PATH="$fakebin:$PATH" + export FALLBACK_RELEASE_DIR="$release_dir" + GITHUB_AUTH_TOKEN="" + CURL_HEADERS=() + REPO="test/arc-node" + download_file "v1.2.3" "$asset_name" "$output_dir" + ) >"$TEST_TMP/curl-fallback.out" 2>&1 + + if [[ "$(cat "$output_dir/$asset_name")" != "fallback asset" ]]; then + cat "$TEST_TMP/curl-fallback.out" >&2 + fail "download_file falls back to curl" + fi + pass "download_file falls back to curl" +} + +test_fixture_install_matrix() { + local fixture_dir="$TEST_TMP/fixture" + local fakebin="$TEST_TMP/fakebin" + local release_dir="$fixture_dir/release" + + mkdir -p "$fixture_dir/build" "$release_dir" "$fakebin" + for binary in "${BINARIES[@]}"; do + printf '#!/usr/bin/env bash\nprintf "%s test binary\\n"\n' "$binary" > "$fixture_dir/build/$binary" + chmod 755 "$fixture_dir/build/$binary" + done + + local target archive_name archive checksum_file + for target in \ + "x86_64-unknown-linux-gnu" \ + "aarch64-unknown-linux-gnu" \ + "aarch64-apple-darwin"; do + archive_name="arc-node-v1.2.3-${target}.tar.gz" + archive="$release_dir/$archive_name" + checksum_file="$release_dir/$archive_name.sha256" + tar -czf "$archive" -C "$fixture_dir/build" "${BINARIES[@]}" + printf '%s %s\n' "$(compute_sha256 "$archive")" "$archive_name" > "$checksum_file" + done + + cat > "$fakebin/uname" <<'EOF' +#!/usr/bin/env bash +case "${1:-}" in + -s) echo "$FAKE_UNAME_S" ;; + -m) echo "$FAKE_UNAME_M" ;; + *) /usr/bin/uname "$@" ;; +esac +EOF + chmod 755 "$fakebin/uname" + + cat > "$fakebin/gh" <<'EOF' +#!/usr/bin/env bash +exit 1 +EOF + chmod 755 "$fakebin/gh" + + cat > "$fakebin/curl" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail + +out="" +url="" +while [[ $# -gt 0 ]]; do + case "$1" in + -o) + out="$2" + shift 2 + ;; + --retry | --retry-delay | --connect-timeout | --max-time) + shift 2 + ;; + -*) + shift + ;; + *) + url="$1" + shift + ;; + esac +done + +case "$url" in + *raw.githubusercontent.com/circlefin/arc-node/main/arcup/arcup) + data='ARCUP_INSTALLER_VERSION="0.2.0"' + ;; + *releases/download/v1.2.3/arc-node-v1.2.3-*.tar.gz*) + name="${url##*/}" + cp "$FIXTURE_RELEASE_DIR/$name" "$out" + exit 0 + ;; + *api.github.com/repos/test/arc-node/releases/tags/v1.2.3) + data='{"assets":[{"name":"arc-node-v1.2.3-x86_64-unknown-linux-gnu.tar.gz"},{"name":"arc-node-v1.2.3-aarch64-unknown-linux-gnu.tar.gz"},{"name":"arc-node-v1.2.3-aarch64-apple-darwin.tar.gz"}]}' + ;; + *) + printf 'unexpected curl URL: %s\n' "$url" >&2 + exit 22 + ;; +esac + +if [[ -n "$out" ]]; then + printf '%s\n' "$data" > "$out" +else + printf '%s\n' "$data" +fi +EOF + chmod 755 "$fakebin/curl" + + local case_name os arch install_dir + while read -r case_name os arch target; do + install_dir="$TEST_TMP/install-$case_name" + mkdir -p "$install_dir" + FIXTURE_RELEASE_DIR="$release_dir" \ + FAKE_UNAME_S="$os" \ + FAKE_UNAME_M="$arch" \ + ARC_GITHUB_TOKEN="" \ + GH_TOKEN="" \ + GITHUB_TOKEN="" \ + PATH="$fakebin:$PATH" \ + ARC_REPO="test/arc-node" \ + ARC_BIN_DIR="$install_dir/bin" \ + "$ROOT_DIR/arcup/arcup" -i "1.2.3" >"$TEST_TMP/install-$case_name.out" 2>&1 + + for binary in "${BINARIES[@]}"; do + [[ -x "$install_dir/bin/$binary" ]] || fail "installs $binary for $target" + done + done <<'EOF' +linux-amd64 Linux x86_64 x86_64-unknown-linux-gnu +linux-arm64 Linux aarch64 aarch64-unknown-linux-gnu +macos-arm64 Darwin arm64 aarch64-apple-darwin +EOF + + pass "fixture install covers all release targets" +} + +test_archive_path_traversal_fails() { + local bad_archive="$TEST_TMP/bad.tar.gz" + local bad_src="$TEST_TMP/bad-src" + + mkdir -p "$bad_src" + printf 'bad\n' > "$bad_src/evil" + if tar --version 2>/dev/null | grep -q "GNU tar"; then + tar -czf "$bad_archive" -C "$bad_src" --transform 's|evil|../evil|' evil + else + tar -czf "$bad_archive" -C "$bad_src" -s '|evil|../evil|' evil + fi + + if ( validate_archive_contents "$bad_archive" ) >"$TEST_TMP/path_traversal.out" 2>&1; then + cat "$TEST_TMP/path_traversal.out" >&2 + fail "archive path traversal fails" + fi + pass "archive path traversal fails" +} + +test_archive_link_entries_fail() { + local bad_archive="$TEST_TMP/link-entry.tar.gz" + local bad_src="$TEST_TMP/link-entry-src" + + mkdir -p "$bad_src" + ln -s /etc/passwd "$bad_src/arc-node-execution" + tar -czf "$bad_archive" -C "$bad_src" arc-node-execution + + if ( validate_archive_contents "$bad_archive" ) >"$TEST_TMP/link-entry.out" 2>&1; then + cat "$TEST_TMP/link-entry.out" >&2 + fail "archive link entries fail" + fi + pass "archive link entries fail" +} + +test_install_binary_rejects_symlink() { + local install_src="$TEST_TMP/install-symlink-src" + local install_bin="$TEST_TMP/install-symlink-bin" + + mkdir -p "$install_src" "$install_bin" + ln -s /etc/passwd "$install_src/arc-node-execution" + + if ( + TMP_DIR="$install_src" + BIN_DIR="$install_bin" + install_binary "arc-node-execution" + ) >"$TEST_TMP/install-symlink.out" 2>&1; then + cat "$TEST_TMP/install-symlink.out" >&2 + fail "install_binary rejects symlink" + fi + pass "install_binary rejects symlink" +} + +test_version_normalization +test_version_comparison +test_target_mapping +test_unknown_architecture_fails +test_github_api_url_rejects_non_https +test_checksum_validation +test_download_error_lists_assets +test_download_file_uses_github_token_api +test_token_api_preserves_custom_header +test_latest_version_retries_anonymous_after_token_failure +test_latest_version_redacts_authenticated_failure +test_download_file_uses_gh_when_available +test_download_file_falls_back_to_curl +test_fixture_install_matrix +test_archive_path_traversal_fails +test_archive_link_entries_fail +test_install_binary_rejects_symlink diff --git a/assets/localdev/genesis.config.ts b/assets/localdev/genesis.config.ts index 08c0cfe..fbf4315 100644 --- a/assets/localdev/genesis.config.ts +++ b/assets/localdev/genesis.config.ts @@ -28,6 +28,8 @@ import { import { bigintReplacer } from '../../scripts/genesis/types' import { LocalDevAccountCreator } from '../../scripts/genesis/AccountCreator' +const UINT64_MAX = (1n << 64n) - 1n + // localBuilderOptionsSchema defines the options to customize the localdev genesis. export const localBuilderOptionsSchema = LocalDevAccountCreator.optionsSchema.and( z.object({ @@ -35,6 +37,8 @@ export const localBuilderOptionsSchema = LocalDevAccountCreator.optionsSchema.an outputGenesisConfig: z.string().optional(), validatorNames: z.array(z.string()).min(1).optional(), hardforks: schemaGenesisConfig.shape.hardforks, + extraAccountBalance: z.bigint().optional(), + blockGasLimit: z.bigint().optional(), }), ) @@ -43,7 +47,7 @@ const build = async (options: z.infer) => { network: 'localdev', chainId: 1337, }) - const { outputControllersConfig, outputGenesisConfig, validatorNames, hardforks, ...accountOptions } = options + const { outputControllersConfig, outputGenesisConfig, validatorNames, hardforks, extraAccountBalance, blockGasLimit, ...accountOptions } = options const accountCreator = new LocalDevAccountCreator(accountOptions) // Default account for hardhat environment. @@ -90,7 +94,7 @@ const build = async (options: z.infer) => { .concat([one.address]) .concat(controllers.map((x) => x.address)) .concat(accountCreator.extraPrefundAccounts().map((x) => x.address)) - .map((address) => ({ address: address, balance: parseEther('1000000') })), + .map((address) => ({ address: address, balance: extraAccountBalance !== undefined ? parseEther(extraAccountBalance.toString()) : parseEther('1000000') })), NativeFiatToken: { proxy: { admin: proxyAdmin.address }, @@ -116,7 +120,7 @@ const build = async (options: z.infer) => { inverseElasticityMultiplier: 5000n, // 50% minBaseFee: 1n, maxBaseFee: parseGwei('1000'), - blockGasLimit: 30_000_000n, + blockGasLimit: blockGasLimit ?? 30_000_000n, }, consensusParams: { timeoutProposeMs: 3000n, @@ -141,12 +145,13 @@ const build = async (options: z.infer) => { PermissionedValidatorManager: { proxy: { admin: proxyAdmin.address }, owner: admin.address, + pauser: admin.address, validatorRegisterers: [admin.address, operator.address], - controllers: controllers.map((account) => account.address), }, - validators: validators.map((x) => ({ + validators: validators.map((x, i) => ({ publicKey: x.publicKey, votingPower: x.votingPower, + controllers: [{ address: controllers[i].address, votingPowerLimit: UINT64_MAX }], })), }, GasGuzzler: true, diff --git a/assets/localdev/genesis.json b/assets/localdev/genesis.json index f4c5026..433c210 100644 --- a/assets/localdev/genesis.json +++ b/assets/localdev/genesis.json @@ -25,6 +25,7 @@ "zero4Block": 0, "zero5Block": 0, "zero6Block": 0, + "zero7Time": 0, "osakaTime": 0 }, "nonce": "0x0", @@ -67,10 +68,10 @@ "nonce": "0x1", "code": "0x73fcff98b65f9ea559ec0df36f4072c7e3be0520df30146080604052600436106100355760003560e01c80636ccea6521461003a575b600080fd5b6101026004803603606081101561005057600080fd5b73ffffffffffffffffffffffffffffffffffffffff8235169160208101359181019060608101604082013564010000000081111561008d57600080fd5b82018360208201111561009f57600080fd5b803590602001918460018302840111640100000000831117156100c157600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610116945050505050565b604080519115158252519081900360200190f35b600061012184610179565b610164578373ffffffffffffffffffffffffffffffffffffffff16610146848461017f565b73ffffffffffffffffffffffffffffffffffffffff16149050610172565b61016f848484610203565b90505b9392505050565b3b151590565b600081516041146101db576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260238152602001806106296023913960400191505060405180910390fd5b60208201516040830151606084015160001a6101f98682858561042d565b9695505050505050565b60008060608573ffffffffffffffffffffffffffffffffffffffff16631626ba7e60e01b86866040516024018083815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561026f578181015183820152602001610257565b50505050905090810190601f16801561029c5780820380516001836020036101000a031916815260200191505b50604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009098169790971787525181519196909550859450925090508083835b6020831061036957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161032c565b6001836020036101000a038019825116818451168082178552505050505050905001915050600060405180830381855afa9150503d80600081146103c9576040519150601f19603f3d011682016040523d82523d6000602084013e6103ce565b606091505b50915091508180156103e257506020815110155b80156101f9575080517f1626ba7e00000000000000000000000000000000000000000000000000000000906020808401919081101561042057600080fd5b5051149695505050505050565b60007f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08211156104a8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806106726026913960400191505060405180910390fd5b8360ff16601b141580156104c057508360ff16601c14155b15610516576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602681526020018061064c6026913960400191505060405180910390fd5b600060018686868660405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015610572573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff811661061f57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f45435265636f7665723a20696e76616c6964207369676e617475726500000000604482015290519081900360640190fd5b9594505050505056fe45435265636f7665723a20696e76616c6964207369676e6174757265206c656e67746845435265636f7665723a20696e76616c6964207369676e6174757265202776272076616c756545435265636f7665723a20696e76616c6964207369676e6174757265202773272076616c7565a2646970667358221220289705d6ae8701c9ed586541fa0ba342f754518aa4f0e59dbbda380bc9f2323964736f6c634300060c0033" }, - "0x1551f05F23614163494d2E58C0E5Dd0e402930E2": { + "0x695D85E3437DaA1F8f6DE97C5aEdF75B82deA80a": { "balance": "0x0", "nonce": "0x1", - "code": "0x608060405234801561000f575f5ffd5b5060043610610111575f3560e01c80638da5cb5b1161009e5780639fd0506d1161006e5780639fd0506d146103cb578063da980fed146103d3578063e30c3978146103e6578063f2fde38b146103ee578063f77c479114610401575f5ffd5b80638da5cb5b146101b15780638e207848146101d15780639242164f146101e45780639fd02a36146102e8575f5ffd5b8063554bab3c116100e4578063554bab3c146101585780635c975abb1461016b578063715018a61461019957806379ba5097146101a15780638456cb59146101a9575f5ffd5b8063032b901b1461011557806306cb5b661461012a5780632bbdb79f1461013d5780633f4ba83a14610150575b5f5ffd5b610128610123366004610de7565b610409565b005b610128610138366004610e09565b6104b7565b61012861014b366004610e2f565b61054e565b61012861065b565b610128610166366004610e09565b6106a7565b5f5160206113325f395f51905f5254600160a01b900460ff1660405190151581526020015b60405180910390f35b61012861072b565b61012861073e565b61012861078b565b6101b96107dd565b6040516001600160a01b039091168152602001610190565b6101286101df366004610e46565b610811565b6102826040805160c0810182525f80825260208201819052918101829052606081018290526080810182905260a08101829052905f5160206113125f395f51905f526040805160c08101825282546001600160401b038082168352600160401b820481166020840152600160801b9091041691810191909152600182015460608201526002820154608082015260039091015460a082015292915050565b60405161019091905f60c0820190506001600160401b0383511682526001600160401b0360208401511660208301526001600160401b036040840151166040830152606083015160608301526080830151608083015260a083015160a083015292915050565b6103be60408051610100810182525f80825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e08101829052905f5160206113125f395f51905f52604080516101008101825260059092015461ffff80821684526201000082048116602085015264010000000082048116928401929092526601000000000000810482166060840152600160401b810482166080840152600160501b8104821660a0840152600160601b8104821660c0840152600160701b90041660e082015292915050565b6040516101909190610e5f565b6101b9610967565b6101286103e1366004610eff565b61097c565b6101b9610b5a565b6101286103fc366004610e09565b610b82565b6101b9610c07565b610411610c2f565b610419610c7a565b5f8161ffff161161043d5760405163811da5f160e01b815260040160405180910390fd5b7f668f09ce856848ead6cb1ddee963f15ef833cea8958030868f867aec84385205805461ffff191661ffff83161781556040515f5160206113125f395f51905f52917faba8150f8eae6a1acc6824830944cdc19c670da2fae4fe28f4dfa04c0d6932d4916104ab9190610f11565b60405180910390a15050565b6104bf610cb2565b6001600160a01b0381166104e6576040516371ab47bb60e11b815260040160405180910390fd5b7f958f8fec699b51a1249f513eceda5429078000657f74abd1721bba363087af0080546001600160a01b0319166001600160a01b03831690811782556040517f1304018cfe79741dcf02ba6b61d39cc4757d59395d03224d9925c7aa83002146905f90a25050565b610556610c2f565b61055e610c7a565b5f811161057e57604051635251cbd760e01b815260040160405180910390fd5b7f668f09ce856848ead6cb1ddee963f15ef833cea8958030868f867aec84385203819055604080515f5160206113125f395f51905f5280546001600160401b03808216845281851c81166020850152608091821c16938301939093527f668f09ce856848ead6cb1ddee963f15ef833cea8958030868f867aec843852015460608301527f668f09ce856848ead6cb1ddee963f15ef833cea8958030868f867aec84385202549282019290925260a081018390527f413a821e8d0eadcc30efbe627a655111b9f28f5999003a85b7718a14b0e329ef9060c0016104ab565b610663610ce4565b5f5160206113325f395f51905f52805460ff60a01b191681556040517f7805862f689e2f13df9f062ff482ad3ad112aca9e0847911ed832e158c525b33905f90a150565b6106af610cb2565b6001600160a01b0381166106d65760405163a74995ab60e01b815260040160405180910390fd5b5f5160206113325f395f51905f5280546001600160a01b0319166001600160a01b03831690811782556040517fb80482a293ca2e013eda8683c9bd7fc8347cfdaeea5ede58cba46df502c2a604905f90a25050565b610733610cb2565b61073c5f610d1c565b565b3380610748610b5a565b6001600160a01b03161461077f5760405163118cdaa760e01b81526001600160a01b03821660048201526024015b60405180910390fd5b61078881610d1c565b50565b610793610ce4565b5f5160206113325f395f51905f52805460ff60a01b1916600160a01b1781556040517f6985a02210a168e66602d3235cb6db0e70f92b3ba4d376a33c0f3d9434bff625905f90a150565b5f807f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c1993005b546001600160a01b031692915050565b610819610c2f565b610821610c7a565b60646108306020830183610f99565b6001600160401b0316111561085857604051633e82ffd960e21b815260040160405180910390fd5b61271061086b6040830160208401610f99565b6001600160401b031611156108935760405163f87b4d6d60e01b815260040160405180910390fd5b8060800135816060013511156108bc576040516336b1b98d60e11b815260040160405180910390fd5b5f8160a00135116108e057604051635251cbd760e01b815260040160405180910390fd5b6127106108f36060830160408401610f99565b6001600160401b0316111561091b5760405163279eca6760e21b815260040160405180910390fd5b5f5160206113125f395f51905f5281816109358282610fb4565b9050507f413a821e8d0eadcc30efbe627a655111b9f28f5999003a85b7718a14b0e329ef826040516104ab9190611071565b5f805f5160206113325f395f51905f52610801565b610984610c2f565b61098c610c7a565b5f61099a6020830183610de7565b61ffff16116109bc5760405163811da5f160e01b815260040160405180910390fd5b5f6109cd6040830160208401610de7565b61ffff16116109ef5760405163055dd8c360e01b815260040160405180910390fd5b5f610a006060830160408401610de7565b61ffff1611610a2257604051632de6d8ed60e11b815260040160405180910390fd5b5f610a336080830160608401610de7565b61ffff1611610a5557604051634c14d46960e11b815260040160405180910390fd5b5f610a6660a0830160808401610de7565b61ffff1611610a88576040516396e398e160e01b815260040160405180910390fd5b5f610a9960c0830160a08401610de7565b61ffff1611610abb57604051632218f17b60e21b815260040160405180910390fd5b5f610acc60e0830160c08401610de7565b61ffff1611610aee57604051631805fa5160e11b815260040160405180910390fd5b5f5160206113125f395f51905f52817f668f09ce856848ead6cb1ddee963f15ef833cea8958030868f867aec84385205610b2882826110fa565b9050507faba8150f8eae6a1acc6824830944cdc19c670da2fae4fe28f4dfa04c0d6932d4826040516104ab9190611260565b5f807f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c00610801565b610b8a610cb2565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0080546001600160a01b0319166001600160a01b0383169081178255610bce6107dd565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a35050565b5f807f958f8fec699b51a1249f513eceda5429078000657f74abd1721bba363087af00610801565b7f958f8fec699b51a1249f513eceda5429078000657f74abd1721bba363087af0080546001600160a01b0316331461078857604051630e971e6360e11b815260040160405180910390fd5b5f5160206113325f395f51905f528054600160a01b900460ff16156107885760405163ab35696f60e01b815260040160405180910390fd5b33610cbb6107dd565b6001600160a01b03161461073c5760405163118cdaa760e01b8152336004820152602401610776565b5f5160206113325f395f51905f5280546001600160a01b031633146107885760405163daeefd6560e01b815260040160405180910390fd5b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0080546001600160a01b0319168155610d5482610d58565b5050565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b61ffff81168114610788575f5ffd5b8035610de281610dc8565b919050565b5f60208284031215610df7575f5ffd5b8135610e0281610dc8565b9392505050565b5f60208284031215610e19575f5ffd5b81356001600160a01b0381168114610e02575f5ffd5b5f60208284031215610e3f575f5ffd5b5035919050565b5f60c0828403128015610e57575f5ffd5b509092915050565b5f6101008201905061ffff835116825261ffff602084015116602083015261ffff60408401511660408301526060830151610ea0606084018261ffff169052565b506080830151610eb6608084018261ffff169052565b5060a0830151610ecc60a084018261ffff169052565b5060c0830151610ee260c084018261ffff169052565b5060e0830151610ef860e084018261ffff169052565b5092915050565b5f610100828403128015610e57575f5ffd5b815461ffff8082168352601082901c166020830152610100820190602081901c61ffff166040840152603081901c61ffff166060840152604081901c61ffff166080840152605081901c61ffff1660a0840152606081901c61ffff1660c0840152607081901c61ffff1660e0840152610ef8565b6001600160401b0381168114610788575f5ffd5b5f60208284031215610fa9575f5ffd5b8135610e0281610f85565b8135610fbf81610f85565b6001600160401b03811690508154816001600160401b031982161783556020840135610fea81610f85565b6fffffffffffffffff00000000000000008160401b16836fffffffffffffffffffffffffffffffff198416171784555050505f604083013561102b81610f85565b825467ffffffffffffffff60801b1916608091821b67ffffffffffffffff60801b161783556060840135600184015583013560028301555060a090910135600390910155565b60c08101823561108081610f85565b6001600160401b03168252602083013561109981610f85565b6001600160401b0316602083015260408301356110b581610f85565b6001600160401b03166040830152606083810135908301526080808401359083015260a092830135929091019190915290565b5f81356110f481610dc8565b92915050565b813561110581610dc8565b61ffff8116905081548161ffff198216178355602084013561112681610dc8565b63ffff00008160101b168363ffffffff1984161717845550505061116d61114f604084016110e8565b825465ffff00000000191660209190911b65ffff0000000016178255565b61119e61117c606084016110e8565b825467ffff000000000000191660309190911b67ffff00000000000016178255565b6111d36111ad608084016110e8565b825469ffff0000000000000000191660409190911b69ffff000000000000000016178255565b6112026111e260a084016110e8565b82805461ffff60501b191660509290921b61ffff60501b16919091179055565b61123161121160c084016110e8565b82805461ffff60601b191660609290921b61ffff60601b16919091179055565b610d5461124060e084016110e8565b82805461ffff60701b191660709290921b61ffff60701b16919091179055565b6101008101823561127081610dc8565b61ffff168252602083013561128481610dc8565b61ffff16602083015261129960408401610dd7565b61ffff1660408301526112ae60608401610dd7565b61ffff1660608301526112c360808401610dd7565b61ffff1660808301526112d860a08401610dd7565b61ffff1660a08301526112ed60c08401610dd7565b61ffff1660c083015261130260e08401610dd7565b61ffff811660e0840152610ef856fe668f09ce856848ead6cb1ddee963f15ef833cea8958030868f867aec843852000642d7922329a434cf4fd17a3c95eb692c24fd95f9f94d0b55420a5d895f4a00a2646970667358221220300b20da9342d83646d128d553cf6c6bd30c424da33ceef743adc9013f59da1264736f6c634300081d0033" + "code": "0x608060405234801561000f575f5ffd5b5060043610610111575f3560e01c80638da5cb5b1161009e5780639fd0506d1161006e5780639fd0506d146103cb578063da980fed146103d3578063e30c3978146103e6578063f2fde38b146103ee578063f77c479114610401575f5ffd5b80638da5cb5b146101b15780638e207848146101d15780639242164f146101e45780639fd02a36146102e8575f5ffd5b8063554bab3c116100e4578063554bab3c146101585780635c975abb1461016b578063715018a61461019957806379ba5097146101a15780638456cb59146101a9575f5ffd5b8063032b901b1461011557806306cb5b661461012a5780632bbdb79f1461013d5780633f4ba83a14610150575b5f5ffd5b610128610123366004610e06565b610409565b005b610128610138366004610e28565b6104b7565b61012861014b366004610e4e565b61054e565b61012861065b565b610128610166366004610e28565b6106b9565b5f5160206113515f395f51905f5254600160a01b900460ff1660405190151581526020015b60405180910390f35b61012861073d565b610128610750565b61012861079a565b6101b96107fc565b6040516001600160a01b039091168152602001610190565b6101286101df366004610e65565b610830565b6102826040805160c0810182525f80825260208201819052918101829052606081018290526080810182905260a08101829052905f5160206113315f395f51905f526040805160c08101825282546001600160401b038082168352600160401b820481166020840152600160801b9091041691810191909152600182015460608201526002820154608082015260039091015460a082015292915050565b60405161019091905f60c0820190506001600160401b0383511682526001600160401b0360208401511660208301526001600160401b036040840151166040830152606083015160608301526080830151608083015260a083015160a083015292915050565b6103be60408051610100810182525f80825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e08101829052905f5160206113315f395f51905f52604080516101008101825260059092015461ffff80821684526201000082048116602085015264010000000082048116928401929092526601000000000000810482166060840152600160401b810482166080840152600160501b8104821660a0840152600160601b8104821660c0840152600160701b90041660e082015292915050565b6040516101909190610e7e565b6101b9610986565b6101286103e1366004610f1e565b61099b565b6101b9610b79565b6101286103fc366004610e28565b610ba1565b6101b9610c26565b610411610c4e565b610419610c99565b5f8161ffff161161043d5760405163811da5f160e01b815260040160405180910390fd5b7f668f09ce856848ead6cb1ddee963f15ef833cea8958030868f867aec84385205805461ffff191661ffff83161781556040515f5160206113315f395f51905f52917faba8150f8eae6a1acc6824830944cdc19c670da2fae4fe28f4dfa04c0d6932d4916104ab9190610f30565b60405180910390a15050565b6104bf610cd1565b6001600160a01b0381166104e6576040516371ab47bb60e11b815260040160405180910390fd5b7f958f8fec699b51a1249f513eceda5429078000657f74abd1721bba363087af0080546001600160a01b0319166001600160a01b03831690811782556040517f1304018cfe79741dcf02ba6b61d39cc4757d59395d03224d9925c7aa83002146905f90a25050565b610556610c4e565b61055e610c99565b5f811161057e57604051635251cbd760e01b815260040160405180910390fd5b7f668f09ce856848ead6cb1ddee963f15ef833cea8958030868f867aec84385203819055604080515f5160206113315f395f51905f5280546001600160401b03808216845281851c81166020850152608091821c16938301939093527f668f09ce856848ead6cb1ddee963f15ef833cea8958030868f867aec843852015460608301527f668f09ce856848ead6cb1ddee963f15ef833cea8958030868f867aec84385202549282019290925260a081018390527f413a821e8d0eadcc30efbe627a655111b9f28f5999003a85b7718a14b0e329ef9060c0016104ab565b610663610d03565b5f5160206113515f395f51905f528054600160a01b900460ff16156106b657805460ff60a01b191681556040517f7805862f689e2f13df9f062ff482ad3ad112aca9e0847911ed832e158c525b33905f90a15b50565b6106c1610cd1565b6001600160a01b0381166106e85760405163a74995ab60e01b815260040160405180910390fd5b5f5160206113515f395f51905f5280546001600160a01b0319166001600160a01b03831690811782556040517fb80482a293ca2e013eda8683c9bd7fc8347cfdaeea5ede58cba46df502c2a604905f90a25050565b610745610cd1565b61074e5f610d3b565b565b338061075a610b79565b6001600160a01b0316146107915760405163118cdaa760e01b81526001600160a01b03821660048201526024015b60405180910390fd5b6106b681610d3b565b6107a2610d03565b5f5160206113515f395f51905f528054600160a01b900460ff166106b657805460ff60a01b1916600160a01b1781556040517f6985a02210a168e66602d3235cb6db0e70f92b3ba4d376a33c0f3d9434bff625905f90a150565b5f807f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c1993005b546001600160a01b031692915050565b610838610c4e565b610840610c99565b606461084f6020830183610fb8565b6001600160401b0316111561087757604051633e82ffd960e21b815260040160405180910390fd5b61271061088a6040830160208401610fb8565b6001600160401b031611156108b25760405163f87b4d6d60e01b815260040160405180910390fd5b8060800135816060013511156108db576040516336b1b98d60e11b815260040160405180910390fd5b5f8160a00135116108ff57604051635251cbd760e01b815260040160405180910390fd5b6127106109126060830160408401610fb8565b6001600160401b0316111561093a5760405163279eca6760e21b815260040160405180910390fd5b5f5160206113315f395f51905f5281816109548282610fd3565b9050507f413a821e8d0eadcc30efbe627a655111b9f28f5999003a85b7718a14b0e329ef826040516104ab9190611090565b5f805f5160206113515f395f51905f52610820565b6109a3610c4e565b6109ab610c99565b5f6109b96020830183610e06565b61ffff16116109db5760405163811da5f160e01b815260040160405180910390fd5b5f6109ec6040830160208401610e06565b61ffff1611610a0e5760405163055dd8c360e01b815260040160405180910390fd5b5f610a1f6060830160408401610e06565b61ffff1611610a4157604051632de6d8ed60e11b815260040160405180910390fd5b5f610a526080830160608401610e06565b61ffff1611610a7457604051634c14d46960e11b815260040160405180910390fd5b5f610a8560a0830160808401610e06565b61ffff1611610aa7576040516396e398e160e01b815260040160405180910390fd5b5f610ab860c0830160a08401610e06565b61ffff1611610ada57604051632218f17b60e21b815260040160405180910390fd5b5f610aeb60e0830160c08401610e06565b61ffff1611610b0d57604051631805fa5160e11b815260040160405180910390fd5b5f5160206113315f395f51905f52817f668f09ce856848ead6cb1ddee963f15ef833cea8958030868f867aec84385205610b478282611119565b9050507faba8150f8eae6a1acc6824830944cdc19c670da2fae4fe28f4dfa04c0d6932d4826040516104ab919061127f565b5f807f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c00610820565b610ba9610cd1565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0080546001600160a01b0319166001600160a01b0383169081178255610bed6107fc565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a35050565b5f807f958f8fec699b51a1249f513eceda5429078000657f74abd1721bba363087af00610820565b7f958f8fec699b51a1249f513eceda5429078000657f74abd1721bba363087af0080546001600160a01b031633146106b657604051630e971e6360e11b815260040160405180910390fd5b5f5160206113515f395f51905f528054600160a01b900460ff16156106b65760405163ab35696f60e01b815260040160405180910390fd5b33610cda6107fc565b6001600160a01b03161461074e5760405163118cdaa760e01b8152336004820152602401610788565b5f5160206113515f395f51905f5280546001600160a01b031633146106b65760405163daeefd6560e01b815260040160405180910390fd5b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0080546001600160a01b0319168155610d7382610d77565b5050565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b61ffff811681146106b6575f5ffd5b8035610e0181610de7565b919050565b5f60208284031215610e16575f5ffd5b8135610e2181610de7565b9392505050565b5f60208284031215610e38575f5ffd5b81356001600160a01b0381168114610e21575f5ffd5b5f60208284031215610e5e575f5ffd5b5035919050565b5f60c0828403128015610e76575f5ffd5b509092915050565b5f6101008201905061ffff835116825261ffff602084015116602083015261ffff60408401511660408301526060830151610ebf606084018261ffff169052565b506080830151610ed5608084018261ffff169052565b5060a0830151610eeb60a084018261ffff169052565b5060c0830151610f0160c084018261ffff169052565b5060e0830151610f1760e084018261ffff169052565b5092915050565b5f610100828403128015610e76575f5ffd5b815461ffff8082168352601082901c166020830152610100820190602081901c61ffff166040840152603081901c61ffff166060840152604081901c61ffff166080840152605081901c61ffff1660a0840152606081901c61ffff1660c0840152607081901c61ffff1660e0840152610f17565b6001600160401b03811681146106b6575f5ffd5b5f60208284031215610fc8575f5ffd5b8135610e2181610fa4565b8135610fde81610fa4565b6001600160401b03811690508154816001600160401b03198216178355602084013561100981610fa4565b6fffffffffffffffff00000000000000008160401b16836fffffffffffffffffffffffffffffffff198416171784555050505f604083013561104a81610fa4565b825467ffffffffffffffff60801b1916608091821b67ffffffffffffffff60801b161783556060840135600184015583013560028301555060a090910135600390910155565b60c08101823561109f81610fa4565b6001600160401b0316825260208301356110b881610fa4565b6001600160401b0316602083015260408301356110d481610fa4565b6001600160401b03166040830152606083810135908301526080808401359083015260a092830135929091019190915290565b5f813561111381610de7565b92915050565b813561112481610de7565b61ffff8116905081548161ffff198216178355602084013561114581610de7565b63ffff00008160101b168363ffffffff1984161717845550505061118c61116e60408401611107565b825465ffff00000000191660209190911b65ffff0000000016178255565b6111bd61119b60608401611107565b825467ffff000000000000191660309190911b67ffff00000000000016178255565b6111f26111cc60808401611107565b825469ffff0000000000000000191660409190911b69ffff000000000000000016178255565b61122161120160a08401611107565b82805461ffff60501b191660509290921b61ffff60501b16919091179055565b61125061123060c08401611107565b82805461ffff60601b191660609290921b61ffff60601b16919091179055565b610d7361125f60e08401611107565b82805461ffff60701b191660709290921b61ffff60701b16919091179055565b6101008101823561128f81610de7565b61ffff16825260208301356112a381610de7565b61ffff1660208301526112b860408401610df6565b61ffff1660408301526112cd60608401610df6565b61ffff1660608301526112e260808401610df6565b61ffff1660808301526112f760a08401610df6565b61ffff1660a083015261130c60c08401610df6565b61ffff1660c083015261132160e08401610df6565b61ffff811660e0840152610f1756fe668f09ce856848ead6cb1ddee963f15ef833cea8958030868f867aec843852000642d7922329a434cf4fd17a3c95eb692c24fd95f9f94d0b55420a5d895f4a00a2646970667358221220046372fe4609a57ed6251208e9ef58c11217d4f47c96763ebfa709c47905168464736f6c634300081d0033" }, "0x3600000000000000000000000000000000000001": { "balance": "0x0", @@ -78,7 +79,7 @@ "code": "0x608060405260043610610049575f3560e01c80633659cfe6146100535780634f1ef286146100725780635c60da1b146100855780638f283970146100b5578063f851a440146100d4575b6100516100e8565b005b34801561005e575f5ffd5b5061005161006d3660046104f3565b6100fa565b61005161008036600461050c565b610134565b348015610090575f5ffd5b50610099610197565b6040516001600160a01b03909116815260200160405180910390f35b3480156100c0575f5ffd5b506100516100cf3660046104f3565b6101a5565b3480156100df575f5ffd5b506100996101c5565b6100f86100f3610197565b6101ce565b565b6101026101ec565b6001600160a01b0316330361012c576101298160405180602001604052805f81525061021e565b50565b6101296100e8565b61013c6101ec565b6001600160a01b0316330361018f5761018a8383838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061021e92505050565b505050565b61018a6100e8565b5f6101a0610277565b905090565b6101ad6101ec565b6001600160a01b0316330361012c576101298161029e565b5f6101a06101ec565b365f5f375f5f365f845af43d5f5f3e8080156101e8573d5ff35b3d5ffd5b5f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b610227826102f2565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a280511561026b5761018a8282610370565b6102736103e2565b5050565b5f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61020f565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6102c76101ec565b604080516001600160a01b03928316815291841660208301520160405180910390a161012981610401565b806001600160a01b03163b5f0361032c57604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5b80546001600160a01b0319166001600160a01b039290921691909117905550565b60605f5f846001600160a01b03168460405161038c919061058a565b5f60405180830381855af49150503d805f81146103c4576040519150601f19603f3d011682016040523d82523d5f602084013e6103c9565b606091505b50915091506103d9858383610451565b95945050505050565b34156100f85760405163b398979f60e01b815260040160405180910390fd5b6001600160a01b03811661042a57604051633173bdd160e11b81525f6004820152602401610323565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610361034f565b60608261046657610461826104b0565b6104a9565b815115801561047d57506001600160a01b0384163b155b156104a657604051639996b31560e01b81526001600160a01b0385166004820152602401610323565b50805b9392505050565b8051156104bf57805160208201fd5b60405163d6bda27560e01b815260040160405180910390fd5b80356001600160a01b03811681146104ee575f5ffd5b919050565b5f60208284031215610503575f5ffd5b6104a9826104d8565b5f5f5f6040848603121561051e575f5ffd5b610527846104d8565b9250602084013567ffffffffffffffff811115610542575f5ffd5b8401601f81018613610552575f5ffd5b803567ffffffffffffffff811115610568575f5ffd5b866020828401011115610579575f5ffd5b939660209190910195509293505050565b5f82518060208501845e5f92019182525091905056fea26469706673582212204c0881af9b906ca964ce2bc1cc4525350de8dd57658dacb806266290556eb9ae64736f6c634300081d0033", "storage": { "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x000000000000000000000000a0ee7a142d267c1f36714e4a8f75612f20a79720", - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0000000000000000000000001551f05f23614163494d2e58c0e5dd0e402930e2", + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x000000000000000000000000695d85e3437daa1f8f6de97c5aedf75b82dea80a", "0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300": "0x00000000000000000000000023618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f", "0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00": "0x0000000000000000000000000000000000000000000000000000000000000001", "0x958f8fec699b51a1249f513eceda5429078000657f74abd1721bba363087af00": "0x00000000000000000000000023618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f", @@ -90,10 +91,10 @@ "0x668f09ce856848ead6cb1ddee963f15ef833cea8958030868f867aec84385205": "0x0000000000000000000000000000000001f403e801f403e801f403e801f40bb8" } }, - "0xbE869B9952F16af1c0B3C68C17eB3aB61DccaEAb": { + "0xfCc314DD5Ad756C6bBA725617438C0d25450a0dE": { "balance": "0x0", "nonce": "0x1", - "code": "0x608060405234801561000f575f5ffd5b50600436106100e5575f3560e01c80638da5cb5b11610088578063c4899f0c11610063578063c4899f0c146101cc578063e30c3978146101df578063f2fde38b146101e7578063f94e1867146101fa575f5ffd5b80638da5cb5b14610184578063b5d89627146101a4578063b86444b1146101c4575f5ffd5b80634ebae617116100c35780634ebae61714610138578063715018a61461014d57806377db16691461015557806379ba50971461017c575f5ffd5b806303a9b132146100e957806324408a68146101105780632469d67214610125575b5f5ffd5b6100fd5f51602061138e5f395f51905f5281565b6040519081526020015b60405180910390f35b61011861020d565b6040516101079190610ffc565b6100fd61013336600461108e565b6103ed565b61014b61014636600461114f565b610598565b005b61014b61065b565b7fb58da0dce03316992faea3e12c60705b8ac05a309e27e3bc8421e5b271c9d204546100fd565b61014b61066e565b61018c6106b6565b6040516001600160a01b039091168152602001610107565b6101b76101b236600461114f565b6106ea565b6040516101079190611166565b6100fd61080f565b61014b6101da366004611178565b61082d565b61018c610996565b61014b6101f5366004611199565b6109be565b61014b61020836600461114f565b610a43565b60605f51602061138e5f395f51905f525f6102477fb58da0dce03316992faea3e12c60705b8ac05a309e27e3bc8421e5b271c9d201610c4d565b9050806001600160401b038111156102615761026161105f565b6040519080825280602002602001820160405280156102b557816020015b6102a26040805160608101909152805f8152606060208201525f60409091015290565b81526020019060019003908161027f5790505b5092505f5b818110156103e7575f6102d06001850183610c56565b5f818152602086905260409081902081516060810190925280549293509091829060ff16600281111561030557610305610f61565b600281111561031657610316610f61565b815260200160018201805461032a906111bf565b80601f0160208091040260200160405190810160405280929190818152602001828054610356906111bf565b80156103a15780601f10610378576101008083540402835291602001916103a1565b820191905f5260205f20905b81548152906001019060200180831161038457829003601f168201915b5050509183525050600291909101546001600160401b031660209091015285518690849081106103d3576103d36111f7565b6020908102919091010152506001016102ba565b50505090565b5f6103f6610c68565b602083511461041857604051630717c3c160e01b815260040160405180910390fd5b82516020808501919091205f8181527fb58da0dce03316992faea3e12c60705b8ac05a309e27e3bc8421e5b271c9d2039092526040909120545f51602061138e5f395f51905f529190819060ff1615610490576040516338a14a4160e01b815260040161048791815260200190565b60405180910390fd5b50600482018054905f6104a28361121f565b909155506040805160608101909152909350806001815260208082018890526001600160401b0387166040928301525f868152908590522081518154829060ff191660018360028111156104f8576104f8610f61565b0217905550602082015160018201906105119082611283565b50604091820151600291909101805467ffffffffffffffff19166001600160401b039092169190911790555f82815260038401602052819020805460ff191660011790555183907fea20c1f9de86768933c1d4eff47ce667e26f886c4877d1bedc253e42b2f04a7c90610587908790899061133d565b60405180910390a250505b92915050565b6105a0610c68565b5f8181525f51602061138e5f395f51905f5260208190526040909120805460019060ff1660028111156105d5576105d5610f61565b1483906105f8576040516355b54a8d60e01b815260040161048791815260200190565b50805460ff191660021781556106116001830184610c9a565b5060028101546040516001600160401b03909116815283907fbc5136437746415b39af863272d0ca406634cf14bb7c3e5fa03855781add87e59060200160405180910390a2505050565b610663610c68565b61066c5f610ca5565b565b3380610678610996565b6001600160a01b0316146106aa5760405163118cdaa760e01b81526001600160a01b0382166004820152602401610487565b6106b381610ca5565b50565b5f807f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c1993005b546001600160a01b031692915050565b61070d6040805160608101909152805f8152606060208201525f60409091015290565b5f8281525f51602061138e5f395f51905f5260208190526040918290208251606081019093528054919291829060ff16600281111561074e5761074e610f61565b600281111561075f5761075f610f61565b8152602001600182018054610773906111bf565b80601f016020809104026020016040519081016040528092919081815260200182805461079f906111bf565b80156107ea5780601f106107c1576101008083540402835291602001916107ea565b820191905f5260205f20905b8154815290600101906020018083116107cd57829003601f168201915b5050509183525050600291909101546001600160401b03166020909101529392505050565b5f5f51602061138e5f395f51905f5261082781610ce1565b91505090565b610835610c68565b5f5f51602061138e5f395f51905f525f848152602082905260408120919250815460ff16600281111561086a5761086a610f61565b1415849061088e576040516355b54a8d60e01b815260040161048791815260200190565b5060028101546001600160401b0390811690841681036108c157604051632bf0186d60e21b815260040160405180910390fd5b6002825460ff1660028111156108d9576108d9610f61565b1480156108ee57505f816001600160401b0316115b801561090157506001600160401b038416155b1561092f57600161091184610ce1565b1161092f5760405163aff8365b60e01b815260040160405180910390fd5b60028201805467ffffffffffffffff19166001600160401b03868116918217909255604080519284168352602083019190915286917f4a7e1cc2e075be9a3c913bf00ec8ff10f6630f23433f65eee31e20ef69110ae4910160405180910390a25050505050565b5f807f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c006106da565b6109c6610c68565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0080546001600160a01b0319166001600160a01b0383169081178255610a0a6106b6565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a35050565b610a4b610c68565b5f5f51602061138e5f395f51905f525f838152602082905260408120919250815460ff166002811115610a8057610a80610f61565b14158390610aa4576040516355b54a8d60e01b815260040161048791815260200190565b506002815460ff166002811115610abd57610abd610f61565b148015610ad6575060028101546001600160401b031615155b15610b04576001610ae683610ce1565b11610b045760405163aff8365b60e01b815260040160405180910390fd5b5f816001018054610b14906111bf565b80601f0160208091040260200160405190810160405280929190818152602001828054610b40906111bf565b8015610b8b5780601f10610b6257610100808354040283529160200191610b8b565b820191905f5260205f20905b815481529060010190602001808311610b6e57829003601f168201915b5050835160208501206002870154949550936001600160401b03169250610bb89150506001860187610d47565b505f868152602086905260408120805460ff1916815590610bdc6001830182610f17565b50600201805467ffffffffffffffff191690555f828152600386016020908152604091829020805460ff1916905590516001600160401b038316815287917f70d022b699831c183688ed9d9d1a1cbf586698b506eaacfd5195a4e3738d285b910160405180910390a2505050505050565b5f610592825490565b5f610c618383610d52565b9392505050565b33610c716106b6565b6001600160a01b03161461066c5760405163118cdaa760e01b8152336004820152602401610487565b5f610c618383610d78565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0080546001600160a01b0319168155610cdd82610dc4565b5050565b5f5f610cef83600101610c4d565b90505f5b81811015610d40575f610d096001860183610c56565b5f818152602087905260409020600201549091506001600160401b031615610d3757610d348461121f565b93505b50600101610cf3565b5050919050565b5f610c618383610e34565b5f825f018281548110610d6757610d676111f7565b905f5260205f200154905092915050565b5f818152600183016020526040812054610dbd57508154600181810184555f848152602080822090930184905584548482528286019093526040902091909155610592565b505f610592565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b5f8181526001830160205260408120548015610f0e575f610e56600183611366565b85549091505f90610e6990600190611366565b9050808214610ec8575f865f018281548110610e8757610e876111f7565b905f5260205f200154905080875f018481548110610ea757610ea76111f7565b5f918252602080832090910192909255918252600188019052604090208390555b8554869080610ed957610ed9611379565b600190038181905f5260205f20015f90559055856001015f8681526020019081526020015f205f905560019350505050610592565b5f915050610592565b508054610f23906111bf565b5f825580601f10610f32575050565b601f0160209004905f5260205f20908101906106b391905b80821115610f5d575f8155600101610f4a565b5090565b634e487b7160e01b5f52602160045260245ffd5b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b5f815160038110610fc257634e487b7160e01b5f52602160045260245ffd5b80845250602082015160606020850152610fdf6060850182610f75565b6040938401516001600160401b0316949093019390935250919050565b5f602082016020835280845180835260408501915060408160051b8601019250602086015f5b8281101561105357603f1987860301845261103e858351610fa3565b94506020938401939190910190600101611022565b50929695505050505050565b634e487b7160e01b5f52604160045260245ffd5b80356001600160401b0381168114611089575f5ffd5b919050565b5f5f6040838503121561109f575f5ffd5b82356001600160401b038111156110b4575f5ffd5b8301601f810185136110c4575f5ffd5b80356001600160401b038111156110dd576110dd61105f565b604051601f8201601f19908116603f011681016001600160401b038111828210171561110b5761110b61105f565b604052818152828201602001871015611122575f5ffd5b816020840160208301375f6020838301015280945050505061114660208401611073565b90509250929050565b5f6020828403121561115f575f5ffd5b5035919050565b602081525f610c616020830184610fa3565b5f5f60408385031215611189575f5ffd5b8235915061114660208401611073565b5f602082840312156111a9575f5ffd5b81356001600160a01b0381168114610c61575f5ffd5b600181811c908216806111d357607f821691505b6020821081036111f157634e487b7160e01b5f52602260045260245ffd5b50919050565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b5f600182016112305761123061120b565b5060010190565b601f82111561127e57805f5260205f20601f840160051c8101602085101561125c5750805b601f840160051c820191505b8181101561127b575f8155600101611268565b50505b505050565b81516001600160401b0381111561129c5761129c61105f565b6112b0816112aa84546111bf565b84611237565b6020601f8211600181146112e2575f83156112cb5750848201515b5f19600385901b1c1916600184901b17845561127b565b5f84815260208120601f198516915b8281101561131157878501518255602094850194600190920191016112f1565b508482101561132e57868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b6001600160401b0383168152604060208201525f61135e6040830184610f75565b949350505050565b818103818111156105925761059261120b565b634e487b7160e01b5f52603160045260245ffdfeb58da0dce03316992faea3e12c60705b8ac05a309e27e3bc8421e5b271c9d200a26469706673582212205cd5370d39b2e3b86d8a3f498e338403bf1ea4bc76c4dcb7a720d82b33431ab664736f6c634300081d0033" + "code": "0x608060405234801561000f575f5ffd5b50600436106100e5575f3560e01c80638da5cb5b11610088578063c4899f0c11610063578063c4899f0c146101cc578063e30c3978146101df578063f2fde38b146101e7578063f94e1867146101fa575f5ffd5b80638da5cb5b14610184578063b5d89627146101a4578063b86444b1146101c4575f5ffd5b80634ebae617116100c35780634ebae61714610138578063715018a61461014d57806377db16691461015557806379ba50971461017c575f5ffd5b806303a9b132146100e957806324408a68146101105780632469d67214610125575b5f5ffd5b6100fd5f5160206113f05f395f51905f5281565b6040519081526020015b60405180910390f35b61011861020d565b604051610107919061104a565b6100fd6101333660046110dc565b6103ed565b61014b61014636600461119d565b610598565b005b61014b6106a0565b7fb58da0dce03316992faea3e12c60705b8ac05a309e27e3bc8421e5b271c9d204546100fd565b61014b6106b3565b61018c6106fb565b6040516001600160a01b039091168152602001610107565b6101b76101b236600461119d565b61072f565b60405161010791906111b4565b6100fd610854565b61014b6101da3660046111c6565b610872565b61018c6109db565b61014b6101f53660046111e7565b610a03565b61014b61020836600461119d565b610a88565b60605f5160206113f05f395f51905f525f6102477fb58da0dce03316992faea3e12c60705b8ac05a309e27e3bc8421e5b271c9d201610c92565b9050806001600160401b03811115610261576102616110ad565b6040519080825280602002602001820160405280156102b557816020015b6102a26040805160608101909152805f8152606060208201525f60409091015290565b81526020019060019003908161027f5790505b5092505f5b818110156103e7575f6102d06001850183610c9b565b5f818152602086905260409081902081516060810190925280549293509091829060ff16600281111561030557610305610fa6565b600281111561031657610316610fa6565b815260200160018201805461032a9061120d565b80601f01602080910402602001604051908101604052809291908181526020018280546103569061120d565b80156103a15780601f10610378576101008083540402835291602001916103a1565b820191905f5260205f20905b81548152906001019060200180831161038457829003601f168201915b5050509183525050600291909101546001600160401b031660209091015285518690849081106103d3576103d3611245565b6020908102919091010152506001016102ba565b50505090565b5f6103f6610cad565b602083511461041857604051630717c3c160e01b815260040160405180910390fd5b82516020808501919091205f8181527fb58da0dce03316992faea3e12c60705b8ac05a309e27e3bc8421e5b271c9d2039092526040909120545f5160206113f05f395f51905f529190819060ff1615610490576040516338a14a4160e01b815260040161048791815260200190565b60405180910390fd5b50600482018054905f6104a28361126d565b909155506040805160608101909152909350806001815260208082018890526001600160401b0387166040928301525f868152908590522081518154829060ff191660018360028111156104f8576104f8610fa6565b02179055506020820151600182019061051190826112d1565b50604091820151600291909101805467ffffffffffffffff19166001600160401b039092169190911790555f82815260038401602052819020805460ff191660011790555183907fea20c1f9de86768933c1d4eff47ce667e26f886c4877d1bedc253e42b2f04a7c90610587908790899061138b565b60405180910390a250505b92915050565b6105a0610cad565b5f5f5160206113f05f395f51905f525f838152602082905260408120919250815460ff1660028111156105d5576105d5610fa6565b141583906105f9576040516355b54a8d60e01b815260040161048791815260200190565b506001815460ff16600281111561061257610612610fa6565b8254859260ff909116911461063c57604051635c12665560e01b81526004016104879291906113b4565b5050805460ff191660021781556106566001830184610cdf565b5060028101546040516001600160401b03909116815283907fbc5136437746415b39af863272d0ca406634cf14bb7c3e5fa03855781add87e59060200160405180910390a2505050565b6106a8610cad565b6106b15f610cea565b565b33806106bd6109db565b6001600160a01b0316146106ef5760405163118cdaa760e01b81526001600160a01b0382166004820152602401610487565b6106f881610cea565b50565b5f807f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c1993005b546001600160a01b031692915050565b6107526040805160608101909152805f8152606060208201525f60409091015290565b5f8281525f5160206113f05f395f51905f5260208190526040918290208251606081019093528054919291829060ff16600281111561079357610793610fa6565b60028111156107a4576107a4610fa6565b81526020016001820180546107b89061120d565b80601f01602080910402602001604051908101604052809291908181526020018280546107e49061120d565b801561082f5780601f106108065761010080835404028352916020019161082f565b820191905f5260205f20905b81548152906001019060200180831161081257829003601f168201915b5050509183525050600291909101546001600160401b03166020909101529392505050565b5f5f5160206113f05f395f51905f5261086c81610d26565b91505090565b61087a610cad565b5f5f5160206113f05f395f51905f525f848152602082905260408120919250815460ff1660028111156108af576108af610fa6565b141584906108d3576040516355b54a8d60e01b815260040161048791815260200190565b5060028101546001600160401b03908116908416810361090657604051632bf0186d60e21b815260040160405180910390fd5b6002825460ff16600281111561091e5761091e610fa6565b14801561093357505f816001600160401b0316115b801561094657506001600160401b038416155b1561097457600161095684610d26565b116109745760405163aff8365b60e01b815260040160405180910390fd5b60028201805467ffffffffffffffff19166001600160401b03868116918217909255604080519284168352602083019190915286917f4a7e1cc2e075be9a3c913bf00ec8ff10f6630f23433f65eee31e20ef69110ae4910160405180910390a25050505050565b5f807f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0061071f565b610a0b610cad565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0080546001600160a01b0319166001600160a01b0383169081178255610a4f6106fb565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a35050565b610a90610cad565b5f5f5160206113f05f395f51905f525f838152602082905260408120919250815460ff166002811115610ac557610ac5610fa6565b14158390610ae9576040516355b54a8d60e01b815260040161048791815260200190565b506002815460ff166002811115610b0257610b02610fa6565b148015610b1b575060028101546001600160401b031615155b15610b49576001610b2b83610d26565b11610b495760405163aff8365b60e01b815260040160405180910390fd5b5f816001018054610b599061120d565b80601f0160208091040260200160405190810160405280929190818152602001828054610b859061120d565b8015610bd05780601f10610ba757610100808354040283529160200191610bd0565b820191905f5260205f20905b815481529060010190602001808311610bb357829003601f168201915b5050835160208501206002870154949550936001600160401b03169250610bfd9150506001860187610d8c565b505f868152602086905260408120805460ff1916815590610c216001830182610f5c565b50600201805467ffffffffffffffff191690555f828152600386016020908152604091829020805460ff1916905590516001600160401b038316815287917f70d022b699831c183688ed9d9d1a1cbf586698b506eaacfd5195a4e3738d285b910160405180910390a2505050505050565b5f610592825490565b5f610ca68383610d97565b9392505050565b33610cb66106fb565b6001600160a01b0316146106b15760405163118cdaa760e01b8152336004820152602401610487565b5f610ca68383610dbd565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0080546001600160a01b0319168155610d2282610e09565b5050565b5f5f610d3483600101610c92565b90505f5b81811015610d85575f610d4e6001860183610c9b565b5f818152602087905260409020600201549091506001600160401b031615610d7c57610d798461126d565b93505b50600101610d38565b5050919050565b5f610ca68383610e79565b5f825f018281548110610dac57610dac611245565b905f5260205f200154905092915050565b5f818152600183016020526040812054610e0257508154600181810184555f848152602080822090930184905584548482528286019093526040902091909155610592565b505f610592565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b5f8181526001830160205260408120548015610f53575f610e9b6001836113c8565b85549091505f90610eae906001906113c8565b9050808214610f0d575f865f018281548110610ecc57610ecc611245565b905f5260205f200154905080875f018481548110610eec57610eec611245565b5f918252602080832090910192909255918252600188019052604090208390555b8554869080610f1e57610f1e6113db565b600190038181905f5260205f20015f90559055856001015f8681526020019081526020015f205f905560019350505050610592565b5f915050610592565b508054610f689061120d565b5f825580601f10610f77575050565b601f0160209004905f5260205f20908101906106f891905b80821115610fa2575f8155600101610f8f565b5090565b634e487b7160e01b5f52602160045260245ffd5b60038110610fd657634e487b7160e01b5f52602160045260245ffd5b9052565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b611013828251610fba565b5f60208201516060602085015261102d6060850182610fda565b6040938401516001600160401b0316949093019390935250919050565b5f602082016020835280845180835260408501915060408160051b8601019250602086015f5b828110156110a157603f1987860301845261108c858351611008565b94506020938401939190910190600101611070565b50929695505050505050565b634e487b7160e01b5f52604160045260245ffd5b80356001600160401b03811681146110d7575f5ffd5b919050565b5f5f604083850312156110ed575f5ffd5b82356001600160401b03811115611102575f5ffd5b8301601f81018513611112575f5ffd5b80356001600160401b0381111561112b5761112b6110ad565b604051601f8201601f19908116603f011681016001600160401b0381118282101715611159576111596110ad565b604052818152828201602001871015611170575f5ffd5b816020840160208301375f60208383010152809450505050611194602084016110c1565b90509250929050565b5f602082840312156111ad575f5ffd5b5035919050565b602081525f610ca66020830184611008565b5f5f604083850312156111d7575f5ffd5b82359150611194602084016110c1565b5f602082840312156111f7575f5ffd5b81356001600160a01b0381168114610ca6575f5ffd5b600181811c9082168061122157607f821691505b60208210810361123f57634e487b7160e01b5f52602260045260245ffd5b50919050565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b5f6001820161127e5761127e611259565b5060010190565b601f8211156112cc57805f5260205f20601f840160051c810160208510156112aa5750805b601f840160051c820191505b818110156112c9575f81556001016112b6565b50505b505050565b81516001600160401b038111156112ea576112ea6110ad565b6112fe816112f8845461120d565b84611285565b6020601f821160018114611330575f83156113195750848201515b5f19600385901b1c1916600184901b1784556112c9565b5f84815260208120601f198516915b8281101561135f578785015182556020948501946001909201910161133f565b508482101561137c57868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b6001600160401b0383168152604060208201525f6113ac6040830184610fda565b949350505050565b82815260408101610ca66020830184610fba565b8181038181111561059257610592611259565b634e487b7160e01b5f52603160045260245ffdfeb58da0dce03316992faea3e12c60705b8ac05a309e27e3bc8421e5b271c9d200a2646970667358221220e5d087314472ac48a65ba1a9c38dbf61b71eb3c2cc0f9b03ade2ac62ec97aecc64736f6c634300081d0033" }, "0x3600000000000000000000000000000000000002": { "balance": "0x0", @@ -101,7 +102,7 @@ "code": "0x608060405260043610610049575f3560e01c80633659cfe6146100535780634f1ef286146100725780635c60da1b146100855780638f283970146100b5578063f851a440146100d4575b6100516100e8565b005b34801561005e575f5ffd5b5061005161006d3660046104f3565b6100fa565b61005161008036600461050c565b610134565b348015610090575f5ffd5b50610099610197565b6040516001600160a01b03909116815260200160405180910390f35b3480156100c0575f5ffd5b506100516100cf3660046104f3565b6101a5565b3480156100df575f5ffd5b506100996101c5565b6100f86100f3610197565b6101ce565b565b6101026101ec565b6001600160a01b0316330361012c576101298160405180602001604052805f81525061021e565b50565b6101296100e8565b61013c6101ec565b6001600160a01b0316330361018f5761018a8383838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061021e92505050565b505050565b61018a6100e8565b5f6101a0610277565b905090565b6101ad6101ec565b6001600160a01b0316330361012c576101298161029e565b5f6101a06101ec565b365f5f375f5f365f845af43d5f5f3e8080156101e8573d5ff35b3d5ffd5b5f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b610227826102f2565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a280511561026b5761018a8282610370565b6102736103e2565b5050565b5f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61020f565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6102c76101ec565b604080516001600160a01b03928316815291841660208301520160405180910390a161012981610401565b806001600160a01b03163b5f0361032c57604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5b80546001600160a01b0319166001600160a01b039290921691909117905550565b60605f5f846001600160a01b03168460405161038c919061058a565b5f60405180830381855af49150503d805f81146103c4576040519150601f19603f3d011682016040523d82523d5f602084013e6103c9565b606091505b50915091506103d9858383610451565b95945050505050565b34156100f85760405163b398979f60e01b815260040160405180910390fd5b6001600160a01b03811661042a57604051633173bdd160e11b81525f6004820152602401610323565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610361034f565b60608261046657610461826104b0565b6104a9565b815115801561047d57506001600160a01b0384163b155b156104a657604051639996b31560e01b81526001600160a01b0385166004820152602401610323565b50805b9392505050565b8051156104bf57805160208201fd5b60405163d6bda27560e01b815260040160405180910390fd5b80356001600160a01b03811681146104ee575f5ffd5b919050565b5f60208284031215610503575f5ffd5b6104a9826104d8565b5f5f5f6040848603121561051e575f5ffd5b610527846104d8565b9250602084013567ffffffffffffffff811115610542575f5ffd5b8401601f81018613610552575f5ffd5b803567ffffffffffffffff811115610568575f5ffd5b866020828401011115610579575f5ffd5b939660209190910195509293505050565b5f82518060208501845e5f92019182525091905056fea26469706673582212204c0881af9b906ca964ce2bc1cc4525350de8dd57658dacb806266290556eb9ae64736f6c634300081d0033", "storage": { "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x000000000000000000000000a0ee7a142d267c1f36714e4a8f75612f20a79720", - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x000000000000000000000000be869b9952f16af1c0b3c68c17eb3ab61dccaeab", + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x000000000000000000000000fcc314dd5ad756c6bba725617438c0d25450a0de", "0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00": "0x0000000000000000000000000000000000000000000000000000000000000001", "0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300": "0x0000000000000000000000003600000000000000000000000000000000000003", "0x0499f4c2de309a54da703ff88d2d815fa145b3fb32cf77b2528c8033db8fd7ee": "0x0000000000000000000000000000000000000000000000000000000000000002", @@ -143,10 +144,10 @@ "0xb58da0dce03316992faea3e12c60705b8ac05a309e27e3bc8421e5b271c9d204": "0x0000000000000000000000000000000000000000000000000000000000000006" } }, - "0x92b26CF0580faECF86cdd16069aF99a81aadc3ad": { + "0x98C74F7eF54BA22be94415d7C967B352c6a42D6d": { "balance": "0x0", "nonce": "0x1", - "code": "0x608060405234801561000f575f5ffd5b50600436106101dc575f3560e01c806379ba509711610109578063d2c20cb31161009e578063ead130111161006e578063ead130111461048d578063ec4a44d7146104a1578063f2fde38b146104b5578063f6a74ed7146104c8575f5ffd5b8063d2c20cb31461042b578063d773a7411461043e578063da3003f914610472578063e30c397814610485575f5ffd5b80638da5cb5b116100d95780638da5cb5b146103d25780639fd0506d146103da578063aa5ecb52146103e2578063b429afeb146103f5575f5ffd5b806379ba5097146103b35780637f77403d146103bb5780637ffc51fd146103c35780638456cb59146103ca575f5ffd5b8063485cc9551161017f5780635c975abb1161014f5780635c975abb146103595780635e5feee614610377578063602a9eee1461038a578063715018a6146103ab575f5ffd5b8063485cc955146102b85780634cb4850a146102cb578063554bab3c146102de5780635acac2b6146102f1575f5ffd5b8063263a3402116101ba578063263a34021461028b5780632eb5c6581461029557806337ec36d1146102a85780633f4ba83a146102b0575f5ffd5b8063048544a4146101e057806306433b1b1461022c5780631904bb2e1461026b575b5f5ffd5b6102176101ee366004611498565b6001600160a01b03165f9081525f51602061187e5f395f51905f52602052604090205460ff1690565b60405190151581526020015b60405180910390f35b6102537f000000000000000000000000360000000000000000000000000000000000000281565b6040516001600160a01b039091168152602001610223565b61027e610279366004611498565b6104db565b60405161022391906114e6565b6102936105b6565b005b6102936102a336600461155c565b610661565b610293610745565b6102936107e5565b6102936102c6366004611591565b610831565b6102936102d93660046115c2565b6109bd565b6102936102ec366004611498565b610ade565b6103416102ff366004611498565b6001600160a01b03165f9081527fe90ec3add3e251bfbe914c9e482b511e91a3b187718c1dc10223f64a8a644a0160205260409020546001600160401b031690565b6040516001600160401b039091168152602001610223565b5f51602061185e5f395f51905f5254600160a01b900460ff16610217565b610293610385366004611498565b610b62565b61039d61039836600461166f565b610c3d565b604051908152602001610223565b610293610ce4565b610293610cf7565b610293610d3f565b6103415f81565b610293610dbd565b610253610e0f565b610253610e43565b6102936103f0366004611498565b610e58565b610217610403366004611498565b6001600160a01b03165f9081525f51602061183e5f395f51905f526020526040902054151590565b6102936104393660046116e8565b610f17565b61039d61044c366004611498565b6001600160a01b03165f9081525f51602061183e5f395f51905f52602052604090205490565b610293610480366004611498565b611026565b6102536110e9565b61039d5f51602061183e5f395f51905f5281565b61039d5f51602061187e5f395f51905f5281565b6102936104c3366004611498565b611111565b6102936104d6366004611498565b611196565b6104fe6040805160608101909152805f8152606060208201525f60409091015290565b5f5f51602061183e5f395f51905f526001600160a01b038481165f908152602083905260409081902054905163b5d8962760e01b815260048101829052929350917f00000000000000000000000036000000000000000000000000000000000000029091169063b5d89627906024015f60405180830381865afa158015610587573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526105ae9190810190611730565b949350505050565b6105be611268565b6105c66112a5565b335f9081525f51602061183e5f395f51905f526020819052604091829020549151634ebae61760e01b8152600481018390529091907f00000000000000000000000036000000000000000000000000000000000000026001600160a01b031690634ebae617906024015b5f604051808303815f87803b158015610647575f5ffd5b505af1158015610659573d5f5f3e3d5ffd5b505050505050565b6106696112dd565b6001600160a01b038216610690576040516371ab47bb60e11b815260040160405180910390fd5b6001600160a01b0382165f9081525f51602061183e5f395f51905f52602081905260408220549091036106d6576040516308c2ec5b60e41b815260040160405180910390fd5b6001600160a01b0383165f818152600183016020908152604091829020805467ffffffffffffffff19166001600160401b03871690811790915591519182527fcf03087b939040458ea3c3a6791532c7c4432baf98e770abc930afc9cda81d87910160405180910390a2505050565b61074d6112dd565b7f00000000000000000000000036000000000000000000000000000000000000026001600160a01b03166379ba50976040518163ffffffff1660e01b81526004015f604051808303815f87803b1580156107a5575f5ffd5b505af11580156107b7573d5f5f3e3d5ffd5b50506040517f54b70ab40993761a2b0e96f42fdf47939a56830ede1325df83e05ed33e3e8fd592505f9150a1565b6107ed61130f565b5f51602061185e5f395f51905f52805460ff60a01b191681556040517f7805862f689e2f13df9f062ff482ad3ad112aca9e0847911ed832e158c525b33905f90a150565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff1615906001600160401b03165f811580156108755750825b90505f826001600160401b031660011480156108905750303b155b90508115801561089e575080155b156108bc5760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff1916600117855583156108e657845460ff60401b1916600160401b1785555b6001600160a01b03871661090d576040516342cad95760e01b815260040160405180910390fd5b6001600160a01b0386166109345760405163a74995ab60e01b815260040160405180910390fd5b61093c611347565b6109458761134f565b5f5f51602061185e5f395f51905f5280546001600160a01b0319166001600160a01b0389161790555083156109b457845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50505050505050565b6109c5611268565b6109cd6112a5565b335f9081525f51602061183e5f395f51905f5260208181526040808420547fe90ec3add3e251bfbe914c9e482b511e91a3b187718c1dc10223f64a8a644a01909252909220549091906001600160401b03908116908416811015610a5457604051635af79e0d60e11b81526001600160401b03821660048201526024015b60405180910390fd5b60405163312267c360e21b8152600481018390526001600160401b03851660248201527f00000000000000000000000036000000000000000000000000000000000000026001600160a01b03169063c4899f0c906044015f604051808303815f87803b158015610ac2575f5ffd5b505af1158015610ad4573d5f5f3e3d5ffd5b5050505050505050565b610ae66112dd565b6001600160a01b038116610b0d5760405163a74995ab60e01b815260040160405180910390fd5b5f51602061185e5f395f51905f5280546001600160a01b0319166001600160a01b03831690811782556040517fb80482a293ca2e013eda8683c9bd7fc8347cfdaeea5ede58cba46df502c2a604905f90a25050565b610b6a6112dd565b6001600160a01b038116610b91576040516342cad95760e01b815260040160405180910390fd5b60405163f2fde38b60e01b81526001600160a01b0382811660048301527f0000000000000000000000003600000000000000000000000000000000000002169063f2fde38b906024015f604051808303815f87803b158015610bf1575f5ffd5b505af1158015610c03573d5f5f3e3d5ffd5b50506040516001600160a01b03841692507ff8e2a8d4b93bd709cc57c9f21b79403c532f960ef18f77d0c23403cae0de78d991505f90a250565b5f610c4661138b565b610c4e6112a5565b604051631234eb3960e11b81526001600160a01b037f00000000000000000000000036000000000000000000000000000000000000021690632469d67290610c9c9085905f906004016117fc565b6020604051808303815f875af1158015610cb8573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610cdc9190611826565b90505b919050565b610cec6112dd565b610cf55f61134f565b565b3380610d016110e9565b6001600160a01b031614610d335760405163118cdaa760e01b81526001600160a01b0382166004820152602401610a4b565b610d3c8161134f565b50565b610d47611268565b610d4f6112a5565b335f9081525f51602061183e5f395f51905f52602081905260409182902054915163f94e186760e01b8152600481018390529091907f00000000000000000000000036000000000000000000000000000000000000026001600160a01b03169063f94e186790602401610630565b610dc561130f565b5f51602061185e5f395f51905f52805460ff60a01b1916600160a01b1781556040517f6985a02210a168e66602d3235cb6db0e70f92b3ba4d376a33c0f3d9434bff625905f90a150565b5f807f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c1993005b546001600160a01b031692915050565b5f805f51602061185e5f395f51905f52610e33565b610e606112dd565b6001600160a01b038116610e8757604051634a1653dd60e11b815260040160405180910390fd5b6001600160a01b0381165f9081525f51602061187e5f395f51905f52602081905260409091205460ff16610ece576040516335c0bed760e21b815260040160405180910390fd5b6001600160a01b0382165f81815260208390526040808220805460ff19169055517f195d741377fcd6e23556d115769d0b5f54decc24349a916f6de7371077fe7fd99190a25050565b610f1f6112dd565b815f03610f3f576040516309e56b6960e21b815260040160405180910390fd5b6001600160a01b038316610f66576040516371ab47bb60e11b815260040160405180910390fd5b6001600160a01b0383165f9081525f51602061183e5f395f51905f52602081905260409091205415610fab57604051633dd8918760e01b815260040160405180910390fd5b6001600160a01b0384165f8181526020838152604080832087905560018501825291829020805467ffffffffffffffff19166001600160401b03871690811790915591519182528592917fa1f041d5612451bd93cf2d0b47fbfde4782731f7612386774a8ab2b4caf57955910160405180910390a350505050565b61102e6112dd565b6001600160a01b03811661105557604051634a1653dd60e11b815260040160405180910390fd5b6001600160a01b0381165f9081525f51602061187e5f395f51905f52602081905260409091205460ff161561109d5760405163348917ed60e01b815260040160405180910390fd5b6001600160a01b0382165f81815260208390526040808220805460ff19166001179055517fd25faf73acccddf9a7ac429be872d2109cd7bd0c2370658b7c019518f3159d8e9190a25050565b5f807f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c00610e33565b6111196112dd565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0080546001600160a01b0319166001600160a01b038316908117825561115d610e0f565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a35050565b61119e6112dd565b6001600160a01b0381166111c5576040516371ab47bb60e11b815260040160405180910390fd5b6001600160a01b0381165f9081525f51602061183e5f395f51905f526020819052604082205490910361120b576040516308c2ec5b60e41b815260040160405180910390fd5b6001600160a01b0382165f8181526020838152604080832083905560018501909152808220805467ffffffffffffffff19169055517f33d83959be2573f5453b12eb9d43b3499bc57d96bd2f067ba44803c859e811139190a25050565b335f9081525f51602061183e5f395f51905f5260208190526040822054909103610d3c57604051630e971e6360e11b815260040160405180910390fd5b5f51602061185e5f395f51905f528054600160a01b900460ff1615610d3c5760405163ab35696f60e01b815260040160405180910390fd5b336112e6610e0f565b6001600160a01b031614610cf55760405163118cdaa760e01b8152336004820152602401610a4b565b5f51602061185e5f395f51905f5280546001600160a01b03163314610d3c5760405163daeefd6560e01b815260040160405180910390fd5b610cf56113c9565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0080546001600160a01b031916815561138782611412565b5050565b335f9081525f51602061187e5f395f51905f52602081905260409091205460ff16610d3c5760405163eb4086bb60e01b815260040160405180910390fd5b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff16610cf557604051631afcd79f60e31b815260040160405180910390fd5b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b80356001600160a01b0381168114610cdf575f5ffd5b5f602082840312156114a8575f5ffd5b6114b182611482565b9392505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f82516003811061150957634e487b7160e01b5f52602160045260245ffd5b8060208401525060208301516060604084015261152960808401826114b8565b90506001600160401b0360408501511660608401528091505092915050565b6001600160401b0381168114610d3c575f5ffd5b5f5f6040838503121561156d575f5ffd5b61157683611482565b9150602083013561158681611548565b809150509250929050565b5f5f604083850312156115a2575f5ffd5b6115ab83611482565b91506115b960208401611482565b90509250929050565b5f602082840312156115d2575f5ffd5b81356114b181611548565b634e487b7160e01b5f52604160045260245ffd5b604051606081016001600160401b0381118282101715611613576116136115dd565b60405290565b604051601f8201601f191681016001600160401b0381118282101715611641576116416115dd565b604052919050565b5f6001600160401b03821115611661576116616115dd565b50601f01601f191660200190565b5f6020828403121561167f575f5ffd5b81356001600160401b03811115611694575f5ffd5b8201601f810184136116a4575f5ffd5b80356116b76116b282611649565b611619565b8181528560208385010111156116cb575f5ffd5b816020840160208301375f91810160200191909152949350505050565b5f5f5f606084860312156116fa575f5ffd5b61170384611482565b925060208401359150604084013561171a81611548565b809150509250925092565b8051610cdf81611548565b5f60208284031215611740575f5ffd5b81516001600160401b03811115611755575f5ffd5b820160608185031215611766575f5ffd5b61176e6115f1565b81516003811061177c575f5ffd5b815260208201516001600160401b03811115611796575f5ffd5b8201601f810186136117a6575f5ffd5b80516117b46116b282611649565b8181528760208385010111156117c8575f5ffd5b8160208401602083015e5f602083830101528060208501525050506117ef60408301611725565b6040820152949350505050565b604081525f61180e60408301856114b8565b90506001600160401b03831660208301529392505050565b5f60208284031215611836575f5ffd5b505191905056fee90ec3add3e251bfbe914c9e482b511e91a3b187718c1dc10223f64a8a644a000642d7922329a434cf4fd17a3c95eb692c24fd95f9f94d0b55420a5d895f4a0036c39aeb5f498ae36546fc14573b003abf87227a5a2df6caec16ee566f1ad800a2646970667358221220b2ba8c761bf75081c2d92a909c7cfe262281693aff1055fb8f3984e0036cc33764736f6c634300081d0033" + "code": "0x608060405234801561000f575f5ffd5b50600436106101dc575f3560e01c806379ba509711610109578063d2c20cb31161009e578063ead130111161006e578063ead130111461042f578063ec4a44d714610443578063f2fde38b14610457578063f6a74ed71461046a575f5ffd5b8063d2c20cb3146103ee578063d773a74114610401578063da3003f914610414578063e30c397814610427575f5ffd5b80638da5cb5b116100d95780638da5cb5b146103955780639fd0506d1461039d578063aa5ecb52146103a5578063b429afeb146103b8575f5ffd5b806379ba5097146103765780637f77403d1461037e5780637ffc51fd146103865780638456cb591461038d575f5ffd5b8063485cc9551161017f5780635c975abb1161014f5780635c975abb1461031c5780635e5feee61461033a578063602a9eee1461034d578063715018a61461036e575f5ffd5b8063485cc955146102b85780634cb4850a146102cb578063554bab3c146102de5780635acac2b6146102f1575f5ffd5b8063263a3402116101ba578063263a34021461028b5780632eb5c6581461029557806337ec36d1146102a85780633f4ba83a146102b0575f5ffd5b8063048544a4146101e057806306433b1b1461022c5780631904bb2e1461026b575b5f5ffd5b6102176101ee366004611514565b6001600160a01b03165f9081525f5160206118f35f395f51905f52602052604090205460ff1690565b60405190151581526020015b60405180910390f35b6102537f000000000000000000000000360000000000000000000000000000000000000281565b6040516001600160a01b039091168152602001610223565b61027e610279366004611514565b61047d565b604051610223919061155b565b610293610558565b005b6102936102a33660046115d1565b610603565b6102936106e7565b610293610787565b6102936102c6366004611606565b6107e5565b6102936102d9366004611637565b610971565b6102936102ec366004611514565b610a92565b6103046102ff366004611514565b610b16565b6040516001600160401b039091168152602001610223565b5f5160206118d35f395f51905f5254600160a01b900460ff16610217565b610293610348366004611514565b610b84565b61036061035b3660046116e4565b610c5f565b604051908152602001610223565b610293610d06565b610293610d19565b610293610d5e565b6103045f81565b610293610ddc565b610253610e3e565b610253610e72565b6102936103b3366004611514565b610e87565b6102176103c6366004611514565b6001600160a01b03165f9081525f5160206118b35f395f51905f526020526040902054151590565b6102936103fc36600461175d565b610f46565b61036061040f366004611514565b611055565b610293610422366004611514565b6110a2565b610253611165565b6103605f5160206118b35f395f51905f5281565b6103605f5160206118f35f395f51905f5281565b610293610465366004611514565b61118d565b610293610478366004611514565b611212565b6104a06040805160608101909152805f8152606060208201525f60409091015290565b5f5f5160206118b35f395f51905f526001600160a01b038481165f908152602083905260409081902054905163b5d8962760e01b815260048101829052929350917f00000000000000000000000036000000000000000000000000000000000000029091169063b5d89627906024015f60405180830381865afa158015610529573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f1916820160405261055091908101906117a5565b949350505050565b6105606112e4565b610568611321565b335f9081525f5160206118b35f395f51905f526020819052604091829020549151634ebae61760e01b8152600481018390529091907f00000000000000000000000036000000000000000000000000000000000000026001600160a01b031690634ebae617906024015b5f604051808303815f87803b1580156105e9575f5ffd5b505af11580156105fb573d5f5f3e3d5ffd5b505050505050565b61060b611359565b6001600160a01b038216610632576040516371ab47bb60e11b815260040160405180910390fd5b6001600160a01b0382165f9081525f5160206118b35f395f51905f5260208190526040822054909103610678576040516308c2ec5b60e41b815260040160405180910390fd5b6001600160a01b0383165f818152600183016020908152604091829020805467ffffffffffffffff19166001600160401b03871690811790915591519182527fcf03087b939040458ea3c3a6791532c7c4432baf98e770abc930afc9cda81d87910160405180910390a2505050565b6106ef611359565b7f00000000000000000000000036000000000000000000000000000000000000026001600160a01b03166379ba50976040518163ffffffff1660e01b81526004015f604051808303815f87803b158015610747575f5ffd5b505af1158015610759573d5f5f3e3d5ffd5b50506040517f54b70ab40993761a2b0e96f42fdf47939a56830ede1325df83e05ed33e3e8fd592505f9150a1565b61078f61138b565b5f5160206118d35f395f51905f528054600160a01b900460ff16156107e257805460ff60a01b191681556040517f7805862f689e2f13df9f062ff482ad3ad112aca9e0847911ed832e158c525b33905f90a15b50565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff1615906001600160401b03165f811580156108295750825b90505f826001600160401b031660011480156108445750303b155b905081158015610852575080155b156108705760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561089a57845460ff60401b1916600160401b1785555b6001600160a01b0387166108c1576040516342cad95760e01b815260040160405180910390fd5b6001600160a01b0386166108e85760405163a74995ab60e01b815260040160405180910390fd5b6108f06113c3565b6108f9876113cb565b5f5f5160206118d35f395f51905f5280546001600160a01b0319166001600160a01b03891617905550831561096857845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50505050505050565b6109796112e4565b610981611321565b335f9081525f5160206118b35f395f51905f5260208181526040808420547fe90ec3add3e251bfbe914c9e482b511e91a3b187718c1dc10223f64a8a644a01909252909220549091906001600160401b03908116908416811015610a0857604051635af79e0d60e11b81526001600160401b03821660048201526024015b60405180910390fd5b60405163312267c360e21b8152600481018390526001600160401b03851660248201527f00000000000000000000000036000000000000000000000000000000000000026001600160a01b03169063c4899f0c906044015f604051808303815f87803b158015610a76575f5ffd5b505af1158015610a88573d5f5f3e3d5ffd5b5050505050505050565b610a9a611359565b6001600160a01b038116610ac15760405163a74995ab60e01b815260040160405180910390fd5b5f5160206118d35f395f51905f5280546001600160a01b0319166001600160a01b03831690811782556040517fb80482a293ca2e013eda8683c9bd7fc8347cfdaeea5ede58cba46df502c2a604905f90a25050565b6001600160a01b0381165f9081525f5160206118b35f395f51905f52602081905260408220548203610b5b576040516308c2ec5b60e41b815260040160405180910390fd5b6001600160a01b039092165f90815260019092016020525060409020546001600160401b031690565b610b8c611359565b6001600160a01b038116610bb3576040516342cad95760e01b815260040160405180910390fd5b60405163f2fde38b60e01b81526001600160a01b0382811660048301527f0000000000000000000000003600000000000000000000000000000000000002169063f2fde38b906024015f604051808303815f87803b158015610c13575f5ffd5b505af1158015610c25573d5f5f3e3d5ffd5b50506040516001600160a01b03841692507ff8e2a8d4b93bd709cc57c9f21b79403c532f960ef18f77d0c23403cae0de78d991505f90a250565b5f610c68611407565b610c70611321565b604051631234eb3960e11b81526001600160a01b037f00000000000000000000000036000000000000000000000000000000000000021690632469d67290610cbe9085905f90600401611871565b6020604051808303815f875af1158015610cda573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610cfe919061189b565b90505b919050565b610d0e611359565b610d175f6113cb565b565b3380610d23611165565b6001600160a01b031614610d555760405163118cdaa760e01b81526001600160a01b03821660048201526024016109ff565b6107e2816113cb565b610d666112e4565b610d6e611321565b335f9081525f5160206118b35f395f51905f52602081905260409182902054915163f94e186760e01b8152600481018390529091907f00000000000000000000000036000000000000000000000000000000000000026001600160a01b03169063f94e1867906024016105d2565b610de461138b565b5f5160206118d35f395f51905f528054600160a01b900460ff166107e257805460ff60a01b1916600160a01b1781556040517f6985a02210a168e66602d3235cb6db0e70f92b3ba4d376a33c0f3d9434bff625905f90a150565b5f807f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c1993005b546001600160a01b031692915050565b5f805f5160206118d35f395f51905f52610e62565b610e8f611359565b6001600160a01b038116610eb657604051634a1653dd60e11b815260040160405180910390fd5b6001600160a01b0381165f9081525f5160206118f35f395f51905f52602081905260409091205460ff16610efd576040516335c0bed760e21b815260040160405180910390fd5b6001600160a01b0382165f81815260208390526040808220805460ff19169055517f195d741377fcd6e23556d115769d0b5f54decc24349a916f6de7371077fe7fd99190a25050565b610f4e611359565b815f03610f6e576040516309e56b6960e21b815260040160405180910390fd5b6001600160a01b038316610f95576040516371ab47bb60e11b815260040160405180910390fd5b6001600160a01b0383165f9081525f5160206118b35f395f51905f52602081905260409091205415610fda57604051633dd8918760e01b815260040160405180910390fd5b6001600160a01b0384165f8181526020838152604080832087905560018501825291829020805467ffffffffffffffff19166001600160401b03871690811790915591519182528592917fa1f041d5612451bd93cf2d0b47fbfde4782731f7612386774a8ab2b4caf57955910160405180910390a350505050565b6001600160a01b0381165f9081525f5160206118b35f395f51905f526020819052604082205480830361109b576040516308c2ec5b60e41b815260040160405180910390fd5b9392505050565b6110aa611359565b6001600160a01b0381166110d157604051634a1653dd60e11b815260040160405180910390fd5b6001600160a01b0381165f9081525f5160206118f35f395f51905f52602081905260409091205460ff16156111195760405163348917ed60e01b815260040160405180910390fd5b6001600160a01b0382165f81815260208390526040808220805460ff19166001179055517fd25faf73acccddf9a7ac429be872d2109cd7bd0c2370658b7c019518f3159d8e9190a25050565b5f807f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c00610e62565b611195611359565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0080546001600160a01b0319166001600160a01b03831690811782556111d9610e3e565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a35050565b61121a611359565b6001600160a01b038116611241576040516371ab47bb60e11b815260040160405180910390fd5b6001600160a01b0381165f9081525f5160206118b35f395f51905f5260208190526040822054909103611287576040516308c2ec5b60e41b815260040160405180910390fd5b6001600160a01b0382165f8181526020838152604080832083905560018501909152808220805467ffffffffffffffff19169055517f33d83959be2573f5453b12eb9d43b3499bc57d96bd2f067ba44803c859e811139190a25050565b335f9081525f5160206118b35f395f51905f52602081905260408220549091036107e257604051630e971e6360e11b815260040160405180910390fd5b5f5160206118d35f395f51905f528054600160a01b900460ff16156107e25760405163ab35696f60e01b815260040160405180910390fd5b33611362610e3e565b6001600160a01b031614610d175760405163118cdaa760e01b81523360048201526024016109ff565b5f5160206118d35f395f51905f5280546001600160a01b031633146107e25760405163daeefd6560e01b815260040160405180910390fd5b610d17611445565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0080546001600160a01b03191681556114038261148e565b5050565b335f9081525f5160206118f35f395f51905f52602081905260409091205460ff166107e25760405163eb4086bb60e01b815260040160405180910390fd5b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff16610d1757604051631afcd79f60e31b815260040160405180910390fd5b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b80356001600160a01b0381168114610d01575f5ffd5b5f60208284031215611524575f5ffd5b61109b826114fe565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f82516003811061157e57634e487b7160e01b5f52602160045260245ffd5b8060208401525060208301516060604084015261159e608084018261152d565b90506001600160401b0360408501511660608401528091505092915050565b6001600160401b03811681146107e2575f5ffd5b5f5f604083850312156115e2575f5ffd5b6115eb836114fe565b915060208301356115fb816115bd565b809150509250929050565b5f5f60408385031215611617575f5ffd5b611620836114fe565b915061162e602084016114fe565b90509250929050565b5f60208284031215611647575f5ffd5b813561109b816115bd565b634e487b7160e01b5f52604160045260245ffd5b604051606081016001600160401b038111828210171561168857611688611652565b60405290565b604051601f8201601f191681016001600160401b03811182821017156116b6576116b6611652565b604052919050565b5f6001600160401b038211156116d6576116d6611652565b50601f01601f191660200190565b5f602082840312156116f4575f5ffd5b81356001600160401b03811115611709575f5ffd5b8201601f81018413611719575f5ffd5b803561172c611727826116be565b61168e565b818152856020838501011115611740575f5ffd5b816020840160208301375f91810160200191909152949350505050565b5f5f5f6060848603121561176f575f5ffd5b611778846114fe565b925060208401359150604084013561178f816115bd565b809150509250925092565b8051610d01816115bd565b5f602082840312156117b5575f5ffd5b81516001600160401b038111156117ca575f5ffd5b8201606081850312156117db575f5ffd5b6117e3611666565b8151600381106117f1575f5ffd5b815260208201516001600160401b0381111561180b575f5ffd5b8201601f8101861361181b575f5ffd5b8051611829611727826116be565b81815287602083850101111561183d575f5ffd5b8160208401602083015e5f602083830101528060208501525050506118646040830161179a565b6040820152949350505050565b604081525f611883604083018561152d565b90506001600160401b03831660208301529392505050565b5f602082840312156118ab575f5ffd5b505191905056fee90ec3add3e251bfbe914c9e482b511e91a3b187718c1dc10223f64a8a644a000642d7922329a434cf4fd17a3c95eb692c24fd95f9f94d0b55420a5d895f4a0036c39aeb5f498ae36546fc14573b003abf87227a5a2df6caec16ee566f1ad800a2646970667358221220642f9a6a9dcca31815d2c62a7e16a0101b8be0fd9834c20c55b2b5bfc4847b8f64736f6c634300081d0033" }, "0x3600000000000000000000000000000000000003": { "balance": "0x0", @@ -154,9 +155,10 @@ "code": "0x608060405260043610610049575f3560e01c80633659cfe6146100535780634f1ef286146100725780635c60da1b146100855780638f283970146100b5578063f851a440146100d4575b6100516100e8565b005b34801561005e575f5ffd5b5061005161006d3660046104f3565b6100fa565b61005161008036600461050c565b610134565b348015610090575f5ffd5b50610099610197565b6040516001600160a01b03909116815260200160405180910390f35b3480156100c0575f5ffd5b506100516100cf3660046104f3565b6101a5565b3480156100df575f5ffd5b506100996101c5565b6100f86100f3610197565b6101ce565b565b6101026101ec565b6001600160a01b0316330361012c576101298160405180602001604052805f81525061021e565b50565b6101296100e8565b61013c6101ec565b6001600160a01b0316330361018f5761018a8383838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061021e92505050565b505050565b61018a6100e8565b5f6101a0610277565b905090565b6101ad6101ec565b6001600160a01b0316330361012c576101298161029e565b5f6101a06101ec565b365f5f375f5f365f845af43d5f5f3e8080156101e8573d5ff35b3d5ffd5b5f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b610227826102f2565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a280511561026b5761018a8282610370565b6102736103e2565b5050565b5f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61020f565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6102c76101ec565b604080516001600160a01b03928316815291841660208301520160405180910390a161012981610401565b806001600160a01b03163b5f0361032c57604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5b80546001600160a01b0319166001600160a01b039290921691909117905550565b60605f5f846001600160a01b03168460405161038c919061058a565b5f60405180830381855af49150503d805f81146103c4576040519150601f19603f3d011682016040523d82523d5f602084013e6103c9565b606091505b50915091506103d9858383610451565b95945050505050565b34156100f85760405163b398979f60e01b815260040160405180910390fd5b6001600160a01b03811661042a57604051633173bdd160e11b81525f6004820152602401610323565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610361034f565b60608261046657610461826104b0565b6104a9565b815115801561047d57506001600160a01b0384163b155b156104a657604051639996b31560e01b81526001600160a01b0385166004820152602401610323565b50805b9392505050565b8051156104bf57805160208201fd5b60405163d6bda27560e01b815260040160405180910390fd5b80356001600160a01b03811681146104ee575f5ffd5b919050565b5f60208284031215610503575f5ffd5b6104a9826104d8565b5f5f5f6040848603121561051e575f5ffd5b610527846104d8565b9250602084013567ffffffffffffffff811115610542575f5ffd5b8401601f81018613610552575f5ffd5b803567ffffffffffffffff811115610568575f5ffd5b866020828401011115610579575f5ffd5b939660209190910195509293505050565b5f82518060208501845e5f92019182525091905056fea26469706673582212204c0881af9b906ca964ce2bc1cc4525350de8dd57658dacb806266290556eb9ae64736f6c634300081d0033", "storage": { "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x000000000000000000000000a0ee7a142d267c1f36714e4a8f75612f20a79720", - "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x00000000000000000000000092b26cf0580faecf86cdd16069af99a81aadc3ad", + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x00000000000000000000000098c74f7ef54ba22be94415d7c967b352c6a42d6d", "0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00": "0x0000000000000000000000000000000000000000000000000000000000000001", "0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300": "0x00000000000000000000000023618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f", + "0x0642d7922329a434cf4fd17a3c95eb692c24fd95f9f94d0b55420a5d895f4a00": "0x00000000000000000000000023618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f", "0xa4ec95f560ce81c285cd4e5462195a1e0b5658e60e5765bb1a90935498ab31ef": "0x0000000000000000000000000000000000000000000000000000000000000001", "0x19b40b2473dd966ccd4d715d67bfd92096d9806e14811df67ea648479a5da0bc": "0x0000000000000000000000000000000000000000000000000000000000000002", "0xbe5b579ee369ec22a252ada0ff6e111a69f0d836b1eb118e23ac21f1c0c00785": "0x0000000000000000000000000000000000000000000000000000000000000003", @@ -176,7 +178,7 @@ "nonce": "0x1", "code": "0x608060405234801561000f575f5ffd5b50600436106100cb575f3560e01c8063c4d66de811610088578063e8746fed11610063578063e8746fed146101b0578063e877a526146101c3578063f2fde38b146101fa578063fab48ccf1461020d575f5ffd5b8063c4d66de814610182578063ca2f731314610195578063e30c3978146101a8575f5ffd5b806331d798b5146100cf578063715018a61461011b57806379ba5097146101255780637a7ceb161461012d5780638a8ee6ac146101405780638da5cb5b14610162575b5f5ffd5b6101066100dd366004610980565b6001600160a01b03165f9081525f516020610a335f395f51905f52602052604090205460ff1690565b60405190151581526020015b60405180910390f35b610123610220565b005b610123610233565b61012361013b3660046109ad565b610280565b6101545f516020610a535f395f51905f5281565b604051908152602001610112565b61016a610385565b6040516001600160a01b039091168152602001610112565b610123610190366004610980565b6103b9565b6101236101a3366004610980565b6104f5565b61016a6105ab565b6101236101be3660046109ad565b6105d3565b6101066101d1366004610980565b6001600160a01b03165f9081525f516020610a535f395f51905f52602052604090205460ff1690565b610123610208366004610980565b610717565b61012361021b366004610980565b61079c565b610228610855565b6102315f610887565b565b338061023d6105ab565b6001600160a01b0316146102745760405163118cdaa760e01b81526001600160a01b03821660048201526024015b60405180910390fd5b61027d81610887565b50565b335f9081525f516020610a335f395f51905f5260205260409020545f516020610a535f395f51905f529060ff166102ca57604051637628c31d60e01b815260040160405180910390fd5b5f516020610a535f395f51905f52825f5b8181101561037d575f8686838181106102f6576102f6610a1e565b905060200201602081019061030b9190610980565b6001600160a01b0381165f9081526020869052604090205490915060ff1615610374576001600160a01b0381165f81815260208690526040808220805460ff19169055517fc904e1b03de0c20d7fcf9dbd056daf1bd3815e93f251199de815fd0f0b96e1669190a25b506001016102db565b505050505050565b5f807f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c1993005b546001600160a01b031692915050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff16159067ffffffffffffffff165f811580156103fe5750825b90505f8267ffffffffffffffff16600114801561041a5750303b155b905081158015610428575080155b156104465760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561047057845460ff60401b1916600160401b1785555b6001600160a01b0386166104975760405163d92e233d60e01b815260040160405180910390fd5b61049f6108bf565b6104a886610887565b831561037d57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a1505050505050565b6104fd610855565b6001600160a01b0381166105245760405163d92e233d60e01b815260040160405180910390fd5b6001600160a01b0381165f9081525f516020610a335f395f51905f5260205260409020545f516020610a535f395f51905f529060ff16156105a7576001600160a01b0382165f818152600183016020526040808220805460ff19169055517f2ee9bf58aeff79a8ee48ae2ebaea69b7fc533af3060aaa8d8956b225785db10a9190a25b5050565b5f807f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c006103a9565b335f9081525f516020610a335f395f51905f5260205260409020545f516020610a535f395f51905f529060ff1661061d57604051637628c31d60e01b815260040160405180910390fd5b5f610626610385565b90505f516020610a535f395f51905f52835f5b8181101561070e575f87878381811061065457610654610a1e565b90506020020160208101906106699190610980565b9050846001600160a01b0316816001600160a01b03160361069d5760405163266f648160e01b815260040160405180910390fd5b6001600160a01b0381165f9081526020859052604090205460ff16610705576001600160a01b0381165f81815260208690526040808220805460ff19166001179055517ffa4507bc1f9c730e6e95897024f1fe7d576cf2deb53579d55c14f1ac3439e1149190a25b50600101610639565b50505050505050565b61071f610855565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0080546001600160a01b0319166001600160a01b0383169081178255610763610385565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a35050565b6107a4610855565b6001600160a01b0381166107cb5760405163d92e233d60e01b815260040160405180910390fd5b6001600160a01b0381165f9081525f516020610a335f395f51905f5260205260409020545f516020610a535f395f51905f529060ff166105a7576001600160a01b0382165f81815260018381016020526040808320805460ff1916909217909155517f1ac0162c9c4096b260eb12be2731ca291739aa6ed7c94780f52c55df88589e7c9190a25050565b3361085e610385565b6001600160a01b0316146102315760405163118cdaa760e01b815233600482015260240161026b565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0080546001600160a01b03191681556105a7826108c7565b610231610937565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff1661023157604051631afcd79f60e31b815260040160405180910390fd5b5f60208284031215610990575f5ffd5b81356001600160a01b03811681146109a6575f5ffd5b9392505050565b5f5f602083850312156109be575f5ffd5b823567ffffffffffffffff8111156109d4575f5ffd5b8301601f810185136109e4575f5ffd5b803567ffffffffffffffff8111156109fa575f5ffd5b8560208260051b8401011115610a0e575f5ffd5b6020919091019590945092505050565b634e487b7160e01b5f52603260045260245ffdfe1d7e1388d3ae56f3d9c18b1ce8d2b3b1a238a0edf682d2053af5d8a1d2f12f011d7e1388d3ae56f3d9c18b1ce8d2b3b1a238a0edf682d2053af5d8a1d2f12f00a264697066735822122057fdea9cb356478df3dad5dbaec8a47476bd3266f5a49729c6d02d8e049ee9b864736f6c634300081d0033" }, - "0x360Eb67EDbA456Bbe01512679f36c2717AA65121": { + "0x36059b615370eB999e8eC0c9401835B407834221": { "balance": "0x0", "nonce": "0x1", "code": "0x608060405260043610610049575f3560e01c80633659cfe6146100535780634f1ef286146100725780635c60da1b146100855780638f283970146100b5578063f851a440146100d4575b6100516100e8565b005b34801561005e575f5ffd5b5061005161006d3660046104f3565b6100fa565b61005161008036600461050c565b610134565b348015610090575f5ffd5b50610099610197565b6040516001600160a01b03909116815260200160405180910390f35b3480156100c0575f5ffd5b506100516100cf3660046104f3565b6101a5565b3480156100df575f5ffd5b506100996101c5565b6100f86100f3610197565b6101ce565b565b6101026101ec565b6001600160a01b0316330361012c576101298160405180602001604052805f81525061021e565b50565b6101296100e8565b61013c6101ec565b6001600160a01b0316330361018f5761018a8383838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061021e92505050565b505050565b61018a6100e8565b5f6101a0610277565b905090565b6101ad6101ec565b6001600160a01b0316330361012c576101298161029e565b5f6101a06101ec565b365f5f375f5f365f845af43d5f5f3e8080156101e8573d5ff35b3d5ffd5b5f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b610227826102f2565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a280511561026b5761018a8282610370565b6102736103e2565b5050565b5f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61020f565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6102c76101ec565b604080516001600160a01b03928316815291841660208301520160405180910390a161012981610401565b806001600160a01b03163b5f0361032c57604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5b80546001600160a01b0319166001600160a01b039290921691909117905550565b60605f5f846001600160a01b03168460405161038c919061058a565b5f60405180830381855af49150503d805f81146103c4576040519150601f19603f3d011682016040523d82523d5f602084013e6103c9565b606091505b50915091506103d9858383610451565b95945050505050565b34156100f85760405163b398979f60e01b815260040160405180910390fd5b6001600160a01b03811661042a57604051633173bdd160e11b81525f6004820152602401610323565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610361034f565b60608261046657610461826104b0565b6104a9565b815115801561047d57506001600160a01b0384163b155b156104a657604051639996b31560e01b81526001600160a01b0385166004820152602401610323565b50805b9392505050565b8051156104bf57805160208201fd5b60405163d6bda27560e01b815260040160405180910390fd5b80356001600160a01b03811681146104ee575f5ffd5b919050565b5f60208284031215610503575f5ffd5b6104a9826104d8565b5f5f5f6040848603121561051e575f5ffd5b610527846104d8565b9250602084013567ffffffffffffffff811115610542575f5ffd5b8401601f81018613610552575f5ffd5b803567ffffffffffffffff811115610568575f5ffd5b866020828401011115610579575f5ffd5b939660209190910195509293505050565b5f82518060208501845e5f92019182525091905056fea26469706673582212204c0881af9b906ca964ce2bc1cc4525350de8dd57658dacb806266290556eb9ae64736f6c634300081d0033", @@ -230,10 +232,10 @@ "nonce": "0x1", "code": "0x608060405234801561000f575f5ffd5b506004361061003f575f3560e01c8063c3b2c4f814610043578063ea94cddd14610058578063f884e35514610083575b5f5ffd5b610056610051366004610231565b610099565b005b61006660036003609b1b0181565b6040516001600160a01b0390911681526020015b60405180910390f35b61008b5f5481565b60405190815260200161007a565b5f805481806100a7836102c7565b9091555060405190915081907fb252e055da754c72fbf7542cf424b190808a9b541e912894c5e15b4238c41501905f90a2604051631595ec0b60e01b81525f90819060036003609b1b0190631595ec0b9061010c9033908d908d908d90600401610313565b5f604051808303815f875af1158015610127573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f1916820160405261014e919081019061035d565b915091508161017b578060405163768cb35160e11b81526004016101729190610428565b60405180910390fd5b85896001600160a01b0316336001600160a01b03167feb15ee720798341c37739df41be53acfbbf70ae6802dade35457beec6e47a5e48b8b6040516101c192919061045d565b6040519081900381206101d9918b908b908b9061046c565b60405180910390a4505050505050505050565b5f5f83601f8401126101fc575f5ffd5b50813567ffffffffffffffff811115610213575f5ffd5b60208301915083602082850101111561022a575f5ffd5b9250929050565b5f5f5f5f5f5f60808789031215610246575f5ffd5b86356001600160a01b038116811461025c575f5ffd5b9550602087013567ffffffffffffffff811115610277575f5ffd5b61028389828a016101ec565b90965094505060408701359250606087013567ffffffffffffffff8111156102a9575f5ffd5b6102b589828a016101ec565b979a9699509497509295939492505050565b5f600182016102e457634e487b7160e01b5f52601160045260245ffd5b5060010190565b81835281816020850137505f828201602090810191909152601f909101601f19169091010190565b6001600160a01b038581168252841660208201526060604082018190525f9061033f90830184866102eb565b9695505050505050565b634e487b7160e01b5f52604160045260245ffd5b5f5f6040838503121561036e575f5ffd5b8251801515811461037d575f5ffd5b602084015190925067ffffffffffffffff811115610399575f5ffd5b8301601f810185136103a9575f5ffd5b805167ffffffffffffffff8111156103c3576103c3610349565b604051601f8201601f19908116603f0116810167ffffffffffffffff811182821017156103f2576103f2610349565b604052818152828201602001871015610409575f5ffd5b8160208401602083015e5f602083830101528093505050509250929050565b602081525f82518060208401528060208501604085015e5f604082850101526040601f19601f83011684010191505092915050565b818382375f9101908152919050565b848152606060208201525f6104856060830185876102eb565b90508260408301529594505050505056fea2646970667358221220dc659493b17f8025af73b0b01138f0edcef92362d7b72a66dced7d061709f59964736f6c634300081d0033" }, - "0xA3E6c63b16321E39a61551Dc1A38689b04d62E42": { + "0x522fAf9A91c41c443c66765030741e4AaCe147D0": { "balance": "0x0", "nonce": "0x1", - "code": "0x608060405234801561000f575f5ffd5b50600436106100fb575f3560e01c806372425d9d11610093578063bce38bd711610063578063bce38bd7146101d4578063c3077fa9146101e7578063ea94cddd146101fa578063ee82ac5e14610208575f5ffd5b806372425d9d1461018e57806382ad56cb1461019457806386d516e8146101b4578063a8b0574e146101ba575f5ffd5b8063399542e9116100ce578063399542e9146101455780633e64a6961461016757806342cbb15c1461016d5780634d2301cc14610173575f5ffd5b80630f28c97d146100ff578063252dba421461011457806327e86d6e146101355780633408e4701461013f575b5f5ffd5b425b6040519081526020015b60405180910390f35b61012761012236600461093d565b61021a565b60405161010b9291906109aa565b435f190140610101565b46610101565b610158610153366004610a24565b61038e565b60405161010b93929190610ae8565b48610101565b43610101565b610101610181366004610b0f565b6001600160a01b03163190565b44610101565b6101a76101a236600461093d565b6103ac565b60405161010b9190610b3c565b45610101565b415b6040516001600160a01b03909116815260200161010b565b6101a76101e2366004610a24565b610586565b6101586101f536600461093d565b61072c565b6101bc60036003609b1b0181565b610101610216366004610b4e565b4090565b436060828067ffffffffffffffff81111561023757610237610b65565b60405190808252806020026020018201604052801561026a57816020015b60608152602001906001900390816102555790505b5091505f5b81811015610385575f8060036003609b1b01631595ec0b338a8a8781811061029957610299610b79565b90506020028101906102ab9190610b8d565b6102b9906020810190610b0f565b8b8b888181106102cb576102cb610b79565b90506020028101906102dd9190610b8d565b6102eb906020810190610bab565b6040518563ffffffff1660e01b815260040161030a9493929190610bee565b5f604051808303815f875af1158015610325573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f1916820160405261034c9190810190610c36565b915091508161035d57805160208201fd5b8085848151811061037057610370610b79565b6020908102919091010152505060010161026f565b50509250929050565b5f5f606061039d86868661074a565b91989097509095509350505050565b6060818067ffffffffffffffff8111156103c8576103c8610b65565b60405190808252806020026020018201604052801561040d57816020015b604080518082019091525f8152606060208201528152602001906001900390816103e65790505b5091505f5b8181101561057e575f8060036003609b1b01631595ec0b3389898781811061043c5761043c610b79565b905060200281019061044e9190610cfd565b61045c906020810190610b0f565b8a8a8881811061046e5761046e610b79565b90506020028101906104809190610cfd565b61048e906040810190610bab565b6040518563ffffffff1660e01b81526004016104ad9493929190610bee565b5f604051808303815f875af11580156104c8573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526104ef9190810190610c36565b91509150604051806040016040528083151581526020018281525085848151811061051c5761051c610b79565b602002602001018190525086868481811061053957610539610b79565b905060200281019061054b9190610cfd565b61055c906040810190602001610d11565b158015610567575081155b1561057457805160208201fd5b5050600101610412565b505092915050565b6060818067ffffffffffffffff8111156105a2576105a2610b65565b6040519080825280602002602001820160405280156105e757816020015b604080518082019091525f8152606060208201528152602001906001900390816105c05790505b5091505f5b81811015610723575f8060036003609b1b01631595ec0b3389898781811061061657610616610b79565b90506020028101906106289190610b8d565b610636906020810190610b0f565b8a8a8881811061064857610648610b79565b905060200281019061065a9190610b8d565b610668906020810190610bab565b6040518563ffffffff1660e01b81526004016106879493929190610bee565b5f604051808303815f875af11580156106a2573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526106c99190810190610c36565b915091508780156106d8575081155b156106e557805160208201fd5b604051806040016040528083151581526020018281525085848151811061070e5761070e610b79565b602090810291909101015250506001016105ec565b50509392505050565b5f5f606061073c6001868661074a565b919790965090945092505050565b4380406060838067ffffffffffffffff81111561076957610769610b65565b6040519080825280602002602001820160405280156107ae57816020015b604080518082019091525f8152606060208201528152602001906001900390816107875790505b5091505f5b818110156108ea575f8060036003609b1b01631595ec0b338b8b878181106107dd576107dd610b79565b90506020028101906107ef9190610b8d565b6107fd906020810190610b0f565b8c8c8881811061080f5761080f610b79565b90506020028101906108219190610b8d565b61082f906020810190610bab565b6040518563ffffffff1660e01b815260040161084e9493929190610bee565b5f604051808303815f875af1158015610869573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f191682016040526108909190810190610c36565b9150915089801561089f575081155b156108ac57805160208201fd5b60405180604001604052808315158152602001828152508584815181106108d5576108d5610b79565b602090810291909101015250506001016107b3565b505093509350939050565b5f5f83601f840112610905575f5ffd5b50813567ffffffffffffffff81111561091c575f5ffd5b6020830191508360208260051b8501011115610936575f5ffd5b9250929050565b5f5f6020838503121561094e575f5ffd5b823567ffffffffffffffff811115610964575f5ffd5b610970858286016108f5565b90969095509350505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b5f604082018483526040602084015280845180835260608501915060608160051b8601019250602086015f5b82811015610a0757605f198786030184526109f285835161097c565b945060209384019391909101906001016109d6565b5092979650505050505050565b8015158114610a21575f5ffd5b50565b5f5f5f60408486031215610a36575f5ffd5b8335610a4181610a14565b9250602084013567ffffffffffffffff811115610a5c575f5ffd5b610a68868287016108f5565b9497909650939450505050565b5f82825180855260208501945060208160051b830101602085015f5b83811015610adc57601f1985840301885281518051151584526020810151905060406020850152610ac5604085018261097c565b6020998a0199909450929092019150600101610a91565b50909695505050505050565b838152826020820152606060408201525f610b066060830184610a75565b95945050505050565b5f60208284031215610b1f575f5ffd5b81356001600160a01b0381168114610b35575f5ffd5b9392505050565b602081525f610b356020830184610a75565b5f60208284031215610b5e575f5ffd5b5035919050565b634e487b7160e01b5f52604160045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b5f8235603e19833603018112610ba1575f5ffd5b9190910192915050565b5f5f8335601e19843603018112610bc0575f5ffd5b83018035915067ffffffffffffffff821115610bda575f5ffd5b602001915036819003821315610936575f5ffd5b6001600160a01b038581168252841660208201526060604082018190528101829052818360808301375f818301608090810191909152601f909201601f191601019392505050565b5f5f60408385031215610c47575f5ffd5b8251610c5281610a14565b602084015190925067ffffffffffffffff811115610c6e575f5ffd5b8301601f81018513610c7e575f5ffd5b805167ffffffffffffffff811115610c9857610c98610b65565b604051601f8201601f19908116603f0116810167ffffffffffffffff81118282101715610cc757610cc7610b65565b604052818152828201602001871015610cde575f5ffd5b8160208401602083015e5f602083830101528093505050509250929050565b5f8235605e19833603018112610ba1575f5ffd5b5f60208284031215610d21575f5ffd5b8135610b3581610a1456fea26469706673582212204bc0d4ad06fa2e58a61c2358062875287957e77a064f68bedb35e66fb6d5adc064736f6c634300081d0033" + "code": "0x608060405234801561000f575f5ffd5b50600436106100fb575f3560e01c806372425d9d11610093578063bce38bd711610063578063bce38bd7146101d4578063c3077fa9146101e7578063ea94cddd146101fa578063ee82ac5e14610208575f5ffd5b806372425d9d1461018e57806382ad56cb1461019457806386d516e8146101b4578063a8b0574e146101ba575f5ffd5b8063399542e9116100ce578063399542e9146101455780633e64a6961461016757806342cbb15c1461016d5780634d2301cc14610173575f5ffd5b80630f28c97d146100ff578063252dba421461011457806327e86d6e146101355780633408e4701461013f575b5f5ffd5b425b6040519081526020015b60405180910390f35b610127610122366004610831565b61021a565b60405161010b92919061089e565b435f190140610101565b46610101565b610158610153366004610918565b610328565b60405161010b939291906109dc565b48610101565b43610101565b610101610181366004610a03565b6001600160a01b03163190565b44610101565b6101a76101a2366004610831565b610346565b60405161010b9190610a30565b45610101565b415b6040516001600160a01b03909116815260200161010b565b6101a76101e2366004610918565b6104b5565b6101586101f5366004610831565b6105d0565b6101bc60036003609b1b0181565b610101610216366004610a42565b4090565b436060828067ffffffffffffffff81111561023757610237610a59565b60405190808252806020026020018201604052801561026a57816020015b60608152602001906001900390816102555790505b5091505f5b8181101561031f575f5f6102e63389898681811061028f5761028f610a6d565b90506020028101906102a19190610a81565b6102af906020810190610a03565b8a8a878181106102c1576102c1610a6d565b90506020028101906102d39190610a81565b6102e1906020810190610a9f565b6105ee565b91509150816102f757805160208201fd5b8085848151811061030a5761030a610a6d565b6020908102919091010152505060010161026f565b50509250929050565b5f5f60606103378686866106c9565b91989097509095509350505050565b6060818067ffffffffffffffff81111561036257610362610a59565b6040519080825280602002602001820160405280156103a757816020015b604080518082019091525f8152606060208201528152602001906001900390816103805790505b5091505f5b818110156104ad575f5f61041e338888868181106103cc576103cc610a6d565b90506020028101906103de9190610ae2565b6103ec906020810190610a03565b8989878181106103fe576103fe610a6d565b90506020028101906104109190610ae2565b6102e1906040810190610a9f565b91509150604051806040016040528083151581526020018281525085848151811061044b5761044b610a6d565b602002602001018190525086868481811061046857610468610a6d565b905060200281019061047a9190610ae2565b61048b906040810190602001610af6565b158015610496575081155b156104a357805160208201fd5b50506001016103ac565b505092915050565b6060818067ffffffffffffffff8111156104d1576104d1610a59565b60405190808252806020026020018201604052801561051657816020015b604080518082019091525f8152606060208201528152602001906001900390816104ef5790505b5091505f5b818110156105c7575f5f61056d3388888681811061053b5761053b610a6d565b905060200281019061054d9190610a81565b61055b906020810190610a03565b8989878181106102c1576102c1610a6d565b9150915087801561057c575081155b1561058957805160208201fd5b60405180604001604052808315158152602001828152508584815181106105b2576105b2610a6d565b6020908102919091010152505060010161051b565b50509392505050565b5f5f60606105e0600186866106c9565b919790965090945092505050565b5f60605f5f60036003609b1b016001600160a01b03168888888860405160240161061b9493929190610b11565b60408051601f198184030181529181526020820180516001600160e01b0316631595ec0b60e01b179052516106509190610b59565b5f604051808303815f865af19150503d805f8114610689576040519150601f19603f3d011682016040523d82523d5f602084013e61068e565b606091505b509150915081156106b757808060200190518101906106ad9190610b6f565b90945092506106be565b5f93508092505b505094509492505050565b4380406060838067ffffffffffffffff8111156106e8576106e8610a59565b60405190808252806020026020018201604052801561072d57816020015b604080518082019091525f8152606060208201528152602001906001900390816107065790505b5091505f5b818110156107de575f5f610784338a8a8681811061075257610752610a6d565b90506020028101906107649190610a81565b610772906020810190610a03565b8b8b878181106102c1576102c1610a6d565b91509150898015610793575081155b156107a057805160208201fd5b60405180604001604052808315158152602001828152508584815181106107c9576107c9610a6d565b60209081029190910101525050600101610732565b505093509350939050565b5f5f83601f8401126107f9575f5ffd5b50813567ffffffffffffffff811115610810575f5ffd5b6020830191508360208260051b850101111561082a575f5ffd5b9250929050565b5f5f60208385031215610842575f5ffd5b823567ffffffffffffffff811115610858575f5ffd5b610864858286016107e9565b90969095509350505050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b5f604082018483526040602084015280845180835260608501915060608160051b8601019250602086015f5b828110156108fb57605f198786030184526108e6858351610870565b945060209384019391909101906001016108ca565b5092979650505050505050565b8015158114610915575f5ffd5b50565b5f5f5f6040848603121561092a575f5ffd5b833561093581610908565b9250602084013567ffffffffffffffff811115610950575f5ffd5b61095c868287016107e9565b9497909650939450505050565b5f82825180855260208501945060208160051b830101602085015f5b838110156109d057601f19858403018852815180511515845260208101519050604060208501526109b96040850182610870565b6020998a0199909450929092019150600101610985565b50909695505050505050565b838152826020820152606060408201525f6109fa6060830184610969565b95945050505050565b5f60208284031215610a13575f5ffd5b81356001600160a01b0381168114610a29575f5ffd5b9392505050565b602081525f610a296020830184610969565b5f60208284031215610a52575f5ffd5b5035919050565b634e487b7160e01b5f52604160045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b5f8235603e19833603018112610a95575f5ffd5b9190910192915050565b5f5f8335601e19843603018112610ab4575f5ffd5b83018035915067ffffffffffffffff821115610ace575f5ffd5b60200191503681900382131561082a575f5ffd5b5f8235605e19833603018112610a95575f5ffd5b5f60208284031215610b06575f5ffd5b8135610a2981610908565b6001600160a01b038581168252841660208201526060604082018190528101829052818360808301375f818301608090810191909152601f909201601f191601019392505050565b5f82518060208501845e5f920191825250919050565b5f5f60408385031215610b80575f5ffd5b8251610b8b81610908565b602084015190925067ffffffffffffffff811115610ba7575f5ffd5b8301601f81018513610bb7575f5ffd5b805167ffffffffffffffff811115610bd157610bd1610a59565b604051601f8201601f19908116603f0116810167ffffffffffffffff81118282101715610c0057610c00610a59565b604052818152828201602001871015610c17575f5ffd5b8160208401602083015e5f60208383010152809350505050925092905056fea26469706673582212203a46c6d8768363ec59bf5aa5a61ddd8f421c4777c568057c3340d263f7960bad64736f6c634300081d0033" }, "0x298122B4bF05CC897662e535C18417f44C7f274b": { "balance": "0x0", @@ -262,1272 +264,1272 @@ "0x1800000000000000000000000000000000000002": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000003": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000004": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000005": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000006": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000007": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000008": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000009": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000000a": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000000b": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000000c": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000000d": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000000e": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000000f": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000010": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000011": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000012": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000013": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000014": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000015": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000016": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000017": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000018": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000019": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000001a": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000001b": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000001c": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000001d": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000001e": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000001f": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000020": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000021": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000022": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000023": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000024": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000025": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000026": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000027": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000028": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000029": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000002a": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000002b": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000002c": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000002d": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000002e": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000002f": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000030": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000031": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000032": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000033": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000034": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000035": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000036": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000037": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000038": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000039": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000003a": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000003b": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000003c": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000003d": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000003e": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000003f": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000040": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000041": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000042": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000043": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000044": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000045": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000046": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000047": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000048": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000049": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000004a": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000004b": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000004c": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000004d": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000004e": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000004f": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000050": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000051": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000052": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000053": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000054": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000055": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000056": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000057": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000058": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000059": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000005a": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000005b": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000005c": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000005d": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000005e": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000005f": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000060": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000061": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000062": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000063": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000064": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000065": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000066": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000067": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000068": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000069": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000006a": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000006b": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000006c": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000006d": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000006e": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000006f": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000070": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000071": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000072": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000073": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000074": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000075": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000076": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000077": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000078": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000079": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000007a": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000007b": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000007c": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000007d": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000007e": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000007f": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000080": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000081": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000082": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000083": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000084": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000085": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000086": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000087": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000088": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000089": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000008a": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000008b": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000008c": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000008d": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000008e": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000008f": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000090": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000091": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000092": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000093": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000094": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000095": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000096": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000097": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000098": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x1800000000000000000000000000000000000099": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000009a": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000009b": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000009c": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000009d": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000009e": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x180000000000000000000000000000000000009f": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000a0": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000a1": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000a2": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000a3": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000a4": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000a5": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000a6": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000a7": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000a8": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000a9": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000aa": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000ab": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000ac": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000ad": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000ae": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000af": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000b0": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000b1": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000b2": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000b3": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000b4": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000b5": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000b6": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000b7": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000b8": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000b9": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000ba": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000bb": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000bc": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000bd": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000be": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000bf": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000c0": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000c1": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000c2": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000c3": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000c4": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000c5": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000c6": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000c7": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000c8": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000c9": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000ca": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000cb": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000cc": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000cd": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000ce": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000cf": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000d0": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000d1": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000d2": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000d3": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000d4": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000d5": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000d6": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000d7": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000d8": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000d9": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000da": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000db": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000dc": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000dd": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000de": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000df": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000e0": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000e1": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000e2": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000e3": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000e4": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000e5": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000e6": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000e7": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000e8": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000e9": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000ea": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000eb": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000ec": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000ed": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000ee": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000ef": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000f0": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000f1": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000f2": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000f3": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000f4": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000f5": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000f6": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000f7": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000f8": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000f9": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000fa": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000fb": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000fc": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000fd": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000fe": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0x18000000000000000000000000000000000000ff": { "balance": "0x0", "nonce": "0x1", - "code": "0x01" + "code": "0xef" }, "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266": { "balance": "0xd3c21bcecceda1000000" @@ -1580,18 +1582,17 @@ "0x1800000000000000000000000000000000000000": { "balance": "0x0", "nonce": "0x1", - "code": "0x01", + "code": "0xef", "storage": { - "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000003600000000000000000000000000000000000000", "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000000d3c21bcecceda10000000" } }, "0x1800000000000000000000000000000000000001": { "balance": "0x0", "nonce": "0x1", - "code": "0x01", + "code": "0xef", "storage": { - "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000003600000000000000000000000000000000000000" + "0xc141df5e4f31a4ad000065d1c11b38075b88c74e6e761baa68cd227ec7b803c6": "0x0000000000000000000000000000000000000000000000000000000000000001" } } } diff --git a/assets/mainnet/.gitignore b/assets/mainnet/.gitignore new file mode 100644 index 0000000..9abf911 --- /dev/null +++ b/assets/mainnet/.gitignore @@ -0,0 +1,4 @@ +validator*.json + +genesis-*.json +config-*.json diff --git a/assets/mainnet/config.json b/assets/mainnet/config.json new file mode 100644 index 0000000..28880af --- /dev/null +++ b/assets/mainnet/config.json @@ -0,0 +1,230 @@ +{ + "timestamp": "1778544000", + "coinbase": "0x3141592653589793238462643383279502884197", + "NativeFiatToken": { + "proxy": { + "admin": "0x9005E53E3ee2f27999F15e7a52C58f804Fc716e0" + }, + "owner": "0xf5a5658b55983E2Aa037cAC7A8431B510E8A97F4", + "pauser": "0x0000000000000000000000000000000000000000", + "masterMinter": "0x0000000000000000000000000000000000000000", + "blacklister": "0x0000000000000000000000000000000000000000", + "rescuer": "0x0000000000000000000000000000000000000000", + "minters": [] + }, + "ProtocolConfig": { + "proxy": { + "admin": "0xBD3738ab866eff9B0908Ef8985d00eECA22DA4eF" + }, + "owner": "0xA5FeD552f38E11291Dd2Fc9cb91e2a5F6Ae86eD4", + "controller": "0x41fE044f1f71ff69F46F35f41EC93369a0E94733", + "pauser": "0x85c8825829fC649694D569bcCc374E8382Df2D12", + "feeParams": { + "alpha": "20", + "kRate": "200", + "inverseElasticityMultiplier": "5000", + "minBaseFee": "20000000000", + "maxBaseFee": "20000000000000", + "blockGasLimit": "30000000" + }, + "consensusParams": { + "timeoutProposeMs": "3000", + "timeoutProposeDeltaMs": "500", + "timeoutPrevoteMs": "1000", + "timeoutPrevoteDeltaMs": "500", + "timeoutPrecommitMs": "1000", + "timeoutPrecommitDeltaMs": "500", + "timeoutRebroadcastMs": "5000", + "targetBlockTimeMs": "500" + } + }, + "ValidatorManager": { + "proxy": { + "admin": "0x20Db45729BC366833107524804d16cEb44e946c6" + }, + "PermissionedValidatorManager": { + "proxy": { + "admin": "0x131E6B8E466aC8046c38c3ae7de77595CfEAf0D1" + }, + "owner": "0x20E61a9CC8d010928Aa9997e4e773e84dE1B8306", + "pauser": "0x5bC1d44F8e844863cbC45D2f425F4c3758faf7b8", + "validatorRegisterers": [ + "0xF65A7d2E6C16d263B7B263369deD8C78f3aa4813", + "0x38c57d852eddE831CE368D127CDabf5AdB86Ce96" + ] + }, + "validators": [ + { + "publicKey": "0x12a68aa84643efd6fb79b4097ef9b5ae1bef849c9c75e8eba2b977e09d227c35", + "votingPower": "2000", + "controllers": [ + { + "address": "0x5f86a4dCBD49c86Becc4FB89680f66FeC8eF358a", + "votingPowerLimit": "2000" + }, + { + "address": "0x6F80738F31019B70B616Ac9Fac1C853dFC35Cc37", + "votingPowerLimit": "0" + } + ] + }, + { + "publicKey": "0xae89fa207bb808e8a34e05b5892a16625e8b66b92e597f8866aa586b487dfbfe", + "votingPower": "2000", + "controllers": [ + { + "address": "0xb0100454798fD2a11d7e7Bde0804a29171d2F492", + "votingPowerLimit": "2000" + }, + { + "address": "0xA53dd220650550BC68dFC63faAF954b85eAfED7c", + "votingPowerLimit": "0" + } + ] + }, + { + "publicKey": "0xd5c96c5d0daf70f25fb5bfc20c0747b2ce0408cc6e54b8494f3a62b61ffca7cd", + "votingPower": "2000", + "controllers": [ + { + "address": "0x64637C1f9899684722da4657d94396ec4Fd85E09", + "votingPowerLimit": "2000" + }, + { + "address": "0x882b0d0E0952170a7d3D62946327fF3F6d7AD329", + "votingPowerLimit": "0" + } + ] + }, + { + "publicKey": "0xed259c1abb397823afb31cb36089547367221242de3057df803a0b091e9a1c8d", + "votingPower": "2000", + "controllers": [ + { + "address": "0x5c2220b253c4E02Eec51A3959b9f67E599E8ECF9", + "votingPowerLimit": "2000" + }, + { + "address": "0x386976F806739bDA4126a18eEF4157F92Fb53Cce", + "votingPowerLimit": "0" + } + ] + }, + { + "publicKey": "0xf43562191fe65ba250ac32d4602efb664586bf4e8150e4c80cf2dbf64650d2a9", + "votingPower": "2000", + "controllers": [ + { + "address": "0xB0556E9AE15cA9e02fFD66C1651134bf343ad2B2", + "votingPowerLimit": "2000" + }, + { + "address": "0x5e887aFa6E659995A546e72B9959594D8DAEDD71", + "votingPowerLimit": "0" + } + ] + }, + { + "publicKey": "0xf927d6659307a219c3a59b51a31c29412b6d2b1c1717697a0ae0d5c9999ba740", + "votingPower": "2000", + "controllers": [ + { + "address": "0x5802620312d16b94E70147AAf730c662Ceee821C", + "votingPowerLimit": "2000" + }, + { + "address": "0xBFB35262B8dF18829D33Da3A220DdE13505Cf485", + "votingPowerLimit": "0" + } + ] + }, + { + "publicKey": "0xda4f31b2d26acac4143d67f6b0ebc5a7b9fa829e4b63970f28130bff361316c9", + "votingPower": "2000", + "controllers": [ + { + "address": "0x9c4a7E4964d0b14c3B436b522F0BFA33DD517350", + "votingPowerLimit": "2000" + }, + { + "address": "0xF75431B62AECD7da6AF24b6c0dFEd419579760c8", + "votingPowerLimit": "0" + } + ] + }, + { + "publicKey": "0x7bd0ca04c9423d39c25ee7747d2a5977acf19ff9d7f835f8dc1a746c83e71070", + "votingPower": "2000", + "controllers": [ + { + "address": "0x22C1Ba671ad7c01d00Afb8E159424A8568B6b3a7", + "votingPowerLimit": "2000" + }, + { + "address": "0x4e5bF0aBC4EFA3c94Bc1ed23b1dB4b40De43AafA", + "votingPowerLimit": "0" + } + ] + }, + { + "publicKey": "0xc3fbb251a140db946283233a3fe5762a05a8305e2a8eaefc41d5df2820e071fc", + "votingPower": "2000", + "controllers": [ + { + "address": "0xD28Ae083CFEefd89cb2b0f6466351d4C5630Ac5F", + "votingPowerLimit": "2000" + }, + { + "address": "0x034BB1f6239a70f956d2b18054CF7Bf7Bbe77Cc4", + "votingPowerLimit": "0" + } + ] + }, + { + "publicKey": "0xdeb9620fb017834b90e4b0cf66fdc7fd06b3edb68f0147938e59a9fa7db83380", + "votingPower": "2000", + "controllers": [ + { + "address": "0x5dD30B6C339ec59eB7fBeD6026C43978e5663FbE", + "votingPowerLimit": "2000" + }, + { + "address": "0x7dCCcD3f36dDc820265F6e5758044EA3d4739C80", + "votingPowerLimit": "0" + } + ] + }, + { + "publicKey": "0x254192a339b277911fae7dac3a52ed4b0196bb90670fb3b893f521849086fc8d", + "votingPower": "2000", + "controllers": [ + { + "address": "0x4e8FC724848B3F119E031C1e17c5e68Af75D29C2", + "votingPowerLimit": "2000" + }, + { + "address": "0x5519855988085f55Ac7aCcD7aED1C53988D9aF18", + "votingPowerLimit": "0" + } + ] + } + ] + }, + "Denylist": { + "proxy": { + "address": "0x3600000000000000000000000000000000000004", + "admin": "0xAfb5b6a4725459959ef931a3e5df758a72A8cA7f" + }, + "owner": "0x49ec36db19623e4DaDc1Aa821CbA2D1476F8E859", + "denylisters": [] + }, + "prefund": [ + { + "address": "0x50A2b0B577eC24d7ce1aeD372A8a6fd14CE1bE57", + "balance": "10000000000000000000000" + } + ], + "hardforks": { + "osakaTime": 0 + } +} diff --git a/assets/mainnet/genesis.config.ts b/assets/mainnet/genesis.config.ts new file mode 100644 index 0000000..393fdc9 --- /dev/null +++ b/assets/mainnet/genesis.config.ts @@ -0,0 +1,177 @@ +// Copyright 2026 Circle Internet Group, Inc. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import fs from 'fs' +import path from 'path' +import { Address, Hex, parseEther, parseGwei, zeroAddress } from 'viem' +import { createBuilderContext, buildGenesis, GenesisConfig } from '../../scripts/genesis' +import { bigintReplacer } from '../../scripts/genesis/types' + +type Validator = { + publicKey: Hex + activateController: Address + removeController: Address +} + +const initialVotingPower = 2000n + +const build = async () => { + const ctx = await createBuilderContext({ network: 'mainnet', chainId: 5042 }) + + // The sequences of validator registration must follow the order of the following array due to registrationId assignment. + const validators: Validator[] = [ + { + publicKey: '0x12a68aa84643efd6fb79b4097ef9b5ae1bef849c9c75e8eba2b977e09d227c35', + activateController: '0x5f86a4dCBD49c86Becc4FB89680f66FeC8eF358a', + removeController: '0x6F80738F31019B70B616Ac9Fac1C853dFC35Cc37', + }, + { + publicKey: '0xae89fa207bb808e8a34e05b5892a16625e8b66b92e597f8866aa586b487dfbfe', + activateController: '0xb0100454798fD2a11d7e7Bde0804a29171d2F492', + removeController: '0xA53dd220650550BC68dFC63faAF954b85eAfED7c', + }, + { + publicKey: '0xd5c96c5d0daf70f25fb5bfc20c0747b2ce0408cc6e54b8494f3a62b61ffca7cd', + activateController: '0x64637C1f9899684722da4657d94396ec4Fd85E09', + removeController: '0x882b0d0E0952170a7d3D62946327fF3F6d7AD329', + }, + { + publicKey: '0xed259c1abb397823afb31cb36089547367221242de3057df803a0b091e9a1c8d', + activateController: '0x5c2220b253c4E02Eec51A3959b9f67E599E8ECF9', + removeController: '0x386976F806739bDA4126a18eEF4157F92Fb53Cce', + }, + { + publicKey: '0xf43562191fe65ba250ac32d4602efb664586bf4e8150e4c80cf2dbf64650d2a9', + activateController: '0xB0556E9AE15cA9e02fFD66C1651134bf343ad2B2', + removeController: '0x5e887aFa6E659995A546e72B9959594D8DAEDD71', + }, + { + publicKey: '0xf927d6659307a219c3a59b51a31c29412b6d2b1c1717697a0ae0d5c9999ba740', + activateController: '0x5802620312d16b94E70147AAf730c662Ceee821C', + removeController: '0xBFB35262B8dF18829D33Da3A220DdE13505Cf485', + }, + { + publicKey: '0xda4f31b2d26acac4143d67f6b0ebc5a7b9fa829e4b63970f28130bff361316c9', + activateController: '0x9c4a7E4964d0b14c3B436b522F0BFA33DD517350', + removeController: '0xF75431B62AECD7da6AF24b6c0dFEd419579760c8', + }, + { + publicKey: '0x7bd0ca04c9423d39c25ee7747d2a5977acf19ff9d7f835f8dc1a746c83e71070', + activateController: '0x22C1Ba671ad7c01d00Afb8E159424A8568B6b3a7', + removeController: '0x4e5bF0aBC4EFA3c94Bc1ed23b1dB4b40De43AafA', + }, + { + publicKey: '0xc3fbb251a140db946283233a3fe5762a05a8305e2a8eaefc41d5df2820e071fc', + activateController: '0xD28Ae083CFEefd89cb2b0f6466351d4C5630Ac5F', + removeController: '0x034BB1f6239a70f956d2b18054CF7Bf7Bbe77Cc4', + }, + { + publicKey: '0xdeb9620fb017834b90e4b0cf66fdc7fd06b3edb68f0147938e59a9fa7db83380', + activateController: '0x5dD30B6C339ec59eB7fBeD6026C43978e5663FbE', + removeController: '0x7dCCcD3f36dDc820265F6e5758044EA3d4739C80', + }, + { + publicKey: '0x254192a339b277911fae7dac3a52ed4b0196bb90670fb3b893f521849086fc8d', + activateController: '0x4e8FC724848B3F119E031C1e17c5e68Af75D29C2', + removeController: '0x5519855988085f55Ac7aCcD7aED1C53988D9aF18', + }, + ] + + const config: GenesisConfig = { + timestamp: BigInt(Math.floor(new Date('2026-05-12T00:00:00-00:00').getTime() / 1000)), + + coinbase: '0x3141592653589793238462643383279502884197', + + NativeFiatToken: { + proxy: { admin: '0x9005E53E3ee2f27999F15e7a52C58f804Fc716e0' }, + owner: '0xf5a5658b55983E2Aa037cAC7A8431B510E8A97F4', + pauser: zeroAddress, + masterMinter: zeroAddress, + blacklister: zeroAddress, + rescuer: zeroAddress, + minters: [], + }, + + ProtocolConfig: { + proxy: { admin: '0xBD3738ab866eff9B0908Ef8985d00eECA22DA4eF' }, + owner: '0xA5FeD552f38E11291Dd2Fc9cb91e2a5F6Ae86eD4', + controller: '0x41fE044f1f71ff69F46F35f41EC93369a0E94733', + pauser: '0x85c8825829fC649694D569bcCc374E8382Df2D12', + feeParams: { + alpha: 20n, + kRate: 200n, + inverseElasticityMultiplier: 5000n, + minBaseFee: parseGwei('20'), + maxBaseFee: parseGwei('20000'), + blockGasLimit: 30_000_000n, + }, + consensusParams: { + timeoutProposeMs: 3000n, + timeoutProposeDeltaMs: 500n, + timeoutPrevoteMs: 1000n, + timeoutPrevoteDeltaMs: 500n, + timeoutPrecommitMs: 1000n, + timeoutPrecommitDeltaMs: 500n, + timeoutRebroadcastMs: 5000n, + targetBlockTimeMs: 500n, + }, + }, + + ValidatorManager: { + proxy: { admin: '0x20Db45729BC366833107524804d16cEb44e946c6' }, + PermissionedValidatorManager: { + proxy: { admin: '0x131E6B8E466aC8046c38c3ae7de77595CfEAf0D1' }, + owner: '0x20E61a9CC8d010928Aa9997e4e773e84dE1B8306', + pauser: '0x5bC1d44F8e844863cbC45D2f425F4c3758faf7b8', + validatorRegisterers: [ + '0xF65A7d2E6C16d263B7B263369deD8C78f3aa4813', + '0x38c57d852eddE831CE368D127CDabf5AdB86Ce96', + ], + }, + // Each validator gets a pair of controllers: activate + // (limit = initial voting power, can raise voting power) and remove + // (limit = 0, can only zero it out). + validators: validators.map((v) => ({ + publicKey: v.publicKey, + votingPower: initialVotingPower, + controllers: [ + { address: v.activateController, votingPowerLimit: initialVotingPower }, + { address: v.removeController, votingPowerLimit: 0n }, + ], + })), + }, + + Denylist: { + proxy: { + address: '0x3600000000000000000000000000000000000004', + admin: '0xAfb5b6a4725459959ef931a3e5df758a72A8cA7f', + }, + owner: '0x49ec36db19623e4DaDc1Aa821CbA2D1476F8E859', + denylisters: [], + }, + + prefund: [{ address: '0x50A2b0B577eC24d7ce1aeD372A8a6fd14CE1bE57', balance: parseEther('10000') }], + hardforks: { osakaTime: 0 }, + } + + fs.writeFileSync( + path.join(ctx.projectRoot, `assets/${ctx.network}/config.json`), + JSON.stringify(config, bigintReplacer, 2) + '\n', + ) + return await buildGenesis(ctx, config) +} + +export default build diff --git a/assets/mainnet/genesis.json b/assets/mainnet/genesis.json new file mode 100644 index 0000000..1799184 --- /dev/null +++ b/assets/mainnet/genesis.json @@ -0,0 +1,1583 @@ +{ + "config": { + "chainId": 5042, + "daoForkSupport": false, + "terminalTotalDifficulty": "0x0", + "terminalTotalDifficultyPassed": true, + "homesteadBlock": 0, + "eip150Block": 0, + "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "arrowGlacierBlock": 0, + "grayGlacierBlock": 0, + "shanghaiTime": 0, + "cancunTime": 0, + "pragueTime": 0, + "osakaTime": 0 + }, + "nonce": "0x0", + "timestamp": "0x6a026d80", + "extraData": "0x", + "gasLimit": "0x1c9c380", + "difficulty": "0x0", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x3141592653589793238462643383279502884197", + "number": "0x0", + "alloc": { + "0x3600000000000000000000000000000000000000": { + "balance": "0x0", + "nonce": "0x1", + "code": "0x60806040526004361061005a5760003560e01c80635c60da1b116100435780635c60da1b146101315780638f2839701461016f578063f851a440146101af5761005a565b80633659cfe6146100645780634f1ef286146100a4575b6100626101c4565b005b34801561007057600080fd5b506100626004803603602081101561008757600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166101de565b610062600480360360408110156100ba57600080fd5b73ffffffffffffffffffffffffffffffffffffffff82351691908101906040810160208201356401000000008111156100f257600080fd5b82018360208201111561010457600080fd5b8035906020019184600183028401116401000000008311171561012657600080fd5b509092509050610232565b34801561013d57600080fd5b50610146610309565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b34801561017b57600080fd5b506100626004803603602081101561019257600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16610318565b3480156101bb57600080fd5b50610146610420565b6101cc610430565b6101dc6101d76104c4565b6104e9565b565b6101e661050d565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102275761022281610532565b61022f565b61022f6101c4565b50565b61023a61050d565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102fc5761027683610532565b60003073ffffffffffffffffffffffffffffffffffffffff16348484604051808383808284376040519201945060009350909150508083038185875af1925050503d80600081146102e3576040519150601f19603f3d011682016040523d82523d6000602084013e6102e8565b606091505b50509050806102f657600080fd5b50610304565b6103046101c4565b505050565b60006103136104c4565b905090565b61032061050d565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156102275773ffffffffffffffffffffffffffffffffffffffff81166103bf576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260368152602001806106606036913960400191505060405180910390fd5b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6103e861050d565b6040805173ffffffffffffffffffffffffffffffffffffffff928316815291841660208301528051918290030190a161022281610587565b600061031361050d565b3b151590565b61043861050d565b73ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614156104bc576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603281526020018061062e6032913960400191505060405180910390fd5b6101dc6101dc565b7f7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c35490565b3660008037600080366000845af43d6000803e808015610508573d6000f35b3d6000fd5b7f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b5490565b61053b816105ab565b6040805173ffffffffffffffffffffffffffffffffffffffff8316815290517fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b9181900360200190a150565b7f10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b55565b6105b48161042a565b610609576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603b815260200180610696603b913960400191505060405180910390fd5b7f7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c35556fe43616e6e6f742063616c6c2066616c6c6261636b2066756e6374696f6e2066726f6d207468652070726f78792061646d696e43616e6e6f74206368616e6765207468652061646d696e206f6620612070726f787920746f20746865207a65726f206164647265737343616e6e6f742073657420612070726f787920696d706c656d656e746174696f6e20746f2061206e6f6e2d636f6e74726163742061646472657373a2646970667358221220015908007e367e7f333ec38084b88f027c06160d2c19e5bdd8027e8d06acf8bf64736f6c634300060c0033", + "storage": { + "0x10d6a54a4754c8869d6886b5f5d7fbfa5b4522237ea5c60d11bc4e7a1ff9390b": "0x0000000000000000000000009005e53e3ee2f27999f15e7a52c58f804fc716e0", + "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3": "0x000000000000000000000000c6ad664ac6679f4ce74e10e91449c93ec1ae3ca6", + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000000f5a5658b55983e2aa037cac7a8431b510e8a97f4", + "0x0000000000000000000000000000000000000000000000000000000000000001": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000004": "0x5553444300000000000000000000000000000000000000000000000000000008", + "0x0000000000000000000000000000000000000000000000000000000000000005": "0x5553444300000000000000000000000000000000000000000000000000000008", + "0x0000000000000000000000000000000000000000000000000000000000000006": "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000007": "0x5553440000000000000000000000000000000000000000000000000000000006", + "0x0000000000000000000000000000000000000000000000000000000000000008": "0x0000000000000000000000010000000000000000000000000000000000000000", + "0x000000000000000000000000000000000000000000000000000000000000000e": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x0000000000000000000000000000000000000000000000000000000000000012": "0x0000000000000000000000000000000000000000000000000000000000000003" + } + }, + "0xC6AD664ac6679F4Ce74e10E91449C93Ec1ae3cA6": { + "balance": "0x0", + "nonce": "0x1", + "code": "0x608060405234801561001057600080fd5b506004361061038e5760003560e01c806388b7ab63116101de578063bd1024301161010f578063dd62ed3e116100ad578063ef55bec61161007c578063ef55bec61461115b578063f2fde38b146111c7578063f9f92be4146111fa578063fe575a871461122d5761038e565b8063dd62ed3e14611073578063e3ee160e146110ae578063e5a6b10f1461111a578063e94a0102146111225761038e565b8063d505accf116100e9578063d505accf14610f95578063d608ea6414610ff3578063d916948714611063578063dd0743b31461106b5761038e565b8063bd10243014610ea1578063ccd92d3e14610ea9578063cf09299514610eb15761038e565b8063a457c2d71161017c578063aa271e1a11610156578063aa271e1a14610d30578063ad38bf2214610d63578063b2118a8d14610d96578063b7b7289914610dd95761038e565b8063a457c2d714610c8b578063a9059cbb14610cc4578063aa20e1e414610cfd5761038e565b806395d89b41116101b857806395d89b4114610b9b5780639fd0506d14610ba35780639fd5a6cf14610bab578063a0cc6a6814610c835761038e565b806388b7ab6314610a7c5780638a6db9c314610b605780638da5cb5b14610b935761038e565b806339509351116102c3578063554bab3c116102615780637ecebe00116102305780637ecebe0014610a315780637f2eecc314610a645780637fd0991614610a6c5780638456cb5914610a745761038e565b8063554bab3c146109755780635a049a70146109a85780635c975abb146109f657806370a08231146109fe5761038e565b806342966c681161029d57806342966c6814610855578063430239b4146108725780634e44d9561461093457806354fd4d501461096d5761038e565b806339509351146107db5780633f4ba83a1461081457806340c10f191461081c5761038e565b80633092afd5116103305780633357162b1161030a5780633357162b146105ae57806335d99f351461079a5780633644e515146107cb57806338a63183146107d35761038e565b80633092afd51461055557806330adf81f14610588578063313ce567146105905761038e565b80631a8952661161036c5780631a8952661461047757806323b872dd146104ac5780632ab60045146104ef5780632fc81e09146105225761038e565b806306fdde0314610393578063095ea7b31461041057806318160ddd1461045d575b600080fd5b61039b611260565b6040805160208082528351818301528351919283929083019185019080838360005b838110156103d55781810151838201526020016103bd565b50505050905090810190601f1680156104025780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6104496004803603604081101561042657600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813516906020013561130c565b604080519115158252519081900360200190f35b6104656113ae565b60408051918252519081900360200190f35b6104aa6004803603602081101561048d57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff1661144e565b005b610449600480360360608110156104c257600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81358116916020810135909116906040013561150b565b6104aa6004803603602081101561050557600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16611703565b6104aa6004803603602081101561053857600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16611864565b6104496004803603602081101561056b57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166118cc565b6104656119c5565b6105986119e9565b6040805160ff9092168252519081900360200190f35b6104aa60048036036101008110156105c557600080fd5b8101906020810181356401000000008111156105e057600080fd5b8201836020820111156105f257600080fd5b8035906020019184600183028401116401000000008311171561061457600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929594936020810193503591505064010000000081111561066757600080fd5b82018360208201111561067957600080fd5b8035906020019184600183028401116401000000008311171561069b57600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092959493602081019350359150506401000000008111156106ee57600080fd5b82018360208201111561070057600080fd5b8035906020019184600183028401116401000000008311171561072257600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295505050813560ff16925050602081013573ffffffffffffffffffffffffffffffffffffffff908116916040810135821691606082013581169160800135166119f2565b6107a2611d34565b6040805173ffffffffffffffffffffffffffffffffffffffff9092168252519081900360200190f35b610465611d50565b6107a2611d5f565b610449600480360360408110156107f157600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135611d7b565b6104aa611e13565b6104496004803603604081101561083257600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135611ed6565b6104aa6004803603602081101561086b57600080fd5b5035612324565b6104aa6004803603604081101561088857600080fd5b8101906020810181356401000000008111156108a357600080fd5b8201836020820111156108b557600080fd5b803590602001918460208302840111640100000000831117156108d757600080fd5b9193909290916020810190356401000000008111156108f557600080fd5b82018360208201111561090757600080fd5b8035906020019184600183028401116401000000008311171561092957600080fd5b509092509050612658565b6104496004803603604081101561094a57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813516906020013561280f565b61039b6129a2565b6104aa6004803603602081101561098b57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166129d9565b6104aa600480360360a08110156109be57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060208101359060ff6040820135169060608101359060800135612b40565b610449612bde565b61046560048036036020811015610a1457600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16612bff565b61046560048036036020811015610a4757600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16612c0a565b610465612c32565b6107a2612c56565b6104aa612c6e565b6104aa600480360360e0811015610a9257600080fd5b73ffffffffffffffffffffffffffffffffffffffff823581169260208101359091169160408201359160608101359160808201359160a08101359181019060e0810160c0820135640100000000811115610aeb57600080fd5b820183602082011115610afd57600080fd5b80359060200191846001830284011164010000000083111715610b1f57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550612d48945050505050565b61046560048036036020811015610b7657600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16612dea565b6107a2612e12565b61039b612e2e565b6107a2612ea7565b6104aa600480360360a0811015610bc157600080fd5b73ffffffffffffffffffffffffffffffffffffffff823581169260208101359091169160408201359160608101359181019060a081016080820135640100000000811115610c0e57600080fd5b820183602082011115610c2057600080fd5b80359060200191846001830284011164010000000083111715610c4257600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550612ec3945050505050565b610465612f5a565b61044960048036036040811015610ca157600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135612f7e565b61044960048036036040811015610cda57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135169060200135613016565b6104aa60048036036020811015610d1357600080fd5b503573ffffffffffffffffffffffffffffffffffffffff166130ae565b61044960048036036020811015610d4657600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16613215565b6104aa60048036036020811015610d7957600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16613240565b6104aa60048036036060811015610dac57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135811691602081013590911690604001356133a7565b6104aa60048036036060811015610def57600080fd5b73ffffffffffffffffffffffffffffffffffffffff82351691602081013591810190606081016040820135640100000000811115610e2c57600080fd5b820183602082011115610e3e57600080fd5b80359060200191846001830284011164010000000083111715610e6057600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092955061343d945050505050565b6107a26134d2565b6104656134ee565b6104aa600480360360e0811015610ec757600080fd5b73ffffffffffffffffffffffffffffffffffffffff823581169260208101359091169160408201359160608101359160808201359160a08101359181019060e0810160c0820135640100000000811115610f2057600080fd5b820183602082011115610f3257600080fd5b80359060200191846001830284011164010000000083111715610f5457600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506134f7945050505050565b6104aa600480360360e0811015610fab57600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060408101359060608101359060ff6080820135169060a08101359060c00135613590565b6104aa6004803603602081101561100957600080fd5b81019060208101813564010000000081111561102457600080fd5b82018360208201111561103657600080fd5b8035906020019184600183028401116401000000008311171561105857600080fd5b509092509050613629565b610465613712565b6107a2613736565b6104656004803603604081101561108957600080fd5b5073ffffffffffffffffffffffffffffffffffffffff8135811691602001351661374e565b6104aa60048036036101208110156110c557600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060408101359060608101359060808101359060a08101359060ff60c0820135169060e0810135906101000135613786565b61039b61382c565b6104496004803603604081101561113857600080fd5b5073ffffffffffffffffffffffffffffffffffffffff81351690602001356138a5565b6104aa600480360361012081101561117257600080fd5b5073ffffffffffffffffffffffffffffffffffffffff813581169160208101359091169060408101359060608101359060808101359060a08101359060ff60c0820135169060e08101359061010001356138dd565b6104aa600480360360208110156111dd57600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16613978565b6104aa6004803603602081101561121057600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16613acb565b6104496004803603602081101561124357600080fd5b503573ffffffffffffffffffffffffffffffffffffffff16613b88565b6004805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f810184900484028201840190925281815292918301828280156113045780601f106112d957610100808354040283529160200191611304565b820191906000526020600020905b8154815290600101906020018083116112e757829003601f168201915b505050505081565b60015460009074010000000000000000000000000000000000000000900460ff161561139957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b6113a4338484613b93565b5060015b92915050565b60008073180000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166318160ddd6040518163ffffffff1660e01b815260040160206040518083038186803b15801561140b57600080fd5b505afa15801561141f573d6000803e3d6000fd5b505050506040513d602081101561143557600080fd5b505190506114488164e8d4a51000613cda565b91505090565b60025473ffffffffffffffffffffffffffffffffffffffff1633146114be576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c8152602001806158c5602c913960400191505060405180910390fd5b6114c781613ced565b60405173ffffffffffffffffffffffffffffffffffffffff8216907f117e3210bb9aa7d9baff172026820255c6f6c30ba8999d1c2fd88e2848137c4e90600090a250565b60015460009074010000000000000000000000000000000000000000900460ff161561159857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b336115a281613cf8565b156115f8576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615bc76025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff85166000908152600a60209081526040808320338452909152902054831115611681576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260288152602001806159ac6028913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff85166000908152600a602090815260408083203384529091529020546116bc9084613da7565b73ffffffffffffffffffffffffffffffffffffffff86166000908152600a602090815260408083203384529091529020556116f8858585613e1e565b506001949350505050565b60005473ffffffffffffffffffffffffffffffffffffffff16331461178957604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff81166117f5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a815260200180615823602a913960400191505060405180910390fd5b600e80547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83169081179091556040517fe475e580d85111348e40d8ca33cfdd74c30fe1655c2d8537a13abc10065ffa5a90600090a250565b60125460ff1660011461187657600080fd5b60006118813061410c565b9050801561189457611894308383613e1e565b61189d30614135565b5050601280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166002179055565b60085460009073ffffffffffffffffffffffffffffffffffffffff16331461193f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602981526020018061589c6029913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff82166000818152600c6020908152604080832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00169055600d909152808220829055517fe94479a9f7e1952cc78f2d6baab678adc1b772d936c6583def489e524cb666929190a2506001919050565b7f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c981565b60065460ff1681565b60085474010000000000000000000000000000000000000000900460ff1615611a66576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a815260200180615a27602a913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8416611ad2576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602f815260200180615938602f913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8316611b3e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806157fa6029913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8216611baa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602e8152602001806159d4602e913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8116611c16576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526028815260200180615b3f6028913960400191505060405180910390fd5b8751611c299060049060208b0190615593565b508651611c3d9060059060208a0190615593565b508551611c51906007906020890190615593565b50600680547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff001660ff8716179055600880547fffffffffffffffffffffffff000000000000000000000000000000000000000090811673ffffffffffffffffffffffffffffffffffffffff8781169190911790925560018054821686841617905560028054909116918416919091179055611ceb81614140565b5050600880547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1674010000000000000000000000000000000000000000179055505050505050565b60085473ffffffffffffffffffffffffffffffffffffffff1681565b6000611d5a614187565b905090565b600e5473ffffffffffffffffffffffffffffffffffffffff1690565b60015460009074010000000000000000000000000000000000000000900460ff1615611e0857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b6113a433848461427c565b60015473ffffffffffffffffffffffffffffffffffffffff163314611e83576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526022815260200180615af36022913960400191505060405180910390fd5b600180547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff1690556040517f7805862f689e2f13df9f062ff482ad3ad112aca9e0847911ed832e158c525b3390600090a1565b336000908152600c602052604081205460ff16611f3e576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806159176021913960400191505060405180910390fd5b33611f4881613cf8565b15611f9e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615bc76025913960400191505060405180910390fd5b60015474010000000000000000000000000000000000000000900460ff161561202857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8416612094576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602381526020018061578f6023913960400191505060405180910390fd5b600083116120ed576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602981526020018061584d6029913960400191505060405180910390fd5b336000908152600d602052604090205480841115612156576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602e815260200180615ac5602e913960400191505060405180910390fd5b336000908152600d6020526040902084820390557318000000000000000000000000000000000000006340c10f19866121948764e8d4a510006142c6565b6040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b1580156121e757600080fd5b505af11580156121fb573d6000803e3d6000fd5b505050506040513d602081101561221157600080fd5b505161227e57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f4e6174697665206d696e74206661696c65640000000000000000000000000000604482015290519081900360640190fd5b60408051858152905173ffffffffffffffffffffffffffffffffffffffff87169133917fab8530f87dc9b59234c4623bf917212bb2536d647574c8e7e5da92c2ede0c9f89181900360200190a360408051858152905173ffffffffffffffffffffffffffffffffffffffff8716916000917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9181900360200190a3506001949350505050565b336000908152600c602052604090205460ff1661238c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260218152602001806159176021913960400191505060405180910390fd5b60015474010000000000000000000000000000000000000000900460ff161561241657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b6000811161246f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260298152602001806157666029913960400191505060405180910390fd5b60006124808264e8d4a510006142c6565b905033318111156124dc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806158f16026913960400191505060405180910390fd5b604080517f9dc29fac00000000000000000000000000000000000000000000000000000000815233600482015260248101839052905173180000000000000000000000000000000000000091639dc29fac9160448083019260209291908290030181600087803b15801561254f57600080fd5b505af1158015612563573d6000803e3d6000fd5b505050506040513d602081101561257957600080fd5b50516125e657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f4e6174697665206275726e206661696c65640000000000000000000000000000604482015290519081900360640190fd5b60408051838152905133917fcc16f5dbb4873280815c1ee09dbd06736cffcc184412cf7a71a0fdb75d397ca5919081900360200190a260408051838152905160009133917fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9181900360200190a35050565b60125460ff1660021461266a57600080fd5b61267660058383615611565b5060005b838110156127b8576003600086868481811061269257fe5b6020908102929092013573ffffffffffffffffffffffffffffffffffffffff168352508101919091526040016000205460ff1661271a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252603d8152602001806156b3603d913960400191505060405180910390fd5b61274b85858381811061272957fe5b9050602002013573ffffffffffffffffffffffffffffffffffffffff16614135565b6003600086868481811061275b57fe5b6020908102929092013573ffffffffffffffffffffffffffffffffffffffff1683525081019190915260400160002080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016905560010161267a565b506127c230614135565b505030600090815260036020819052604090912080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff009081169091556012805490911690911790555050565b60015460009074010000000000000000000000000000000000000000900460ff161561289c57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b60085473ffffffffffffffffffffffffffffffffffffffff16331461290c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602981526020018061589c6029913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff83166000818152600c6020908152604080832080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055600d825291829020859055815185815291517f46980fca912ef9bcdbd36877427b6b90e860769f604e89c0e67720cece530d209281900390910190a250600192915050565b60408051808201909152600181527f3200000000000000000000000000000000000000000000000000000000000000602082015290565b60005473ffffffffffffffffffffffffffffffffffffffff163314612a5f57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8116612acb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260288152602001806157136028913960400191505060405180910390fd5b600180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691909117918290556040519116907fb80482a293ca2e013eda8683c9bd7fc8347cfdaeea5ede58cba46df502c2a60490600090a250565b60015474010000000000000000000000000000000000000000900460ff1615612bca57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b612bd785858585856142d2565b5050505050565b60015474010000000000000000000000000000000000000000900460ff1681565b60006113a88261410c565b73ffffffffffffffffffffffffffffffffffffffff1660009081526011602052604090205490565b7fd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de881565b73180000000000000000000000000000000000000081565b60015473ffffffffffffffffffffffffffffffffffffffff163314612cde576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526022815260200180615af36022913960400191505060405180910390fd5b600180547fffffffffffffffffffffff00ffffffffffffffffffffffffffffffffffffffff16740100000000000000000000000000000000000000001790556040517f6985a02210a168e66602d3235cb6db0e70f92b3ba4d376a33c0f3d9434bff62590600090a1565b60015474010000000000000000000000000000000000000000900460ff1615612dd257604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b612de187878787878787614312565b50505050505050565b73ffffffffffffffffffffffffffffffffffffffff166000908152600d602052604090205490565b60005473ffffffffffffffffffffffffffffffffffffffff1690565b6005805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f810184900484028201840190925281815292918301828280156113045780601f106112d957610100808354040283529160200191611304565b60015473ffffffffffffffffffffffffffffffffffffffff1681565b60015474010000000000000000000000000000000000000000900460ff1615612f4d57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b612bd78585858585614433565b7f7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a226781565b60015460009074010000000000000000000000000000000000000000900460ff161561300b57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b6113a43384846146f7565b60015460009074010000000000000000000000000000000000000000900460ff16156130a357604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b6113a4338484613e1e565b60005473ffffffffffffffffffffffffffffffffffffffff16331461313457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff81166131a0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602f815260200180615938602f913960400191505060405180910390fd5b600880547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691909117918290556040519116907fdb66dfa9c6b8f5226fe9aac7e51897ae8ee94ac31dc70bb6c9900b2574b707e690600090a250565b73ffffffffffffffffffffffffffffffffffffffff166000908152600c602052604090205460ff1690565b60005473ffffffffffffffffffffffffffffffffffffffff1633146132c657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8116613332576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526032815260200180615b956032913960400191505060405180910390fd5b600280547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff83811691909117918290556040519116907fc67398012c111ce95ecb7429b933096c977380ee6c421175a71a4a4c6c88c06e90600090a250565b600e5473ffffffffffffffffffffffffffffffffffffffff163314613417576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260248152602001806159676024913960400191505060405180910390fd5b61343873ffffffffffffffffffffffffffffffffffffffff84168383614753565b505050565b60015474010000000000000000000000000000000000000000900460ff16156134c757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b6134388383836147e0565b60025473ffffffffffffffffffffffffffffffffffffffff1681565b64e8d4a5100081565b60015474010000000000000000000000000000000000000000900460ff161561358157604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b612de1878787878787876148ea565b60015474010000000000000000000000000000000000000000900460ff161561361a57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b612de187878787878787614988565b60085474010000000000000000000000000000000000000000900460ff168015613656575060125460ff16155b61365f57600080fd5b61366b60048383615611565b506136e082828080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505060408051808201909152600181527f3200000000000000000000000000000000000000000000000000000000000000602082015291506149ca9050565b600f555050601280547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055565b7f158b0a9edf7a828aad02f63cd515c68ef2f50ba807396f6d12842833a159742981565b73180000000000000000000000000000000000000181565b73ffffffffffffffffffffffffffffffffffffffff9182166000908152600a6020908152604080832093909416825291909152205490565b60015474010000000000000000000000000000000000000000900460ff161561381057604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b6138218989898989898989896149e0565b505050505050505050565b6007805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f810184900484028201840190925281815292918301828280156113045780601f106112d957610100808354040283529160200191611304565b73ffffffffffffffffffffffffffffffffffffffff919091166000908152601060209081526040808320938352929052205460ff1690565b60015474010000000000000000000000000000000000000000900460ff161561396757604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f5061757361626c653a2070617573656400000000000000000000000000000000604482015290519081900360640190fd5b613821898989898989898989614a24565b60005473ffffffffffffffffffffffffffffffffffffffff1633146139fe57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8116613a6a576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806157b26026913960400191505060405180910390fd5b6000546040805173ffffffffffffffffffffffffffffffffffffffff9283168152918316602083015280517f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09281900390910190a1613ac881614140565b50565b60025473ffffffffffffffffffffffffffffffffffffffff163314613b3b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602c8152602001806158c5602c913960400191505060405180910390fd5b613b4481614135565b60405173ffffffffffffffffffffffffffffffffffffffff8216907fffa4e6181777692565cf28528fc88fd1516ea86b56da075235fa575af6a4b85590600090a250565b60006113a882613cf8565b73ffffffffffffffffffffffffffffffffffffffff8316613bff576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526024815260200180615aa16024913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8216613c6b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260228152602001806157d86022913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8084166000818152600a6020908152604080832094871680845294825291829020859055815185815291517f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9259281900390910190a3505050565b6000613ce68383614a68565b9392505050565b613ac8816000614ae9565b600073180000000000000000000000000000000000000173ffffffffffffffffffffffffffffffffffffffff16638e204c43836040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b158015613d7557600080fd5b505afa158015613d89573d6000803e3d6000fd5b505050506040513d6020811015613d9f57600080fd5b505192915050565b600082821115613e1857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f536166654d6174683a207375627472616374696f6e206f766572666c6f770000604482015290519081900360640190fd5b50900390565b73ffffffffffffffffffffffffffffffffffffffff8316613e8a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615a7c6025913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8216613ef6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260238152602001806156f06023913960400191505060405180910390fd5b6000613f078264e8d4a510006142c6565b90508373ffffffffffffffffffffffffffffffffffffffff1631811115613f79576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806158766026913960400191505060405180910390fd5b604080517fbeabacc800000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8681166004830152851660248201526044810183905290517318000000000000000000000000000000000000009163beabacc89160648083019260209291908290030181600087803b15801561400a57600080fd5b505af115801561401e573d6000803e3d6000fd5b505050506040513d602081101561403457600080fd5b50516140a157604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601660248201527f4e6174697665207472616e73666572206661696c656400000000000000000000604482015290519081900360640190fd5b8273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040518082815260200191505060405180910390a350505050565b600073ffffffffffffffffffffffffffffffffffffffff821631613ce68164e8d4a51000613cda565b613ac8816001614ae9565b600080547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b6004805460408051602060026001851615610100027fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0190941693909304601f8101849004840282018401909252818152600093611d5a93919290918301828280156142345780601f1061420957610100808354040283529160200191614234565b820191906000526020600020905b81548152906001019060200180831161421757829003601f168201915b50505050506040518060400160405280600181526020017f3200000000000000000000000000000000000000000000000000000000000000815250614277614d39565b614d3d565b73ffffffffffffffffffffffffffffffffffffffff8084166000908152600a602090815260408083209386168352929052205461343890849084906142c19085614db1565b613b93565b6000613ce68383614e25565b612bd78585848487604051602001808481526020018381526020018260ff1660f81b815260010193505050506040516020818303038152906040526147e0565b73ffffffffffffffffffffffffffffffffffffffff86163314614380576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615a026025913960400191505060405180910390fd5b61438c87838686614e98565b604080517fd099cc98ef71107a616c4f0f941f04c322d8e254fe26b3c6668db87aae413de860208083019190915273ffffffffffffffffffffffffffffffffffffffff808b1683850152891660608301526080820188905260a0820187905260c0820186905260e080830186905283518084039091018152610100909201909252805191012061441e90889083614f52565b61442887836150d0565b612de1878787613e1e565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8214806144615750428210155b6144cc57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f46696174546f6b656e56323a207065726d697420697320657870697265640000604482015290519081900360640190fd5b60006145746144d9614187565b73ffffffffffffffffffffffffffffffffffffffff80891660008181526011602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98184015280840194909452938b166060840152608083018a905260a083019390935260c08083018990528151808403909101815260e090920190528051910120615155565b905073fcff98b65f9ea559ec0df36f4072c7e3be0520df636ccea6528783856040518463ffffffff1660e01b8152600401808473ffffffffffffffffffffffffffffffffffffffff16815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b838110156146015781810151838201526020016145e9565b50505050905090810190601f16801561462e5780820380516001836020036101000a031916815260200191505b5094505050505060206040518083038186803b15801561464d57600080fd5b505af4158015614661573d6000803e3d6000fd5b505050506040513d602081101561467757600080fd5b50516146e457604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f454950323631323a20696e76616c6964207369676e6174757265000000000000604482015290519081900360640190fd5b6146ef868686613b93565b505050505050565b61343883836142c184604051806060016040528060258152602001615c116025913973ffffffffffffffffffffffffffffffffffffffff808a166000908152600a60209081526040808320938c1683529290522054919061518f565b6040805173ffffffffffffffffffffffffffffffffffffffff8416602482015260448082018490528251808303909101815260649091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fa9059cbb00000000000000000000000000000000000000000000000000000000179052613438908490615240565b6147ea8383615318565b614864837f158b0a9edf7a828aad02f63cd515c68ef2f50ba807396f6d12842833a159742960001b8585604051602001808481526020018373ffffffffffffffffffffffffffffffffffffffff16815260200182815260200193505050506040516020818303038152906040528051906020012083614f52565b73ffffffffffffffffffffffffffffffffffffffff8316600081815260106020908152604080832086845290915280822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055518492917f1cdd46ff242716cdaa72d159d339a485b3438398348d68f09d7c8c0a59353d8191a3505050565b6148f687838686614e98565b604080517f7c7c6cdb67a18743f49ec6fa9b35f50d52ed05cbed4cc592e13b44501c1a226760208083019190915273ffffffffffffffffffffffffffffffffffffffff808b1683850152891660608301526080820188905260a0820187905260c0820186905260e080830186905283518084039091018152610100909201909252805191012061441e90889083614f52565b612de187878787868689604051602001808481526020018381526020018260ff1660f81b81526001019350505050604051602081830303815290604052614433565b6000466149d8848483614d3d565b949350505050565b61382189898989898988888b604051602001808481526020018381526020018260ff1660f81b815260010193505050506040516020818303038152906040526148ea565b61382189898989898988888b604051602001808481526020018381526020018260ff1660f81b81526001019350505050604051602081830303815290604052614312565b6000808211614ad857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f536166654d6174683a206469766973696f6e206279207a65726f000000000000604482015290519081900360640190fd5b818381614ae157fe5b049392505050565b8015614c86573073ffffffffffffffffffffffffffffffffffffffff16638da5cb5b6040518163ffffffff1660e01b815260040160206040518083038186803b158015614b3557600080fd5b505afa158015614b49573d6000803e3d6000fd5b505050506040513d6020811015614b5f57600080fd5b505173ffffffffffffffffffffffffffffffffffffffff83811691161415614bd2576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b815260200180615a51602b913960400191505060405180910390fd5b604080517fe5c7160b00000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8416600482015290517318000000000000000000000000000000000000019163e5c7160b9160248083019260209291908290030181600087803b158015614c5457600080fd5b505af1158015614c68573d6000803e3d6000fd5b505050506040513d6020811015614c7e57600080fd5b50614d359050565b604080517f31b2302000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff841660048201529051731800000000000000000000000000000000000001916331b230209160248083019260209291908290030181600087803b158015614d0857600080fd5b505af1158015614d1c573d6000803e3d6000fd5b505050506040513d6020811015614d3257600080fd5b50505b5050565b4690565b8251602093840120825192840192909220604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f8187015280820194909452606084019190915260808301919091523060a0808401919091528151808403909101815260c09092019052805191012090565b600082820183811015613ce657604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b600082614e34575060006113a8565b82820282848281614e4157fe5b0414613ce6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602181526020018061598b6021913960400191505060405180910390fd5b814211614ef0576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b81526020018061573b602b913960400191505060405180910390fd5b804210614f48576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180615bec6025913960400191505060405180910390fd5b614d328484615318565b73fcff98b65f9ea559ec0df36f4072c7e3be0520df636ccea65284614f7e614f78614187565b86615155565b846040518463ffffffff1660e01b8152600401808473ffffffffffffffffffffffffffffffffffffffff16815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b83811015614fed578181015183820152602001614fd5565b50505050905090810190601f16801561501a5780820380516001836020036101000a031916815260200191505b5094505050505060206040518083038186803b15801561503957600080fd5b505af415801561504d573d6000803e3d6000fd5b505050506040513d602081101561506357600080fd5b505161343857604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601e60248201527f46696174546f6b656e56323a20696e76616c6964207369676e61747572650000604482015290519081900360640190fd5b73ffffffffffffffffffffffffffffffffffffffff8216600081815260106020908152604080832085845290915280822080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00166001179055518392917f98de503528ee59b575ef0c0a2576a82497bfc029a5685b209e9ec333479b10a591a35050565b6040517f19010000000000000000000000000000000000000000000000000000000000008152600281019290925260228201526042902090565b60008184841115615238576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b838110156151fd5781810151838201526020016151e5565b50505050905090810190601f16801561522a5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b60606152a2826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c65648152508573ffffffffffffffffffffffffffffffffffffffff166153a29092919063ffffffff16565b805190915015613438578080602001905160208110156152c157600080fd5b5051613438576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a815260200180615b15602a913960400191505060405180910390fd5b73ffffffffffffffffffffffffffffffffffffffff8216600090815260106020908152604080832084845290915290205460ff1615614d35576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602e815260200180615b67602e913960400191505060405180910390fd5b60606149d88484600085856153b68561550d565b61542157604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604482015290519081900360640190fd5b600060608673ffffffffffffffffffffffffffffffffffffffff1685876040518082805190602001908083835b6020831061548b57805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161544e565b6001836020036101000a03801982511681845116808217855250505050505090500191505060006040518083038185875af1925050503d80600081146154ed576040519150601f19603f3d011682016040523d82523d6000602084013e6154f2565b606091505b5091509150615502828286615513565b979650505050505050565b3b151590565b60608315615522575081613ce6565b8251156155325782518084602001fd5b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526020600482018181528451602484015284518593919283926044019190850190808383600083156151fd5781810151838201526020016151e5565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106155d457805160ff1916838001178555615601565b82800160010185558215615601579182015b828111156156015782518255916020019190600101906155e6565b5061560d92915061569d565b5090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f10615670578280017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00823516178555615601565b82800160010185558215615601579182015b82811115615601578235825591602001919060010190615682565b5b8082111561560d576000815560010161569e56fe46696174546f6b656e56325f323a20426c61636b6c697374696e672070726576696f75736c7920756e626c61636b6c6973746564206163636f756e742145524332303a207472616e7366657220746f20746865207a65726f20616464726573735061757361626c653a206e65772070617573657220697320746865207a65726f206164647265737346696174546f6b656e56323a20617574686f72697a6174696f6e206973206e6f74207965742076616c696446696174546f6b656e3a206275726e20616d6f756e74206e6f742067726561746572207468616e203046696174546f6b656e3a206d696e7420746f20746865207a65726f20616464726573734f776e61626c653a206e6577206f776e657220697320746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737346696174546f6b656e3a206e65772070617573657220697320746865207a65726f2061646472657373526573637561626c653a206e6577207265736375657220697320746865207a65726f206164647265737346696174546f6b656e3a206d696e7420616d6f756e74206e6f742067726561746572207468616e203045524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636546696174546f6b656e3a2063616c6c6572206973206e6f7420746865206d61737465724d696e746572426c61636b6c69737461626c653a2063616c6c6572206973206e6f742074686520626c61636b6c697374657246696174546f6b656e3a206275726e20616d6f756e7420657863656564732062616c616e636546696174546f6b656e3a2063616c6c6572206973206e6f742061206d696e74657246696174546f6b656e3a206e6577206d61737465724d696e74657220697320746865207a65726f2061646472657373526573637561626c653a2063616c6c6572206973206e6f74207468652072657363756572536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f7745524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636546696174546f6b656e3a206e657720626c61636b6c697374657220697320746865207a65726f206164647265737346696174546f6b656e56323a2063616c6c6572206d7573742062652074686520706179656546696174546f6b656e3a20636f6e747261637420697320616c726561647920696e697469616c697a65644e617469766546696174546f6b656e56325f323a2063616e6e6f7420626c61636b6c697374206f776e657245524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737346696174546f6b656e3a206d696e7420616d6f756e742065786365656473206d696e746572416c6c6f77616e63655061757361626c653a2063616c6c6572206973206e6f7420746865207061757365725361666545524332303a204552433230206f7065726174696f6e20646964206e6f74207375636365656446696174546f6b656e3a206e6577206f776e657220697320746865207a65726f206164647265737346696174546f6b656e56323a20617574686f72697a6174696f6e2069732075736564206f722063616e63656c6564426c61636b6c69737461626c653a206e657720626c61636b6c697374657220697320746865207a65726f2061646472657373426c61636b6c69737461626c653a206163636f756e7420697320626c61636b6c697374656446696174546f6b656e56323a20617574686f72697a6174696f6e206973206578706972656445524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa2646970667358221220e06530c9518c3f8a05f1004a77ca5bf40453a85869b880a2f64f7273f330cb2a64736f6c634300060c0033" + }, + "0xfcFf98B65F9ea559EC0df36F4072C7E3BE0520Df": { + "balance": "0x0", + "nonce": "0x1", + "code": "0x73fcff98b65f9ea559ec0df36f4072c7e3be0520df30146080604052600436106100355760003560e01c80636ccea6521461003a575b600080fd5b6101026004803603606081101561005057600080fd5b73ffffffffffffffffffffffffffffffffffffffff8235169160208101359181019060608101604082013564010000000081111561008d57600080fd5b82018360208201111561009f57600080fd5b803590602001918460018302840111640100000000831117156100c157600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610116945050505050565b604080519115158252519081900360200190f35b600061012184610179565b610164578373ffffffffffffffffffffffffffffffffffffffff16610146848461017f565b73ffffffffffffffffffffffffffffffffffffffff16149050610172565b61016f848484610203565b90505b9392505050565b3b151590565b600081516041146101db576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260238152602001806106296023913960400191505060405180910390fd5b60208201516040830151606084015160001a6101f98682858561042d565b9695505050505050565b60008060608573ffffffffffffffffffffffffffffffffffffffff16631626ba7e60e01b86866040516024018083815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561026f578181015183820152602001610257565b50505050905090810190601f16801561029c5780820380516001836020036101000a031916815260200191505b50604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181529181526020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff000000000000000000000000000000000000000000000000000000009098169790971787525181519196909550859450925090508083835b6020831061036957805182527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0909201916020918201910161032c565b6001836020036101000a038019825116818451168082178552505050505050905001915050600060405180830381855afa9150503d80600081146103c9576040519150601f19603f3d011682016040523d82523d6000602084013e6103ce565b606091505b50915091508180156103e257506020815110155b80156101f9575080517f1626ba7e00000000000000000000000000000000000000000000000000000000906020808401919081101561042057600080fd5b5051149695505050505050565b60007f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a08211156104a8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260268152602001806106726026913960400191505060405180910390fd5b8360ff16601b141580156104c057508360ff16601c14155b15610516576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602681526020018061064c6026913960400191505060405180910390fd5b600060018686868660405160008152602001604052604051808581526020018460ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015610572573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff811661061f57604080517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f45435265636f7665723a20696e76616c6964207369676e617475726500000000604482015290519081900360640190fd5b9594505050505056fe45435265636f7665723a20696e76616c6964207369676e6174757265206c656e67746845435265636f7665723a20696e76616c6964207369676e6174757265202776272076616c756545435265636f7665723a20696e76616c6964207369676e6174757265202773272076616c7565a2646970667358221220289705d6ae8701c9ed586541fa0ba342f754518aa4f0e59dbbda380bc9f2323964736f6c634300060c0033" + }, + "0x695D85E3437DaA1F8f6DE97C5aEdF75B82deA80a": { + "balance": "0x0", + "nonce": "0x1", + "code": "0x608060405234801561000f575f5ffd5b5060043610610111575f3560e01c80638da5cb5b1161009e5780639fd0506d1161006e5780639fd0506d146103cb578063da980fed146103d3578063e30c3978146103e6578063f2fde38b146103ee578063f77c479114610401575f5ffd5b80638da5cb5b146101b15780638e207848146101d15780639242164f146101e45780639fd02a36146102e8575f5ffd5b8063554bab3c116100e4578063554bab3c146101585780635c975abb1461016b578063715018a61461019957806379ba5097146101a15780638456cb59146101a9575f5ffd5b8063032b901b1461011557806306cb5b661461012a5780632bbdb79f1461013d5780633f4ba83a14610150575b5f5ffd5b610128610123366004610e06565b610409565b005b610128610138366004610e28565b6104b7565b61012861014b366004610e4e565b61054e565b61012861065b565b610128610166366004610e28565b6106b9565b5f5160206113515f395f51905f5254600160a01b900460ff1660405190151581526020015b60405180910390f35b61012861073d565b610128610750565b61012861079a565b6101b96107fc565b6040516001600160a01b039091168152602001610190565b6101286101df366004610e65565b610830565b6102826040805160c0810182525f80825260208201819052918101829052606081018290526080810182905260a08101829052905f5160206113315f395f51905f526040805160c08101825282546001600160401b038082168352600160401b820481166020840152600160801b9091041691810191909152600182015460608201526002820154608082015260039091015460a082015292915050565b60405161019091905f60c0820190506001600160401b0383511682526001600160401b0360208401511660208301526001600160401b036040840151166040830152606083015160608301526080830151608083015260a083015160a083015292915050565b6103be60408051610100810182525f80825260208201819052918101829052606081018290526080810182905260a0810182905260c0810182905260e08101829052905f5160206113315f395f51905f52604080516101008101825260059092015461ffff80821684526201000082048116602085015264010000000082048116928401929092526601000000000000810482166060840152600160401b810482166080840152600160501b8104821660a0840152600160601b8104821660c0840152600160701b90041660e082015292915050565b6040516101909190610e7e565b6101b9610986565b6101286103e1366004610f1e565b61099b565b6101b9610b79565b6101286103fc366004610e28565b610ba1565b6101b9610c26565b610411610c4e565b610419610c99565b5f8161ffff161161043d5760405163811da5f160e01b815260040160405180910390fd5b7f668f09ce856848ead6cb1ddee963f15ef833cea8958030868f867aec84385205805461ffff191661ffff83161781556040515f5160206113315f395f51905f52917faba8150f8eae6a1acc6824830944cdc19c670da2fae4fe28f4dfa04c0d6932d4916104ab9190610f30565b60405180910390a15050565b6104bf610cd1565b6001600160a01b0381166104e6576040516371ab47bb60e11b815260040160405180910390fd5b7f958f8fec699b51a1249f513eceda5429078000657f74abd1721bba363087af0080546001600160a01b0319166001600160a01b03831690811782556040517f1304018cfe79741dcf02ba6b61d39cc4757d59395d03224d9925c7aa83002146905f90a25050565b610556610c4e565b61055e610c99565b5f811161057e57604051635251cbd760e01b815260040160405180910390fd5b7f668f09ce856848ead6cb1ddee963f15ef833cea8958030868f867aec84385203819055604080515f5160206113315f395f51905f5280546001600160401b03808216845281851c81166020850152608091821c16938301939093527f668f09ce856848ead6cb1ddee963f15ef833cea8958030868f867aec843852015460608301527f668f09ce856848ead6cb1ddee963f15ef833cea8958030868f867aec84385202549282019290925260a081018390527f413a821e8d0eadcc30efbe627a655111b9f28f5999003a85b7718a14b0e329ef9060c0016104ab565b610663610d03565b5f5160206113515f395f51905f528054600160a01b900460ff16156106b657805460ff60a01b191681556040517f7805862f689e2f13df9f062ff482ad3ad112aca9e0847911ed832e158c525b33905f90a15b50565b6106c1610cd1565b6001600160a01b0381166106e85760405163a74995ab60e01b815260040160405180910390fd5b5f5160206113515f395f51905f5280546001600160a01b0319166001600160a01b03831690811782556040517fb80482a293ca2e013eda8683c9bd7fc8347cfdaeea5ede58cba46df502c2a604905f90a25050565b610745610cd1565b61074e5f610d3b565b565b338061075a610b79565b6001600160a01b0316146107915760405163118cdaa760e01b81526001600160a01b03821660048201526024015b60405180910390fd5b6106b681610d3b565b6107a2610d03565b5f5160206113515f395f51905f528054600160a01b900460ff166106b657805460ff60a01b1916600160a01b1781556040517f6985a02210a168e66602d3235cb6db0e70f92b3ba4d376a33c0f3d9434bff625905f90a150565b5f807f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c1993005b546001600160a01b031692915050565b610838610c4e565b610840610c99565b606461084f6020830183610fb8565b6001600160401b0316111561087757604051633e82ffd960e21b815260040160405180910390fd5b61271061088a6040830160208401610fb8565b6001600160401b031611156108b25760405163f87b4d6d60e01b815260040160405180910390fd5b8060800135816060013511156108db576040516336b1b98d60e11b815260040160405180910390fd5b5f8160a00135116108ff57604051635251cbd760e01b815260040160405180910390fd5b6127106109126060830160408401610fb8565b6001600160401b0316111561093a5760405163279eca6760e21b815260040160405180910390fd5b5f5160206113315f395f51905f5281816109548282610fd3565b9050507f413a821e8d0eadcc30efbe627a655111b9f28f5999003a85b7718a14b0e329ef826040516104ab9190611090565b5f805f5160206113515f395f51905f52610820565b6109a3610c4e565b6109ab610c99565b5f6109b96020830183610e06565b61ffff16116109db5760405163811da5f160e01b815260040160405180910390fd5b5f6109ec6040830160208401610e06565b61ffff1611610a0e5760405163055dd8c360e01b815260040160405180910390fd5b5f610a1f6060830160408401610e06565b61ffff1611610a4157604051632de6d8ed60e11b815260040160405180910390fd5b5f610a526080830160608401610e06565b61ffff1611610a7457604051634c14d46960e11b815260040160405180910390fd5b5f610a8560a0830160808401610e06565b61ffff1611610aa7576040516396e398e160e01b815260040160405180910390fd5b5f610ab860c0830160a08401610e06565b61ffff1611610ada57604051632218f17b60e21b815260040160405180910390fd5b5f610aeb60e0830160c08401610e06565b61ffff1611610b0d57604051631805fa5160e11b815260040160405180910390fd5b5f5160206113315f395f51905f52817f668f09ce856848ead6cb1ddee963f15ef833cea8958030868f867aec84385205610b478282611119565b9050507faba8150f8eae6a1acc6824830944cdc19c670da2fae4fe28f4dfa04c0d6932d4826040516104ab919061127f565b5f807f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c00610820565b610ba9610cd1565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0080546001600160a01b0319166001600160a01b0383169081178255610bed6107fc565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a35050565b5f807f958f8fec699b51a1249f513eceda5429078000657f74abd1721bba363087af00610820565b7f958f8fec699b51a1249f513eceda5429078000657f74abd1721bba363087af0080546001600160a01b031633146106b657604051630e971e6360e11b815260040160405180910390fd5b5f5160206113515f395f51905f528054600160a01b900460ff16156106b65760405163ab35696f60e01b815260040160405180910390fd5b33610cda6107fc565b6001600160a01b03161461074e5760405163118cdaa760e01b8152336004820152602401610788565b5f5160206113515f395f51905f5280546001600160a01b031633146106b65760405163daeefd6560e01b815260040160405180910390fd5b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0080546001600160a01b0319168155610d7382610d77565b5050565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b61ffff811681146106b6575f5ffd5b8035610e0181610de7565b919050565b5f60208284031215610e16575f5ffd5b8135610e2181610de7565b9392505050565b5f60208284031215610e38575f5ffd5b81356001600160a01b0381168114610e21575f5ffd5b5f60208284031215610e5e575f5ffd5b5035919050565b5f60c0828403128015610e76575f5ffd5b509092915050565b5f6101008201905061ffff835116825261ffff602084015116602083015261ffff60408401511660408301526060830151610ebf606084018261ffff169052565b506080830151610ed5608084018261ffff169052565b5060a0830151610eeb60a084018261ffff169052565b5060c0830151610f0160c084018261ffff169052565b5060e0830151610f1760e084018261ffff169052565b5092915050565b5f610100828403128015610e76575f5ffd5b815461ffff8082168352601082901c166020830152610100820190602081901c61ffff166040840152603081901c61ffff166060840152604081901c61ffff166080840152605081901c61ffff1660a0840152606081901c61ffff1660c0840152607081901c61ffff1660e0840152610f17565b6001600160401b03811681146106b6575f5ffd5b5f60208284031215610fc8575f5ffd5b8135610e2181610fa4565b8135610fde81610fa4565b6001600160401b03811690508154816001600160401b03198216178355602084013561100981610fa4565b6fffffffffffffffff00000000000000008160401b16836fffffffffffffffffffffffffffffffff198416171784555050505f604083013561104a81610fa4565b825467ffffffffffffffff60801b1916608091821b67ffffffffffffffff60801b161783556060840135600184015583013560028301555060a090910135600390910155565b60c08101823561109f81610fa4565b6001600160401b0316825260208301356110b881610fa4565b6001600160401b0316602083015260408301356110d481610fa4565b6001600160401b03166040830152606083810135908301526080808401359083015260a092830135929091019190915290565b5f813561111381610de7565b92915050565b813561112481610de7565b61ffff8116905081548161ffff198216178355602084013561114581610de7565b63ffff00008160101b168363ffffffff1984161717845550505061118c61116e60408401611107565b825465ffff00000000191660209190911b65ffff0000000016178255565b6111bd61119b60608401611107565b825467ffff000000000000191660309190911b67ffff00000000000016178255565b6111f26111cc60808401611107565b825469ffff0000000000000000191660409190911b69ffff000000000000000016178255565b61122161120160a08401611107565b82805461ffff60501b191660509290921b61ffff60501b16919091179055565b61125061123060c08401611107565b82805461ffff60601b191660609290921b61ffff60601b16919091179055565b610d7361125f60e08401611107565b82805461ffff60701b191660709290921b61ffff60701b16919091179055565b6101008101823561128f81610de7565b61ffff16825260208301356112a381610de7565b61ffff1660208301526112b860408401610df6565b61ffff1660408301526112cd60608401610df6565b61ffff1660608301526112e260808401610df6565b61ffff1660808301526112f760a08401610df6565b61ffff1660a083015261130c60c08401610df6565b61ffff1660c083015261132160e08401610df6565b61ffff811660e0840152610f1756fe668f09ce856848ead6cb1ddee963f15ef833cea8958030868f867aec843852000642d7922329a434cf4fd17a3c95eb692c24fd95f9f94d0b55420a5d895f4a00a2646970667358221220046372fe4609a57ed6251208e9ef58c11217d4f47c96763ebfa709c47905168464736f6c634300081d0033" + }, + "0x3600000000000000000000000000000000000001": { + "balance": "0x0", + "nonce": "0x1", + "code": "0x608060405260043610610049575f3560e01c80633659cfe6146100535780634f1ef286146100725780635c60da1b146100855780638f283970146100b5578063f851a440146100d4575b6100516100e8565b005b34801561005e575f5ffd5b5061005161006d3660046104f3565b6100fa565b61005161008036600461050c565b610134565b348015610090575f5ffd5b50610099610197565b6040516001600160a01b03909116815260200160405180910390f35b3480156100c0575f5ffd5b506100516100cf3660046104f3565b6101a5565b3480156100df575f5ffd5b506100996101c5565b6100f86100f3610197565b6101ce565b565b6101026101ec565b6001600160a01b0316330361012c576101298160405180602001604052805f81525061021e565b50565b6101296100e8565b61013c6101ec565b6001600160a01b0316330361018f5761018a8383838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061021e92505050565b505050565b61018a6100e8565b5f6101a0610277565b905090565b6101ad6101ec565b6001600160a01b0316330361012c576101298161029e565b5f6101a06101ec565b365f5f375f5f365f845af43d5f5f3e8080156101e8573d5ff35b3d5ffd5b5f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b610227826102f2565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a280511561026b5761018a8282610370565b6102736103e2565b5050565b5f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61020f565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6102c76101ec565b604080516001600160a01b03928316815291841660208301520160405180910390a161012981610401565b806001600160a01b03163b5f0361032c57604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5b80546001600160a01b0319166001600160a01b039290921691909117905550565b60605f5f846001600160a01b03168460405161038c919061058a565b5f60405180830381855af49150503d805f81146103c4576040519150601f19603f3d011682016040523d82523d5f602084013e6103c9565b606091505b50915091506103d9858383610451565b95945050505050565b34156100f85760405163b398979f60e01b815260040160405180910390fd5b6001600160a01b03811661042a57604051633173bdd160e11b81525f6004820152602401610323565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610361034f565b60608261046657610461826104b0565b6104a9565b815115801561047d57506001600160a01b0384163b155b156104a657604051639996b31560e01b81526001600160a01b0385166004820152602401610323565b50805b9392505050565b8051156104bf57805160208201fd5b60405163d6bda27560e01b815260040160405180910390fd5b80356001600160a01b03811681146104ee575f5ffd5b919050565b5f60208284031215610503575f5ffd5b6104a9826104d8565b5f5f5f6040848603121561051e575f5ffd5b610527846104d8565b9250602084013567ffffffffffffffff811115610542575f5ffd5b8401601f81018613610552575f5ffd5b803567ffffffffffffffff811115610568575f5ffd5b866020828401011115610579575f5ffd5b939660209190910195509293505050565b5f82518060208501845e5f92019182525091905056fea26469706673582212204c0881af9b906ca964ce2bc1cc4525350de8dd57658dacb806266290556eb9ae64736f6c634300081d0033", + "storage": { + "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x000000000000000000000000bd3738ab866eff9b0908ef8985d00eeca22da4ef", + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x000000000000000000000000695d85e3437daa1f8f6de97c5aedf75b82dea80a", + "0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300": "0x000000000000000000000000a5fed552f38e11291dd2fc9cb91e2a5f6ae86ed4", + "0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x958f8fec699b51a1249f513eceda5429078000657f74abd1721bba363087af00": "0x00000000000000000000000041fe044f1f71ff69f46f35f41ec93369a0e94733", + "0x0642d7922329a434cf4fd17a3c95eb692c24fd95f9f94d0b55420a5d895f4a00": "0x00000000000000000000000085c8825829fc649694d569bccc374e8382df2d12", + "0x668f09ce856848ead6cb1ddee963f15ef833cea8958030868f867aec84385200": "0x0000000000000000000000000000138800000000000000c80000000000000014", + "0x668f09ce856848ead6cb1ddee963f15ef833cea8958030868f867aec84385201": "0x00000000000000000000000000000000000000000000000000000004a817c800", + "0x668f09ce856848ead6cb1ddee963f15ef833cea8958030868f867aec84385202": "0x000000000000000000000000000000000000000000000000000012309ce54000", + "0x668f09ce856848ead6cb1ddee963f15ef833cea8958030868f867aec84385203": "0x0000000000000000000000000000000000000000000000000000000001c9c380", + "0x668f09ce856848ead6cb1ddee963f15ef833cea8958030868f867aec84385205": "0x0000000000000000000000000000000001f4138801f403e801f403e801f40bb8" + } + }, + "0xfCc314DD5Ad756C6bBA725617438C0d25450a0dE": { + "balance": "0x0", + "nonce": "0x1", + "code": "0x608060405234801561000f575f5ffd5b50600436106100e5575f3560e01c80638da5cb5b11610088578063c4899f0c11610063578063c4899f0c146101cc578063e30c3978146101df578063f2fde38b146101e7578063f94e1867146101fa575f5ffd5b80638da5cb5b14610184578063b5d89627146101a4578063b86444b1146101c4575f5ffd5b80634ebae617116100c35780634ebae61714610138578063715018a61461014d57806377db16691461015557806379ba50971461017c575f5ffd5b806303a9b132146100e957806324408a68146101105780632469d67214610125575b5f5ffd5b6100fd5f5160206113f05f395f51905f5281565b6040519081526020015b60405180910390f35b61011861020d565b604051610107919061104a565b6100fd6101333660046110dc565b6103ed565b61014b61014636600461119d565b610598565b005b61014b6106a0565b7fb58da0dce03316992faea3e12c60705b8ac05a309e27e3bc8421e5b271c9d204546100fd565b61014b6106b3565b61018c6106fb565b6040516001600160a01b039091168152602001610107565b6101b76101b236600461119d565b61072f565b60405161010791906111b4565b6100fd610854565b61014b6101da3660046111c6565b610872565b61018c6109db565b61014b6101f53660046111e7565b610a03565b61014b61020836600461119d565b610a88565b60605f5160206113f05f395f51905f525f6102477fb58da0dce03316992faea3e12c60705b8ac05a309e27e3bc8421e5b271c9d201610c92565b9050806001600160401b03811115610261576102616110ad565b6040519080825280602002602001820160405280156102b557816020015b6102a26040805160608101909152805f8152606060208201525f60409091015290565b81526020019060019003908161027f5790505b5092505f5b818110156103e7575f6102d06001850183610c9b565b5f818152602086905260409081902081516060810190925280549293509091829060ff16600281111561030557610305610fa6565b600281111561031657610316610fa6565b815260200160018201805461032a9061120d565b80601f01602080910402602001604051908101604052809291908181526020018280546103569061120d565b80156103a15780601f10610378576101008083540402835291602001916103a1565b820191905f5260205f20905b81548152906001019060200180831161038457829003601f168201915b5050509183525050600291909101546001600160401b031660209091015285518690849081106103d3576103d3611245565b6020908102919091010152506001016102ba565b50505090565b5f6103f6610cad565b602083511461041857604051630717c3c160e01b815260040160405180910390fd5b82516020808501919091205f8181527fb58da0dce03316992faea3e12c60705b8ac05a309e27e3bc8421e5b271c9d2039092526040909120545f5160206113f05f395f51905f529190819060ff1615610490576040516338a14a4160e01b815260040161048791815260200190565b60405180910390fd5b50600482018054905f6104a28361126d565b909155506040805160608101909152909350806001815260208082018890526001600160401b0387166040928301525f868152908590522081518154829060ff191660018360028111156104f8576104f8610fa6565b02179055506020820151600182019061051190826112d1565b50604091820151600291909101805467ffffffffffffffff19166001600160401b039092169190911790555f82815260038401602052819020805460ff191660011790555183907fea20c1f9de86768933c1d4eff47ce667e26f886c4877d1bedc253e42b2f04a7c90610587908790899061138b565b60405180910390a250505b92915050565b6105a0610cad565b5f5f5160206113f05f395f51905f525f838152602082905260408120919250815460ff1660028111156105d5576105d5610fa6565b141583906105f9576040516355b54a8d60e01b815260040161048791815260200190565b506001815460ff16600281111561061257610612610fa6565b8254859260ff909116911461063c57604051635c12665560e01b81526004016104879291906113b4565b5050805460ff191660021781556106566001830184610cdf565b5060028101546040516001600160401b03909116815283907fbc5136437746415b39af863272d0ca406634cf14bb7c3e5fa03855781add87e59060200160405180910390a2505050565b6106a8610cad565b6106b15f610cea565b565b33806106bd6109db565b6001600160a01b0316146106ef5760405163118cdaa760e01b81526001600160a01b0382166004820152602401610487565b6106f881610cea565b50565b5f807f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c1993005b546001600160a01b031692915050565b6107526040805160608101909152805f8152606060208201525f60409091015290565b5f8281525f5160206113f05f395f51905f5260208190526040918290208251606081019093528054919291829060ff16600281111561079357610793610fa6565b60028111156107a4576107a4610fa6565b81526020016001820180546107b89061120d565b80601f01602080910402602001604051908101604052809291908181526020018280546107e49061120d565b801561082f5780601f106108065761010080835404028352916020019161082f565b820191905f5260205f20905b81548152906001019060200180831161081257829003601f168201915b5050509183525050600291909101546001600160401b03166020909101529392505050565b5f5f5160206113f05f395f51905f5261086c81610d26565b91505090565b61087a610cad565b5f5f5160206113f05f395f51905f525f848152602082905260408120919250815460ff1660028111156108af576108af610fa6565b141584906108d3576040516355b54a8d60e01b815260040161048791815260200190565b5060028101546001600160401b03908116908416810361090657604051632bf0186d60e21b815260040160405180910390fd5b6002825460ff16600281111561091e5761091e610fa6565b14801561093357505f816001600160401b0316115b801561094657506001600160401b038416155b1561097457600161095684610d26565b116109745760405163aff8365b60e01b815260040160405180910390fd5b60028201805467ffffffffffffffff19166001600160401b03868116918217909255604080519284168352602083019190915286917f4a7e1cc2e075be9a3c913bf00ec8ff10f6630f23433f65eee31e20ef69110ae4910160405180910390a25050505050565b5f807f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0061071f565b610a0b610cad565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0080546001600160a01b0319166001600160a01b0383169081178255610a4f6106fb565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a35050565b610a90610cad565b5f5f5160206113f05f395f51905f525f838152602082905260408120919250815460ff166002811115610ac557610ac5610fa6565b14158390610ae9576040516355b54a8d60e01b815260040161048791815260200190565b506002815460ff166002811115610b0257610b02610fa6565b148015610b1b575060028101546001600160401b031615155b15610b49576001610b2b83610d26565b11610b495760405163aff8365b60e01b815260040160405180910390fd5b5f816001018054610b599061120d565b80601f0160208091040260200160405190810160405280929190818152602001828054610b859061120d565b8015610bd05780601f10610ba757610100808354040283529160200191610bd0565b820191905f5260205f20905b815481529060010190602001808311610bb357829003601f168201915b5050835160208501206002870154949550936001600160401b03169250610bfd9150506001860187610d8c565b505f868152602086905260408120805460ff1916815590610c216001830182610f5c565b50600201805467ffffffffffffffff191690555f828152600386016020908152604091829020805460ff1916905590516001600160401b038316815287917f70d022b699831c183688ed9d9d1a1cbf586698b506eaacfd5195a4e3738d285b910160405180910390a2505050505050565b5f610592825490565b5f610ca68383610d97565b9392505050565b33610cb66106fb565b6001600160a01b0316146106b15760405163118cdaa760e01b8152336004820152602401610487565b5f610ca68383610dbd565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0080546001600160a01b0319168155610d2282610e09565b5050565b5f5f610d3483600101610c92565b90505f5b81811015610d85575f610d4e6001860183610c9b565b5f818152602087905260409020600201549091506001600160401b031615610d7c57610d798461126d565b93505b50600101610d38565b5050919050565b5f610ca68383610e79565b5f825f018281548110610dac57610dac611245565b905f5260205f200154905092915050565b5f818152600183016020526040812054610e0257508154600181810184555f848152602080822090930184905584548482528286019093526040902091909155610592565b505f610592565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b5f8181526001830160205260408120548015610f53575f610e9b6001836113c8565b85549091505f90610eae906001906113c8565b9050808214610f0d575f865f018281548110610ecc57610ecc611245565b905f5260205f200154905080875f018481548110610eec57610eec611245565b5f918252602080832090910192909255918252600188019052604090208390555b8554869080610f1e57610f1e6113db565b600190038181905f5260205f20015f90559055856001015f8681526020019081526020015f205f905560019350505050610592565b5f915050610592565b508054610f689061120d565b5f825580601f10610f77575050565b601f0160209004905f5260205f20908101906106f891905b80821115610fa2575f8155600101610f8f565b5090565b634e487b7160e01b5f52602160045260245ffd5b60038110610fd657634e487b7160e01b5f52602160045260245ffd5b9052565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b611013828251610fba565b5f60208201516060602085015261102d6060850182610fda565b6040938401516001600160401b0316949093019390935250919050565b5f602082016020835280845180835260408501915060408160051b8601019250602086015f5b828110156110a157603f1987860301845261108c858351611008565b94506020938401939190910190600101611070565b50929695505050505050565b634e487b7160e01b5f52604160045260245ffd5b80356001600160401b03811681146110d7575f5ffd5b919050565b5f5f604083850312156110ed575f5ffd5b82356001600160401b03811115611102575f5ffd5b8301601f81018513611112575f5ffd5b80356001600160401b0381111561112b5761112b6110ad565b604051601f8201601f19908116603f011681016001600160401b0381118282101715611159576111596110ad565b604052818152828201602001871015611170575f5ffd5b816020840160208301375f60208383010152809450505050611194602084016110c1565b90509250929050565b5f602082840312156111ad575f5ffd5b5035919050565b602081525f610ca66020830184611008565b5f5f604083850312156111d7575f5ffd5b82359150611194602084016110c1565b5f602082840312156111f7575f5ffd5b81356001600160a01b0381168114610ca6575f5ffd5b600181811c9082168061122157607f821691505b60208210810361123f57634e487b7160e01b5f52602260045260245ffd5b50919050565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b5f6001820161127e5761127e611259565b5060010190565b601f8211156112cc57805f5260205f20601f840160051c810160208510156112aa5750805b601f840160051c820191505b818110156112c9575f81556001016112b6565b50505b505050565b81516001600160401b038111156112ea576112ea6110ad565b6112fe816112f8845461120d565b84611285565b6020601f821160018114611330575f83156113195750848201515b5f19600385901b1c1916600184901b1784556112c9565b5f84815260208120601f198516915b8281101561135f578785015182556020948501946001909201910161133f565b508482101561137c57868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b6001600160401b0383168152604060208201525f6113ac6040830184610fda565b949350505050565b82815260408101610ca66020830184610fba565b8181038181111561059257610592611259565b634e487b7160e01b5f52603160045260245ffdfeb58da0dce03316992faea3e12c60705b8ac05a309e27e3bc8421e5b271c9d200a2646970667358221220e5d087314472ac48a65ba1a9c38dbf61b71eb3c2cc0f9b03ade2ac62ec97aecc64736f6c634300081d0033" + }, + "0x3600000000000000000000000000000000000002": { + "balance": "0x0", + "nonce": "0x1", + "code": "0x608060405260043610610049575f3560e01c80633659cfe6146100535780634f1ef286146100725780635c60da1b146100855780638f283970146100b5578063f851a440146100d4575b6100516100e8565b005b34801561005e575f5ffd5b5061005161006d3660046104f3565b6100fa565b61005161008036600461050c565b610134565b348015610090575f5ffd5b50610099610197565b6040516001600160a01b03909116815260200160405180910390f35b3480156100c0575f5ffd5b506100516100cf3660046104f3565b6101a5565b3480156100df575f5ffd5b506100996101c5565b6100f86100f3610197565b6101ce565b565b6101026101ec565b6001600160a01b0316330361012c576101298160405180602001604052805f81525061021e565b50565b6101296100e8565b61013c6101ec565b6001600160a01b0316330361018f5761018a8383838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061021e92505050565b505050565b61018a6100e8565b5f6101a0610277565b905090565b6101ad6101ec565b6001600160a01b0316330361012c576101298161029e565b5f6101a06101ec565b365f5f375f5f365f845af43d5f5f3e8080156101e8573d5ff35b3d5ffd5b5f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b610227826102f2565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a280511561026b5761018a8282610370565b6102736103e2565b5050565b5f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61020f565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6102c76101ec565b604080516001600160a01b03928316815291841660208301520160405180910390a161012981610401565b806001600160a01b03163b5f0361032c57604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5b80546001600160a01b0319166001600160a01b039290921691909117905550565b60605f5f846001600160a01b03168460405161038c919061058a565b5f60405180830381855af49150503d805f81146103c4576040519150601f19603f3d011682016040523d82523d5f602084013e6103c9565b606091505b50915091506103d9858383610451565b95945050505050565b34156100f85760405163b398979f60e01b815260040160405180910390fd5b6001600160a01b03811661042a57604051633173bdd160e11b81525f6004820152602401610323565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610361034f565b60608261046657610461826104b0565b6104a9565b815115801561047d57506001600160a01b0384163b155b156104a657604051639996b31560e01b81526001600160a01b0385166004820152602401610323565b50805b9392505050565b8051156104bf57805160208201fd5b60405163d6bda27560e01b815260040160405180910390fd5b80356001600160a01b03811681146104ee575f5ffd5b919050565b5f60208284031215610503575f5ffd5b6104a9826104d8565b5f5f5f6040848603121561051e575f5ffd5b610527846104d8565b9250602084013567ffffffffffffffff811115610542575f5ffd5b8401601f81018613610552575f5ffd5b803567ffffffffffffffff811115610568575f5ffd5b866020828401011115610579575f5ffd5b939660209190910195509293505050565b5f82518060208501845e5f92019182525091905056fea26469706673582212204c0881af9b906ca964ce2bc1cc4525350de8dd57658dacb806266290556eb9ae64736f6c634300081d0033", + "storage": { + "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x00000000000000000000000020db45729bc366833107524804d16ceb44e946c6", + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x000000000000000000000000fcc314dd5ad756c6bba725617438c0d25450a0de", + "0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300": "0x0000000000000000000000003600000000000000000000000000000000000003", + "0x0499f4c2de309a54da703ff88d2d815fa145b3fb32cf77b2528c8033db8fd7ee": "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x0499f4c2de309a54da703ff88d2d815fa145b3fb32cf77b2528c8033db8fd7ef": "0x0000000000000000000000000000000000000000000000000000000000000041", + "0xe76fb05174802623d7f5b79138467ede24c07a48fb794feb0f195f82c29e25f6": "0x12a68aa84643efd6fb79b4097ef9b5ae1bef849c9c75e8eba2b977e09d227c35", + "0x0499f4c2de309a54da703ff88d2d815fa145b3fb32cf77b2528c8033db8fd7f0": "0x00000000000000000000000000000000000000000000000000000000000007d0", + "0x2559e4fd6bae46fd4a933fbdfc2024f30e3f30caf9b658f266112e99c269472e": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x3c22fd2722f4c68bee66b0771765c1374bf0b4f04712a4b14dee7a423e372484": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x69b629ad7a5ed64ea24217eca51dd1c81c56db4820bffd61bdc454fa86c45a37": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x799a8c5a05520e2bd7f998b6bef5c37455a07d2f4637750d18e8585f32667020": "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x799a8c5a05520e2bd7f998b6bef5c37455a07d2f4637750d18e8585f32667021": "0x0000000000000000000000000000000000000000000000000000000000000041", + "0x88471ae418c3e7b280a001c3ca67ebba02159f936da01ae6a2e84cabdddd1ff9": "0xae89fa207bb808e8a34e05b5892a16625e8b66b92e597f8866aa586b487dfbfe", + "0x799a8c5a05520e2bd7f998b6bef5c37455a07d2f4637750d18e8585f32667022": "0x00000000000000000000000000000000000000000000000000000000000007d0", + "0x2559e4fd6bae46fd4a933fbdfc2024f30e3f30caf9b658f266112e99c269472f": "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x095131cda7b4097ad9172b46969f424386afbffb0fe58d634a10ceead00fe90f": "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x3aef2944b926031ba11588b90de1c1af67c2876a1083cfaadcc46dc3a23f394b": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x5823124c2578a0659c1d18b6eac7cd5d39d30d9e0a439c0dc79350c911078892": "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x5823124c2578a0659c1d18b6eac7cd5d39d30d9e0a439c0dc79350c911078893": "0x0000000000000000000000000000000000000000000000000000000000000041", + "0xe4bbd97d770f505bda53f50c8dce4c86cb0dc400a1b5c97a9aacf7305b0907af": "0xd5c96c5d0daf70f25fb5bfc20c0747b2ce0408cc6e54b8494f3a62b61ffca7cd", + "0x5823124c2578a0659c1d18b6eac7cd5d39d30d9e0a439c0dc79350c911078894": "0x00000000000000000000000000000000000000000000000000000000000007d0", + "0x2559e4fd6bae46fd4a933fbdfc2024f30e3f30caf9b658f266112e99c2694730": "0x0000000000000000000000000000000000000000000000000000000000000003", + "0xe0ed4630bad2f511c8f295d4c67204cad855df4001547c1a63b7ba76c4642997": "0x0000000000000000000000000000000000000000000000000000000000000003", + "0x49c0e421f0d792e126c0e387181e587935dc85ee33a0e4ed906fd76b2f9693ea": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x57b2761a28897dbfc1a24bc792964a22adcbfdba40250a97c45c81e485bce164": "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x57b2761a28897dbfc1a24bc792964a22adcbfdba40250a97c45c81e485bce165": "0x0000000000000000000000000000000000000000000000000000000000000041", + "0x7561b913640321094a9a7cd9b03d1d14d3f4896580bd453b53749e3a0a7f8f2f": "0xed259c1abb397823afb31cb36089547367221242de3057df803a0b091e9a1c8d", + "0x57b2761a28897dbfc1a24bc792964a22adcbfdba40250a97c45c81e485bce166": "0x00000000000000000000000000000000000000000000000000000000000007d0", + "0x2559e4fd6bae46fd4a933fbdfc2024f30e3f30caf9b658f266112e99c2694731": "0x0000000000000000000000000000000000000000000000000000000000000004", + "0x2b469f95fec2239186437567ca1ebd5a0dcb6f582da84f4a3e535a89c60299ce": "0x0000000000000000000000000000000000000000000000000000000000000004", + "0xd90b9770d39268440db589a25d9a3728ca5f5f41cd9a6f5d3bb3c9c0233e6014": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x5217b172c9c68060ac3f2cea97f04d4b6a19ffc38de72bde7614d108da793171": "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x5217b172c9c68060ac3f2cea97f04d4b6a19ffc38de72bde7614d108da793172": "0x0000000000000000000000000000000000000000000000000000000000000041", + "0xb81fd3b52b30fb1cee951216c9225dedbf87cab17122027c6b61d64229d70ce1": "0xf43562191fe65ba250ac32d4602efb664586bf4e8150e4c80cf2dbf64650d2a9", + "0x5217b172c9c68060ac3f2cea97f04d4b6a19ffc38de72bde7614d108da793173": "0x00000000000000000000000000000000000000000000000000000000000007d0", + "0x2559e4fd6bae46fd4a933fbdfc2024f30e3f30caf9b658f266112e99c2694732": "0x0000000000000000000000000000000000000000000000000000000000000005", + "0x1f0160c61ae74490159fc3ec6e25ec82a084ca7c5a75f5a3e2fb22830c3d10db": "0x0000000000000000000000000000000000000000000000000000000000000005", + "0x762650592f09e61c6bfeb64d440a3ee1b1cca58fc3e4b2ede70cf75b4603c6ed": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x036c38b635f62c71be584c9bd1b40365acf30671f9f753f98080692c1a7880c9": "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x036c38b635f62c71be584c9bd1b40365acf30671f9f753f98080692c1a7880ca": "0x0000000000000000000000000000000000000000000000000000000000000041", + "0xcf73efe09ec4d087234a83ec0d87436622fc85763f310d044eb6fa2053eb09e9": "0xf927d6659307a219c3a59b51a31c29412b6d2b1c1717697a0ae0d5c9999ba740", + "0x036c38b635f62c71be584c9bd1b40365acf30671f9f753f98080692c1a7880cb": "0x00000000000000000000000000000000000000000000000000000000000007d0", + "0x2559e4fd6bae46fd4a933fbdfc2024f30e3f30caf9b658f266112e99c2694733": "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x83618a90b7985f85f10fb07aeb429c9df200b04d98de4740e2108171b60b9ba4": "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x0a50deff56ce24acaf1ca5cb14126c2427e9f4cab71cc938011fb80d615013ed": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x7d4d1af396c360acccda295ce959178f0a4741206f750526f43f024051b37c9a": "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x7d4d1af396c360acccda295ce959178f0a4741206f750526f43f024051b37c9b": "0x0000000000000000000000000000000000000000000000000000000000000041", + "0xc5ecd545e569aa1fc231f287db5e889a0a7f8bf1c6040f6219e3417aa6cd3a69": "0xda4f31b2d26acac4143d67f6b0ebc5a7b9fa829e4b63970f28130bff361316c9", + "0x7d4d1af396c360acccda295ce959178f0a4741206f750526f43f024051b37c9c": "0x00000000000000000000000000000000000000000000000000000000000007d0", + "0x2559e4fd6bae46fd4a933fbdfc2024f30e3f30caf9b658f266112e99c2694734": "0x0000000000000000000000000000000000000000000000000000000000000007", + "0xaa91b6cbf3a62bac4363f28c9e7a17ed03fa172b9e36d4d699be2e0ba2104a29": "0x0000000000000000000000000000000000000000000000000000000000000007", + "0x9f29139c8b3ecf5fbec2c769003875b0502f8bd2e08b6cb11dfeb19d50b8a69e": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x6ebdd2e8f28a6177d4fd7d1440de91296636b8ba71407c25ccc02e95a11d5959": "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x6ebdd2e8f28a6177d4fd7d1440de91296636b8ba71407c25ccc02e95a11d595a": "0x0000000000000000000000000000000000000000000000000000000000000041", + "0xdc3456907dbce854efbe5fab4c6dd881a300a762d86e448ce767c600db77d6dc": "0x7bd0ca04c9423d39c25ee7747d2a5977acf19ff9d7f835f8dc1a746c83e71070", + "0x6ebdd2e8f28a6177d4fd7d1440de91296636b8ba71407c25ccc02e95a11d595b": "0x00000000000000000000000000000000000000000000000000000000000007d0", + "0x2559e4fd6bae46fd4a933fbdfc2024f30e3f30caf9b658f266112e99c2694735": "0x0000000000000000000000000000000000000000000000000000000000000008", + "0xa40cb6afff5604565ae4febfee9e9b6d28aac41cc6b469b086a5c8c372146e04": "0x0000000000000000000000000000000000000000000000000000000000000008", + "0xe48ec2b679bea5ea62eeb32cdf010d3534f751b0981989eb6f5135ace326e0d5": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x1036a022fb9dcf8f9f9ca34ad6b12868abc3041e29322d43806d11dfd2808b54": "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x1036a022fb9dcf8f9f9ca34ad6b12868abc3041e29322d43806d11dfd2808b55": "0x0000000000000000000000000000000000000000000000000000000000000041", + "0x9aa5d26f5fcc5e1e798ab3b392cbd29d19e0e696108ce71f798ed565faf3c83b": "0xc3fbb251a140db946283233a3fe5762a05a8305e2a8eaefc41d5df2820e071fc", + "0x1036a022fb9dcf8f9f9ca34ad6b12868abc3041e29322d43806d11dfd2808b56": "0x00000000000000000000000000000000000000000000000000000000000007d0", + "0x2559e4fd6bae46fd4a933fbdfc2024f30e3f30caf9b658f266112e99c2694736": "0x0000000000000000000000000000000000000000000000000000000000000009", + "0x2ce5e9b7f4d6cc2943d8914ec8fc903f2be8b733f74fca6e808d595ab7c333a4": "0x0000000000000000000000000000000000000000000000000000000000000009", + "0xcce14a1bd7d43386fe00a9f61cc79c3cb7b3416a9a5d35d2046bb8a4f9beac9e": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x9bdfb94158fad0c6f7b28212d5c61394304fac97217ff5b47fb1264fb4d3d263": "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x9bdfb94158fad0c6f7b28212d5c61394304fac97217ff5b47fb1264fb4d3d264": "0x0000000000000000000000000000000000000000000000000000000000000041", + "0xfdb1936dbf9415dbe0461d4456dcf559f839ed0cf1e9580a9f8264ef6427550b": "0xdeb9620fb017834b90e4b0cf66fdc7fd06b3edb68f0147938e59a9fa7db83380", + "0x9bdfb94158fad0c6f7b28212d5c61394304fac97217ff5b47fb1264fb4d3d265": "0x00000000000000000000000000000000000000000000000000000000000007d0", + "0x2559e4fd6bae46fd4a933fbdfc2024f30e3f30caf9b658f266112e99c2694737": "0x000000000000000000000000000000000000000000000000000000000000000a", + "0xdc0be93e54ba6a7b6fe6e83b098eef5a9f0c78c7a0d0527364d7e5fc618e3e55": "0x000000000000000000000000000000000000000000000000000000000000000a", + "0x2032597be0a44488174621966df442c8c77e5c686ec742a4600cb0cd295f8561": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x29c5c54da8fa03ee409f98f51fe246e9e0fab30d34a5258186bfe0c2feabaaa3": "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x29c5c54da8fa03ee409f98f51fe246e9e0fab30d34a5258186bfe0c2feabaaa4": "0x0000000000000000000000000000000000000000000000000000000000000041", + "0x069f8539631136694cc8b55b72f751fc9b09894db1e54fc694ed567b3f9f6eab": "0x254192a339b277911fae7dac3a52ed4b0196bb90670fb3b893f521849086fc8d", + "0x29c5c54da8fa03ee409f98f51fe246e9e0fab30d34a5258186bfe0c2feabaaa5": "0x00000000000000000000000000000000000000000000000000000000000007d0", + "0x2559e4fd6bae46fd4a933fbdfc2024f30e3f30caf9b658f266112e99c2694738": "0x000000000000000000000000000000000000000000000000000000000000000b", + "0x0180ad68974e030dadf48dba740cc80485e9121ea87ebc1bf162543accaf3904": "0x000000000000000000000000000000000000000000000000000000000000000b", + "0x07e80b26f488a70b4989560a271056bd9762fb9fabea5a36d0af4e627b669688": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0xb58da0dce03316992faea3e12c60705b8ac05a309e27e3bc8421e5b271c9d201": "0x000000000000000000000000000000000000000000000000000000000000000b", + "0xb58da0dce03316992faea3e12c60705b8ac05a309e27e3bc8421e5b271c9d204": "0x000000000000000000000000000000000000000000000000000000000000000c" + } + }, + "0x98C74F7eF54BA22be94415d7C967B352c6a42D6d": { + "balance": "0x0", + "nonce": "0x1", + "code": "0x608060405234801561000f575f5ffd5b50600436106101dc575f3560e01c806379ba509711610109578063d2c20cb31161009e578063ead130111161006e578063ead130111461042f578063ec4a44d714610443578063f2fde38b14610457578063f6a74ed71461046a575f5ffd5b8063d2c20cb3146103ee578063d773a74114610401578063da3003f914610414578063e30c397814610427575f5ffd5b80638da5cb5b116100d95780638da5cb5b146103955780639fd0506d1461039d578063aa5ecb52146103a5578063b429afeb146103b8575f5ffd5b806379ba5097146103765780637f77403d1461037e5780637ffc51fd146103865780638456cb591461038d575f5ffd5b8063485cc9551161017f5780635c975abb1161014f5780635c975abb1461031c5780635e5feee61461033a578063602a9eee1461034d578063715018a61461036e575f5ffd5b8063485cc955146102b85780634cb4850a146102cb578063554bab3c146102de5780635acac2b6146102f1575f5ffd5b8063263a3402116101ba578063263a34021461028b5780632eb5c6581461029557806337ec36d1146102a85780633f4ba83a146102b0575f5ffd5b8063048544a4146101e057806306433b1b1461022c5780631904bb2e1461026b575b5f5ffd5b6102176101ee366004611514565b6001600160a01b03165f9081525f5160206118f35f395f51905f52602052604090205460ff1690565b60405190151581526020015b60405180910390f35b6102537f000000000000000000000000360000000000000000000000000000000000000281565b6040516001600160a01b039091168152602001610223565b61027e610279366004611514565b61047d565b604051610223919061155b565b610293610558565b005b6102936102a33660046115d1565b610603565b6102936106e7565b610293610787565b6102936102c6366004611606565b6107e5565b6102936102d9366004611637565b610971565b6102936102ec366004611514565b610a92565b6103046102ff366004611514565b610b16565b6040516001600160401b039091168152602001610223565b5f5160206118d35f395f51905f5254600160a01b900460ff16610217565b610293610348366004611514565b610b84565b61036061035b3660046116e4565b610c5f565b604051908152602001610223565b610293610d06565b610293610d19565b610293610d5e565b6103045f81565b610293610ddc565b610253610e3e565b610253610e72565b6102936103b3366004611514565b610e87565b6102176103c6366004611514565b6001600160a01b03165f9081525f5160206118b35f395f51905f526020526040902054151590565b6102936103fc36600461175d565b610f46565b61036061040f366004611514565b611055565b610293610422366004611514565b6110a2565b610253611165565b6103605f5160206118b35f395f51905f5281565b6103605f5160206118f35f395f51905f5281565b610293610465366004611514565b61118d565b610293610478366004611514565b611212565b6104a06040805160608101909152805f8152606060208201525f60409091015290565b5f5f5160206118b35f395f51905f526001600160a01b038481165f908152602083905260409081902054905163b5d8962760e01b815260048101829052929350917f00000000000000000000000036000000000000000000000000000000000000029091169063b5d89627906024015f60405180830381865afa158015610529573d5f5f3e3d5ffd5b505050506040513d5f823e601f3d908101601f1916820160405261055091908101906117a5565b949350505050565b6105606112e4565b610568611321565b335f9081525f5160206118b35f395f51905f526020819052604091829020549151634ebae61760e01b8152600481018390529091907f00000000000000000000000036000000000000000000000000000000000000026001600160a01b031690634ebae617906024015b5f604051808303815f87803b1580156105e9575f5ffd5b505af11580156105fb573d5f5f3e3d5ffd5b505050505050565b61060b611359565b6001600160a01b038216610632576040516371ab47bb60e11b815260040160405180910390fd5b6001600160a01b0382165f9081525f5160206118b35f395f51905f5260208190526040822054909103610678576040516308c2ec5b60e41b815260040160405180910390fd5b6001600160a01b0383165f818152600183016020908152604091829020805467ffffffffffffffff19166001600160401b03871690811790915591519182527fcf03087b939040458ea3c3a6791532c7c4432baf98e770abc930afc9cda81d87910160405180910390a2505050565b6106ef611359565b7f00000000000000000000000036000000000000000000000000000000000000026001600160a01b03166379ba50976040518163ffffffff1660e01b81526004015f604051808303815f87803b158015610747575f5ffd5b505af1158015610759573d5f5f3e3d5ffd5b50506040517f54b70ab40993761a2b0e96f42fdf47939a56830ede1325df83e05ed33e3e8fd592505f9150a1565b61078f61138b565b5f5160206118d35f395f51905f528054600160a01b900460ff16156107e257805460ff60a01b191681556040517f7805862f689e2f13df9f062ff482ad3ad112aca9e0847911ed832e158c525b33905f90a15b50565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff1615906001600160401b03165f811580156108295750825b90505f826001600160401b031660011480156108445750303b155b905081158015610852575080155b156108705760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561089a57845460ff60401b1916600160401b1785555b6001600160a01b0387166108c1576040516342cad95760e01b815260040160405180910390fd5b6001600160a01b0386166108e85760405163a74995ab60e01b815260040160405180910390fd5b6108f06113c3565b6108f9876113cb565b5f5f5160206118d35f395f51905f5280546001600160a01b0319166001600160a01b03891617905550831561096857845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50505050505050565b6109796112e4565b610981611321565b335f9081525f5160206118b35f395f51905f5260208181526040808420547fe90ec3add3e251bfbe914c9e482b511e91a3b187718c1dc10223f64a8a644a01909252909220549091906001600160401b03908116908416811015610a0857604051635af79e0d60e11b81526001600160401b03821660048201526024015b60405180910390fd5b60405163312267c360e21b8152600481018390526001600160401b03851660248201527f00000000000000000000000036000000000000000000000000000000000000026001600160a01b03169063c4899f0c906044015f604051808303815f87803b158015610a76575f5ffd5b505af1158015610a88573d5f5f3e3d5ffd5b5050505050505050565b610a9a611359565b6001600160a01b038116610ac15760405163a74995ab60e01b815260040160405180910390fd5b5f5160206118d35f395f51905f5280546001600160a01b0319166001600160a01b03831690811782556040517fb80482a293ca2e013eda8683c9bd7fc8347cfdaeea5ede58cba46df502c2a604905f90a25050565b6001600160a01b0381165f9081525f5160206118b35f395f51905f52602081905260408220548203610b5b576040516308c2ec5b60e41b815260040160405180910390fd5b6001600160a01b039092165f90815260019092016020525060409020546001600160401b031690565b610b8c611359565b6001600160a01b038116610bb3576040516342cad95760e01b815260040160405180910390fd5b60405163f2fde38b60e01b81526001600160a01b0382811660048301527f0000000000000000000000003600000000000000000000000000000000000002169063f2fde38b906024015f604051808303815f87803b158015610c13575f5ffd5b505af1158015610c25573d5f5f3e3d5ffd5b50506040516001600160a01b03841692507ff8e2a8d4b93bd709cc57c9f21b79403c532f960ef18f77d0c23403cae0de78d991505f90a250565b5f610c68611407565b610c70611321565b604051631234eb3960e11b81526001600160a01b037f00000000000000000000000036000000000000000000000000000000000000021690632469d67290610cbe9085905f90600401611871565b6020604051808303815f875af1158015610cda573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610cfe919061189b565b90505b919050565b610d0e611359565b610d175f6113cb565b565b3380610d23611165565b6001600160a01b031614610d555760405163118cdaa760e01b81526001600160a01b03821660048201526024016109ff565b6107e2816113cb565b610d666112e4565b610d6e611321565b335f9081525f5160206118b35f395f51905f52602081905260409182902054915163f94e186760e01b8152600481018390529091907f00000000000000000000000036000000000000000000000000000000000000026001600160a01b03169063f94e1867906024016105d2565b610de461138b565b5f5160206118d35f395f51905f528054600160a01b900460ff166107e257805460ff60a01b1916600160a01b1781556040517f6985a02210a168e66602d3235cb6db0e70f92b3ba4d376a33c0f3d9434bff625905f90a150565b5f807f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c1993005b546001600160a01b031692915050565b5f805f5160206118d35f395f51905f52610e62565b610e8f611359565b6001600160a01b038116610eb657604051634a1653dd60e11b815260040160405180910390fd5b6001600160a01b0381165f9081525f5160206118f35f395f51905f52602081905260409091205460ff16610efd576040516335c0bed760e21b815260040160405180910390fd5b6001600160a01b0382165f81815260208390526040808220805460ff19169055517f195d741377fcd6e23556d115769d0b5f54decc24349a916f6de7371077fe7fd99190a25050565b610f4e611359565b815f03610f6e576040516309e56b6960e21b815260040160405180910390fd5b6001600160a01b038316610f95576040516371ab47bb60e11b815260040160405180910390fd5b6001600160a01b0383165f9081525f5160206118b35f395f51905f52602081905260409091205415610fda57604051633dd8918760e01b815260040160405180910390fd5b6001600160a01b0384165f8181526020838152604080832087905560018501825291829020805467ffffffffffffffff19166001600160401b03871690811790915591519182528592917fa1f041d5612451bd93cf2d0b47fbfde4782731f7612386774a8ab2b4caf57955910160405180910390a350505050565b6001600160a01b0381165f9081525f5160206118b35f395f51905f526020819052604082205480830361109b576040516308c2ec5b60e41b815260040160405180910390fd5b9392505050565b6110aa611359565b6001600160a01b0381166110d157604051634a1653dd60e11b815260040160405180910390fd5b6001600160a01b0381165f9081525f5160206118f35f395f51905f52602081905260409091205460ff16156111195760405163348917ed60e01b815260040160405180910390fd5b6001600160a01b0382165f81815260208390526040808220805460ff19166001179055517fd25faf73acccddf9a7ac429be872d2109cd7bd0c2370658b7c019518f3159d8e9190a25050565b5f807f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c00610e62565b611195611359565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0080546001600160a01b0319166001600160a01b03831690811782556111d9610e3e565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a35050565b61121a611359565b6001600160a01b038116611241576040516371ab47bb60e11b815260040160405180910390fd5b6001600160a01b0381165f9081525f5160206118b35f395f51905f5260208190526040822054909103611287576040516308c2ec5b60e41b815260040160405180910390fd5b6001600160a01b0382165f8181526020838152604080832083905560018501909152808220805467ffffffffffffffff19169055517f33d83959be2573f5453b12eb9d43b3499bc57d96bd2f067ba44803c859e811139190a25050565b335f9081525f5160206118b35f395f51905f52602081905260408220549091036107e257604051630e971e6360e11b815260040160405180910390fd5b5f5160206118d35f395f51905f528054600160a01b900460ff16156107e25760405163ab35696f60e01b815260040160405180910390fd5b33611362610e3e565b6001600160a01b031614610d175760405163118cdaa760e01b81523360048201526024016109ff565b5f5160206118d35f395f51905f5280546001600160a01b031633146107e25760405163daeefd6560e01b815260040160405180910390fd5b610d17611445565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0080546001600160a01b03191681556114038261148e565b5050565b335f9081525f5160206118f35f395f51905f52602081905260409091205460ff166107e25760405163eb4086bb60e01b815260040160405180910390fd5b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff16610d1757604051631afcd79f60e31b815260040160405180910390fd5b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b80356001600160a01b0381168114610d01575f5ffd5b5f60208284031215611524575f5ffd5b61109b826114fe565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f82516003811061157e57634e487b7160e01b5f52602160045260245ffd5b8060208401525060208301516060604084015261159e608084018261152d565b90506001600160401b0360408501511660608401528091505092915050565b6001600160401b03811681146107e2575f5ffd5b5f5f604083850312156115e2575f5ffd5b6115eb836114fe565b915060208301356115fb816115bd565b809150509250929050565b5f5f60408385031215611617575f5ffd5b611620836114fe565b915061162e602084016114fe565b90509250929050565b5f60208284031215611647575f5ffd5b813561109b816115bd565b634e487b7160e01b5f52604160045260245ffd5b604051606081016001600160401b038111828210171561168857611688611652565b60405290565b604051601f8201601f191681016001600160401b03811182821017156116b6576116b6611652565b604052919050565b5f6001600160401b038211156116d6576116d6611652565b50601f01601f191660200190565b5f602082840312156116f4575f5ffd5b81356001600160401b03811115611709575f5ffd5b8201601f81018413611719575f5ffd5b803561172c611727826116be565b61168e565b818152856020838501011115611740575f5ffd5b816020840160208301375f91810160200191909152949350505050565b5f5f5f6060848603121561176f575f5ffd5b611778846114fe565b925060208401359150604084013561178f816115bd565b809150509250925092565b8051610d01816115bd565b5f602082840312156117b5575f5ffd5b81516001600160401b038111156117ca575f5ffd5b8201606081850312156117db575f5ffd5b6117e3611666565b8151600381106117f1575f5ffd5b815260208201516001600160401b0381111561180b575f5ffd5b8201601f8101861361181b575f5ffd5b8051611829611727826116be565b81815287602083850101111561183d575f5ffd5b8160208401602083015e5f602083830101528060208501525050506118646040830161179a565b6040820152949350505050565b604081525f611883604083018561152d565b90506001600160401b03831660208301529392505050565b5f602082840312156118ab575f5ffd5b505191905056fee90ec3add3e251bfbe914c9e482b511e91a3b187718c1dc10223f64a8a644a000642d7922329a434cf4fd17a3c95eb692c24fd95f9f94d0b55420a5d895f4a0036c39aeb5f498ae36546fc14573b003abf87227a5a2df6caec16ee566f1ad800a2646970667358221220642f9a6a9dcca31815d2c62a7e16a0101b8be0fd9834c20c55b2b5bfc4847b8f64736f6c634300081d0033" + }, + "0x3600000000000000000000000000000000000003": { + "balance": "0x0", + "nonce": "0x1", + "code": "0x608060405260043610610049575f3560e01c80633659cfe6146100535780634f1ef286146100725780635c60da1b146100855780638f283970146100b5578063f851a440146100d4575b6100516100e8565b005b34801561005e575f5ffd5b5061005161006d3660046104f3565b6100fa565b61005161008036600461050c565b610134565b348015610090575f5ffd5b50610099610197565b6040516001600160a01b03909116815260200160405180910390f35b3480156100c0575f5ffd5b506100516100cf3660046104f3565b6101a5565b3480156100df575f5ffd5b506100996101c5565b6100f86100f3610197565b6101ce565b565b6101026101ec565b6001600160a01b0316330361012c576101298160405180602001604052805f81525061021e565b50565b6101296100e8565b61013c6101ec565b6001600160a01b0316330361018f5761018a8383838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061021e92505050565b505050565b61018a6100e8565b5f6101a0610277565b905090565b6101ad6101ec565b6001600160a01b0316330361012c576101298161029e565b5f6101a06101ec565b365f5f375f5f365f845af43d5f5f3e8080156101e8573d5ff35b3d5ffd5b5f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b610227826102f2565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a280511561026b5761018a8282610370565b6102736103e2565b5050565b5f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61020f565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6102c76101ec565b604080516001600160a01b03928316815291841660208301520160405180910390a161012981610401565b806001600160a01b03163b5f0361032c57604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5b80546001600160a01b0319166001600160a01b039290921691909117905550565b60605f5f846001600160a01b03168460405161038c919061058a565b5f60405180830381855af49150503d805f81146103c4576040519150601f19603f3d011682016040523d82523d5f602084013e6103c9565b606091505b50915091506103d9858383610451565b95945050505050565b34156100f85760405163b398979f60e01b815260040160405180910390fd5b6001600160a01b03811661042a57604051633173bdd160e11b81525f6004820152602401610323565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610361034f565b60608261046657610461826104b0565b6104a9565b815115801561047d57506001600160a01b0384163b155b156104a657604051639996b31560e01b81526001600160a01b0385166004820152602401610323565b50805b9392505050565b8051156104bf57805160208201fd5b60405163d6bda27560e01b815260040160405180910390fd5b80356001600160a01b03811681146104ee575f5ffd5b919050565b5f60208284031215610503575f5ffd5b6104a9826104d8565b5f5f5f6040848603121561051e575f5ffd5b610527846104d8565b9250602084013567ffffffffffffffff811115610542575f5ffd5b8401601f81018613610552575f5ffd5b803567ffffffffffffffff811115610568575f5ffd5b866020828401011115610579575f5ffd5b939660209190910195509293505050565b5f82518060208501845e5f92019182525091905056fea26469706673582212204c0881af9b906ca964ce2bc1cc4525350de8dd57658dacb806266290556eb9ae64736f6c634300081d0033", + "storage": { + "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x000000000000000000000000131e6b8e466ac8046c38c3ae7de77595cfeaf0d1", + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x00000000000000000000000098c74f7ef54ba22be94415d7c967b352c6a42d6d", + "0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300": "0x00000000000000000000000020e61a9cc8d010928aa9997e4e773e84de1b8306", + "0x0642d7922329a434cf4fd17a3c95eb692c24fd95f9f94d0b55420a5d895f4a00": "0x0000000000000000000000005bc1d44f8e844863cbc45d2f425f4c3758faf7b8", + "0x1c6ec1cacbb518b0389f77529ac2e30d6563e25886268b41b48d50c6395a8d2d": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x548a5b3f2c3df95f9d3b8a4dd2bc69ecc72a131136c94413ef16f9b4e537f335": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0xec1e055d6238aede62b966e68ea8e0a0b21c4af7ef8389e2a5ee0e2c385a8c73": "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x483764f4055fa8283ecf55dce48062d552970414119757e1e8a63991968b541a": "0x0000000000000000000000000000000000000000000000000000000000000002", + "0x0967dd3edc72a1cb057b905c3c9359f9251b0a2c12052760c16d77e23de717c6": "0x0000000000000000000000000000000000000000000000000000000000000003", + "0xe7f0e043144823913b30543d456b0f772cb9646ed9471337963c7f3684a16213": "0x0000000000000000000000000000000000000000000000000000000000000003", + "0xaf82ff8349a17d14627c0aff6abb73110b9c7e14624691f2063ba28af9fbc7d4": "0x0000000000000000000000000000000000000000000000000000000000000004", + "0x7bbffe6a7d08f11d15300d7505c818e267c0b80e9d85c55cadaa5ae125757ebe": "0x0000000000000000000000000000000000000000000000000000000000000004", + "0xacf4a072e0ad7c16d4dd5f029b0a39e9a33cf1c9055df127840e04958db38252": "0x0000000000000000000000000000000000000000000000000000000000000005", + "0x27afa9776521e070c3bf1b7db944adb6688e2279f8c75a7eccc8b4abe8e02c6f": "0x0000000000000000000000000000000000000000000000000000000000000005", + "0x4d0a40b24b120de62c44fea1d981476a936f2db6856a9ad380655a40893ce222": "0x0000000000000000000000000000000000000000000000000000000000000006", + "0xd640da1027cd77fbc230500682845251adcb8527179d8a412900e6daf6df5280": "0x0000000000000000000000000000000000000000000000000000000000000006", + "0x163ffef6869e4331a9bc5187b959463cc2d80968d25e55c6b73afbfae8800693": "0x0000000000000000000000000000000000000000000000000000000000000007", + "0xa9b56aa859b852d420f7fabe9cc332dc0c4c4decb3c7d8d470a46736f4e676e9": "0x0000000000000000000000000000000000000000000000000000000000000007", + "0xb3c0c2336a4da51d23a2109cc38ca1cbcd8e41e007cb90005293dc1dbc024554": "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x1b8b25ac282cb00bf2a429aee329a75321b9046048a916088991b8f31379209d": "0x0000000000000000000000000000000000000000000000000000000000000008", + "0x590d7fad0db97d79e0cc340a95f6230133d58f063d82e3ae4829320d45eefa67": "0x0000000000000000000000000000000000000000000000000000000000000009", + "0x36f7325e9d8622c16e5e692fb1a76494187284ed832dcf704bd88142d60be122": "0x0000000000000000000000000000000000000000000000000000000000000009", + "0x7b285fa910def941cb8d797ff7ff9e503e858cf9305e192bab943eea83dbbde7": "0x000000000000000000000000000000000000000000000000000000000000000a", + "0x0cd41ab158b6d95adf760c7842ceb9d75cf60165c1a5976e833d6fc55abdd248": "0x000000000000000000000000000000000000000000000000000000000000000a", + "0xc0e489cbfb5efacf149851728e17e49373162a6636f86152b51575ce2e61e39d": "0x000000000000000000000000000000000000000000000000000000000000000b", + "0x047d7247dc4ef04489e2bf1c5432acd01930a1cf4974c2479d2951a44ba851cc": "0x000000000000000000000000000000000000000000000000000000000000000b", + "0xe3b95e004499627f9ba49eceea49b136dd732e7539bb3659450e89704c607f27": "0x00000000000000000000000000000000000000000000000000000000000007d0", + "0xde25968ff4be3538ffa34e06752bfb0cc70f8e911a0efa6c7803fa9ff47c8233": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x085d1157429fc641e3d17c9054502e4347248f6d17a7092d1817ac49e9f9d172": "0x00000000000000000000000000000000000000000000000000000000000007d0", + "0x44c6dcda9372fa2eb1684d5b99459494830f6ca2385bd25d8fefa582911e8121": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xae72526874b4d44523f9bebcca264c14a1b41aef716271d2e4b80715ddb6c2ec": "0x00000000000000000000000000000000000000000000000000000000000007d0", + "0xb9a88c9aa828493b972e4b55c73da46d4ea14d798639462f1c25355ca173df63": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xee9d097b164ce84829f407edbdf171d5667324ce2788c9b8b6ae54c361cd15a9": "0x00000000000000000000000000000000000000000000000000000000000007d0", + "0x8ad6ac7dee1dbfa50bf134be6f65b1791a8d7e768fc9ebf8f21aa887adc3cf4e": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xc1665827e69e6438bb9a669229c4443a6ad6b9848fd933c44131ba119d116ee3": "0x00000000000000000000000000000000000000000000000000000000000007d0", + "0xcc15aecc4a120be3518700169a5fb3d0b56df7f538f1839aa09d96695274247e": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xec76d38fc12b1c3448395ec2fdebdac75a9dcde690180298c4332c412c8e8c1c": "0x00000000000000000000000000000000000000000000000000000000000007d0", + "0x0ab453a5839bbbca8d73b8429848be79a5adbc5cf3990aabcca065fb951968e6": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xffaf135ec0bcd44b22aa46b43e9f3ba3d2728e2a60d4c55e3e1a2a0b2f73bb3a": "0x00000000000000000000000000000000000000000000000000000000000007d0", + "0xfa4b5c618c52f975495435c4c260c2949b48ed5f9a0fbefea6fcbf4d067e876a": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x8758b3661862833ec1e68b4057d4c3d968f639dcfbcdeeb31c7ecb563c06c48d": "0x00000000000000000000000000000000000000000000000000000000000007d0", + "0xe1832ca4fab71938f45d7756b23da44232ad31297df774671cea85d4acbe6ac8": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x5151797eff20ebe871e505a1ff10f4c43f000a85d507d6c6f6f157107fb0fb9c": "0x00000000000000000000000000000000000000000000000000000000000007d0", + "0x49e46a4d7af1cb84550f6f567de333acbc0799e5327e24dc2d45a99599996aff": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0xd0fe00adfa714409d8434a5e7665a33cc3411b0d0753fd4670011326b7dee2ce": "0x00000000000000000000000000000000000000000000000000000000000007d0", + "0x5b1c14c8ac144ab3635042d92d3331eb063ac143e7d163ffc78ab034c1d6a94b": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x27a904bc0a897d94420bf8993e33a929271e68862825ae75976f923ca91bcf92": "0x00000000000000000000000000000000000000000000000000000000000007d0", + "0x2b81136c55f68fd91cfce4df1cd783c286658c2105ecd4c86baa64325b7d1322": "0x0000000000000000000000000000000000000000000000000000000000000000", + "0x1d61743442fd750af808d6571d9e8809b7d99c4e40497a2fc775acd436624baf": "0x0000000000000000000000000000000000000000000000000000000000000001", + "0xdf008954709fc88e758ca7a2ba528f00fc25eeb49ccfa12177f3fc8bfa6cb1f1": "0x0000000000000000000000000000000000000000000000000000000000000001" + } + }, + "0x4b5A1f0cf56B193cCA1a0B16fCE0713d44abdDbE": { + "balance": "0x0", + "nonce": "0x1", + "code": "0x608060405234801561000f575f5ffd5b50600436106100cb575f3560e01c8063c4d66de811610088578063e8746fed11610063578063e8746fed146101b0578063e877a526146101c3578063f2fde38b146101fa578063fab48ccf1461020d575f5ffd5b8063c4d66de814610182578063ca2f731314610195578063e30c3978146101a8575f5ffd5b806331d798b5146100cf578063715018a61461011b57806379ba5097146101255780637a7ceb161461012d5780638a8ee6ac146101405780638da5cb5b14610162575b5f5ffd5b6101066100dd366004610980565b6001600160a01b03165f9081525f516020610a335f395f51905f52602052604090205460ff1690565b60405190151581526020015b60405180910390f35b610123610220565b005b610123610233565b61012361013b3660046109ad565b610280565b6101545f516020610a535f395f51905f5281565b604051908152602001610112565b61016a610385565b6040516001600160a01b039091168152602001610112565b610123610190366004610980565b6103b9565b6101236101a3366004610980565b6104f5565b61016a6105ab565b6101236101be3660046109ad565b6105d3565b6101066101d1366004610980565b6001600160a01b03165f9081525f516020610a535f395f51905f52602052604090205460ff1690565b610123610208366004610980565b610717565b61012361021b366004610980565b61079c565b610228610855565b6102315f610887565b565b338061023d6105ab565b6001600160a01b0316146102745760405163118cdaa760e01b81526001600160a01b03821660048201526024015b60405180910390fd5b61027d81610887565b50565b335f9081525f516020610a335f395f51905f5260205260409020545f516020610a535f395f51905f529060ff166102ca57604051637628c31d60e01b815260040160405180910390fd5b5f516020610a535f395f51905f52825f5b8181101561037d575f8686838181106102f6576102f6610a1e565b905060200201602081019061030b9190610980565b6001600160a01b0381165f9081526020869052604090205490915060ff1615610374576001600160a01b0381165f81815260208690526040808220805460ff19169055517fc904e1b03de0c20d7fcf9dbd056daf1bd3815e93f251199de815fd0f0b96e1669190a25b506001016102db565b505050505050565b5f807f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c1993005b546001600160a01b031692915050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a008054600160401b810460ff16159067ffffffffffffffff165f811580156103fe5750825b90505f8267ffffffffffffffff16600114801561041a5750303b155b905081158015610428575080155b156104465760405163f92ee8a960e01b815260040160405180910390fd5b845467ffffffffffffffff19166001178555831561047057845460ff60401b1916600160401b1785555b6001600160a01b0386166104975760405163d92e233d60e01b815260040160405180910390fd5b61049f6108bf565b6104a886610887565b831561037d57845460ff60401b19168555604051600181527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a1505050505050565b6104fd610855565b6001600160a01b0381166105245760405163d92e233d60e01b815260040160405180910390fd5b6001600160a01b0381165f9081525f516020610a335f395f51905f5260205260409020545f516020610a535f395f51905f529060ff16156105a7576001600160a01b0382165f818152600183016020526040808220805460ff19169055517f2ee9bf58aeff79a8ee48ae2ebaea69b7fc533af3060aaa8d8956b225785db10a9190a25b5050565b5f807f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c006103a9565b335f9081525f516020610a335f395f51905f5260205260409020545f516020610a535f395f51905f529060ff1661061d57604051637628c31d60e01b815260040160405180910390fd5b5f610626610385565b90505f516020610a535f395f51905f52835f5b8181101561070e575f87878381811061065457610654610a1e565b90506020020160208101906106699190610980565b9050846001600160a01b0316816001600160a01b03160361069d5760405163266f648160e01b815260040160405180910390fd5b6001600160a01b0381165f9081526020859052604090205460ff16610705576001600160a01b0381165f81815260208690526040808220805460ff19166001179055517ffa4507bc1f9c730e6e95897024f1fe7d576cf2deb53579d55c14f1ac3439e1149190a25b50600101610639565b50505050505050565b61071f610855565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0080546001600160a01b0319166001600160a01b0383169081178255610763610385565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a35050565b6107a4610855565b6001600160a01b0381166107cb5760405163d92e233d60e01b815260040160405180910390fd5b6001600160a01b0381165f9081525f516020610a335f395f51905f5260205260409020545f516020610a535f395f51905f529060ff166105a7576001600160a01b0382165f81815260018381016020526040808320805460ff1916909217909155517f1ac0162c9c4096b260eb12be2731ca291739aa6ed7c94780f52c55df88589e7c9190a25050565b3361085e610385565b6001600160a01b0316146102315760405163118cdaa760e01b815233600482015260240161026b565b7f237e158222e3e6968b72b9db0d8043aacf074ad9f650f0d1606b4d82ee432c0080546001600160a01b03191681556105a7826108c7565b610231610937565b7f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c19930080546001600160a01b031981166001600160a01b03848116918217845560405192169182907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a3505050565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a0054600160401b900460ff1661023157604051631afcd79f60e31b815260040160405180910390fd5b5f60208284031215610990575f5ffd5b81356001600160a01b03811681146109a6575f5ffd5b9392505050565b5f5f602083850312156109be575f5ffd5b823567ffffffffffffffff8111156109d4575f5ffd5b8301601f810185136109e4575f5ffd5b803567ffffffffffffffff8111156109fa575f5ffd5b8560208260051b8401011115610a0e575f5ffd5b6020919091019590945092505050565b634e487b7160e01b5f52603260045260245ffdfe1d7e1388d3ae56f3d9c18b1ce8d2b3b1a238a0edf682d2053af5d8a1d2f12f011d7e1388d3ae56f3d9c18b1ce8d2b3b1a238a0edf682d2053af5d8a1d2f12f00a264697066735822122057fdea9cb356478df3dad5dbaec8a47476bd3266f5a49729c6d02d8e049ee9b864736f6c634300081d0033" + }, + "0x3600000000000000000000000000000000000004": { + "balance": "0x0", + "nonce": "0x1", + "code": "0x608060405260043610610049575f3560e01c80633659cfe6146100535780634f1ef286146100725780635c60da1b146100855780638f283970146100b5578063f851a440146100d4575b6100516100e8565b005b34801561005e575f5ffd5b5061005161006d3660046104f3565b6100fa565b61005161008036600461050c565b610134565b348015610090575f5ffd5b50610099610197565b6040516001600160a01b03909116815260200160405180910390f35b3480156100c0575f5ffd5b506100516100cf3660046104f3565b6101a5565b3480156100df575f5ffd5b506100996101c5565b6100f86100f3610197565b6101ce565b565b6101026101ec565b6001600160a01b0316330361012c576101298160405180602001604052805f81525061021e565b50565b6101296100e8565b61013c6101ec565b6001600160a01b0316330361018f5761018a8383838080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061021e92505050565b505050565b61018a6100e8565b5f6101a0610277565b905090565b6101ad6101ec565b6001600160a01b0316330361012c576101298161029e565b5f6101a06101ec565b365f5f375f5f365f845af43d5f5f3e8080156101e8573d5ff35b3d5ffd5b5f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b610227826102f2565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a280511561026b5761018a8282610370565b6102736103e2565b5050565b5f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc61020f565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6102c76101ec565b604080516001600160a01b03928316815291841660208301520160405180910390a161012981610401565b806001600160a01b03163b5f0361032c57604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5b80546001600160a01b0319166001600160a01b039290921691909117905550565b60605f5f846001600160a01b03168460405161038c919061058a565b5f60405180830381855af49150503d805f81146103c4576040519150601f19603f3d011682016040523d82523d5f602084013e6103c9565b606091505b50915091506103d9858383610451565b95945050505050565b34156100f85760405163b398979f60e01b815260040160405180910390fd5b6001600160a01b03811661042a57604051633173bdd160e11b81525f6004820152602401610323565b807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610361034f565b60608261046657610461826104b0565b6104a9565b815115801561047d57506001600160a01b0384163b155b156104a657604051639996b31560e01b81526001600160a01b0385166004820152602401610323565b50805b9392505050565b8051156104bf57805160208201fd5b60405163d6bda27560e01b815260040160405180910390fd5b80356001600160a01b03811681146104ee575f5ffd5b919050565b5f60208284031215610503575f5ffd5b6104a9826104d8565b5f5f5f6040848603121561051e575f5ffd5b610527846104d8565b9250602084013567ffffffffffffffff811115610542575f5ffd5b8401601f81018613610552575f5ffd5b803567ffffffffffffffff811115610568575f5ffd5b866020828401011115610579575f5ffd5b939660209190910195509293505050565b5f82518060208501845e5f92019182525091905056fea26469706673582212204c0881af9b906ca964ce2bc1cc4525350de8dd57658dacb806266290556eb9ae64736f6c634300081d0033", + "storage": { + "0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103": "0x000000000000000000000000afb5b6a4725459959ef931a3e5df758a72a8ca7f", + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc": "0x0000000000000000000000004b5a1f0cf56b193cca1a0b16fce0713d44abddbe", + "0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300": "0x00000000000000000000000049ec36db19623e4dadc1aa821cba2d1476f8e859", + "0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00": "0x0000000000000000000000000000000000000000000000000000000000000001" + } + }, + "0x4e59b44847b379578588920cA78FbF26c0B4956C": { + "balance": "0x0", + "nonce": "0x1", + "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3" + }, + "0x3fab184622dc19b6109349b94811493bf2a45362": { + "balance": "0x0", + "nonce": "0x1" + }, + "0xcA11bde05977b3631167028862bE2a173976CA11": { + "balance": "0x0", + "nonce": "0x1", + "code": "0x6080604052600436106100f35760003560e01c80634d2301cc1161008a578063a8b0574e11610059578063a8b0574e1461025a578063bce38bd714610275578063c3077fa914610288578063ee82ac5e1461029b57600080fd5b80634d2301cc146101ec57806372425d9d1461022157806382ad56cb1461023457806386d516e81461024757600080fd5b80633408e470116100c65780633408e47014610191578063399542e9146101a45780633e64a696146101c657806342cbb15c146101d957600080fd5b80630f28c97d146100f8578063174dea711461011a578063252dba421461013a57806327e86d6e1461015b575b600080fd5b34801561010457600080fd5b50425b6040519081526020015b60405180910390f35b61012d610128366004610a85565b6102ba565b6040516101119190610bbe565b61014d610148366004610a85565b6104ef565b604051610111929190610bd8565b34801561016757600080fd5b50437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0140610107565b34801561019d57600080fd5b5046610107565b6101b76101b2366004610c60565b610690565b60405161011193929190610cba565b3480156101d257600080fd5b5048610107565b3480156101e557600080fd5b5043610107565b3480156101f857600080fd5b50610107610207366004610ce2565b73ffffffffffffffffffffffffffffffffffffffff163190565b34801561022d57600080fd5b5044610107565b61012d610242366004610a85565b6106ab565b34801561025357600080fd5b5045610107565b34801561026657600080fd5b50604051418152602001610111565b61012d610283366004610c60565b61085a565b6101b7610296366004610a85565b610a1a565b3480156102a757600080fd5b506101076102b6366004610d18565b4090565b60606000828067ffffffffffffffff8111156102d8576102d8610d31565b60405190808252806020026020018201604052801561031e57816020015b6040805180820190915260008152606060208201528152602001906001900390816102f65790505b5092503660005b8281101561047757600085828151811061034157610341610d60565b6020026020010151905087878381811061035d5761035d610d60565b905060200281019061036f9190610d8f565b6040810135958601959093506103886020850185610ce2565b73ffffffffffffffffffffffffffffffffffffffff16816103ac6060870187610dcd565b6040516103ba929190610e32565b60006040518083038185875af1925050503d80600081146103f7576040519150601f19603f3d011682016040523d82523d6000602084013e6103fc565b606091505b50602080850191909152901515808452908501351761046d577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260846000fd5b5050600101610325565b508234146104e6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f4d756c746963616c6c333a2076616c7565206d69736d6174636800000000000060448201526064015b60405180910390fd5b50505092915050565b436060828067ffffffffffffffff81111561050c5761050c610d31565b60405190808252806020026020018201604052801561053f57816020015b606081526020019060019003908161052a5790505b5091503660005b8281101561068657600087878381811061056257610562610d60565b90506020028101906105749190610e42565b92506105836020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166105a66020850185610dcd565b6040516105b4929190610e32565b6000604051808303816000865af19150503d80600081146105f1576040519150601f19603f3d011682016040523d82523d6000602084013e6105f6565b606091505b5086848151811061060957610609610d60565b602090810291909101015290508061067d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b50600101610546565b5050509250929050565b43804060606106a086868661085a565b905093509350939050565b6060818067ffffffffffffffff8111156106c7576106c7610d31565b60405190808252806020026020018201604052801561070d57816020015b6040805180820190915260008152606060208201528152602001906001900390816106e55790505b5091503660005b828110156104e657600084828151811061073057610730610d60565b6020026020010151905086868381811061074c5761074c610d60565b905060200281019061075e9190610e76565b925061076d6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166107906040850185610dcd565b60405161079e929190610e32565b6000604051808303816000865af19150503d80600081146107db576040519150601f19603f3d011682016040523d82523d6000602084013e6107e0565b606091505b506020808401919091529015158083529084013517610851577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260646000fd5b50600101610714565b6060818067ffffffffffffffff81111561087657610876610d31565b6040519080825280602002602001820160405280156108bc57816020015b6040805180820190915260008152606060208201528152602001906001900390816108945790505b5091503660005b82811015610a105760008482815181106108df576108df610d60565b602002602001015190508686838181106108fb576108fb610d60565b905060200281019061090d9190610e42565b925061091c6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff1661093f6020850185610dcd565b60405161094d929190610e32565b6000604051808303816000865af19150503d806000811461098a576040519150601f19603f3d011682016040523d82523d6000602084013e61098f565b606091505b506020830152151581528715610a07578051610a07576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b506001016108c3565b5050509392505050565b6000806060610a2b60018686610690565b919790965090945092505050565b60008083601f840112610a4b57600080fd5b50813567ffffffffffffffff811115610a6357600080fd5b6020830191508360208260051b8501011115610a7e57600080fd5b9250929050565b60008060208385031215610a9857600080fd5b823567ffffffffffffffff811115610aaf57600080fd5b610abb85828601610a39565b90969095509350505050565b6000815180845260005b81811015610aed57602081850181015186830182015201610ad1565b81811115610aff576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600082825180855260208086019550808260051b84010181860160005b84811015610bb1578583037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001895281518051151584528401516040858501819052610b9d81860183610ac7565b9a86019a9450505090830190600101610b4f565b5090979650505050505050565b602081526000610bd16020830184610b32565b9392505050565b600060408201848352602060408185015281855180845260608601915060608160051b870101935082870160005b82811015610c52577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0888703018452610c40868351610ac7565b95509284019290840190600101610c06565b509398975050505050505050565b600080600060408486031215610c7557600080fd5b83358015158114610c8557600080fd5b9250602084013567ffffffffffffffff811115610ca157600080fd5b610cad86828701610a39565b9497909650939450505050565b838152826020820152606060408201526000610cd96060830184610b32565b95945050505050565b600060208284031215610cf457600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610bd157600080fd5b600060208284031215610d2a57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81833603018112610dc357600080fd5b9190910192915050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112610e0257600080fd5b83018035915067ffffffffffffffff821115610e1d57600080fd5b602001915036819003821315610a7e57600080fd5b8183823760009101908152919050565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc1833603018112610dc357600080fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa1833603018112610dc357600080fdfea2646970667358221220bb2b5c71a328032f97c676ae39a1ec2148d3e5d6f73d95e9b17910152d61f16264736f6c634300080c0033" + }, + "0x05f32b3cc3888453ff71b01135b34ff8e41263f2": { + "balance": "0x0", + "nonce": "0x1" + }, + "0x0000F90827F1C53a10cb7A02335B175320002935": { + "balance": "0x0", + "nonce": "0x1", + "code": "0x3373fffffffffffffffffffffffffffffffffffffffe14604657602036036042575f35600143038111604257611fff81430311604257611fff9006545f5260205ff35b5f5ffd5b5f35611fff60014303065500" + }, + "0x3462413Af4609098e1E27A490f554f260213D685": { + "balance": "0x0", + "nonce": "0x1" + }, + "0x000000000022D473030F116dDEE9F6B43aC78BA3": { + "balance": "0x0", + "nonce": "0x1", + "code": "0x6040608081526004908136101561001557600080fd5b600090813560e01c80630d58b1db1461126c578063137c29fe146110755780632a2d80d114610db75780632b67b57014610bde57806330f28b7a14610ade5780633644e51514610a9d57806336c7851614610a285780633ff9dcb1146109a85780634fe02b441461093f57806365d9723c146107ac57806387517c451461067a578063927da105146105c3578063cc53287f146104a3578063edd9444b1461033a5763fe8ec1a7146100c657600080fd5b346103365760c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff833581811161033257610114903690860161164b565b60243582811161032e5761012b903690870161161a565b6101336114e6565b9160843585811161032a5761014b9036908a016115c1565b98909560a43590811161032657610164913691016115c1565b969095815190610173826113ff565b606b82527f5065726d697442617463685769746e6573735472616e7366657246726f6d285460208301527f6f6b656e5065726d697373696f6e735b5d207065726d69747465642c61646472838301527f657373207370656e6465722c75696e74323536206e6f6e63652c75696e74323560608301527f3620646561646c696e652c000000000000000000000000000000000000000000608083015282519a8b9181610222602085018096611f93565b918237018a8152039961025b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe09b8c8101835282611437565b5190209085515161026b81611ebb565b908a5b8181106102f95750506102f6999a6102ed9183516102a081610294602082018095611f66565b03848101835282611437565b519020602089810151858b015195519182019687526040820192909252336060820152608081019190915260a081019390935260643560c08401528260e081015b03908101835282611437565b51902093611cf7565b80f35b8061031161030b610321938c5161175e565b51612054565b61031b828661175e565b52611f0a565b61026e565b8880fd5b8780fd5b8480fd5b8380fd5b5080fd5b5091346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103365767ffffffffffffffff9080358281116103325761038b903690830161164b565b60243583811161032e576103a2903690840161161a565b9390926103ad6114e6565b9160643590811161049f576103c4913691016115c1565b949093835151976103d489611ebb565b98885b81811061047d5750506102f697988151610425816103f9602082018095611f66565b037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08101835282611437565b5190206020860151828701519083519260208401947ffcf35f5ac6a2c28868dc44c302166470266239195f02b0ee408334829333b7668652840152336060840152608083015260a082015260a081526102ed8161141b565b808b61031b8261049461030b61049a968d5161175e565b9261175e565b6103d7565b8680fd5b5082346105bf57602090817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103325780359067ffffffffffffffff821161032e576104f49136910161161a565b929091845b848110610504578580f35b8061051a610515600193888861196c565b61197c565b61052f84610529848a8a61196c565b0161197c565b3389528385528589209173ffffffffffffffffffffffffffffffffffffffff80911692838b528652868a20911690818a5285528589207fffffffffffffffffffffffff000000000000000000000000000000000000000081541690558551918252848201527f89b1add15eff56b3dfe299ad94e01f2b52fbcb80ae1a3baea6ae8c04cb2b98a4853392a2016104f9565b8280fd5b50346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610676816105ff6114a0565b936106086114c3565b6106106114e6565b73ffffffffffffffffffffffffffffffffffffffff968716835260016020908152848420928816845291825283832090871683528152919020549251938316845260a083901c65ffffffffffff169084015260d09190911c604083015281906060820190565b0390f35b50346103365760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336576106b26114a0565b906106bb6114c3565b916106c46114e6565b65ffffffffffff926064358481169081810361032a5779ffffffffffff0000000000000000000000000000000000000000947fda9fa7c1b00402c17d0161b249b1ab8bbec047c5a52207b9c112deffd817036b94338a5260016020527fffffffffffff0000000000000000000000000000000000000000000000000000858b209873ffffffffffffffffffffffffffffffffffffffff809416998a8d5260205283878d209b169a8b8d52602052868c209486156000146107a457504216925b8454921697889360a01b16911617179055815193845260208401523392a480f35b905092610783565b5082346105bf5760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576107e56114a0565b906107ee6114c3565b9265ffffffffffff604435818116939084810361032a57338852602091600183528489209673ffffffffffffffffffffffffffffffffffffffff80911697888b528452858a20981697888a5283528489205460d01c93848711156109175761ffff9085840316116108f05750907f55eb90d810e1700b35a8e7e25395ff7f2b2259abd7415ca2284dfb1c246418f393929133895260018252838920878a528252838920888a5282528389209079ffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffff000000000000000000000000000000000000000000000000000083549260d01b16911617905582519485528401523392a480f35b84517f24d35a26000000000000000000000000000000000000000000000000000000008152fd5b5084517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b503461033657807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610336578060209273ffffffffffffffffffffffffffffffffffffffff61098f6114a0565b1681528084528181206024358252845220549051908152f35b5082346105bf57817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf577f3704902f963766a4e561bbaab6e6cdc1b1dd12f6e9e99648da8843b3f46b918d90359160243533855284602052818520848652602052818520818154179055815193845260208401523392a280f35b8234610a9a5760807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610a9a57610a606114a0565b610a686114c3565b610a706114e6565b6064359173ffffffffffffffffffffffffffffffffffffffff8316830361032e576102f6936117a1565b80fd5b503461033657817ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657602090610ad7611b1e565b9051908152f35b508290346105bf576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf57610b1a3661152a565b90807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c36011261033257610b4c611478565b9160e43567ffffffffffffffff8111610bda576102f694610b6f913691016115c1565b939092610b7c8351612054565b6020840151828501519083519260208401947f939c21a48a8dbe3a9a2404a1d46691e4d39f6583d6ec6b35714604c986d801068652840152336060840152608083015260a082015260a08152610bd18161141b565b51902091611c25565b8580fd5b509134610336576101007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033657610c186114a0565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc360160c08112610332576080855191610c51836113e3565b1261033257845190610c6282611398565b73ffffffffffffffffffffffffffffffffffffffff91602435838116810361049f578152604435838116810361049f57602082015265ffffffffffff606435818116810361032a5788830152608435908116810361049f576060820152815260a435938285168503610bda576020820194855260c4359087830182815260e43567ffffffffffffffff811161032657610cfe90369084016115c1565b929093804211610d88575050918591610d786102f6999a610d7e95610d238851611fbe565b90898c511690519083519260208401947ff3841cd1ff0085026a6327b620b67997ce40f282c88a8e905a7a5626e310f3d086528401526060830152608082015260808152610d70816113ff565b519020611bd9565b916120c7565b519251169161199d565b602492508a51917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b5091346103365760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc93818536011261033257610df36114a0565b9260249081359267ffffffffffffffff9788851161032a578590853603011261049f578051978589018981108282111761104a578252848301358181116103265785019036602383011215610326578382013591610e50836115ef565b90610e5d85519283611437565b838252602093878584019160071b83010191368311611046578801905b828210610fe9575050508a526044610e93868801611509565b96838c01978852013594838b0191868352604435908111610fe557610ebb90369087016115c1565b959096804211610fba575050508998995151610ed681611ebb565b908b5b818110610f9757505092889492610d7892610f6497958351610f02816103f98682018095611f66565b5190209073ffffffffffffffffffffffffffffffffffffffff9a8b8b51169151928551948501957faf1b0d30d2cab0380e68f0689007e3254993c596f2fdd0aaa7f4d04f794408638752850152830152608082015260808152610d70816113ff565b51169082515192845b848110610f78578580f35b80610f918585610f8b600195875161175e565b5161199d565b01610f6d565b80610311610fac8e9f9e93610fb2945161175e565b51611fbe565b9b9a9b610ed9565b8551917fcd21db4f000000000000000000000000000000000000000000000000000000008352820152fd5b8a80fd5b6080823603126110465785608091885161100281611398565b61100b85611509565b8152611018838601611509565b838201526110278a8601611607565b8a8201528d611037818701611607565b90820152815201910190610e7a565b8c80fd5b84896041867f4e487b7100000000000000000000000000000000000000000000000000000000835252fd5b5082346105bf576101407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126105bf576110b03661152a565b91807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7c360112610332576110e2611478565b67ffffffffffffffff93906101043585811161049f5761110590369086016115c1565b90936101243596871161032a57611125610bd1966102f6983691016115c1565b969095825190611134826113ff565b606482527f5065726d69745769746e6573735472616e7366657246726f6d28546f6b656e5060208301527f65726d697373696f6e73207065726d69747465642c6164647265737320737065848301527f6e6465722c75696e74323536206e6f6e63652c75696e7432353620646561646c60608301527f696e652c0000000000000000000000000000000000000000000000000000000060808301528351948591816111e3602085018096611f93565b918237018b8152039361121c7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe095868101835282611437565b5190209261122a8651612054565b6020878101518589015195519182019687526040820192909252336060820152608081019190915260a081019390935260e43560c08401528260e081016102e1565b5082346105bf576020807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261033257813567ffffffffffffffff92838211610bda5736602383011215610bda5781013592831161032e576024906007368386831b8401011161049f57865b8581106112e5578780f35b80821b83019060807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc83360301126103265761139288876001946060835161132c81611398565b611368608461133c8d8601611509565b9485845261134c60448201611509565b809785015261135d60648201611509565b809885015201611509565b918291015273ffffffffffffffffffffffffffffffffffffffff80808093169516931691166117a1565b016112da565b6080810190811067ffffffffffffffff8211176113b457604052565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6060810190811067ffffffffffffffff8211176113b457604052565b60a0810190811067ffffffffffffffff8211176113b457604052565b60c0810190811067ffffffffffffffff8211176113b457604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff8211176113b457604052565b60c4359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b600080fd5b6004359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6024359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b6044359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b359073ffffffffffffffffffffffffffffffffffffffff8216820361149b57565b7ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc01906080821261149b576040805190611563826113e3565b8082941261149b57805181810181811067ffffffffffffffff8211176113b457825260043573ffffffffffffffffffffffffffffffffffffffff8116810361149b578152602435602082015282526044356020830152606435910152565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020838186019501011161149b57565b67ffffffffffffffff81116113b45760051b60200190565b359065ffffffffffff8216820361149b57565b9181601f8401121561149b5782359167ffffffffffffffff831161149b576020808501948460061b01011161149b57565b91909160608184031261149b576040805191611666836113e3565b8294813567ffffffffffffffff9081811161149b57830182601f8201121561149b578035611693816115ef565b926116a087519485611437565b818452602094858086019360061b8501019381851161149b579086899897969594939201925b8484106116e3575050505050855280820135908501520135910152565b90919293949596978483031261149b578851908982019082821085831117611730578a928992845261171487611509565b81528287013583820152815201930191908897969594936116c6565b602460007f4e487b710000000000000000000000000000000000000000000000000000000081526041600452fd5b80518210156117725760209160051b010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b92919273ffffffffffffffffffffffffffffffffffffffff604060008284168152600160205282828220961695868252602052818120338252602052209485549565ffffffffffff8760a01c16804211611884575082871696838803611812575b5050611810955016926118b5565b565b878484161160001461184f57602488604051907ff96fb0710000000000000000000000000000000000000000000000000000000082526004820152fd5b7fffffffffffffffffffffffff000000000000000000000000000000000000000084846118109a031691161790553880611802565b602490604051907fd81b2f2e0000000000000000000000000000000000000000000000000000000082526004820152fd5b9060006064926020958295604051947f23b872dd0000000000000000000000000000000000000000000000000000000086526004860152602485015260448401525af13d15601f3d116001600051141617161561190e57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5452414e534645525f46524f4d5f4641494c45440000000000000000000000006044820152fd5b91908110156117725760061b0190565b3573ffffffffffffffffffffffffffffffffffffffff8116810361149b5790565b9065ffffffffffff908160608401511673ffffffffffffffffffffffffffffffffffffffff908185511694826020820151169280866040809401511695169560009187835260016020528383208984526020528383209916988983526020528282209184835460d01c03611af5579185611ace94927fc6a377bfc4eb120024a8ac08eef205be16b817020812c73223e81d1bdb9708ec98979694508715600014611ad35779ffffffffffff00000000000000000000000000000000000000009042165b60a01b167fffffffffffff00000000000000000000000000000000000000000000000000006001860160d01b1617179055519384938491604091949373ffffffffffffffffffffffffffffffffffffffff606085019616845265ffffffffffff809216602085015216910152565b0390a4565b5079ffffffffffff000000000000000000000000000000000000000087611a60565b600484517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b467f00000000000000000000000000000000000000000000000000000000000013b203611b69577fa88a1b742ab6890402e6ee74f2359f3723ab547c6dc3b850249bdbffebe2b18f90565b60405160208101907f8cad95687ba82c2ce50e74f7b754645e5117c3a5bec8151c0726d5857980a86682527f9ac997416e8ff9d2ff6bebeb7149f65cdae5e32e2b90440b566bb3044041d36a604082015246606082015230608082015260808152611bd3816113ff565b51902090565b611be1611b1e565b906040519060208201927f190100000000000000000000000000000000000000000000000000000000000084526022830152604282015260428152611bd381611398565b9192909360a435936040840151804211611cc65750602084510151808611611c955750918591610d78611c6594611c60602088015186611e47565b611bd9565b73ffffffffffffffffffffffffffffffffffffffff809151511692608435918216820361149b57611810936118b5565b602490604051907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b602490604051907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b959093958051519560409283830151804211611e175750848803611dee57611d2e918691610d7860209b611c608d88015186611e47565b60005b868110611d42575050505050505050565b611d4d81835161175e565b5188611d5a83878a61196c565b01359089810151808311611dbe575091818888886001968596611d84575b50505050505001611d31565b611db395611dad9273ffffffffffffffffffffffffffffffffffffffff6105159351169561196c565b916118b5565b803888888883611d78565b6024908651907f3728b83d0000000000000000000000000000000000000000000000000000000082526004820152fd5b600484517fff633a38000000000000000000000000000000000000000000000000000000008152fd5b6024908551907fcd21db4f0000000000000000000000000000000000000000000000000000000082526004820152fd5b9073ffffffffffffffffffffffffffffffffffffffff600160ff83161b9216600052600060205260406000209060081c6000526020526040600020818154188091551615611e9157565b60046040517f756688fe000000000000000000000000000000000000000000000000000000008152fd5b90611ec5826115ef565b611ed26040519182611437565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0611f0082946115ef565b0190602036910137565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114611f375760010190565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b805160208092019160005b828110611f7f575050505090565b835185529381019392810192600101611f71565b9081519160005b838110611fab575050016000815290565b8060208092840101518185015201611f9a565b60405160208101917f65626cad6cb96493bf6f5ebea28756c966f023ab9e8a83a7101849d5573b3678835273ffffffffffffffffffffffffffffffffffffffff8082511660408401526020820151166060830152606065ffffffffffff9182604082015116608085015201511660a082015260a0815260c0810181811067ffffffffffffffff8211176113b45760405251902090565b6040516020808201927f618358ac3db8dc274f0cd8829da7e234bd48cd73c4a740aede1adec9846d06a1845273ffffffffffffffffffffffffffffffffffffffff81511660408401520151606082015260608152611bd381611398565b919082604091031261149b576020823592013590565b6000843b61222e5750604182036121ac576120e4828201826120b1565b939092604010156117725760209360009360ff6040608095013560f81c5b60405194855216868401526040830152606082015282805260015afa156121a05773ffffffffffffffffffffffffffffffffffffffff806000511691821561217657160361214c57565b60046040517f815e1d64000000000000000000000000000000000000000000000000000000008152fd5b60046040517f8baa579f000000000000000000000000000000000000000000000000000000008152fd5b6040513d6000823e3d90fd5b60408203612204576121c0918101906120b1565b91601b7f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff84169360ff1c019060ff8211611f375760209360009360ff608094612102565b60046040517f4be6321b000000000000000000000000000000000000000000000000000000008152fd5b929391601f928173ffffffffffffffffffffffffffffffffffffffff60646020957fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0604051988997889687947f1626ba7e000000000000000000000000000000000000000000000000000000009e8f8752600487015260406024870152816044870152868601378b85828601015201168101030192165afa9081156123a857829161232a575b507fffffffff000000000000000000000000000000000000000000000000000000009150160361230057565b60046040517fb0669cbc000000000000000000000000000000000000000000000000000000008152fd5b90506020813d82116123a0575b8161234460209383611437565b810103126103365751907fffffffff0000000000000000000000000000000000000000000000000000000082168203610a9a57507fffffffff0000000000000000000000000000000000000000000000000000000090386122d4565b3d9150612337565b6040513d84823e3d90fdfea164736f6c6343000811000a" + }, + "0x1800000000000000000000000000000000000002": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000003": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000004": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000005": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000006": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000007": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000008": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000009": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000000a": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000000b": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000000c": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000000d": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000000e": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000000f": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000010": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000011": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000012": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000013": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000014": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000015": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000016": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000017": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000018": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000019": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000001a": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000001b": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000001c": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000001d": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000001e": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000001f": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000020": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000021": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000022": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000023": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000024": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000025": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000026": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000027": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000028": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000029": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000002a": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000002b": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000002c": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000002d": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000002e": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000002f": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000030": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000031": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000032": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000033": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000034": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000035": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000036": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000037": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000038": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000039": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000003a": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000003b": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000003c": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000003d": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000003e": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000003f": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000040": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000041": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000042": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000043": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000044": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000045": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000046": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000047": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000048": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000049": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000004a": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000004b": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000004c": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000004d": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000004e": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000004f": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000050": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000051": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000052": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000053": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000054": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000055": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000056": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000057": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000058": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000059": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000005a": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000005b": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000005c": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000005d": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000005e": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000005f": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000060": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000061": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000062": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000063": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000064": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000065": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000066": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000067": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000068": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000069": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000006a": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000006b": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000006c": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000006d": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000006e": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000006f": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000070": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000071": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000072": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000073": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000074": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000075": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000076": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000077": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000078": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000079": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000007a": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000007b": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000007c": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000007d": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000007e": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000007f": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000080": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000081": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000082": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000083": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000084": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000085": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000086": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000087": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000088": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000089": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000008a": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000008b": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000008c": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000008d": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000008e": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000008f": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000090": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000091": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000092": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000093": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000094": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000095": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000096": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000097": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000098": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x1800000000000000000000000000000000000099": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000009a": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000009b": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000009c": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000009d": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000009e": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x180000000000000000000000000000000000009f": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000a0": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000a1": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000a2": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000a3": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000a4": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000a5": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000a6": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000a7": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000a8": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000a9": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000aa": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000ab": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000ac": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000ad": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000ae": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000af": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000b0": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000b1": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000b2": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000b3": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000b4": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000b5": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000b6": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000b7": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000b8": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000b9": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000ba": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000bb": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000bc": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000bd": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000be": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000bf": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000c0": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000c1": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000c2": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000c3": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000c4": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000c5": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000c6": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000c7": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000c8": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000c9": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000ca": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000cb": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000cc": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000cd": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000ce": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000cf": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000d0": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000d1": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000d2": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000d3": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000d4": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000d5": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000d6": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000d7": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000d8": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000d9": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000da": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000db": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000dc": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000dd": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000de": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000df": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000e0": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000e1": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000e2": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000e3": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000e4": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000e5": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000e6": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000e7": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000e8": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000e9": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000ea": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000eb": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000ec": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000ed": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000ee": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000ef": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000f0": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000f1": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000f2": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000f3": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000f4": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000f5": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000f6": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000f7": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000f8": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000f9": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000fa": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000fb": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000fc": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000fd": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000fe": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x18000000000000000000000000000000000000ff": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef" + }, + "0x50A2b0B577eC24d7ce1aeD372A8a6fd14CE1bE57": { + "balance": "0x21e19e0c9bab2400000" + }, + "0x1800000000000000000000000000000000000000": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000002": "0x00000000000000000000000000000000000000000000021e19e0c9bab2400000" + } + }, + "0x1800000000000000000000000000000000000001": { + "balance": "0x0", + "nonce": "0x1", + "code": "0xef", + "storage": { + "0xc141df5e4f31a4ad000065d1c11b38075b88c74e6e761baa68cd227ec7b803c6": "0x0000000000000000000000000000000000000000000000000000000000000001" + } + } + } +} diff --git a/contracts/scripts/Addresses.sol b/contracts/scripts/Addresses.sol index 8a21dc3..a051475 100644 --- a/contracts/scripts/Addresses.sol +++ b/contracts/scripts/Addresses.sol @@ -36,7 +36,7 @@ library Addresses { // ============ Predeployed Contracts ============ address internal constant DETERMINISTIC_DEPLOYER_PROXY = 0x4e59b44847b379578588920cA78FbF26c0B4956C; address internal constant MULTICALL3 = 0xcA11bde05977b3631167028862bE2a173976CA11; - address internal constant MULTICALL3_FROM = 0xA3E6c63b16321E39a61551Dc1A38689b04d62E42; + address internal constant MULTICALL3_FROM = 0x522fAf9A91c41c443c66765030741e4AaCe147D0; address internal constant MEMO = 0x5294E9927c3306DcBaDb03fe70b92e01cCede505; // ============ Helpers ============ diff --git a/contracts/scripts/ProtocolConfigManagement.s.sol b/contracts/scripts/ProtocolConfigManagement.s.sol index 98ccde4..c72cad3 100644 --- a/contracts/scripts/ProtocolConfigManagement.s.sol +++ b/contracts/scripts/ProtocolConfigManagement.s.sol @@ -103,10 +103,18 @@ contract ProtocolConfigManagement is Script { console.log("targetBlockTimeMs:", params.targetBlockTimeMs); } + function printPauseState() public view { + console.log("ProtocolConfig PauseState:"); + console.log("pauser:", PROTOCOL_CONFIG.pauser()); + console.log("paused:", PROTOCOL_CONFIG.paused()); + } + function printAllParams() public view { printFeeParams(); console.log(""); printConsensusParams(); + console.log(""); + printPauseState(); } // ============ Internal Helpers ============ diff --git a/contracts/scripts/ValidatorManagement.s.sol b/contracts/scripts/ValidatorManagement.s.sol index 0170cc3..947628d 100644 --- a/contracts/scripts/ValidatorManagement.s.sol +++ b/contracts/scripts/ValidatorManagement.s.sol @@ -101,6 +101,13 @@ contract ValidatorManagement is Script { * @notice Register's a new validators public key * @dev Requires VALIDATOR_REGISTERER_KEY to be set in the environment * @dev Requires VALIDATOR_PUBLIC_KEY_BYTES to be set in the environment + * + * Runs a cheap sanity check (length, not all-zero) before broadcasting. Full ed25519 + * curve-point validation cannot be done on-chain, and a bad key on-chain forces the + * consensus layer to skip the affected validator — reducing BFT security, and halting + * consensus entirely if the surviving validators no longer hold enough voting power + * for quorum. Operators should additionally cross-check VALIDATOR_PUBLIC_KEY_BYTES + * with off-chain tooling before running this script. */ function registerValidator() public returns (uint256 _registrationId) { uint256 _validatorRegistererKey = vm.envUint( @@ -110,12 +117,14 @@ contract ValidatorManagement is Script { "VALIDATOR_PUBLIC_KEY_BYTES" ); + requirePublicKeyBasicSanity(_validatorPublicKey); + vm.startBroadcast(_validatorRegistererKey); _registrationId = PERMISSIONED_VALIDATOR_MANAGER.registerValidator(_validatorPublicKey); vm.stopBroadcast(); console.log( - "Registered validator with registrationId:", _registrationId, + "Registered validator with registrationId:", _registrationId, "and public key:", vm.toString(_validatorPublicKey) ); } @@ -236,6 +245,23 @@ contract ValidatorManagement is Script { // ============ Internal Utils ============ + /** + * @notice Cheap off-chain sanity check on an ed25519 public key. + * @dev Catches obvious operator mistakes (wrong length, zero/placeholder bytes) before + * they reach the registry. Full curve-point validation requires ed25519 math that + * is not feasible on-chain; operators must validate with off-chain tooling as well. + * Public (rather than internal) to allow direct unit testing without going through + * environment-variable plumbing. + */ + function requirePublicKeyBasicSanity(bytes memory publicKey) public pure { + require(publicKey.length == 32, "Public key must be 32 bytes"); + bool allZero = true; + for (uint256 i = 0; i < publicKey.length; i++) { + if (publicKey[i] != 0x00) { allZero = false; break; } + } + require(!allZero, "Public key must not be all zero"); + } + /** * @notice Sanity-checks that a controller is valid for a voting power update */ diff --git a/contracts/src/batch/IMulticall3From.sol b/contracts/src/batch/IMulticall3From.sol index 78fe930..9626f0b 100644 --- a/contracts/src/batch/IMulticall3From.sol +++ b/contracts/src/batch/IMulticall3From.sol @@ -24,11 +24,16 @@ pragma solidity ^0.8.29; /// @dev EOA-only: aggregate-family entrypoints invoke the callFrom precompile, /// which requires the sender argument (`msg.sender` of Multicall3From) /// to equal the precompile caller or `tx.origin`. A contract caller is -/// neither, so callFrom reverts and the entire batch reverts regardless -/// of `requireSuccess` / `allowFailure`. +/// neither, so the precompile rejects every subcall. /// -/// Target-contract reverts are captured per-call in the `(success, -/// returnData)` tuple and gated by `requireSuccess` / `allowFailure`. +/// Precompile rejections are captured per-call in the `(success, +/// returnData)` tuple, just like target-contract reverts. Failure- +/// tolerant entrypoints (`aggregate3` with `allowFailure = true`, +/// `tryAggregate(false, …)`, `tryBlockAndAggregate(false, …)`) return +/// `success = false` with the precompile's revert reason in +/// `returnData`. Hard-fail entrypoints (`aggregate`, +/// `blockAndAggregate`, `tryAggregate(true, …)`, and `allowFailure = +/// false` paths) still revert the entire batch. interface IMulticall3From { struct Call { address target; diff --git a/contracts/src/batch/Multicall3From.sol b/contracts/src/batch/Multicall3From.sol index 16329ef..e173b08 100644 --- a/contracts/src/batch/Multicall3From.sol +++ b/contracts/src/batch/Multicall3From.sol @@ -29,28 +29,24 @@ import {IMulticall3From} from "./IMulticall3From.sol"; /// Reentrancy is safe: the contract holds no state that could be /// corrupted by a reentrant call. /// @dev EOA-only: contract callers hit the callFrom sender-spoofing -/// constraint and the entire batch reverts regardless of -/// `requireSuccess` / `allowFailure`. See {IMulticall3From}. +/// constraint. Failure-tolerant paths return `success = false`; +/// hard-fail paths revert the batch. See {IMulticall3From}. contract Multicall3From is IMulticall3From { ICallFrom public constant CALL_FROM = ICallFrom(Precompiles.CALL_FROM); /// @inheritdoc IMulticall3From - function aggregate(Call[] calldata calls) - external - returns (uint256 blockNumber, bytes[] memory returnData) - { + function aggregate(Call[] calldata calls) external returns (uint256 blockNumber, bytes[] memory returnData) { blockNumber = block.number; uint256 length = calls.length; returnData = new bytes[](length); - for (uint256 i = 0; i < length;) { - (bool success, bytes memory result) = CALL_FROM.callFrom(msg.sender, calls[i].target, calls[i].callData); + for (uint256 i = 0; i < length; ++i) { + (bool success, bytes memory result) = _callFrom(msg.sender, calls[i].target, calls[i].callData); if (!success) { assembly { revert(add(result, 0x20), mload(result)) } } returnData[i] = result; - unchecked { ++i; } } } @@ -58,15 +54,14 @@ contract Multicall3From is IMulticall3From { function aggregate3(Call3[] calldata calls) external returns (Result[] memory returnData) { uint256 length = calls.length; returnData = new Result[](length); - for (uint256 i = 0; i < length;) { - (bool success, bytes memory result) = CALL_FROM.callFrom(msg.sender, calls[i].target, calls[i].callData); + for (uint256 i = 0; i < length; ++i) { + (bool success, bytes memory result) = _callFrom(msg.sender, calls[i].target, calls[i].callData); returnData[i] = Result(success, result); if (!calls[i].allowFailure && !success) { assembly { revert(add(result, 0x20), mload(result)) } } - unchecked { ++i; } } } @@ -79,21 +74,17 @@ contract Multicall3From is IMulticall3From { } /// @inheritdoc IMulticall3From - function tryAggregate(bool requireSuccess, Call[] calldata calls) - external - returns (Result[] memory returnData) - { + function tryAggregate(bool requireSuccess, Call[] calldata calls) external returns (Result[] memory returnData) { uint256 length = calls.length; returnData = new Result[](length); - for (uint256 i = 0; i < length;) { - (bool success, bytes memory result) = CALL_FROM.callFrom(msg.sender, calls[i].target, calls[i].callData); + for (uint256 i = 0; i < length; ++i) { + (bool success, bytes memory result) = _callFrom(msg.sender, calls[i].target, calls[i].callData); if (requireSuccess && !success) { assembly { revert(add(result, 0x20), mload(result)) } } returnData[i] = Result(success, result); - unchecked { ++i; } } } @@ -166,15 +157,33 @@ contract Multicall3From is IMulticall3From { blockHash = blockhash(block.number); uint256 length = calls.length; returnData = new Result[](length); - for (uint256 i = 0; i < length;) { - (bool success, bytes memory result) = CALL_FROM.callFrom(msg.sender, calls[i].target, calls[i].callData); + for (uint256 i = 0; i < length; ++i) { + (bool success, bytes memory result) = _callFrom(msg.sender, calls[i].target, calls[i].callData); if (requireSuccess && !success) { assembly { revert(add(result, 0x20), mload(result)) } } returnData[i] = Result(success, result); - unchecked { ++i; } + } + } + + /// @dev Low-level call to the callFrom precompile. Captures framework-level + /// reverts (e.g. static context, sender-spoofing rejection) into the + /// return tuple instead of propagating them, so callers can honor + /// allowFailure / requireSuccess uniformly. + function _callFrom(address sender, address target, bytes calldata data) + internal + returns (bool success, bytes memory returnData) + { + (bool ok, bytes memory raw) = address(CALL_FROM).call( + abi.encodeCall(ICallFrom.callFrom, (sender, target, data)) + ); + if (ok) { + (success, returnData) = abi.decode(raw, (bool, bytes)); + } else { + success = false; + returnData = raw; } } } diff --git a/contracts/src/common/roles/Pausable.sol b/contracts/src/common/roles/Pausable.sol index 5577015..eaf3785 100644 --- a/contracts/src/common/roles/Pausable.sol +++ b/contracts/src/common/roles/Pausable.sol @@ -29,7 +29,6 @@ import {Ownable2StepUpgradeable} from "@openzeppelin/contracts-upgradeable/acces * @dev Uses ERC-7201 namespaced storage pattern for upgrade safety. */ abstract contract Pausable is Ownable2StepUpgradeable { - // ============ Errors ============ /// @notice Thrown when pauser address is zero @@ -157,20 +156,26 @@ abstract contract Pausable is Ownable2StepUpgradeable { /** * @notice Pauses the contract * @dev Only callable by pauser + * @dev Idempotent: repeated calls while paused leave state unchanged and emit no event. */ function pause() external virtual onlyPauser { PausableStorage storage $ = _getPausableStorage(); - $.paused = true; - emit Pause(); + if (!$.paused) { + $.paused = true; + emit Pause(); + } } /** * @notice Unpauses the contract * @dev Only callable by pauser + * @dev Idempotent: repeated calls while unpaused leave state unchanged and emit no event. */ function unpause() external virtual onlyPauser { PausableStorage storage $ = _getPausableStorage(); - $.paused = false; - emit Unpause(); + if ($.paused) { + $.paused = false; + emit Unpause(); + } } } diff --git a/contracts/src/pq/IPQ.sol b/contracts/src/pq/IPQ.sol index 912411e..5bb9af1 100644 --- a/contracts/src/pq/IPQ.sol +++ b/contracts/src/pq/IPQ.sol @@ -15,15 +15,17 @@ // limitations under the License. pragma solidity ^0.8.29; -/// @title IPQ — Post-Quantum cryptography precompile interface -/// @notice Exposes post-quantum cryptographic primitives to on-chain callers. +/// @title IPQ — Experimental post-quantum cryptography precompile interface +/// @notice Exposes post-quantum cryptographic primitives for early integrations. /// Additional algorithms may be added in future hardforks. interface IPQ { /// @notice Verify an SLH-DSA-SHA2-128s signature (FIPS 205). - /// @dev Gas cost: 230,000 base + 6 per 32-byte word of msg (same rate as KECCAK256). + /// @dev Since PQ signatures are still very new, we recommend not to solely rely on them for + /// authentication, but pair them with classical signatures. Gas cost: 230,000 base + 6 + /// per 32-byte word of message (same rate as KECCAK256). /// @param vk Verifying key (32 bytes) - /// @param msg Message that was signed + /// @param message Message that was signed /// @param sig Signature (7856 bytes) /// @return True if the signature is valid - function verifySlhDsaSha2128s(bytes memory vk, bytes memory msg, bytes memory sig) external view returns (bool); + function verifySlhDsaSha2128s(bytes memory vk, bytes memory message, bytes memory sig) external view returns (bool); } diff --git a/contracts/src/validator-manager/ValidatorRegistry.sol b/contracts/src/validator-manager/ValidatorRegistry.sol index 70d41f2..be248d9 100644 --- a/contracts/src/validator-manager/ValidatorRegistry.sol +++ b/contracts/src/validator-manager/ValidatorRegistry.sol @@ -34,6 +34,9 @@ contract ValidatorRegistry is IValidatorRegistry, Ownable2StepUpgradeable { /// @notice Thrown when a referenced registrationId is invalid error InvalidRegistrationId(uint256 registrationId); + /// @notice Thrown when a validator is not in the expected status for an operation + error InvalidValidatorStatus(uint256 registrationId, ValidatorStatus status); + /// @notice Thrown for invalid public key formats error InvalidPublicKeyFormat(); @@ -74,7 +77,8 @@ contract ValidatorRegistry is IValidatorRegistry, Ownable2StepUpgradeable { // Verify storage slot calculation is correct assert( VALIDATOR_REGISTRY_STORAGE_LOCATION - == keccak256(abi.encode(uint256(keccak256("arc.storage.ValidatorRegistry")) - 1)) & ~bytes32(uint256(0xff)) + == keccak256(abi.encode(uint256(keccak256("arc.storage.ValidatorRegistry")) - 1)) + & ~bytes32(uint256(0xff)) ); // Disable initializers on implementation contract @@ -87,7 +91,7 @@ contract ValidatorRegistry is IValidatorRegistry, Ownable2StepUpgradeable { * @inheritdoc IValidatorRegistry */ function registerValidator(bytes memory publicKey, uint64 votingPower) - public + external onlyOwner returns (uint256 registrationId) { @@ -119,7 +123,11 @@ contract ValidatorRegistry is IValidatorRegistry, Ownable2StepUpgradeable { function activateValidator(uint256 registrationId) external onlyOwner { ValidatorRegistryStorage storage $ = _getValidatorRegistryStorage(); Validator storage validatorInfo = $._validatorsByRegistrationId[registrationId]; - require(validatorInfo.status == ValidatorStatus.Registered, InvalidRegistrationId(registrationId)); + require(validatorInfo.status != ValidatorStatus.Unknown, InvalidRegistrationId(registrationId)); + require( + validatorInfo.status == ValidatorStatus.Registered, + InvalidValidatorStatus(registrationId, validatorInfo.status) + ); validatorInfo.status = ValidatorStatus.Active; $._activeValidatorRegistrations.add(registrationId); diff --git a/contracts/src/validator-manager/roles/Controller.sol b/contracts/src/validator-manager/roles/Controller.sol index 4ad1640..886b33b 100644 --- a/contracts/src/validator-manager/roles/Controller.sol +++ b/contracts/src/validator-manager/roles/Controller.sol @@ -157,22 +157,29 @@ abstract contract Controller is Ownable2StepUpgradeable { } /** - * @notice Get the registration ID for a controller + * @notice Get the registration ID for a configured controller. + * @dev Reverts with ControllerNotConfigured if the address is not a controller. + * Use {isController} for a non-reverting configuration check. * @param controller The address to get the registration ID for * @return The registration ID for the controller */ function getRegistrationId(address controller) external view returns (uint256) { ControllerStorage storage $ = _getControllerStorage(); - return $.registrationOf[controller]; + uint256 registrationId = $.registrationOf[controller]; + require(registrationId != 0, ControllerNotConfigured()); + return registrationId; } /** - * @notice Get the voting power limit for a controller + * @notice Get the voting power limit for a configured controller. + * @dev Reverts with ControllerNotConfigured if the address is not a controller. + * Use {isController} for a non-reverting configuration check. * @param controller The address to get the limit for * @return The voting power limit for the controller */ function getVotingPowerLimit(address controller) external view returns (uint64) { ControllerStorage storage $ = _getControllerStorage(); + require($.registrationOf[controller] != 0, ControllerNotConfigured()); return $.votingPowerLimitOf[controller]; } diff --git a/contracts/test/batch/Multicall3From.t.sol b/contracts/test/batch/Multicall3From.t.sol index 59aeaeb..ca527d5 100644 --- a/contracts/test/batch/Multicall3From.t.sol +++ b/contracts/test/batch/Multicall3From.t.sol @@ -52,6 +52,31 @@ contract MockCallFrom { } } +/// @dev Mock callFrom that reverts unconditionally when the target is the +/// designated "revert target". Simulates framework-level reverts (e.g. +/// static context, sender-spoofing rejection) that happen before the +/// child EVM frame is constructed. +contract MockRevertingCallFrom { + address public revertTarget; + + constructor(address _revertTarget) { + revertTarget = _revertTarget; + } + + function callFrom(address sender, address target, bytes calldata data) + external + returns (bool success, bytes memory returnData) + { + if (target == revertTarget) { + revert("subcall precompiles cannot be invoked in static context"); + } + // Normal path: forward to target + (success, returnData) = target.call(data); + // Suppress unused variable warning + sender; + } +} + /// @dev Simple target contract for testing. contract MockTarget { uint256 public value; @@ -427,4 +452,103 @@ contract Multicall3FromTest is Test { function test_getEthBalance() public view { assertEq(multicall.getEthBalance(address(this)), address(this).balance); } + + // ======================== Framework-level revert capture ======================== + + bytes constant EXPECTED_PRECOMPILE_REVERT = + abi.encodeWithSignature("Error(string)", "subcall precompiles cannot be invoked in static context"); + + function test_aggregate3_allowFailure_capturesPrecompileRevert() public { + address revertTarget = address(0xDEAD); + MockRevertingCallFrom revertingMock = new MockRevertingCallFrom(revertTarget); + vm.etch(Addresses.CALL_FROM, address(revertingMock).code); + vm.store(Addresses.CALL_FROM, bytes32(0), bytes32(uint256(uint160(revertTarget)))); + + IMulticall3From.Call3[] memory calls = new IMulticall3From.Call3[](2); + calls[0] = IMulticall3From.Call3({ + target: revertTarget, + allowFailure: true, + callData: abi.encodeCall(MockTarget.setValue, (1)) + }); + calls[1] = IMulticall3From.Call3({ + target: address(target), + allowFailure: false, + callData: abi.encodeCall(MockTarget.setValue, (42)) + }); + + IMulticall3From.Result[] memory results = multicall.aggregate3(calls); + + assertEq(results.length, 2); + assertFalse(results[0].success); + assertEq(results[0].returnData, EXPECTED_PRECOMPILE_REVERT); + assertTrue(results[1].success); + assertEq(target.value(), 42); + } + + function test_tryAggregate_requireSuccessFalse_capturesPrecompileRevert() public { + address revertTarget = address(0xDEAD); + MockRevertingCallFrom revertingMock = new MockRevertingCallFrom(revertTarget); + vm.etch(Addresses.CALL_FROM, address(revertingMock).code); + vm.store(Addresses.CALL_FROM, bytes32(0), bytes32(uint256(uint160(revertTarget)))); + + IMulticall3From.Call[] memory calls = new IMulticall3From.Call[](2); + calls[0] = IMulticall3From.Call({ + target: revertTarget, + callData: abi.encodeCall(MockTarget.setValue, (1)) + }); + calls[1] = IMulticall3From.Call({ + target: address(target), + callData: abi.encodeCall(MockTarget.setValue, (99)) + }); + + IMulticall3From.Result[] memory results = multicall.tryAggregate(false, calls); + + assertEq(results.length, 2); + assertFalse(results[0].success); + assertEq(results[0].returnData, EXPECTED_PRECOMPILE_REVERT); + assertTrue(results[1].success); + assertEq(target.value(), 99); + } + + function test_aggregate3_allowFailureFalse_precompileRevert_stillReverts() public { + address revertTarget = address(0xDEAD); + MockRevertingCallFrom revertingMock = new MockRevertingCallFrom(revertTarget); + vm.etch(Addresses.CALL_FROM, address(revertingMock).code); + vm.store(Addresses.CALL_FROM, bytes32(0), bytes32(uint256(uint160(revertTarget)))); + + IMulticall3From.Call3[] memory calls = new IMulticall3From.Call3[](1); + calls[0] = IMulticall3From.Call3({ + target: revertTarget, + allowFailure: false, + callData: abi.encodeCall(MockTarget.setValue, (1)) + }); + + vm.expectRevert(EXPECTED_PRECOMPILE_REVERT); + multicall.aggregate3(calls); + } + + function test_tryBlockAndAggregate_requireSuccessFalse_capturesPrecompileRevert() public { + address revertTarget = address(0xDEAD); + MockRevertingCallFrom revertingMock = new MockRevertingCallFrom(revertTarget); + vm.etch(Addresses.CALL_FROM, address(revertingMock).code); + vm.store(Addresses.CALL_FROM, bytes32(0), bytes32(uint256(uint160(revertTarget)))); + + IMulticall3From.Call[] memory calls = new IMulticall3From.Call[](2); + calls[0] = IMulticall3From.Call({ + target: revertTarget, + callData: abi.encodeCall(MockTarget.setValue, (1)) + }); + calls[1] = IMulticall3From.Call({ + target: address(target), + callData: abi.encodeCall(MockTarget.setValue, (55)) + }); + + (,, IMulticall3From.Result[] memory results) = multicall.tryBlockAndAggregate(false, calls); + + assertEq(results.length, 2); + assertFalse(results[0].success); + assertEq(results[0].returnData, EXPECTED_PRECOMPILE_REVERT); + assertTrue(results[1].success); + assertEq(target.value(), 55); + } } diff --git a/contracts/test/protocol-config/ProtocolConfig.t.sol b/contracts/test/protocol-config/ProtocolConfig.t.sol index 6c01fdf..b78bd4e 100644 --- a/contracts/test/protocol-config/ProtocolConfig.t.sol +++ b/contracts/test/protocol-config/ProtocolConfig.t.sol @@ -17,6 +17,7 @@ pragma solidity ^0.8.29; import {Test} from "forge-std/Test.sol"; +import {Vm} from "forge-std/Vm.sol"; import {console} from "forge-std/console.sol"; import {ProtocolConfig} from "../../src/protocol-config/ProtocolConfig.sol"; import {IProtocolConfig} from "../../src/protocol-config/interfaces/IProtocolConfig.sol"; @@ -1008,6 +1009,32 @@ contract ProtocolConfigTest is Test { protocolConfig.updateFeeParams(params); } + function test_pauseAndUnpause_IdempotentNoDuplicateEvents() public { + protocolConfig = deployProtocolConfig(owner, controller, pauser); + + vm.prank(pauser); + protocolConfig.pause(); + assertTrue(protocolConfig.paused()); + + vm.recordLogs(); + vm.prank(pauser); + protocolConfig.pause(); + Vm.Log[] memory pauseLogs = vm.getRecordedLogs(); + assertEq(pauseLogs.length, 0, "repeated pause should not emit"); + assertTrue(protocolConfig.paused()); + + vm.prank(pauser); + protocolConfig.unpause(); + assertFalse(protocolConfig.paused()); + + vm.recordLogs(); + vm.prank(pauser); + protocolConfig.unpause(); + Vm.Log[] memory unpauseLogs = vm.getRecordedLogs(); + assertEq(unpauseLogs.length, 0, "repeated unpause should not emit"); + assertFalse(protocolConfig.paused()); + } + // ============================================================================ // ACCESS CONTROL TESTS // ============================================================================ diff --git a/contracts/test/scripts/ValidatorManagement.t.sol b/contracts/test/scripts/ValidatorManagement.t.sol index 6d3377e..24caa26 100644 --- a/contracts/test/scripts/ValidatorManagement.t.sol +++ b/contracts/test/scripts/ValidatorManagement.t.sol @@ -136,6 +136,29 @@ contract ValidatorManagementTest is TestUtils { assertEq(uint256(_validator.status), 1); // Registered } + function test_RequirePublicKeyBasicSanity_AcceptsValidKey() public view { + validatorManagement.requirePublicKeyBasicSanity(newValidatorPublicKey); + } + + function test_RequirePublicKeyBasicSanity_RejectsWrongLengthKey() public { + // 31-byte key — one short of the required 32. + bytes memory tooShort = hex"c992c8696818bda11d628f38584022a6332c144b7f929b4d972bd39a23244a"; + vm.expectRevert("Public key must be 32 bytes"); + validatorManagement.requirePublicKeyBasicSanity(tooShort); + + // 33-byte key — one longer than the required 32. + bytes memory tooLong = + hex"c992c8696818bda11d628f38584022a6332c144b7f929b4d972bd39a23244aecbc"; + vm.expectRevert("Public key must be 32 bytes"); + validatorManagement.requirePublicKeyBasicSanity(tooLong); + } + + function test_RequirePublicKeyBasicSanity_RejectsAllZeroKey() public { + bytes memory allZero = new bytes(32); + vm.expectRevert("Public key must not be all zero"); + validatorManagement.requirePublicKeyBasicSanity(allZero); + } + function test_ConfigureController_Succeeds() public { uint256 _registrationId = _registerValidator(); _configureController(_registrationId); diff --git a/contracts/test/validator-manager/ValidatorRegistry.t.sol b/contracts/test/validator-manager/ValidatorRegistry.t.sol index b9c4d97..70f8d36 100644 --- a/contracts/test/validator-manager/ValidatorRegistry.t.sol +++ b/contracts/test/validator-manager/ValidatorRegistry.t.sol @@ -338,7 +338,11 @@ contract ValidatorRegistryTest is TestUtils { vm.startPrank(owner); // Registration should fail because validator1 public key is already in genesis - vm.expectRevert(abi.encodeWithSelector(ValidatorRegistry.ValidatorAlreadyRegistered.selector, keccak256(validator1PublicKey))); + vm.expectRevert( + abi.encodeWithSelector( + ValidatorRegistry.ValidatorAlreadyRegistered.selector, keccak256(validator1PublicKey) + ) + ); registry.registerValidator(validator1PublicKey, 200); vm.stopPrank(); @@ -476,7 +480,11 @@ contract ValidatorRegistryTest is TestUtils { _validatorRegistry.activateValidator(registrationId); // Try to activate the same validator again (should fail) - vm.expectRevert(abi.encodeWithSelector(ValidatorRegistry.InvalidRegistrationId.selector, registrationId)); + vm.expectRevert( + abi.encodeWithSelector( + ValidatorRegistry.InvalidValidatorStatus.selector, registrationId, ValidatorStatus.Active + ) + ); _validatorRegistry.activateValidator(registrationId); vm.stopPrank(); diff --git a/contracts/test/validator-manager/roles/Controller.t.sol b/contracts/test/validator-manager/roles/Controller.t.sol index b263e8b..b1b3c51 100644 --- a/contracts/test/validator-manager/roles/Controller.t.sol +++ b/contracts/test/validator-manager/roles/Controller.t.sol @@ -49,11 +49,6 @@ contract ControllerTest is Test { assertFalse(mockController.isController(controllerAddr)); assertFalse(mockController.isController(anotherController)); assertFalse(mockController.isController(address(0))); - - // Initially all controllers should have registration ID 0 - assertEq(mockController.getRegistrationId(controllerAddr), 0); - assertEq(mockController.getRegistrationId(anotherController), 0); - assertEq(mockController.getRegistrationId(address(0)), 0); } function test_ConfigureController_Success() public { @@ -116,8 +111,9 @@ contract ControllerTest is Test { // Verify controller was not configured assertFalse(mockController.isController(controllerAddr)); - // Verify registration ID remains 0 - assertEq(mockController.getRegistrationId(controllerAddr), 0); + // Registration ID getter must revert for unconfigured addresses + vm.expectRevert(Controller.ControllerNotConfigured.selector); + mockController.getRegistrationId(controllerAddr); } function test_ConfigureController_AlreadyConfigured() public { @@ -185,9 +181,11 @@ contract ControllerTest is Test { // Verify controller is no longer configured assertFalse(mockController.isController(controllerAddr)); - // Registration ID and voting power limit should be cleared - assertEq(mockController.getRegistrationId(controllerAddr), 0); - assertEq(mockController.getVotingPowerLimit(controllerAddr), 0); + // Getters must revert for unconfigured addresses + vm.expectRevert(Controller.ControllerNotConfigured.selector); + mockController.getRegistrationId(controllerAddr); + vm.expectRevert(Controller.ControllerNotConfigured.selector); + mockController.getVotingPowerLimit(controllerAddr); vm.stopPrank(); } @@ -248,7 +246,8 @@ contract ControllerTest is Test { // Only the second one should remain assertFalse(mockController.isController(controllerAddr)); assertTrue(mockController.isController(anotherController)); - assertEq(mockController.getVotingPowerLimit(controllerAddr), 0); + vm.expectRevert(Controller.ControllerNotConfigured.selector); + mockController.getVotingPowerLimit(controllerAddr); assertEq(mockController.getVotingPowerLimit(anotherController), defaultVotingPowerLimit); vm.stopPrank(); @@ -321,9 +320,11 @@ contract ControllerTest is Test { assertTrue(mockController.isController(anotherController)); // Verify registration ID is cleared after removal - assertEq(mockController.getRegistrationId(controllerAddr), 0); + vm.expectRevert(Controller.ControllerNotConfigured.selector); + mockController.getRegistrationId(controllerAddr); assertEq(mockController.getRegistrationId(anotherController), 2); - assertEq(mockController.getVotingPowerLimit(controllerAddr), 0); + vm.expectRevert(Controller.ControllerNotConfigured.selector); + mockController.getVotingPowerLimit(controllerAddr); assertEq(mockController.getVotingPowerLimit(anotherController), defaultVotingPowerLimit); // Re-configure the first one with a different registration ID @@ -342,8 +343,6 @@ contract ControllerTest is Test { mockController.removeController(anotherController); assertFalse(mockController.isController(controllerAddr)); assertFalse(mockController.isController(anotherController)); - assertEq(mockController.getVotingPowerLimit(controllerAddr), 0); - assertEq(mockController.getVotingPowerLimit(anotherController), 0); vm.stopPrank(); } diff --git a/crates/consensus-db/src/repositories/decided_blocks.rs b/crates/consensus-db/src/repositories/decided_blocks.rs index 7a66f5b..aeb8ac9 100644 --- a/crates/consensus-db/src/repositories/decided_blocks.rs +++ b/crates/consensus-db/src/repositories/decided_blocks.rs @@ -25,7 +25,6 @@ use arc_consensus_types::block::DecidedBlock; pub trait DecidedBlocksRepository { type Error: std::error::Error + Send + Sync + 'static; - #[allow(dead_code)] // unused for now async fn get(&self, height: Height) -> Result, Self::Error>; async fn store( diff --git a/crates/eth-engine/Cargo.toml b/crates/eth-engine/Cargo.toml index 473ff6f..7fc0237 100644 --- a/crates/eth-engine/Cargo.toml +++ b/crates/eth-engine/Cargo.toml @@ -24,6 +24,7 @@ alloy-sol-macro = { workspace = true } alloy-sol-types = { workspace = true } arc-consensus-types = { workspace = true } arc-execution-config = { workspace = true } +arc-shared = { workspace = true } async-trait = { workspace = true } backon = { workspace = true } diff --git a/crates/eth-engine/src/abi_utils.rs b/crates/eth-engine/src/abi_utils.rs index 043e84b..5f9a5ab 100644 --- a/crates/eth-engine/src/abi_utils.rs +++ b/crates/eth-engine/src/abi_utils.rs @@ -16,10 +16,11 @@ use std::time::Duration; -use tracing::debug; +use tracing::{debug, error}; use arc_consensus_types::signing::PublicKey; use arc_consensus_types::{Address, ConsensusParams, Validator, ValidatorSet}; +use arc_shared::metrics::validator_set::record_skipped_validator; use malachitebft_core_types::{LinearTimeouts, VotingPower}; use alloy_sol_macro::sol; @@ -51,32 +52,55 @@ sol! { function consensusParams() external view override returns (ContractConsensusParams memory); } -/// Decode validator set from ABI-encoded result +/// Decode validator set from ABI-encoded result. +/// +/// Validators with malformed public keys are skipped (logged at ERROR and counted in +/// `arc_validator_set_skipped_total`) rather than aborting the whole decode. The chain +/// keeps making progress on the surviving validators, even if they represent a small +/// fraction of the contract's advertised voting power — losing liveness while the +/// registry is corrupted is worse than running at reduced security until governance +/// removes the bad registrations. The empty-set case still errors, since consensus +/// cannot proceed with zero validators. +/// +/// **Operator alerting on `arc_validator_set_skipped_total` is load-bearing here.** +/// A non-zero rate is the only signal that the on-chain set has malformed keys and +/// the chain is operating with a degraded validator set; without an alert, the +/// degradation is silent. +/// +/// `PublicKey::from_bytes` is deterministic, so every node reaches the same filtered +/// set — there is no fork risk. pub fn abi_decode_validator_set(result: Vec) -> eyre::Result { // Decode the function's return payload exactly as the ABI defines it. let active_validators: Vec = getActiveValidatorSetCall::abi_decode_returns(&result)?; - // Map contract validators to use Malachite's domain type - let validators = active_validators - .into_iter() - .filter(|cv| cv.status == ContractValidatorStatus::Active && cv.votingPower > 0) - .map(|cv| { - // Convert contract publicKey bytes to Malachite's domain type - if cv.publicKey.len() != 32 { - eyre::bail!( - "Public key must be exactly 32 bytes, got {}", - cv.publicKey.len() + let mut validators: Vec = Vec::new(); + let mut skipped: usize = 0; + + for cv in &active_validators { + if cv.status != ContractValidatorStatus::Active || cv.votingPower == 0 { + continue; + } + + match try_decode_validator(cv) { + Ok(validator) => validators.push(validator), + Err(e) => { + error!( + public_key = %format!("0x{}", hex::encode(&cv.publicKey)), + "Skipping active validator with malformed public key: {e:#}", ); + record_skipped_validator(); + skipped = skipped.saturating_add(1); } - let mut pk = [0u8; 32]; - pk.copy_from_slice(&cv.publicKey); + } + } - let public_key = PublicKey::from_bytes(pk); - let voting_power: VotingPower = cv.votingPower; - Ok(Validator::new(public_key, voting_power)) - }) - .collect::>>()?; + if validators.is_empty() { + eyre::bail!( + "No active validators with valid public keys — validator set would be empty \ + ({skipped} active validator(s) skipped due to malformed public keys)" + ); + } debug!("ABI decoded validators:"); for validator in &validators { @@ -91,6 +115,23 @@ pub fn abi_decode_validator_set(result: Vec) -> eyre::Result { Ok(ValidatorSet::new(validators)) } +/// Convert a single `ContractValidator` from the ABI payload into the domain `Validator` type. +fn try_decode_validator(cv: &ContractValidator) -> eyre::Result { + if cv.publicKey.len() != 32 { + eyre::bail!( + "Public key must be exactly 32 bytes, got {}", + cv.publicKey.len() + ); + } + let mut pk = [0u8; 32]; + pk.copy_from_slice(&cv.publicKey); + + let public_key = PublicKey::from_bytes(pk) + .map_err(|e| eyre::eyre!("Failed to decode public key bytes: {e}"))?; + let voting_power: VotingPower = cv.votingPower; + Ok(Validator::new(public_key, voting_power)) +} + pub fn abi_decode_consensus_params(result: Vec) -> eyre::Result { // Decode the function's return payload exactly as the ABI defines it. let contract_params: ContractConsensusParams = @@ -159,8 +200,8 @@ mod tests { pub_keys.sort_unstable_by(|pk, pk2| { let pk_bytes: [u8; 32] = hex::decode(pk).unwrap().try_into().unwrap(); let pk2_bytes: [u8; 32] = hex::decode(pk2).unwrap().try_into().unwrap(); - let a1 = Address::from_public_key(&PublicKey::from_bytes(pk_bytes)); - let a2 = Address::from_public_key(&PublicKey::from_bytes(pk2_bytes)); + let a1 = Address::from_public_key(&PublicKey::from_bytes(pk_bytes).unwrap()); + let a2 = Address::from_public_key(&PublicKey::from_bytes(pk2_bytes).unwrap()); a1.cmp(&a2) }); @@ -179,8 +220,119 @@ mod tests { } } + /// A known-good ed25519 public key used to populate valid test validators. + const VALID_PK_A: &str = "c992c8696818bda11d628f38584022a6332c144b7f929b4d972bd39a23244aec"; + /// A second known-good ed25519 public key. + const VALID_PK_B: &str = "35121369a803f64463e1688af1ba5d963a40b7d71eeffadd8496f1d5b8d61d53"; + /// A third known-good ed25519 public key. + const VALID_PK_C: &str = "eda755457e2e7b8cb56956372611f5b5d37698eef26aa7fdb01616a6e7824f22"; + + /// Build an Active `ContractValidator` with the given 32-byte public key and voting power. + fn active_validator(pk_bytes: [u8; 32], voting_power: u64) -> ContractValidator { + ContractValidator { + status: ContractValidatorStatus::Active, + publicKey: pk_bytes.to_vec().into(), + votingPower: voting_power, + } + } + + fn pk(hex_str: &str) -> [u8; 32] { + hex::decode(hex_str).unwrap().try_into().unwrap() + } + + /// A 32-byte blob that `PublicKey::from_bytes` rejects as malformed. Derived from + /// `VALID_PK_A` by flipping bit 3 of byte 13 (`0x40` → `0x48`) — the same technique + /// used by `test_corrupted_public_key_leaves_set_empty_and_errors`. The result is + /// distinct from all `VALID_PK_*` constants so it can be combined with any of them + /// in the same test. + const MALFORMED_PK: &str = "c992c8696818bda11d628f38584822a6332c144b7f929b4d972bd39a23244aec"; + + fn malformed_pk_bytes() -> [u8; 32] { + let pk_bytes = pk(MALFORMED_PK); + assert!( + PublicKey::from_bytes(pk_bytes).is_err(), + "test precondition: MALFORMED_PK must be rejected by PublicKey::from_bytes", + ); + pk_bytes + } + + fn encode_validators(validators: Vec) -> Vec { + getActiveValidatorSetCall::abi_encode_returns(&validators) + } + + #[test] + fn test_skip_single_malformed_validator_in_set() { + // 3 active validators, middle one has a malformed public key. The decoder should + // drop the malformed validator and return a set of 2 with accurate voting power. + let validators = vec![ + active_validator(pk(VALID_PK_A), 10), + active_validator(malformed_pk_bytes(), 10), + active_validator(pk(VALID_PK_B), 10), + ]; + let encoded = encode_validators(validators); + + let valset = abi_decode_validator_set(encoded).expect("set should decode"); + + assert_eq!(valset.validators.len(), 2); + // Total voting power reflects only the retained validators; the malformed one is excluded. + assert_eq!(valset.total_voting_power(), 20); + } + + #[test] + fn test_majority_malformed_still_decodes_reduced_set() { + // 3 active validators with equal voting power; 2 are malformed. Even though malformed + // VP (2/3) far exceeds the BFT byzantine threshold, the surviving validator is + // returned — we prefer reduced security over a halted chain, and rely on operators + // alerting on `arc_validator_set_skipped_total` to drive recovery. + let validators = vec![ + active_validator(pk(VALID_PK_A), 10), + active_validator(malformed_pk_bytes(), 10), + active_validator(malformed_pk_bytes(), 10), + ]; + let encoded = encode_validators(validators); + + let valset = abi_decode_validator_set(encoded).expect("set should decode"); + + assert_eq!(valset.validators.len(), 1); + assert_eq!(valset.total_voting_power(), 10); + } + + #[test] + fn test_minority_malformed_decodes_reduced_set() { + // 4 active validators with equal voting power; 1 is malformed. Returns the reduced + // set with 3 validators and a recomputed total voting power. + let validators = vec![ + active_validator(pk(VALID_PK_A), 10), + active_validator(pk(VALID_PK_B), 10), + active_validator(pk(VALID_PK_C), 10), + active_validator(malformed_pk_bytes(), 10), + ]; + let encoded = encode_validators(validators); + + let valset = abi_decode_validator_set(encoded).expect("set should decode"); + + assert_eq!(valset.validators.len(), 3); + assert_eq!(valset.total_voting_power(), 30); + } + #[test] - fn test_corrupted_public_key_validation_fails() { + fn test_lopsided_voting_power_decodes_reduced_set() { + // Heavy-VP validator survives, light-VP one is malformed. Confirms that the + // returned total reflects only the survivors, not the contract's advertised total. + let validators = vec![ + active_validator(pk(VALID_PK_A), 90), + active_validator(malformed_pk_bytes(), 10), + ]; + let encoded = encode_validators(validators); + + let valset = abi_decode_validator_set(encoded).expect("set should decode"); + + assert_eq!(valset.validators.len(), 1); + assert_eq!(valset.total_voting_power(), 90); + } + + #[test] + fn test_corrupted_public_key_leaves_set_empty_and_errors() { let pub_key = "c992c8696818bda11d628f38584022a6332c144b7f929b4d972bd39a23244aec"; let raw_valset = [ @@ -201,21 +353,14 @@ mod tests { let flip_byte_index = bytes.len() - 19; // Flip a bit to corrupt the public key bytes[flip_byte_index] ^= 0b0000_1000; - // Then, try with corrupted pubkey - let result = std::panic::catch_unwind(|| { - let _ = abi_decode_validator_set(bytes.clone()); - }); - assert!(result.is_err(), "Expected panic on malformed public key"); - - let payload = result.unwrap_err(); - let panic_msg = payload - .downcast_ref::() - .map(|s| s.as_str()) - .or_else(|| payload.downcast_ref::<&str>().copied()) - .unwrap_or(""); + // With only one validator and that validator malformed, the filtered set is empty — + // abi_decode_validator_set returns an error rather than producing an empty ValidatorSet. + let err = abi_decode_validator_set(bytes) + .expect_err("decoding a sole malformed public key should fail"); + let msg = format!("{err:#}"); assert!( - panic_msg.contains("MalformedPublicKey"), - "Expected panic with 'MalformedPublicKey', got: {panic_msg}", + msg.contains("validator set would be empty"), + "unexpected error: {msg}", ); } diff --git a/crates/eth-engine/src/engine.rs b/crates/eth-engine/src/engine.rs index 39a8734..180116a 100644 --- a/crates/eth-engine/src/engine.rs +++ b/crates/eth-engine/src/engine.rs @@ -224,21 +224,15 @@ impl Engine { /// /// This is the fallback when `--genesis` is not provided. pub fn set_osaka_from_chain_id(&self, chain_id: u64) { - use arc_execution_config::chain_ids::*; - use arc_execution_config::chainspec::{DEVNET, LOCAL_DEV, TESTNET}; + use arc_execution_config::chainspec::bundled_chainspec_for_chain_id; use reth_chainspec::EthereumHardforks; - let chainspec = match chain_id { - LOCALDEV_CHAIN_ID => LOCAL_DEV.clone(), - DEVNET_CHAIN_ID => DEVNET.clone(), - TESTNET_CHAIN_ID => TESTNET.clone(), - _ => { - tracing::warn!( - chain_id, - "Unknown chain ID for Osaka activation; defaulting to V4 (Osaka disabled)" - ); - return; - } + let Some(chainspec) = bundled_chainspec_for_chain_id(chain_id) else { + tracing::warn!( + chain_id, + "Unknown chain ID for Osaka activation; defaulting to V4 (Osaka disabled)" + ); + return; }; tracing::info!( @@ -760,6 +754,39 @@ mod tests { ); } + #[test] + fn test_set_osaka_from_chain_id_mainnet() { + use arc_execution_config::chainspec::bundled_chainspec_for_chain_id; + + let engine = mock_engine(); + engine.set_osaka_from_chain_id(MAINNET_CHAIN_ID); + + let expected = bundled_chainspec_for_chain_id(MAINNET_CHAIN_ID).is_some(); + assert_eq!( + engine.use_v5(0), + expected, + "mainnet use_v5(0) must agree with bundle status" + ); + } + + /// Second call to `set_is_osaka_active` are silently ignored. + #[test] + fn test_set_is_osaka_active_subsequent_calls_are_ignored() { + let engine = mock_engine(); + engine.set_is_osaka_active(Arc::new(|_| true)); + engine.set_is_osaka_active(Arc::new(|_| false)); + assert!( + engine.use_v5(0), + "subsequent calls to set_is_osaka_active must be ignored" + ); + } + + #[test] + fn test_subscription_endpoint_none_for_mock() { + let engine = mock_engine(); + assert!(engine.subscription_endpoint().is_none()); + } + #[tokio::test] async fn wait_for_disconnect_never_resolves_for_mock_engine() { let engine = mock_engine(); diff --git a/crates/eth-engine/tests/integration.rs b/crates/eth-engine/tests/integration.rs index a20b32e..695662b 100644 --- a/crates/eth-engine/tests/integration.rs +++ b/crates/eth-engine/tests/integration.rs @@ -159,6 +159,7 @@ async fn test_engine() { None, true, true, + 160 * 1024 * 1024, std::time::Duration::from_secs(0), // disable rebroadcast in integration tests ); let node_handle = NodeBuilder::new(node_config) diff --git a/crates/evm-node/src/node.rs b/crates/evm-node/src/node.rs index fd2ff96..9bafb97 100644 --- a/crates/evm-node/src/node.rs +++ b/crates/evm-node/src/node.rs @@ -99,10 +99,13 @@ pub struct ArcNode { pub wait_for_payload: bool, /// When true (default), pending-tx RPCs are restricted /// (`eth_subscribe("newPendingTransactions")`, `eth_newPendingTransactionFilter`, - /// and `eth_getBlockByNumber("pending")`). When false, all requests are forwarded. + /// `eth_getBlockByNumber("pending")`, and `eth_getTransactionBySenderAndNonce`). + /// When false, all requests are forwarded. /// CLI users opt out of the default via `--arc.expose-pending-txs`. /// `--public-api` also forces this to `true` (and conflicts with `--arc.expose-pending-txs`). pub filter_pending_txs: bool, + /// Maximum batch response size in bytes, mirrors `--rpc.max-response-size`. + pub max_response_body_size: u32, /// Interval between tx rebroadcast rounds. Zero disables rebroadcast. pub rebroadcast_interval: std::time::Duration, } @@ -116,6 +119,7 @@ impl Default for ArcNode { payload_builder_deadline_ms: None, wait_for_payload: true, filter_pending_txs: true, + max_response_body_size: 160 * 1024 * 1024, rebroadcast_interval: crate::rebroadcast::DEFAULT_REBROADCAST_INTERVAL, } } @@ -123,6 +127,7 @@ impl Default for ArcNode { impl ArcNode { /// Creates a new `ArcNode`. + #[allow(clippy::too_many_arguments)] pub fn new( rpc_cfg: ArcRpcConfig, invalid_tx_list_cfg: InvalidTxListConfig, @@ -130,6 +135,7 @@ impl ArcNode { payload_builder_deadline_ms: Option, wait_for_payload: bool, filter_pending_txs: bool, + max_response_body_size: u32, rebroadcast_interval: std::time::Duration, ) -> Self { Self { @@ -139,6 +145,7 @@ impl ArcNode { payload_builder_deadline_ms, wait_for_payload, filter_pending_txs, + max_response_body_size, rebroadcast_interval, } } @@ -512,7 +519,10 @@ where fn add_ons(&self) -> Self::AddOns { ArcAddOns::default() .with_arc_rpc_config(self.rpc_cfg.clone()) - .with_rpc_middleware(ArcRpcLayer::new(self.filter_pending_txs)) + .with_rpc_middleware(ArcRpcLayer::new( + self.filter_pending_txs, + self.max_response_body_size as usize, + )) } } @@ -701,6 +711,7 @@ mod tests { None, true, true, + 160 * 1024 * 1024, crate::rebroadcast::DEFAULT_REBROADCAST_INTERVAL, ); @@ -729,6 +740,7 @@ mod tests { None, true, true, + 160 * 1024 * 1024, crate::rebroadcast::DEFAULT_REBROADCAST_INTERVAL, ); assert!(node.addresses_denylist_config.is_enabled()); @@ -763,6 +775,7 @@ mod tests { None, true, false, + 160 * 1024 * 1024, crate::rebroadcast::DEFAULT_REBROADCAST_INTERVAL, ); assert!(!node.filter_pending_txs); @@ -786,6 +799,7 @@ mod tests { None, false, false, + 160 * 1024 * 1024, crate::rebroadcast::DEFAULT_REBROADCAST_INTERVAL, ); assert!(!node.wait_for_payload); @@ -809,6 +823,7 @@ mod tests { None, true, true, + 160 * 1024 * 1024, std::time::Duration::ZERO, ); assert!(node.rebroadcast_interval.is_zero()); diff --git a/crates/evm-node/src/rebroadcast.rs b/crates/evm-node/src/rebroadcast.rs index 4552b35..910f866 100644 --- a/crates/evm-node/src/rebroadcast.rs +++ b/crates/evm-node/src/rebroadcast.rs @@ -144,7 +144,7 @@ mod tests { fn funded_tx(provider: &MockEthProvider, nonce: u64) -> MockTransaction { let tx = MockTransaction::legacy() - .with_gas_limit(26_000) + .with_gas_limit(21_000) .with_gas_price(1_000_000_000) .with_value(U256::from(1000)) .with_nonce(nonce); diff --git a/crates/evm-node/src/rpc_middleware.rs b/crates/evm-node/src/rpc_middleware.rs index 3c8a648..6128972 100644 --- a/crates/evm-node/src/rpc_middleware.rs +++ b/crates/evm-node/src/rpc_middleware.rs @@ -15,11 +15,9 @@ // limitations under the License. use jsonrpsee::{ - core::middleware::{ - layer::Either, Batch, BatchEntry, BatchEntryErr, Notification, RpcServiceT, - }, + core::middleware::{layer::Either, Batch, BatchEntry, Notification, RpcServiceT}, types::{ErrorObject, ErrorObjectOwned, Request, ResponsePayload}, - MethodResponse, + BatchResponseBuilder, MethodResponse, }; use std::future::Future; use tower::Layer; @@ -29,16 +27,20 @@ const PENDING_TX_SUBSCRIPTION_TYPE: &str = "newPendingTransactions"; const ETH_NEW_PENDING_TX_FILTER_METHOD: &str = "eth_newPendingTransactionFilter"; const ETH_GET_BLOCK_BY_NUMBER_METHOD: &str = "eth_getBlockByNumber"; const PENDING_BLOCK_TAG: &str = "pending"; +const ETH_GET_TX_BY_SENDER_AND_NONCE_METHOD: &str = "eth_getTransactionBySenderAndNonce"; const PENDING_TX_SUBSCRIPTION_ERROR_CODE: i32 = -32001; /// Adds Arc-specific RPC middlewares #[derive(Clone, Debug)] pub struct ArcRpcLayer { /// When true (default), `eth_subscribe("newPendingTransactions")`, - /// `eth_newPendingTransactionFilter`, and `eth_getBlockByNumber("pending")` - /// are blocked. When false, the filter is bypassed and these are allowed. + /// `eth_newPendingTransactionFilter`, `eth_getBlockByNumber("pending")`, + /// and `eth_getTransactionBySenderAndNonce` are blocked. + /// When false, the filter is bypassed and these are allowed. /// CLI users opt out of the default via `--arc.expose-pending-txs`. pub filter_pending_txs: bool, + /// Mirrors `--rpc.max-response-size` from the server config (in bytes). + pub max_response_body_size: usize, } impl Default for ArcRpcLayer { @@ -47,14 +49,18 @@ impl Default for ArcRpcLayer { fn default() -> Self { Self { filter_pending_txs: true, + max_response_body_size: usize::MAX, } } } impl ArcRpcLayer { - /// Creates a new `ArcRpcLayer` with the given filter setting. - pub fn new(filter_pending_txs: bool) -> Self { - Self { filter_pending_txs } + /// Creates a new `ArcRpcLayer` with the given filter and response size settings. + pub fn new(filter_pending_txs: bool, max_response_body_size: usize) -> Self { + Self { + filter_pending_txs, + max_response_body_size, + } } } @@ -68,7 +74,10 @@ where fn layer(&self, inner: S) -> Self::Service { if self.filter_pending_txs { - Either::Left(NoPendingTransactionsRpcMiddleware { service: inner }) + Either::Left(NoPendingTransactionsRpcMiddleware { + service: inner, + max_response_body_size: self.max_response_body_size, + }) } else { Either::Right(inner) } @@ -79,19 +88,26 @@ where #[derive(Clone, Debug)] pub struct NoPendingTransactionsRpcMiddleware { service: S, + max_response_body_size: usize, } impl NoPendingTransactionsRpcMiddleware { /// Creates a new instance of the middleware. pub fn new(service: S) -> Self { - Self { service } + Self { + service, + max_response_body_size: usize::MAX, + } } } impl RpcServiceT for NoPendingTransactionsRpcMiddleware where - S: RpcServiceT - + Send + S: RpcServiceT< + MethodResponse = MethodResponse, + NotificationResponse = MethodResponse, + BatchResponse = MethodResponse, + > + Send + Sync + Clone + 'static, @@ -102,41 +118,40 @@ where fn call<'a>(&self, req: Request<'a>) -> impl Future + Send + 'a { let service = self.service.clone(); - - async move { - // Error if the request is a pending transactions subscription or filter - if let Err(err) = error_if_pending_tx_rpc(&req) { - return MethodResponse::error(req.id(), err); - } - - if is_pending_block_query(&req) { - return null_response(&req); - } - - service.call(req).await - } + async move { intercept_or_forward(&service, req).await } } fn batch<'a>(&self, req: Batch<'a>) -> impl Future + Send + 'a { - // Pending-block filtering is intentionally skipped for batch requests. - // --rpc.pending-block=none (binary default) + network topology make this unexploitable. - let batch = req - .into_iter() - .map( - |request: Result, BatchEntryErr<'_>>| match request { + let service = self.service.clone(); + let max_response_body_size = self.max_response_body_size; + async move { + let mut builder = BatchResponseBuilder::new_with_limit(max_response_body_size); + let mut got_notif = false; + for entry in req { + match entry { Ok(BatchEntry::Call(request)) => { - // Error if the batch contains a pending transactions subscription or filter - error_if_pending_tx_rpc(&request) - .map_err(|err| BatchEntryErr::new(request.id(), err))?; - Ok(BatchEntry::Call(request)) + let response = intercept_or_forward(&service, request).await; + if let Err(too_large) = builder.append(response) { + return too_large; + } } - _ => request, - }, - ) - .collect::>(); - - // Forward the batch to the underlying service - self.service.batch(Batch::from(batch)) + Ok(BatchEntry::Notification(notification)) => { + got_notif = true; + service.notification(notification).await; + } + Err(err) => { + let (error, id) = err.into_parts(); + if let Err(too_large) = builder.append(MethodResponse::error(id, error)) { + return too_large; + } + } + } + } + if builder.is_empty() && got_notif { + return MethodResponse::notification(); + } + MethodResponse::from_batch(builder.finish()) + } } fn notification<'a>( @@ -147,6 +162,20 @@ where } } +/// Intercepts pending-tx RPCs (error or null) or forwards to the inner service. +async fn intercept_or_forward<'a, S>(service: &S, req: Request<'a>) -> MethodResponse +where + S: RpcServiceT + Send + Sync, +{ + if let Err(err) = error_if_pending_tx_rpc(&req) { + return MethodResponse::error(req.id(), err); + } + if is_pool_pending_tx_lookup(&req) || is_pending_block_query(&req) { + return null_response(&req); + } + service.call(req).await +} + /// Returns an error if the request is a pending-tx RPC (subscription or filter) that would leak pending transaction data. fn error_if_pending_tx_rpc<'a>(req: &Request<'a>) -> Result<(), ErrorObject<'a>> { if req.method_name() == ETH_NEW_PENDING_TX_FILTER_METHOD { @@ -174,6 +203,11 @@ fn error_if_pending_tx_rpc<'a>(req: &Request<'a>) -> Result<(), ErrorObject<'a>> Ok(()) } +/// Returns true if the request queries the transaction pool directly for pending tx data. +fn is_pool_pending_tx_lookup(req: &Request<'_>) -> bool { + req.method_name() == ETH_GET_TX_BY_SENDER_AND_NONCE_METHOD +} + /// Returns true if the request is `eth_getBlockByNumber("pending", ...)`. /// /// The consensus engine may briefly expose a pending block via `provider().pending_block()` @@ -457,11 +491,54 @@ mod tests { ); } + // -- pool pending tx lookup: intercepted -- + // + // eth_getTransactionBySenderAndNonce returns null (success, not error) + // because it directly queries the pool for pending tx contents. + + #[tokio::test] + async fn test_enabled_pool_lookup_returns_null() { + let middleware = NoPendingTransactionsRpcMiddleware::new(MockRpcService); + let params = RawValue::from_string("[]".to_string()).unwrap(); + let request = create_request_with_params(ETH_GET_TX_BY_SENDER_AND_NONCE_METHOD, params, 1); + let response = middleware.call(request).await; + + assert!( + response.as_error_code().is_none(), + "should return success, not error" + ); + let json: serde_json::Value = serde_json::from_str(response.into_json().get()).unwrap(); + assert!( + json["result"].is_null(), + "should return null for pool lookup" + ); + } + + #[tokio::test] + async fn test_enabled_pool_lookup_with_params_returns_null() { + let middleware = NoPendingTransactionsRpcMiddleware::new(MockRpcService); + let params = RawValue::from_string( + r#"["0x1234567890abcdef1234567890abcdef12345678", "0x0"]"#.to_string(), + ) + .unwrap(); + let request = create_request_with_params(ETH_GET_TX_BY_SENDER_AND_NONCE_METHOD, params, 2); + let response = middleware.call(request).await; + + assert!( + response.as_error_code().is_none(), + "should return success, not error" + ); + let json: serde_json::Value = serde_json::from_str(response.into_json().get()).unwrap(); + assert!( + json["result"].is_null(), + "should return null for pool lookup with params" + ); + } + // -- batch requests -- // - // Pending-tx subscription/filter are blocked in batch. - // Pending block interception is intentionally NOT applied in batch; falls back to - // --rpc.pending-block=none. + // Batch entries are intercepted per-entry, consistent with the single-request path: + // subscriptions/filters → error, pool lookups/pending block → null. #[tokio::test] async fn test_enabled_batch_blocks_pending_tx_subscription() { @@ -515,7 +592,7 @@ mod tests { } #[tokio::test] - async fn test_enabled_batch_pending_block_passes_through() { + async fn test_enabled_batch_pending_block_returns_null() { let middleware = NoPendingTransactionsRpcMiddleware::new(MockRpcService); let batch = Batch::from(vec![ Ok(BatchEntry::Call(create_request_with_params( @@ -535,6 +612,126 @@ mod tests { assert_eq!(responses.len(), 2); assert_eq!(responses[0]["result"], "success"); + assert!( + responses[1]["result"].is_null(), + "batch pending block should return null" + ); + } + + #[tokio::test] + async fn test_enabled_batch_pool_lookup_returns_null() { + let middleware = NoPendingTransactionsRpcMiddleware::new(MockRpcService); + let batch = Batch::from(vec![ + Ok(BatchEntry::Call(create_request_with_params( + "eth_blockNumber", + RawValue::from_string("[]".to_string()).unwrap(), + 1, + ))), + Ok(BatchEntry::Call(create_request_with_params( + ETH_GET_TX_BY_SENDER_AND_NONCE_METHOD, + RawValue::from_string( + r#"["0x1234567890abcdef1234567890abcdef12345678", "0x0"]"#.to_string(), + ) + .unwrap(), + 2, + ))), + ]); + let response = middleware.batch(batch).await; + let json = response.into_json(); + let responses: Vec = serde_json::from_str(json.get()).unwrap(); + + assert_eq!(responses.len(), 2); + assert_eq!(responses[0]["result"], "success"); + assert!( + responses[1]["result"].is_null(), + "batch pool lookup should return null" + ); + } + + #[tokio::test] + async fn test_enabled_batch_mixed_interceptions() { + let middleware = NoPendingTransactionsRpcMiddleware::new(MockRpcService); + let batch = Batch::from(vec![ + // 0: normal method → success + Ok(BatchEntry::Call(create_request_with_params( + "eth_blockNumber", + RawValue::from_string("[]".to_string()).unwrap(), + 1, + ))), + // 1: pool lookup → null + Ok(BatchEntry::Call(create_request_with_params( + ETH_GET_TX_BY_SENDER_AND_NONCE_METHOD, + RawValue::from_string("[]".to_string()).unwrap(), + 2, + ))), + // 2: pending block → null + Ok(BatchEntry::Call(create_request_with_params( + ETH_GET_BLOCK_BY_NUMBER_METHOD, + RawValue::from_string(r#"["pending", false]"#.to_string()).unwrap(), + 3, + ))), + // 3: pending tx subscription → error + Ok(BatchEntry::Call(create_request_with_params( + ETH_SUBSCRIBE_METHOD, + RawValue::from_string(r#"["newPendingTransactions"]"#.to_string()).unwrap(), + 4, + ))), + ]); + let response = middleware.batch(batch).await; + let json = response.into_json(); + let responses: Vec = serde_json::from_str(json.get()).unwrap(); + + assert_eq!(responses.len(), 4); + assert_eq!( + responses[0]["result"], "success", + "normal method should succeed" + ); + assert!( + responses[1]["result"].is_null(), + "pool lookup should return null" + ); + assert!( + responses[2]["result"].is_null(), + "pending block should return null" + ); + assert!( + responses[3].get("error").is_some(), + "pending tx subscription should error" + ); + } + + #[tokio::test] + async fn test_enabled_batch_notification_excluded_from_response() { + let middleware = NoPendingTransactionsRpcMiddleware::new(MockRpcService); + let batch = Batch::from(vec![ + Ok(BatchEntry::Call(create_request_with_params( + "eth_blockNumber", + RawValue::from_string("[]".to_string()).unwrap(), + 1, + ))), + Ok(BatchEntry::Notification(Notification::new( + Cow::Borrowed("eth_subscription"), + Some(Cow::Owned( + RawValue::from_string(r#"{"subscription":"0x1","result":"0x123"}"#.to_string()) + .unwrap(), + )), + ))), + Ok(BatchEntry::Call(create_request_with_params( + "eth_chainId", + RawValue::from_string("[]".to_string()).unwrap(), + 2, + ))), + ]); + let response = middleware.batch(batch).await; + let json = response.into_json(); + let responses: Vec = serde_json::from_str(json.get()).unwrap(); + + assert_eq!( + responses.len(), + 2, + "notification must not produce a response entry" + ); + assert_eq!(responses[0]["result"], "success"); assert_eq!(responses[1]["result"], "success"); } @@ -546,6 +743,7 @@ mod tests { async fn test_disabled_allows_pending_tx_subscription() { let layer = ArcRpcLayer { filter_pending_txs: false, + ..Default::default() }; let middleware = layer.layer(MockRpcService); let params = RawValue::from_string(r#"["newPendingTransactions"]"#.to_string()).unwrap(); @@ -556,12 +754,18 @@ mod tests { response.as_error_code().is_none(), "filter_pending_txs=false should allow newPendingTransactions" ); + let json: serde_json::Value = serde_json::from_str(response.into_json().get()).unwrap(); + assert_eq!( + json["result"], "success", + "filter_pending_txs=false should forward to inner service" + ); } #[tokio::test] async fn test_disabled_allows_pending_tx_filter() { let layer = ArcRpcLayer { filter_pending_txs: false, + ..Default::default() }; let middleware = layer.layer(MockRpcService); let params = RawValue::from_string(r#"[]"#.to_string()).unwrap(); @@ -571,12 +775,43 @@ mod tests { response.as_error_code().is_none(), "filter_pending_txs=false should allow newPendingTransactionFilter" ); + let json: serde_json::Value = serde_json::from_str(response.into_json().get()).unwrap(); + assert_eq!( + json["result"], "success", + "filter_pending_txs=false should forward to inner service" + ); + } + + #[tokio::test] + async fn test_disabled_allows_pool_lookup() { + let layer = ArcRpcLayer { + filter_pending_txs: false, + ..Default::default() + }; + let middleware = layer.layer(MockRpcService); + let params = RawValue::from_string( + r#"["0x1234567890abcdef1234567890abcdef12345678", "0x0"]"#.to_string(), + ) + .unwrap(); + let request = + create_request_with_params(ETH_GET_TX_BY_SENDER_AND_NONCE_METHOD, params, 102); + let response = middleware.call(request).await; + assert!( + response.as_error_code().is_none(), + "filter_pending_txs=false should allow eth_getTransactionBySenderAndNonce" + ); + let json: serde_json::Value = serde_json::from_str(response.into_json().get()).unwrap(); + assert_eq!( + json["result"], "success", + "filter_pending_txs=false should forward to inner service" + ); } #[tokio::test] async fn test_disabled_allows_pending_block() { let layer = ArcRpcLayer { filter_pending_txs: false, + ..Default::default() }; let middleware = layer.layer(MockRpcService); let params = RawValue::from_string(r#"["pending", false]"#.to_string()).unwrap(); @@ -587,6 +822,11 @@ mod tests { response.as_error_code().is_none(), "filter_pending_txs=false should allow getBlockByNumber(\"pending\")" ); + let json: serde_json::Value = serde_json::from_str(response.into_json().get()).unwrap(); + assert_eq!( + json["result"], "success", + "filter_pending_txs=false should forward to inner service" + ); } // ── ArcRpcLayer::default() ────────────────────────────────────────── diff --git a/crates/evm-specs-tests/src/adapter.rs b/crates/evm-specs-tests/src/adapter.rs index e9b4159..e99fc82 100644 --- a/crates/evm-specs-tests/src/adapter.rs +++ b/crates/evm-specs-tests/src/adapter.rs @@ -132,10 +132,9 @@ pub fn build_evm_env( chain_id: u64, ) -> Result { let spec_id = spec_name.to_spec_id(); - let mut cfg = CfgEnv::default(); + let mut cfg = CfgEnv::new().with_spec_and_mainnet_gas_params(spec_id); cfg.chain_id = chain_id; - cfg.spec = spec_id; let block = unit.block_env(&mut cfg); @@ -295,6 +294,31 @@ mod tests { assert_eq!(env.block_env.number.to::(), 1); } + #[test] + fn build_evm_env_gas_params_track_spec_across_forks() { + let unit = unit_with_chain_id(Some(U256::from(1))); + + let frontier = build_evm_env(&unit, &SpecName::Frontier, 1).expect("frontier env builds"); + let istanbul = build_evm_env(&unit, &SpecName::Istanbul, 1).expect("istanbul env builds"); + let prague = build_evm_env(&unit, &SpecName::Prague, 1).expect("prague env builds"); + + assert_eq!(frontier.cfg_env.spec, SpecId::FRONTIER); + assert_eq!(istanbul.cfg_env.spec, SpecId::ISTANBUL); + assert_eq!(prague.cfg_env.spec, SpecId::PRAGUE); + + // Pre-fix bug: gas_params were stuck on the default (PRAGUE) regardless of + // requested spec, causing pre-Prague fixtures to bill modern SSTORE/SLOAD + // gas. The fix routes through with_spec_and_mainnet_gas_params so the table + // matches the spec. + assert_ne!(frontier.cfg_env.gas_params, prague.cfg_env.gas_params); + assert_ne!(istanbul.cfg_env.gas_params, prague.cfg_env.gas_params); + + let reference_frontier = CfgEnv::new().with_spec_and_mainnet_gas_params(SpecId::FRONTIER); + let reference_istanbul = CfgEnv::new().with_spec_and_mainnet_gas_params(SpecId::ISTANBUL); + assert_eq!(frontier.cfg_env.gas_params, reference_frontier.gas_params); + assert_eq!(istanbul.cfg_env.gas_params, reference_istanbul.gas_params); + } + #[test] fn build_evm_factory_uses_supplied_chain_spec() { let chain_spec = build_default_arc_chain_spec(); diff --git a/crates/evm-specs-tests/src/roots.rs b/crates/evm-specs-tests/src/roots.rs index 1541c12..440a32b 100644 --- a/crates/evm-specs-tests/src/roots.rs +++ b/crates/evm-specs-tests/src/roots.rs @@ -38,6 +38,23 @@ pub struct TestValidationResult { } const ARC_NATIVE_COIN_AUTHORITY: Address = address!("1800000000000000000000000000000000000000"); +const ARC_NATIVE_COIN_CONTROL: Address = address!("1800000000000000000000000000000000000001"); +const ARC_SYSTEM_ACCOUNTING: Address = address!("1800000000000000000000000000000000000002"); +const ARC_CALL_FROM: Address = address!("1800000000000000000000000000000000000003"); +const ARC_PQ: Address = address!("1800000000000000000000000000000000000004"); + +const ARC_SYSTEM_ADDRESSES: &[Address] = &[ + ARC_NATIVE_COIN_AUTHORITY, + ARC_NATIVE_COIN_CONTROL, + ARC_SYSTEM_ACCOUNTING, + ARC_CALL_FROM, + ARC_PQ, +]; + +#[inline] +fn is_arc_system_address(address: &Address) -> bool { + ARC_SYSTEM_ADDRESSES.iter().any(|a| a == address) +} pub fn compute_test_roots( exec_result: &Result< @@ -56,7 +73,12 @@ pub fn compute_test_roots( ) .as_slice(), ), - state_root: state_merkle_trie_root(db.cache.trie_account()), + state_root: state_merkle_trie_root( + db.cache + .trie_account() + .into_iter() + .filter(|(address, _)| !is_arc_system_address(address)), + ), } } @@ -273,6 +295,64 @@ mod tests { ); } + #[test] + fn arc_system_addresses_recognized() { + for arc in ARC_SYSTEM_ADDRESSES { + assert!( + is_arc_system_address(arc), + "{arc} should be recognized as ARC system address" + ); + } + + for non_arc in [ + Address::ZERO, + address!("1800000000000000000000000000000000000005"), + address!("1000000000000000000000000000000000000001"), + ] { + assert!( + !is_arc_system_address(&non_arc), + "{non_arc} should not be recognized as ARC system address" + ); + } + } + + #[test] + fn state_root_filter_excludes_arc_system_accounts_with_state() { + let user_address = address!("4000000000000000000000000000000000000004"); + let user_info = AccountInfo { + balance: U256::from(42), + nonce: 7, + ..Default::default() + }; + + let mut cache_with_arc = CacheState::new(true); + for arc in ARC_SYSTEM_ADDRESSES { + cache_with_arc.insert_account( + *arc, + AccountInfo { + balance: U256::from(999), + nonce: 1, + ..Default::default() + }, + ); + } + cache_with_arc.insert_account(user_address, user_info.clone()); + + let filtered_root = state_merkle_trie_root( + cache_with_arc + .trie_account() + .into_iter() + .filter(|(address, _)| !is_arc_system_address(address)), + ); + + let mut cache_user_only = CacheState::new(true); + cache_user_only.insert_account(user_address, user_info); + let expected_root = state_merkle_trie_root(cache_user_only.trie_account()); + + assert_eq!(filtered_root, expected_root); + assert_ne!(filtered_root, EMPTY_ROOT_HASH); + } + #[test] fn fixture_state_root_matches_expected_fixture_accounts() { let mut accounts = alloy_primitives::map::HashMap::default(); diff --git a/crates/evm/src/evm.rs b/crates/evm/src/evm.rs index d02e00c..01682c0 100644 --- a/crates/evm/src/evm.rs +++ b/crates/evm/src/evm.rs @@ -62,11 +62,13 @@ use reth_evm::execute::BlockBuilder; use reth_evm::{ConfigureEngineEvm, EvmEnvFor, ExecutableTxIterator, ExecutionCtxFor}; use reth_primitives_traits::NodePrimitives; use revm::bytecode::opcode::SELFDESTRUCT; +use revm::bytecode::Bytecode; use revm::context_interface::result::ResultAndState; use revm::handler::evm::{ContextDbError, FrameInitResult}; use revm::handler::instructions::InstructionProvider; use revm::handler::{EvmTr, FrameInitOrResult, FrameResult, FrameTr, Handler, ItemOrResult}; use revm::inspector::{InspectorEvmTr, InspectorHandler, JournalExt}; +use revm::state::AccountInfo; use revm::Database as RevmDatabase; use revm::ExecuteEvm; use revm::{ @@ -79,8 +81,8 @@ use revm::{ state::EvmState, InspectEvm, SystemCallEvm, }; -use revm_context_interface::FrameStack; -use revm_context_interface::Transaction; +use revm_context_interface::journaled_state::{JournalCheckpoint, JournalLoadError}; +use revm_context_interface::{FrameStack, Transaction}; use revm_interpreter::interpreter_action::FrameInit; use revm_interpreter::{CallScheme, CreateScheme, FrameInput, Instruction}; use revm_primitives::{Address, Bytes}; @@ -97,8 +99,8 @@ use arc_precompiles::subcall::SubcallPrecompile; use revm::interpreter::interpreter_action::CallInputs; /// Flat gas cost charged for rejected subcall dispatches (unauthorized caller, wrong scheme, -/// static context, value attached, sender spoofing, init_subcall errors). Charged by -/// `init_subcall_revert` calls. Prevents zero-cost probing of subcall precompile addresses. +/// value attached, sender spoofing, init_subcall errors). Charged by `init_subcall_revert` +/// calls. Prevents zero-cost probing of subcall precompile addresses. const SUBCALL_DISPATCH_COST: u64 = 100; /// Construct a revert `FrameResult` for a subcall precompile rejection. @@ -118,6 +120,61 @@ fn init_subcall_revert(message: &str, call_inputs: &CallInputs) -> FrameResult { }) } +/// Construct an OOG `FrameResult` for a subcall that exceeded its gas budget. +fn subcall_oog(gas: Gas, return_memory_offset: std::ops::Range) -> FrameResult { + FrameResult::Call(CallOutcome { + result: InterpreterResult::new(InstructionResult::OutOfGas, Bytes::new(), gas), + memory_offset: return_memory_offset, + was_precompile_called: true, + precompile_call_logs: Default::default(), + }) +} + +/// Reject a subcall in static context. Mirrors `InstructionResult::CallNotAllowedInsideStatic` +/// halt semantics and the precompile-helper `check_staticcall` path: consume all caller gas. +fn init_subcall_static_revert(call_inputs: &CallInputs) -> FrameResult { + let revert_bytes = arc_precompiles::helpers::revert_message_to_bytes( + "subcall precompiles cannot be invoked in static context", + ); + let mut gas = Gas::new(call_inputs.gas_limit); + gas.spend_all(); + let result = InterpreterResult::new(InstructionResult::Revert, revert_bytes, gas); + FrameResult::Call(CallOutcome { + result, + memory_offset: call_inputs.return_memory_offset.clone(), + was_precompile_called: true, + precompile_call_logs: Default::default(), + }) +} + +/// Loads an account with code, recording its EIP-2929 access cost on `gas`. +/// Skips the DB load when the account is cold and `gas` can't cover it, +/// preventing warming as a side-effect of an intentional OOG. +/// Returns `None` on OOG (caller should `spend_all` and return). +fn load_account_with_code_metered( + journal: &mut J, + address: Address, + gas: &mut Gas, +) -> Result, ::Error> { + let skip_cold_load = gas.remaining() < revm_interpreter::gas::COLD_ACCOUNT_ACCESS_COST; + + match journal.load_account_info_skip_cold_load(address, true, skip_cold_load) { + Ok(info) => { + let cost = if info.is_cold { + revm_interpreter::gas::COLD_ACCOUNT_ACCESS_COST + } else { + revm_interpreter::gas::WARM_STORAGE_READ_COST + }; + if !gas.record_cost(cost) { + return Ok(None); + } + Ok(Some(info.account.into_owned())) + } + Err(JournalLoadError::ColdLoadSkipped) => Ok(None), + Err(JournalLoadError::DBError(err)) => Err(err), + } +} + #[derive(Debug)] pub struct ArcEvm> { /// Inner EVM type. @@ -178,28 +235,6 @@ fn extract_call_transfer_params( } } -/// Deducts a gas cost from a frame's gas limit. Returns `Some(oog_result)` if the -/// frame has insufficient gas, `None` on success. -#[allow(clippy::arithmetic_side_effects)] // Subtractions are guarded by the `< cost` checks. -fn deduct_gas_from_frame(frame_input: &mut FrameInit, cost: u64) -> Option { - match &mut frame_input.frame_input { - FrameInput::Call(inputs) => { - if inputs.gas_limit < cost { - return Some(create_oog_frame_result(frame_input)); - } - inputs.gas_limit -= cost; - } - FrameInput::Create(inputs) => { - if inputs.gas_limit() < cost { - return Some(create_oog_frame_result(frame_input)); - } - inputs.set_gas_limit(inputs.gas_limit() - cost); - } - FrameInput::Empty => {} - } - None -} - fn frame_gas_limit(frame_input: &FrameInit) -> u64 { match &frame_input.frame_input { FrameInput::Call(inputs) => inputs.gas_limit, @@ -341,14 +376,14 @@ where impl ArcEvm { /// Checks if an address is blocklisted by reading from the native coin control precompile storage. /// - /// Returns `(is_blocklisted, is_cold)` where `is_cold` indicates whether the storage slot - /// was a cold access (EIP-2929). Callers use `is_cold` to compute the correct SLOAD gas cost: - /// cold access costs 2100 gas, warm access costs 100 gas. + /// Returns `(is_blocklisted, is_cold)`. The `is_cold` flag, together with the `sload_cost` + /// and `metered_revert` plumbing it feeds, is retained as dormant infrastructure: blocklist + /// SLOADs are currently unmetered (callers pass `meter_sloads = false` and the cost is + /// discarded), but the wiring is kept so re-enabling metering is a single-flag flip rather + /// than a re-architecture. /// /// Note: This calls `journal.sload()` directly, bypassing the interpreter, so revm does not - /// automatically meter gas for these reads. Gas is accounted for externally: - /// - Depth 0: `validate_initial_tx_gas` includes the SLOAD cost in intrinsic gas (always cold). - /// - Depth > 0: `frame_init` deducts the warm/cold-aware SLOAD cost from the child frame's gas limit. + /// automatically meter gas for these reads. fn is_address_blocklisted( &mut self, address: Address, @@ -378,10 +413,15 @@ impl ArcEvm { } /// Extracts transfer parameters (from, to, amount) from a Create frame input. - /// Returns None if value is zero or scheme is Custom. - fn extract_create_transfer_params( + /// + /// For value-carrying CREATE2 frames, this also normalizes the frame scheme to + /// `CreateScheme::Custom { address }` after deriving the address, so revm reuses + /// the precomputed address during frame initialization instead of hashing initcode again. + /// + /// Returns None if value is zero or scheme is already Custom. + fn extract_and_normalize_create_transfer_params( &mut self, - inputs: &revm_interpreter::CreateInputs, + inputs: &mut revm_interpreter::CreateInputs, depth: usize, ) -> Result, ContextDbError> { if inputs.value().is_zero() { @@ -410,21 +450,22 @@ impl ArcEvm { } CreateScheme::Create2 { salt: _ } => { // Nonce doesn't matter for CREATE2 - Ok(Some(( - inputs.caller(), - inputs.created_address(0), - inputs.value(), - ))) + let created_address = inputs.created_address(0); + // Arc needs the created address before revm initializes the create frame + // for blocklist checks and transfer logs. Normalize the frame to a custom + // create so revm reuses the precomputed address instead of hashing the + // initcode a second time. This can be removed after upgrading to a + // revm release containing bluealloy/revm#3664. + inputs.set_scheme(CreateScheme::Custom { + address: created_address, + }); + Ok(Some((inputs.caller(), created_address, inputs.value()))) } CreateScheme::Custom { address: _ } => Ok(None), } } /// Checks blocklist status for transfer participants and returns appropriate result. - /// - /// Computes the total SLOAD gas cost for the blocklist checks performed. For Zero6+, - /// uses EIP-2929 warm/cold pricing via `sload_cost`. For pre-Zero6, uses the fixed - /// `PRECOMPILE_SLOAD_GAS_COST` (2100 gas) per SLOAD. fn check_blocklist_and_create_log( &mut self, from: Address, @@ -432,10 +473,9 @@ impl ArcEvm { amount: U256, frame_input: &FrameInit, ) -> Result> { - // Meter SLOAD gas on revert for nested frames (depth > 0) with Zero6 active. - // Depth 0 is covered by `validate_initial_tx_gas` which charges fixed cold SLOAD costs. - let meter_sloads = - frame_input.depth > 0 && self.hardfork_flags.is_active(ArcHardfork::Zero6); + // Meter SLOAD gas on revert for nested frames (depth > 0). + // Currently SLOAD gas metering is disabled — blocklist checks are unmetered. + let meter_sloads = false; // Zero5: reject CALL/CREATE value transfers involving the zero address. // This prevents accidental burn/mint semantics at the EVM execution layer. @@ -516,13 +556,13 @@ impl ArcEvm { pub(crate) fn before_frame_init( &mut self, - frame_input: &FrameInit, + frame_input: &mut FrameInit, ) -> Result> { // Extract transfer parameters based on frame type - let transfer_params = match &frame_input.frame_input { + let transfer_params = match &mut frame_input.frame_input { FrameInput::Empty => None, FrameInput::Create(inputs) => { - self.extract_create_transfer_params(inputs, frame_input.depth)? + self.extract_and_normalize_create_transfer_params(inputs, frame_input.depth)? } FrameInput::Call(inputs) => extract_call_transfer_params(inputs), }; @@ -578,7 +618,7 @@ where #[inline] fn frame_init( &mut self, - frame_input: ::FrameInit, + mut frame_input: ::FrameInit, ) -> Result, ContextDbError> { // Subcall precompiles must be checked first: they reject value transfers, so // the parent's `before_frame_init` is a no-op (no transfer to check). Running @@ -602,7 +642,7 @@ where // Defense-in-depth: run before_frame_init on the parent frame even though // subcall precompiles currently reject value transfers (making this a no-op). // If a future subcall precompile allows value, the log needs to be handled here. - match self.before_frame_init(&frame_input)? { + match self.before_frame_init(&mut frame_input)? { BeforeFrameInitResult::Reverted(res) => { return Ok(ItemOrResult::Result(res)); } @@ -615,7 +655,7 @@ where } } - // Standard path: blocklist checks, SLOAD gas, revm frame init, transfer log. + // Standard path: blocklist checks, revm frame init, transfer log. match self.checked_frame_init(frame_input)? { FrameInitOutcome::Pushed => Ok(ItemOrResult::Item(self.inner.frame_stack.get())), FrameInitOutcome::Immediate(result) => Ok(ItemOrResult::Result(result)), @@ -665,7 +705,7 @@ where let continuation_key = finished_depth.checked_sub(1); if let Some(key) = continuation_key { if let Some(continuation) = self.subcall_continuations.remove(&key) { - let final_result = Self::complete_subcall(result, continuation)?; + let final_result = self.complete_subcall(result, continuation)?; if stack_empty { // Direct EOA -> precompile: no parent frame to propagate to. return Ok(Some(final_result)); @@ -709,9 +749,8 @@ where I: InstructionProvider, P: PrecompileProvider, { - /// Runs `before_frame_init` (blocklist checks, transfer log), deducts SLOAD gas for - /// nested frames with Zero6 active, then initializes the frame via revm's standard - /// machinery and emits the transfer log on success. + /// Runs `before_frame_init` (blocklist checks, transfer log), then initializes the + /// frame via revm's standard machinery and emits the transfer log on success. /// /// For Zero5+ precompile CALLs, the EIP-7708 Transfer log is pushed before frame init /// (wrapped in a journal checkpoint) so it precedes precompile-emitted logs. For all @@ -723,7 +762,7 @@ where &mut self, mut frame_input: FrameInit, ) -> Result> { - let (maybe_log, sload_gas) = match self.before_frame_init(&frame_input)? { + let (maybe_log, _sload_gas) = match self.before_frame_init(&mut frame_input)? { BeforeFrameInitResult::Reverted(res) => { return Ok(FrameInitOutcome::Immediate(res)); } @@ -732,17 +771,6 @@ where BeforeFrameInitResult::None => (None, 0), }; - // Deduct warm/cold-aware SLOAD gas for nested frames (depth > 0) with Zero6 active. - // Depth 0 is covered by `validate_initial_tx_gas` which charges fixed cold SLOAD costs. - if frame_input.depth > 0 - && sload_gas > 0 - && self.hardfork_flags.is_active(ArcHardfork::Zero6) - { - if let Some(oog) = deduct_gas_from_frame(&mut frame_input, sload_gas) { - return Ok(FrameInitOutcome::Immediate(oog)); - } - } - // Log emission strategy for the EIP-7708 Transfer log: // // **Zero5+ precompile CALL**: Push the Transfer log BEFORE init_frame so it @@ -814,8 +842,7 @@ where /// /// Decodes the precompile input via [`SubcallPrecompile::init_subcall`], stores a /// continuation, and initializes the child frame via [`checked_frame_init`] so that it - /// goes through `before_frame_init` hooks (blocklist checks, transfer log, SLOAD gas - /// deduction). + /// goes through `before_frame_init` hooks (blocklist checks, transfer log). fn init_subcall( &mut self, mut frame_input: ::FrameInit, @@ -839,9 +866,10 @@ where // Reject static context — even when the scheme is CALL, `is_static` can be true // if this call is nested inside a STATICCALL frame higher in the call stack. + // Consume all gas to match upstream `CallNotAllowedInsideStatic` halt semantics + // and the standard precompile-helper `check_staticcall` path. if call_inputs.is_static { - return Ok(ItemOrResult::Result(init_subcall_revert( - "subcall precompiles cannot be invoked in static context", + return Ok(ItemOrResult::Result(init_subcall_static_revert( call_inputs, ))); } @@ -890,98 +918,98 @@ where let return_memory_offset = call_inputs.return_memory_offset.clone(); - // Store continuation keyed by the precompile call's depth. - // - // No journal checkpoint is taken here. The child frame's own checkpoint - // (created by `make_call_frame`) handles commit/revert based on the child's - // success or failure. This avoids an extra `journal.depth` increment that - // would create a depth gap visible to tracing inspectors, causing - // `push_trace` to panic with "Disconnected trace". - // - // Note: if `complete_subcall` returns `success: false` or `Err` when the - // child succeeded, the child's committed state will NOT be reverted. - // See the `SubcallPrecompile` trait docs. - let depth = frame_input.depth; - self.subcall_continuations.insert( - depth, - SubcallContinuation { - precompile, - gas_limit: call_inputs.gas_limit, - init_subcall_gas_overhead: init_result.gas_overhead, - return_memory_offset, - continuation_data: init_result.continuation_data, - }, - ); - // Pre-load the child's caller and target accounts into the journal. The normal EVM // execution path has these already loaded (caller is the executing frame, target was // loaded by the CALL opcode handler), but we're constructing a synthetic child frame // with a potentially-unseen spoofed sender and arbitrary target. // // Both must be present in the journal state map before `make_call_frame` calls - // `transfer_loaded`, which panics on missing accounts. `load_account` (without code) - // is sufficient here — `make_call_frame` will call `load_account_with_code` for the - // target's bytecode separately (since `known_bytecode` is None). + // `transfer_loaded`, which panics on missing accounts. The target is loaded with + // code to support EIP-7702 delegation resolution below. // - // Side effect: these `load_account` calls create `AccountWarmed` journal entries - // outside the child frame's checkpoint scope. If the child reverts, the child's - // journal entries are rolled back but these pre-loads persist — the addresses stay - // warm for the rest of the transaction. + // These loads happen BEFORE the checkpoint so that warmups persist regardless of + // outcome (OOG, child revert, or parent rejection). This matches normal EVM CALL + // semantics where target warming is a side-effect of the CALL opcode, not the + // called code. // - // The target's cold/warm status is captured to charge the EIP-2929 account access - // cost, mirroring the normal CALL opcode's gas metering. Note: when caller==target, - // `load_account(caller)` warms the address first, so `target_load.is_cold` is false - // and only the warm cost (100) is charged — matching normal EVM CALL behavior. + // Note: when caller==target, `load_account(caller)` warms the address first, so + // `target_load.is_cold` is false and only the warm cost (100) is charged. let mut child_inputs = init_result.child_inputs; self.inner .ctx .journal_mut() .load_account(child_inputs.caller)?; - let target_load = self - .inner - .ctx - .journal_mut() - .load_account(child_inputs.target_address)?; - - // EIP-2929 account access cost for the child target. Our `load_account` call - // above pre-warms the target, so revm's internal CALL handler won't charge cold - // access. We charge it explicitly to match the normal CALL opcode's gas metering. - let account_access_cost = if target_load.is_cold { - revm_interpreter::gas::COLD_ACCOUNT_ACCESS_COST - } else { - revm_interpreter::gas::WARM_STORAGE_READ_COST - }; - let total_overhead = init_result.gas_overhead.saturating_add(account_access_cost); + // Track gas for the subcall overhead: ABI decode + target access + delegation. + // Access costs are charged explicitly because our pre-loads warm the accounts, + // so revm's internal CALL handler won't charge them. + let mut gas = Gas::new(call_inputs.gas_limit); + if !gas.record_cost(init_result.gas_overhead) { + gas.spend_all(); + return Ok(ItemOrResult::Result(subcall_oog( + gas, + call_inputs.return_memory_offset.clone(), + ))); + } - let Some(available) = call_inputs.gas_limit.checked_sub(total_overhead) else { - // OOG: total overhead exceeds the caller's gas budget. Consume all gas. - // Remove the continuation inserted above — no child frame will run. - self.subcall_continuations.remove(&depth); - let mut gas = Gas::new(call_inputs.gas_limit); + let Some(target) = load_account_with_code_metered( + self.inner.ctx.journal_mut(), + child_inputs.target_address, + &mut gas, + )? + else { gas.spend_all(); - return Ok(ItemOrResult::Result(FrameResult::Call(CallOutcome { - result: InterpreterResult::new(InstructionResult::OutOfGas, Bytes::new(), gas), - memory_offset: call_inputs.return_memory_offset.clone(), - was_precompile_called: true, - precompile_call_logs: Default::default(), - }))); + return Ok(ItemOrResult::Result(subcall_oog( + gas, + call_inputs.return_memory_offset.clone(), + ))); }; + // Resolve EIP-7702 delegation: if the target has a delegation designator, + // load the delegate's code so the child frame executes correct bytecode. + if let Some(Bytecode::Eip7702(delegation)) = &target.code { + let delegate_address = delegation.address(); + + let Some(delegate) = load_account_with_code_metered( + self.inner.ctx.journal_mut(), + delegate_address, + &mut gas, + )? + else { + gas.spend_all(); + return Ok(ItemOrResult::Result(subcall_oog( + gas, + call_inputs.return_memory_offset.clone(), + ))); + }; + + if let Some(code) = delegate.code { + child_inputs.known_bytecode = Some((delegate.code_hash, code)); + } + } + + // Take a journal checkpoint AFTER account loading so warmups always persist, + // but BEFORE child dispatch so we can revert the child's committed state if + // `complete_subcall` rejects a successful child. + // + // `checkpoint()` increments journal depth, which would create a depth gap visible + // to tracing inspectors (causing `push_trace` to panic with "Disconnected trace"). + // We immediately call `checkpoint_commit()` to restore depth. The checkpoint value + // remains valid for a later revert — see `complete_subcall` for the revert protocol. + let checkpoint = self.inner.ctx.journal_mut().checkpoint(); + self.inner.ctx.journal_mut().checkpoint_commit(); + + let depth = frame_input.depth; + let gas_limit = call_inputs.gas_limit; + // Recalculate child gas with EIP-150 (63/64ths) applied to the gas remaining // after total overhead. This overwrites the gas_limit set by the trait's // `init_subcall`, which only accounted for the ABI decode overhead. - // available / 64 <= available, so the subtraction cannot underflow. + // gas.remaining() / 64 <= gas.remaining(), so the subtraction cannot underflow. #[allow(clippy::arithmetic_side_effects)] - let child_gas_limit = available - (available / 64); + let child_gas_limit = gas.remaining() - (gas.remaining() / 64); child_inputs.gas_limit = child_gas_limit; - // Update the continuation with the total overhead (ABI decode + account access). - self.subcall_continuations - .get_mut(&depth) - .expect("continuation was inserted above") - .init_subcall_gas_overhead = total_overhead; - let child_frame_input = FrameInit { // Call depth is bounded by the EVM stack limit (1024) #[allow(clippy::arithmetic_side_effects)] @@ -991,20 +1019,37 @@ where }; // Initialize the child frame through checked_frame_init so that - // before_frame_init hooks (blocklist, transfer log, SLOAD gas) run on the + // before_frame_init hooks (blocklist, transfer log) run on the // child. This returns owned FrameInitOutcome, releasing &mut self. match self.checked_frame_init(child_frame_input)? { - // Child frame needs execution — return a reference so the execution loop - // drives it. complete_subcall will run in frame_return_result. - FrameInitOutcome::Pushed => Ok(ItemOrResult::Item(self.inner.frame_stack.get())), + // Child frame needs execution — store the continuation so + // frame_return_result can finalize it when the child completes. + FrameInitOutcome::Pushed => { + self.subcall_continuations.insert( + depth, + SubcallContinuation { + precompile, + gas_limit, + init_subcall_gas_overhead: gas.spent(), + return_memory_offset, + continuation_data: init_result.continuation_data, + checkpoint, + }, + ); + Ok(ItemOrResult::Item(self.inner.frame_stack.get())) + } // Child completed immediately (e.g. rejected by allowlist, empty bytecode). // complete_subcall must run now since frame_return_result won't see this child. FrameInitOutcome::Immediate(child_result) => { - let continuation = self - .subcall_continuations - .remove(&depth) - .expect("continuation was inserted above"); - let final_result = Self::complete_subcall(child_result, continuation)?; + let continuation = SubcallContinuation { + precompile, + gas_limit, + init_subcall_gas_overhead: gas.spent(), + return_memory_offset, + continuation_data: init_result.continuation_data, + checkpoint, + }; + let final_result = self.complete_subcall(child_result, continuation)?; Ok(ItemOrResult::Result(final_result)) } } @@ -1015,7 +1060,11 @@ where /// /// Computes the final `FrameResult` from the child outcome and continuation state. /// The caller is responsible for propagating or returning this result. + /// + /// If the child succeeded but `complete_subcall` signals failure, the child's committed + /// state is reverted using the checkpoint stored in the continuation. fn complete_subcall( + &mut self, child_result: FrameResult, continuation: SubcallContinuation, ) -> Result> { @@ -1044,34 +1093,50 @@ where .precompile .complete_subcall(continuation.continuation_data, &child_result); + // Extract completion gas before consuming the result in the match below. + let completion_gas = match &completion_result { + Ok(result) => result.gas_overhead, + Err(_) => 0, // Error case already consumes all gas + }; + // Total gas consumed by the precompile. // - // Normal case: init_subcall overhead + child execution cost. The remainder - // (gas_limit - gas_used) includes the retained 1/64th implicitly, since it - // was never forwarded to the child. + // Normal case: init_subcall overhead + child execution cost + completion overhead. + // The remainder (gas_limit - gas_used) includes the retained 1/64th implicitly, + // since it was never forwarded to the child. // - // Child halted (OOG, StackUnderflow, etc.): the entire gas_limit is consumed, - // matching EVM CALL semantics where a halted child burns the full allocation - // including the retained 1/64th. + // Child halted (OOG, StackUnderflow, etc.): standard CALL semantics burn the full + // allocation including the retained 1/64th. Still compute the metered cost with + // completion overhead; if completion cannot fit in the retained gas, the subcall + // becomes OOG below instead of silently discarding that cost. + let metered_gas_used = continuation + .init_subcall_gas_overhead + .checked_add(child_gas.spent()) + .expect("gas overflow: init_subcall_overhead + child_gas.spent()") + .checked_add(completion_gas) + .expect("gas overflow: metered_gas_used + completion_gas"); let gas_used = if child_halted { - continuation.gas_limit + continuation.gas_limit.max(metered_gas_used) } else { - continuation - .init_subcall_gas_overhead - .saturating_add(child_gas.spent()) + metered_gas_used }; - // No journal checkpoint to commit/revert here. The child frame's own checkpoint - // (taken by `make_call_frame`, resolved in `process_next_action`) already - // committed or reverted based on the child's success/failure. We rely on - // `complete_subcall` not rejecting a successful child — see - // `SubcallPrecompile` trait docs. + let mut gas = Gas::new(continuation.gas_limit); + if !gas.record_cost(gas_used) { + gas.spend_all(); + + // If the child succeeded, its state was committed before completion + // accounting discovered the OOG. Roll it back through the same checkpoint + // protocol used for explicit completion failures. + if child_succeeded { + self.revert_subcall_checkpoint(continuation.checkpoint); + } + + return Ok(subcall_oog(gas, continuation.return_memory_offset)); + } + match completion_result { Ok(result) if result.success => { - let mut gas = Gas::new(continuation.gas_limit); - if !gas.record_cost(gas_used) { - gas.spend_all(); - } // Only forward SSTORE refunds when the child frame itself succeeded. // A reverted child's refunds are invalid — they correspond to state // changes that were rolled back. @@ -1086,31 +1151,23 @@ where precompile_call_logs: Default::default(), })) } - Ok(result) => { - // Precompile signaled failure — no refund. Note: if the child succeeded, - // its committed state is NOT reverted (see SubcallPrecompile trait docs). + completion_failure => { + let output = match completion_failure { + Ok(result) => result.output, + Err(_) => { + gas.spend_all(); + Bytes::new() + } + }; - let mut gas = Gas::new(continuation.gas_limit); - if !gas.record_cost(gas_used) { - gas.spend_all(); + // If the child succeeded, revert its committed state using the + // checkpoint taken before child dispatch. + if child_succeeded { + self.revert_subcall_checkpoint(continuation.checkpoint); } - // No record_refund — precompile signaled failure Ok(FrameResult::Call(CallOutcome { - result: InterpreterResult::new(InstructionResult::Revert, result.output, gas), - memory_offset: continuation.return_memory_offset, - was_precompile_called: true, - precompile_call_logs: Default::default(), - })) - } - Err(_) => { - // complete_subcall error — all gas consumed - Ok(FrameResult::Call(CallOutcome { - result: InterpreterResult::new( - InstructionResult::Revert, - Bytes::new(), - Gas::new_spent(continuation.gas_limit), - ), + result: InterpreterResult::new(InstructionResult::Revert, output, gas), memory_offset: continuation.return_memory_offset, was_precompile_called: true, precompile_call_logs: Default::default(), @@ -1118,6 +1175,26 @@ where } } } + + /// Revert journal state to a checkpoint that was taken without a depth increment. + /// + /// The checkpoint was originally taken via `checkpoint()` + immediate `checkpoint_commit()` + /// (to neutralize the depth side-effect for tracing compatibility). To revert: + /// 1. Call `checkpoint()` to increment depth (so `checkpoint_revert` can decrement it back) + /// 2. Call `checkpoint_revert(saved_cp)` which decrements depth and rolls back state + /// + /// Net depth change: 0. The dummy checkpoint from step 1 is discarded — we revert to + /// the saved position which predates the child's execution. + fn revert_subcall_checkpoint(&mut self, saved_cp: JournalCheckpoint) { + let depth_before = self.inner.ctx.journal_mut().depth(); + let _dummy = self.inner.ctx.journal_mut().checkpoint(); + self.inner.ctx.journal_mut().checkpoint_revert(saved_cp); + debug_assert_eq!( + self.inner.ctx.journal_mut().depth(), + depth_before, + "revert_subcall_checkpoint must not change journal depth" + ); + } } // Implement InspectorEvmTr for ArcEvm @@ -1439,6 +1516,22 @@ where contract: Address, data: Bytes, ) -> Result, Self::Error> { + debug_assert!( + self.subcall_continuations.is_empty(), + "system calls bypass ArcEvm::frame_init and must not start with active Arc subcall continuations" + ); + debug_assert!( + self.subcall_registry.get(&contract).is_none(), + "system-call target {contract:?} is an Arc subcall precompile; explicitly decide whether bypassing ArcEvm::frame_init is correct" + ); + + // Intentionally delegate to revm's system-call path. Current Arc system calls are + // zero-value calls, and upstream EIP system calls are expected to follow revm's + // semantics. Arc frame_init hooks (blocklist checks, transfer logs, SLOAD gas + // deduction, subcall interception) are therefore intentionally not applied here. + // If a future Arc system call depends on those hooks, treat that as an explicit + // execution-semantics design change instead of silently routing through + // ArcEvmHandler. self.inner.system_call_with_caller(caller, contract, data) } @@ -1488,13 +1581,17 @@ impl ArcEvmFactory { } fn get_hardfork_flags(&self, block_env: &BlockEnv) -> ArcHardforkFlags { - // The block height in header is u64, convert should succeed. - self.chain_spec.get_hardfork_flags( - block_env - .number - .try_into() - .expect("Failed to convert block number to u64"), - ) + // `BlockEnv` carries `number`/`timestamp` as U256, but Arc consensus pins both + // to u64 ranges, so these conversions can only fail on malformed inputs. + let number: u64 = block_env + .number + .try_into() + .expect("Failed to convert block number to u64"); + let timestamp: u64 = block_env + .timestamp + .try_into() + .expect("Failed to convert block timestamp to u64"); + self.chain_spec.get_hardfork_flags(number, timestamp) } /// Builds the subcall registry for the given hardfork flags. @@ -1509,7 +1606,7 @@ impl ArcEvmFactory { let mut registry = SubcallRegistry::new(); - if hardfork_flags.is_active(ArcHardfork::Zero6) { + if hardfork_flags.is_active(ArcHardfork::Zero7) { registry.register( CALL_FROM_ADDRESS, Arc::new(CallFromPrecompile), @@ -1543,7 +1640,7 @@ impl EvmFactory for ArcEvmFactory { let ctx = Self::Context::new(db, spec) .with_cfg(input.cfg_env) .with_block(input.block_env); - let mut instruction = EthInstructions::default(); + let mut instruction = EthInstructions::new_mainnet_with_spec(spec); let precompiles = ArcPrecompileProvider::create_precompiles_map(spec, hardfork_flags); let inspector = NoOpInspector {}; let subcall_registry = self.build_subcall_registry(hardfork_flags); @@ -1583,7 +1680,7 @@ impl EvmFactory for ArcEvmFactory { let ctx = Self::Context::new(db, spec) .with_cfg(input.cfg_env) .with_block(input.block_env); - let mut instruction = EthInstructions::default(); + let mut instruction = EthInstructions::new_mainnet_with_spec(spec); let precompiles = ArcPrecompileProvider::create_precompiles_map(spec, hardfork_flags); let subcall_registry = self.build_subcall_registry(hardfork_flags); @@ -1785,7 +1882,7 @@ mod tests { use arc_precompiles::{ native_coin_control, NATIVE_COIN_AUTHORITY_ADDRESS, NATIVE_COIN_CONTROL_ADDRESS, }; - use reth_chainspec::EthChainSpec; + use reth_chainspec::{EthChainSpec, ForkCondition}; use reth_ethereum::evm::revm::{ context::CfgEnv, db::{CacheDB, EmptyDB}, @@ -1833,7 +1930,7 @@ mod tests { PrecompilesMap, > { let spec = revm_spec_by_timestamp_and_block_number(&chain_spec, 0, 0); - let hardfork_flags = chain_spec.get_hardfork_flags(0); + let hardfork_flags = chain_spec.get_hardfork_flags(0, 0); let cfg_env = CfgEnv::new() .with_chain_id(chain_spec.chain_id()) .with_spec_and_mainnet_gas_params(spec); @@ -1842,7 +1939,7 @@ mod tests { let ctx = EthEvmContext::new(db, spec) .with_cfg(cfg_env) .with_block(block_env); - let mut instruction = EthInstructions::default(); + let mut instruction = EthInstructions::new_mainnet_with_spec(spec); let precompiles = ArcPrecompileProvider::create_precompiles_map(spec, hardfork_flags); let inspector = NoOpInspector {}; @@ -1872,6 +1969,41 @@ mod tests { ) } + fn evm_env_with_spec(spec: SpecId) -> EvmEnv { + let cfg_env = CfgEnv::new() + .with_chain_id(LOCAL_DEV.chain_id()) + .with_spec_and_mainnet_gas_params(spec); + + EvmEnv { + block_env: BlockEnv::default(), + cfg_env, + } + } + + #[test] + fn factory_uses_input_spec_for_instruction_tables() { + let factory = ArcEvmFactory::new(LOCAL_DEV.clone()); + let evm = factory.create_evm(InMemoryDB::default(), evm_env_with_spec(SpecId::TANGERINE)); + + assert_eq!(evm.inner.instruction.spec, SpecId::TANGERINE); + assert_eq!( + evm.inner.instruction.instruction_table[opcode::DELEGATECALL as usize].static_gas(), + 700 + ); + + let evm = factory.create_evm_with_inspector( + InMemoryDB::default(), + evm_env_with_spec(SpecId::TANGERINE), + NoOpInspector {}, + ); + + assert_eq!(evm.inner.instruction.spec, SpecId::TANGERINE); + assert_eq!( + evm.inner.instruction.instruction_table[opcode::DELEGATECALL as usize].static_gas(), + 700 + ); + } + fn create_db(accounts: &[(Address, u64)]) -> InMemoryDB { let mut db = InMemoryDB::default(); for (address, balance) in accounts { @@ -2040,7 +2172,7 @@ mod tests { > { let spec = SpecId::PRAGUE; let ctx = Context::new(db, spec); - let instruction = EthInstructions::default(); + let instruction = EthInstructions::new_mainnet_with_spec(spec); let subcall_registry = Arc::new(SubcallRegistry::default()); ArcEvm::new( @@ -2093,6 +2225,54 @@ mod tests { ); } + #[test] + fn test_system_call_delegates_to_revm_system_call_path() { + let chain_spec = LOCAL_DEV.clone(); + let system_contract = Address::repeat_byte(0x21); + let return_value = U256::from(42); + + // PUSH1 0x2a PUSH1 0x00 MSTORE PUSH1 0x20 PUSH1 0x00 RETURN + let runtime = Bytecode::new_raw(Bytes::from(vec![ + 0x60, 0x2a, 0x60, 0x00, 0x52, 0x60, 0x20, 0x60, 0x00, 0xf3, + ])); + + let mut db = create_db(&[]); + db.insert_account_info( + system_contract, + revm::state::AccountInfo { + balance: U256::ZERO, + nonce: 1, + code_hash: keccak256(runtime.bytecode()), + code: Some(runtime), + account_id: None, + }, + ); + let mut evm = create_arc_evm(chain_spec, db); + + let result = alloy_evm::Evm::transact_system_call( + &mut evm, + Address::ZERO, + system_contract, + Bytes::new(), + ) + .expect("system call should execute"); + + assert!(result.result.is_success(), "system call should succeed"); + assert_eq!( + result.result.logs().len(), + 0, + "zero-value system call should emit no logs" + ); + assert_eq!( + result + .result + .output() + .expect("successful system call should return output") + .as_ref(), + &return_value.to_be_bytes::<32>() + ); + } + /// Under Zero5, Arc self-emits EIP-7708 Transfer logs for CALL/CREATE value transfers. #[test] fn test_transact_one_eip7708_log_under_zero5() { @@ -2102,9 +2282,9 @@ mod tests { use revm_primitives::TxKind; let chain_spec = localdev_with_hardforks(&[ - (ArcHardfork::Zero3, 0), - (ArcHardfork::Zero4, 0), - (ArcHardfork::Zero5, 0), + (ArcHardfork::Zero3, ForkCondition::Block(0)), + (ArcHardfork::Zero4, ForkCondition::Block(0)), + (ArcHardfork::Zero5, ForkCondition::Block(0)), ]); let sender = Address::repeat_byte(0x01); let receiver = Address::repeat_byte(0x02); @@ -2113,7 +2293,7 @@ mod tests { caller: sender, kind: TxKind::Call(receiver), value: amount, - gas_limit: 26_000, // Must exceed (21000 + 2*SLOAD) = 25200 for Zero6 blocklist gas + gas_limit: 21_000, gas_price: 0, chain_id: Some(chain_spec.chain_id()), ..Default::default() @@ -2168,9 +2348,9 @@ mod tests { use revm_primitives::TxKind; let chain_spec = localdev_with_hardforks(&[ - (ArcHardfork::Zero3, 0), - (ArcHardfork::Zero4, 0), - (ArcHardfork::Zero5, 0), + (ArcHardfork::Zero3, ForkCondition::Block(0)), + (ArcHardfork::Zero4, ForkCondition::Block(0)), + (ArcHardfork::Zero5, ForkCondition::Block(0)), ]); let sender = Address::repeat_byte(0x01); let receiver = Address::repeat_byte(0x02); @@ -2179,7 +2359,7 @@ mod tests { caller: sender, kind: TxKind::Call(receiver), value: amount, - gas_limit: 26_000, // Must exceed (21000 + 2*SLOAD) = 25200 for Zero6 blocklist gas + gas_limit: 21_000, gas_price: 0, chain_id: Some(chain_spec.chain_id()), ..Default::default() @@ -2237,7 +2417,7 @@ mod tests { .load_account(NATIVE_COIN_CONTROL_ADDRESS) .unwrap(); - let frame = FrameInit { + let mut frame = FrameInit { frame_input: FrameInput::Call(call_input( CallScheme::Call, U256::ZERO, @@ -2248,7 +2428,7 @@ mod tests { depth: 1, }; - let result = evm.before_frame_init(&frame).unwrap(); + let result = evm.before_frame_init(&mut frame).unwrap(); assert!( matches!(result, BeforeFrameInitResult::None), "Zero-value call under Zero5 should return None (no log, no blocklist checks)" @@ -2335,7 +2515,7 @@ mod tests { // Test pre-Zero5 hardforks only — Zero5 enables EIP-7708 which emits different logs. for hardfork in [ArcHardfork::Zero3, ArcHardfork::Zero4] { for test in &test_cases { - let frame = FrameInit { + let mut frame = FrameInit { frame_input: test.frame_input.clone(), memory: SharedMemory::default(), depth: 0, @@ -2350,7 +2530,7 @@ mod tests { .load_account(NATIVE_COIN_CONTROL_ADDRESS) .unwrap(); - let transfer_result = evm.before_frame_init(&frame).unwrap(); + let transfer_result = evm.before_frame_init(&mut frame).unwrap(); // No early return should occur for basic tests assert!( @@ -2397,6 +2577,101 @@ mod tests { } } + #[test] + fn test_create2_with_value_normalizes_to_custom_address() { + let db = CacheDB::new(EmptyDB::default()); + let mut evm = create_test_evm(db, ArcHardforkFlags::with(&[ArcHardfork::Zero5])); + evm.ctx_mut() + .journal_mut() + .load_account(NATIVE_COIN_CONTROL_ADDRESS) + .unwrap(); + + let salt = U256::from(123); + let init_code = Bytes::from(vec![0x60, 0x00, 0x60, 0x00, 0xF3]); + let expected_address = ADDRESS_A.create2(salt.to_be_bytes(), keccak256(&init_code)); + let mut frame = FrameInit { + frame_input: FrameInput::Create(Box::new(CreateInputs::new( + ADDRESS_A, + CreateScheme::Create2 { salt }, + U256::from(1), + init_code, + 100_000, + ))), + memory: SharedMemory::default(), + depth: 0, + }; + + let transfer_result = evm.before_frame_init(&mut frame).unwrap(); + assert!( + matches!(transfer_result, BeforeFrameInitResult::Log(_, _)), + "CREATE2 with value should produce a transfer log, got {transfer_result:?}" + ); + + let FrameInput::Create(inputs) = &frame.frame_input else { + panic!("expected CREATE frame"); + }; + assert_eq!( + inputs.scheme(), + CreateScheme::Custom { + address: expected_address, + }, + "CREATE2 should be normalized to the precomputed custom address" + ); + } + + #[test] + fn test_create2_with_value_rejects_blocklisted_created_address() { + let db = CacheDB::new(EmptyDB::default()); + let mut evm = create_test_evm(db, ArcHardforkFlags::with(&[ArcHardfork::Zero5])); + evm.ctx_mut() + .journal_mut() + .load_account(NATIVE_COIN_CONTROL_ADDRESS) + .unwrap(); + + let salt = U256::from(456); + let init_code = Bytes::from(vec![0x60, 0x00, 0x60, 0x00, 0xF3]); + let expected_address = ADDRESS_A.create2(salt.to_be_bytes(), keccak256(&init_code)); + let storage_slot = + native_coin_control::compute_is_blocklisted_storage_slot(expected_address); + evm.ctx_mut() + .journal_mut() + .sstore( + NATIVE_COIN_CONTROL_ADDRESS, + storage_slot.into(), + U256::from(1), + ) + .unwrap(); + + let mut frame = FrameInit { + frame_input: FrameInput::Create(Box::new(CreateInputs::new( + ADDRESS_A, + CreateScheme::Create2 { salt }, + U256::from(1), + init_code, + 100_000, + ))), + memory: SharedMemory::default(), + depth: 0, + }; + + let transfer_result = evm.before_frame_init(&mut frame).unwrap(); + assert!( + matches!(transfer_result, BeforeFrameInitResult::Reverted(_)), + "CREATE2 to blocklisted created address should revert, got {transfer_result:?}" + ); + + let FrameInput::Create(inputs) = &frame.frame_input else { + panic!("expected CREATE frame"); + }; + assert_eq!( + inputs.scheme(), + CreateScheme::Custom { + address: expected_address, + }, + "rejected CREATE2 should still carry the precomputed custom address" + ); + } + struct BlocklistTestCase { name: &'static str, frame_input: FrameInput, @@ -2417,7 +2692,7 @@ mod tests { test_case.name, hardfork ); - let frame = FrameInit { + let mut frame = FrameInit { frame_input: test_case.frame_input.clone(), memory: SharedMemory::default(), depth: 0, @@ -2433,7 +2708,7 @@ mod tests { // Intentionally do NOT load the account into the journal // This will cause is_address_blocklisted to fail (sload panics when account is not loaded) let transfer_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - evm.before_frame_init(&frame) + evm.before_frame_init(&mut frame) })); assert!( @@ -2478,7 +2753,7 @@ mod tests { .unwrap(); } - let transfer_result = evm.before_frame_init(&frame).unwrap(); + let transfer_result = evm.before_frame_init(&mut frame).unwrap(); if test_case.expected_reverted { assert!( @@ -2704,272 +2979,34 @@ mod tests { } } + /// Guards against the static_gas regression introduced during the revm v29→v32 migration. + /// revm v32 moved SELFDESTRUCT's 5000 base gas (EIP-150) from the instruction handler to + /// the instruction table's static_gas field. Since Arc overrides the instruction entry via + /// insert_instruction, the static_gas must be set explicitly or it silently drops to 0. #[test] - fn test_nested_frame_gas_deduction_zero6() { - use arc_precompiles::helpers::PRECOMPILE_SLOAD_GAS_COST; - use revm::handler::EvmTr; - - // Test that nested frames (depth > 0) with Zero6 get OOG when gas is insufficient - let db = CacheDB::new(EmptyDB::default()); - let mut evm = create_test_evm(db, ArcHardforkFlags::with(&[ArcHardfork::Zero6])); - - // Load native coin control account (required for blocklist checks) - evm.ctx_mut() - .journal_mut() - .load_account(NATIVE_COIN_CONTROL_ADDRESS) - .unwrap(); - - // Create a nested CALL frame (depth=1) with value and insufficient gas - // With value transfer: needs 2 cold SLOADs = 2 * 2100 = 4200 gas - let insufficient_gas = PRECOMPILE_SLOAD_GAS_COST; // Only 2100, need 4200 - let frame_with_value = FrameInit { - frame_input: FrameInput::Call(Box::new(CallInputs { - scheme: CallScheme::Call, - target_address: ADDRESS_B, - bytecode_address: ADDRESS_B, - known_bytecode: None, - value: CallValue::Transfer(U256::from(100)), - input: CallInput::Bytes(Bytes::new()), - gas_limit: insufficient_gas, - is_static: false, - caller: ADDRESS_A, - return_memory_offset: 0..0, - })), - memory: SharedMemory::default(), - depth: 1, // Nested frame - }; - - let result = evm.frame_init(frame_with_value); - - // Should return OOG because gas_limit (2100) < required (4200) - match result { - Ok(ItemOrResult::Result(FrameResult::Call(outcome))) => { - assert_eq!( - outcome.result.result, - InstructionResult::OutOfGas, - "Expected OutOfGas for nested call with insufficient gas" - ); - } - other => panic!( - "Expected Ok(Result(Call(OutOfGas))), got {:?}", - other.map(|r| format!("{:?}", r)) - ), - } + fn selfdestruct_static_gas_is_5000() { + let db = InMemoryDB::default(); + let evm = create_arc_evm(LOCAL_DEV.clone(), db); + let static_gas = + evm.inner.instruction.instruction_table[SELFDESTRUCT as usize].static_gas(); + assert_eq!( + static_gas, 5000, + "SELFDESTRUCT static gas must be 5000 (EIP-150)" + ); } + /// Verifies that `create_arc_evm` dispatches the pre-Zero5 SELFDESTRUCT variant when + /// only Zero3+Zero4 are active. Pre-Zero5 allows SELFDESTRUCT to the zero address + /// (unlike Zero5) and emits `NativeCoinTransferred` (unlike EIP-7708). #[test] - fn test_blocklist_sload_gas_cold_access() { - // Both addresses are fresh (cold), so each sload costs 2100 gas - use arc_precompiles::helpers::PRECOMPILE_SLOAD_GAS_COST; + fn create_arc_evm_dispatches_selfdestruct_zero4() { + use arc_execution_config::{chainspec::localdev_with_hardforks, hardforks::ArcHardfork}; + use revm_primitives::TxKind; - let db = CacheDB::new(EmptyDB::default()); - let mut evm = create_test_evm(db, ArcHardforkFlags::with(&[ArcHardfork::Zero6])); - - // Load native coin control account - evm.ctx_mut() - .journal_mut() - .load_account(NATIVE_COIN_CONTROL_ADDRESS) - .unwrap(); - - let frame = FrameInit { - frame_input: FrameInput::Call(call_input( - CallScheme::Call, - U256::from(100), - ADDRESS_A, - ADDRESS_B, - )), - memory: SharedMemory::default(), - depth: 1, - }; - - let result = evm.before_frame_init(&frame).unwrap(); - match result { - BeforeFrameInitResult::Log(_log, gas) => { - assert_eq!( - gas, - 2 * PRECOMPILE_SLOAD_GAS_COST, - "Two cold SLOADs should cost 2 * 2100 = 4200" - ); - } - other => panic!("Expected Log result, got {:?}", other), - } - } - - #[test] - fn test_blocklist_sload_gas_warm_access() { - // Pre-warm the blocklist slots, then call before_frame_init — should get warm pricing - let db = CacheDB::new(EmptyDB::default()); - let mut evm = create_test_evm(db, ArcHardforkFlags::with(&[ArcHardfork::Zero6])); - - // Load native coin control account - evm.ctx_mut() - .journal_mut() - .load_account(NATIVE_COIN_CONTROL_ADDRESS) - .unwrap(); - - // Pre-warm the blocklist slots by reading them first - let slot_a = compute_is_blocklisted_storage_slot(ADDRESS_A).into(); - let slot_b = compute_is_blocklisted_storage_slot(ADDRESS_B).into(); - evm.inner - .ctx - .journal_mut() - .sload(NATIVE_COIN_CONTROL_ADDRESS, slot_a) - .unwrap(); - evm.inner - .ctx - .journal_mut() - .sload(NATIVE_COIN_CONTROL_ADDRESS, slot_b) - .unwrap(); - - let frame = FrameInit { - frame_input: FrameInput::Call(call_input( - CallScheme::Call, - U256::from(100), - ADDRESS_A, - ADDRESS_B, - )), - memory: SharedMemory::default(), - depth: 1, - }; - - let result = evm.before_frame_init(&frame).unwrap(); - match result { - BeforeFrameInitResult::Log(_log, gas) => { - // Warm SLOAD costs 100 gas each (EIP-2929) - assert_eq!(gas, 200, "Two warm SLOADs should cost 2 * 100 = 200"); - } - other => panic!("Expected Log result, got {:?}", other), - } - } - - #[test] - fn test_blocklist_sload_gas_zero_value_no_charge() { - // Zero-value calls should not perform any SLOADs (no blocklist check needed) - let db = CacheDB::new(EmptyDB::default()); - let mut evm = create_test_evm(db, ArcHardforkFlags::with(&[ArcHardfork::Zero6])); - - // Load native coin control account - evm.ctx_mut() - .journal_mut() - .load_account(NATIVE_COIN_CONTROL_ADDRESS) - .unwrap(); - - // Zero-value CALL - let frame = FrameInit { - frame_input: FrameInput::Call(call_input( - CallScheme::Call, - U256::ZERO, - ADDRESS_A, - ADDRESS_B, - )), - memory: SharedMemory::default(), - depth: 1, - }; - - let result = evm.before_frame_init(&frame).unwrap(); - assert!( - matches!(result, BeforeFrameInitResult::None), - "Zero-value call should return None (no SLOADs, no gas cost)" - ); - - // DelegateCall (no value transfer regardless) - let frame_delegate = FrameInit { - frame_input: FrameInput::Call(call_input( - CallScheme::DelegateCall, - U256::from(100), - ADDRESS_A, - ADDRESS_B, - )), - memory: SharedMemory::default(), - depth: 1, - }; - - let result = evm.before_frame_init(&frame_delegate).unwrap(); - assert!( - matches!(result, BeforeFrameInitResult::None), - "DelegateCall should return None (no SLOADs, no gas cost)" - ); - } - - #[test] - fn test_blocklist_sload_gas_pre_zero6_always_fixed() { - // Pre-Zero6: always uses fixed 2100 per SLOAD regardless of warm/cold - use arc_precompiles::helpers::PRECOMPILE_SLOAD_GAS_COST; - - let db = CacheDB::new(EmptyDB::default()); - let mut evm = create_test_evm(db, ArcHardforkFlags::with(&[ArcHardfork::Zero5])); - - // Load native coin control account - evm.ctx_mut() - .journal_mut() - .load_account(NATIVE_COIN_CONTROL_ADDRESS) - .unwrap(); - - // Pre-warm the slots - let slot_a = compute_is_blocklisted_storage_slot(ADDRESS_A).into(); - let slot_b = compute_is_blocklisted_storage_slot(ADDRESS_B).into(); - evm.inner - .ctx - .journal_mut() - .sload(NATIVE_COIN_CONTROL_ADDRESS, slot_a) - .unwrap(); - evm.inner - .ctx - .journal_mut() - .sload(NATIVE_COIN_CONTROL_ADDRESS, slot_b) - .unwrap(); - - let frame = FrameInit { - frame_input: FrameInput::Call(call_input( - CallScheme::Call, - U256::from(100), - ADDRESS_A, - ADDRESS_B, - )), - memory: SharedMemory::default(), - depth: 1, - }; - - let result = evm.before_frame_init(&frame).unwrap(); - match result { - BeforeFrameInitResult::Log(_log, gas) => { - // Pre-Zero6: always 2100 per SLOAD even when warm - assert_eq!( - gas, - 2 * PRECOMPILE_SLOAD_GAS_COST, - "Pre-Zero6 should always charge fixed 2100 per SLOAD" - ); - } - other => panic!("Expected Log result, got {:?}", other), - } - } - - /// Guards against the static_gas regression introduced during the revm v29→v32 migration. - /// revm v32 moved SELFDESTRUCT's 5000 base gas (EIP-150) from the instruction handler to - /// the instruction table's static_gas field. Since Arc overrides the instruction entry via - /// insert_instruction, the static_gas must be set explicitly or it silently drops to 0. - #[test] - fn selfdestruct_static_gas_is_5000() { - let db = InMemoryDB::default(); - let evm = create_arc_evm(LOCAL_DEV.clone(), db); - let static_gas = - evm.inner.instruction.instruction_table[SELFDESTRUCT as usize].static_gas(); - assert_eq!( - static_gas, 5000, - "SELFDESTRUCT static gas must be 5000 (EIP-150)" - ); - } - - /// Verifies that `create_arc_evm` dispatches the pre-Zero5 SELFDESTRUCT variant when - /// only Zero3+Zero4 are active. Pre-Zero5 allows SELFDESTRUCT to the zero address - /// (unlike Zero5) and emits `NativeCoinTransferred` (unlike EIP-7708). - #[test] - fn create_arc_evm_dispatches_selfdestruct_zero4() { - use arc_execution_config::{chainspec::localdev_with_hardforks, hardforks::ArcHardfork}; - use revm_primitives::TxKind; - - let chain_spec = - localdev_with_hardforks(&[(ArcHardfork::Zero3, 0), (ArcHardfork::Zero4, 0)]); + let chain_spec = localdev_with_hardforks(&[ + (ArcHardfork::Zero3, ForkCondition::Block(0)), + (ArcHardfork::Zero4, ForkCondition::Block(0)), + ]); let sender = Address::repeat_byte(0x11); let contract = Address::repeat_byte(0xBB); @@ -3032,9 +3069,9 @@ mod tests { use revm_primitives::TxKind; let chain_spec = localdev_with_hardforks(&[ - (ArcHardfork::Zero3, 0), - (ArcHardfork::Zero4, 0), - (ArcHardfork::Zero5, 0), + (ArcHardfork::Zero3, ForkCondition::Block(0)), + (ArcHardfork::Zero4, ForkCondition::Block(0)), + (ArcHardfork::Zero5, ForkCondition::Block(0)), ]); let sender = Address::repeat_byte(0x11); @@ -3137,7 +3174,7 @@ mod tests { .expect("selfdestruct"); // 2. Prepare frame to transfer balance from ADDRESS_A to ADDRESS_B - let frame = FrameInit { + let mut frame = FrameInit { frame_input: FrameInput::Call(Box::new(CallInputs { scheme: CallScheme::Call, target_address: ADDRESS_B, @@ -3155,7 +3192,7 @@ mod tests { }; // 3. Should revert on transferring to destructed account - let result = evm.before_frame_init(&frame); + let result = evm.before_frame_init(&mut frame); assert!( matches!(result, Ok(BeforeFrameInitResult::Reverted(_))), "expect revert on transferring to destructed account" @@ -3262,28 +3299,29 @@ mod tests { } #[test] - fn registry_populated_when_zero6_active() { - let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero6]); + fn registry_populated_when_zero7_active() { + let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero7]); let registry = factory().build_subcall_registry(flags); assert!( registry.get(&CALL_FROM_ADDRESS).is_some(), - "CallFrom should be registered when Zero6 is active" + "CallFrom should be registered when Zero7 is active" ); } #[test] - fn registry_empty_when_zero6_not_active() { + fn registry_empty_when_zero7_not_active() { let flags = ArcHardforkFlags::with(&[ ArcHardfork::Zero3, ArcHardfork::Zero4, ArcHardfork::Zero5, + ArcHardfork::Zero6, ]); let registry = factory().build_subcall_registry(flags); assert!( registry.get(&CALL_FROM_ADDRESS).is_none(), - "CallFrom should not be registered when Zero6 is inactive" + "CallFrom should not be registered when Zero7 is inactive" ); } @@ -3300,7 +3338,7 @@ mod tests { #[test] fn allowed_callers_include_memo_and_multicall3_from() { - let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero6]); + let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero7]); let registry = factory().build_subcall_registry(flags); let (_precompile, allowed_callers) = registry @@ -3319,7 +3357,7 @@ mod tests { #[test] fn arbitrary_address_not_allowed() { - let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero6]); + let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero7]); let registry = factory().build_subcall_registry(flags); let (_precompile, allowed_callers) = registry @@ -3332,6 +3370,32 @@ mod tests { "arbitrary address should not be an allowed caller" ); } + + #[test] + fn registry_follows_block_derived_hardfork_flags() { + use arc_execution_config::chainspec::localdev_with_hardforks; + + let spec = localdev_with_hardforks(&[ + (ArcHardfork::Zero3, ForkCondition::Block(0)), + (ArcHardfork::Zero4, ForkCondition::Block(0)), + (ArcHardfork::Zero5, ForkCondition::Block(0)), + (ArcHardfork::Zero6, ForkCondition::Block(0)), + (ArcHardfork::Zero7, ForkCondition::Block(10)), + ]); + let factory = ArcEvmFactory::new(spec.clone()); + + let pre = factory.build_subcall_registry(spec.get_hardfork_flags(9, 0)); + assert!( + pre.get(&CALL_FROM_ADDRESS).is_none(), + "block 9: Zero7 not yet active" + ); + + let post = factory.build_subcall_registry(spec.get_hardfork_flags(10, 0)); + assert!( + post.get(&CALL_FROM_ADDRESS).is_some(), + "block 10: Zero7 active" + ); + } } mod subcall_tests { @@ -3393,6 +3457,21 @@ mod tests { Bytes::from(code) } + /// Returns EVM bytecode that always reverts with the given 4-byte payload. + fn reverting_with_payload_bytecode(payload: [u8; 4]) -> Bytes { + let mut code = vec![PUSH4]; + code.extend_from_slice(&payload); + #[rustfmt::skip] + code.extend_from_slice(&[ + PUSH1, 0x00, + MSTORE, + PUSH1, 0x04, + PUSH1, 0x1c, + REVERT, + ]); + Bytes::from(code) + } + /// Returns EVM bytecode that returns msg.sender as a 32-byte word. fn return_caller_bytecode() -> Bytes { #[rustfmt::skip] @@ -3480,6 +3559,13 @@ mod tests { // ----- Setup helpers ----- + type NoOpTestEvm = ArcEvm< + EthEvmContext, + revm::inspector::NoOpInspector, + EthInstructions>, + PrecompilesMap, + >; + /// Creates an ArcEvm backed by an in-memory DB with accounts and deployed contracts. /// /// `accounts`: list of `(address, balance)` pairs for EOAs. @@ -3490,12 +3576,7 @@ mod tests { accounts: &[(Address, U256)], contracts: &[(Address, Bytes)], call_from_allowlist: &[Address], - ) -> ArcEvm< - EthEvmContext, - revm::inspector::NoOpInspector, - EthInstructions>, - PrecompilesMap, - > { + ) -> NoOpTestEvm { use crate::subcall::AllowedCallers; use crate::subcall_test::{SubcallTestPrecompile, SUBCALL_TEST_ADDRESS}; @@ -3530,7 +3611,7 @@ mod tests { let spec = reth_ethereum::evm::revm_spec_by_timestamp_and_block_number(&chain_spec, 0, 0); - let hardfork_flags = chain_spec.get_hardfork_flags(0); + let hardfork_flags = chain_spec.get_hardfork_flags(0, 0); let mut cfg_env = revm::context::CfgEnv::new() .with_chain_id(chain_spec.chain_id()) .with_spec_and_mainnet_gas_params(spec); @@ -3540,7 +3621,7 @@ mod tests { .with_cfg(cfg_env) .with_block(BlockEnv::default()); let precompiles = ArcPrecompileProvider::create_precompiles_map(spec, hardfork_flags); - let mut instruction = EthInstructions::default(); + let mut instruction = EthInstructions::new_mainnet_with_spec(spec); if hardfork_flags.is_active(ArcHardfork::Zero5) { instruction.insert_instruction( SELFDESTRUCT, @@ -3573,6 +3654,22 @@ mod tests { ) } + fn insert_eip7702_account(evm: &mut NoOpTestEvm, address: Address, delegate: Address) { + use revm::bytecode::eip7702::Eip7702Bytecode; + + let eip7702_code = Bytecode::Eip7702(Arc::new(Eip7702Bytecode::new(delegate))); + evm.inner.ctx.journal_mut().db_mut().insert_account_info( + address, + AccountInfo { + balance: U256::ZERO, + nonce: 1, + code_hash: keccak256(eip7702_code.bytes_slice()), + code: Some(eip7702_code), + account_id: None, + }, + ); + } + // ----- Test addresses ----- const EOA: Address = address!("e000000000000000000000000000000000000001"); @@ -3870,7 +3967,7 @@ mod tests { let spec = reth_ethereum::evm::revm_spec_by_timestamp_and_block_number(&chain_spec, 0, 0); - let hardfork_flags = chain_spec.get_hardfork_flags(0); + let hardfork_flags = chain_spec.get_hardfork_flags(0, 0); let mut cfg_env = revm::context::CfgEnv::new() .with_chain_id(chain_spec.chain_id()) .with_spec_and_mainnet_gas_params(spec); @@ -3880,7 +3977,7 @@ mod tests { .with_cfg(cfg_env) .with_block(BlockEnv::default()); let precompiles = ArcPrecompileProvider::create_precompiles_map(spec, hardfork_flags); - let mut instruction = EthInstructions::default(); + let mut instruction = EthInstructions::new_mainnet_with_spec(spec); if hardfork_flags.is_active(ArcHardfork::Zero5) { instruction.insert_instruction( SELFDESTRUCT, @@ -4378,6 +4475,69 @@ mod tests { assert_eq!(b_balance, U256::ZERO, "contract B should have zero balance"); } + /// STATICCALL → INNER → CALL CallFrom propagates `is_static=true` to the inner + /// CALL frame (scheme stays `Call`). The subcall framework must reject it and + /// consume all caller gas, matching upstream `CallNotAllowedInsideStatic` halt + /// semantics and the standard precompile `check_staticcall` helper. + #[test] + fn test_call_from_is_static_propagated_consumes_all_gas() { + let outer_code = static_call_wrapper_bytecode(WRAPPER_INNER); + let inner_code = wrapper_call_bytecode(CALL_FROM_ADDRESS); + let echo_code = echo_double_bytecode(); + + let mut evm = setup_test_evm( + &[(EOA, U256::from(10_000_000))], + &[ + (WRAPPER, outer_code), + (WRAPPER_INNER, inner_code), + (ECHO_CONTRACT, echo_code), + ], + &[WRAPPER_INNER], + ); + + let inner_calldata = U256::from(42).to_be_bytes::<32>().to_vec(); + let call_from_input = encode_call_from_input(EOA, ECHO_CONTRACT, &inner_calldata); + + let gas_limit: u64 = 1_000_000; + let tx = TxEnv { + caller: EOA, + kind: TxKind::Call(WRAPPER), + value: U256::ZERO, + gas_limit, + gas_price: 0, + chain_id: Some(LOCAL_DEV.chain_id()), + data: call_from_input, + ..Default::default() + }; + + let result = evm.transact_one(tx).expect("transact_one should succeed"); + let (output, gas_used) = match &result { + ExecutionResult::Success { + output, gas_used, .. + } => (output, *gas_used), + other => panic!("expected Success (wrapper catches revert), got {other:?}"), + }; + + assert_eq!( + decode_revert_reason(output.data()), + "subcall precompiles cannot be invoked in static context", + ); + + // The inner CALL forwards (almost) all remaining gas to CallFrom; the static + // rejection consumes all of it. EIP-150 retains 1/64 of the caller's gas at + // each CALL/STATICCALL boundary; with two nested boundaries the unavoidable + // upper bound on retained gas is 1/64 + (1/64)·(63/64) ≈ 3.1%, so gas_used + // must exceed ~96.9% of the limit. The 95% threshold leaves a thin buffer + // for wrapper bookkeeping; tightening it on a future cost change is desirable + // — a regression to the old flat-100-gas behavior would drop gas_used by an + // order of magnitude (to ~5% of the limit). + assert!( + gas_used > gas_limit * 95 / 100, + "expected gas_used > 95% of limit (all gas consumed by static rejection); \ + got gas_used={gas_used} of gas_limit={gas_limit}", + ); + } + /// Nested callFrom targeting the CallFrom precompile address itself. The child frame /// calls CALL_FROM_ADDRESS via `init_with_context` (not our `frame_init`), so the /// subcall interception never fires. Since CALL_FROM_ADDRESS has no deployed code, @@ -5033,7 +5193,7 @@ mod tests { let spec = reth_ethereum::evm::revm_spec_by_timestamp_and_block_number(&chain_spec, 0, 0); - let hardfork_flags = chain_spec.get_hardfork_flags(0); + let hardfork_flags = chain_spec.get_hardfork_flags(0, 0); let mut cfg_env = revm::context::CfgEnv::new() .with_chain_id(chain_spec.chain_id()) .with_spec_and_mainnet_gas_params(spec); @@ -5042,7 +5202,7 @@ mod tests { .with_cfg(cfg_env) .with_block(BlockEnv::default()); let precompiles = ArcPrecompileProvider::create_precompiles_map(spec, hardfork_flags); - let mut instruction = EthInstructions::default(); + let mut instruction = EthInstructions::new_mainnet_with_spec(spec); if hardfork_flags.is_active(ArcHardfork::Zero5) { instruction.insert_instruction( SELFDESTRUCT, @@ -5534,32 +5694,35 @@ mod tests { } // ================================================================ - // tx.origin sender validation tests + // EIP-7702 delegation tests // ================================================================ - /// EOA → WRAPPER → callFrom(sender=SPOOFED_SENDER, target=ECHO, data) - /// SPOOFED_SENDER is neither tx.origin (EOA) nor the actual caller (WRAPPER), - /// so the sender validation rejects it. + /// CallFrom targeting an EIP-7702 delegated account executes the delegate's code. + /// DELEGATED_EOA has delegation → ECHO_CONTRACT, so calling it runs echo_double. #[test] - fn test_call_from_contract_sender_spoofing_rejected() { + fn test_call_from_eip7702_delegation() { + const DELEGATED_EOA: Address = address!("d000000000000000000000000000000000000001"); + let contract_a_code = wrapper_call_bytecode(CALL_FROM_ADDRESS); - let contract_b_code = echo_double_bytecode(); - const CONTRACT_A: Address = WRAPPER; - const CONTRACT_B: Address = ECHO_CONTRACT; + let echo_code = echo_double_bytecode(); let mut evm = setup_test_evm( &[(EOA, U256::from(1_000_000))], - &[(CONTRACT_A, contract_a_code), (CONTRACT_B, contract_b_code)], - &[CONTRACT_A], + &[(WRAPPER, contract_a_code), (ECHO_CONTRACT, echo_code)], + &[WRAPPER], ); + // Insert EIP-7702 delegated account: DELEGATED_EOA delegates to ECHO_CONTRACT + insert_eip7702_account(&mut evm, DELEGATED_EOA, ECHO_CONTRACT); + + // EOA → WRAPPER → CallFrom(sender=EOA, target=DELEGATED_EOA, data=abi(42)) + // DELEGATED_EOA has delegation to ECHO_CONTRACT, so echo_double(42) = 84 let inner_calldata = U256::from(42).to_be_bytes::<32>().to_vec(); - let call_from_input = - encode_call_from_input(SPOOFED_SENDER, CONTRACT_B, &inner_calldata); + let call_from_input = encode_call_from_input(EOA, DELEGATED_EOA, &inner_calldata); let tx = TxEnv { caller: EOA, - kind: TxKind::Call(CONTRACT_A), + kind: TxKind::Call(WRAPPER), value: U256::ZERO, gas_limit: 1_000_000, gas_price: 0, @@ -5571,13 +5734,267 @@ mod tests { let result = evm.transact_one(tx).expect("transact_one should succeed"); match &result { ExecutionResult::Success { output, .. } => { - let reason = decode_revert_reason(output.data()); - assert_eq!(reason, "sender spoofing requires tx.origin as sender"); - } - other => panic!("expected Success (wrapper catches revert), got {other:?}"), - } - } - + let (success, return_data) = decode_call_from_output(output.data()); + assert!(success, "callFrom should report child success"); + let expected = U256::from(84).to_be_bytes::<32>(); + assert_eq!( + return_data, expected, + "should get echo_double(42) = 84 via delegation" + ); + } + other => panic!("expected Success, got {other:?}"), + } + } + + /// SubcallTestPrecompile targeting an EIP-7702 delegated account executes the + /// delegate's code. DELEGATED_EOA delegates to ECHO_CONTRACT, so the subcall + /// test precompile calling it should run echo_double. + #[test] + fn test_subcall_eip7702_delegation() { + use crate::subcall_test::SUBCALL_TEST_ADDRESS; + + const DELEGATED_EOA: Address = address!("d000000000000000000000000000000000000001"); + + let wrapper_code = wrapper_call_bytecode(SUBCALL_TEST_ADDRESS); + let echo_code = echo_double_bytecode(); + + let mut evm = setup_test_evm( + &[(EOA, U256::from(1_000_000))], + &[(WRAPPER, wrapper_code), (ECHO_CONTRACT, echo_code)], + &[], + ); + + // Insert EIP-7702 delegated account: DELEGATED_EOA delegates to ECHO_CONTRACT + insert_eip7702_account(&mut evm, DELEGATED_EOA, ECHO_CONTRACT); + + // EOA → WRAPPER → SubcallTest(target=DELEGATED_EOA, data=abi(21)) + let inner_calldata = U256::from(21).to_be_bytes::<32>().to_vec(); + let subcall_input = encode_subcall_test_input(DELEGATED_EOA, &inner_calldata); + + let tx = TxEnv { + caller: EOA, + kind: TxKind::Call(WRAPPER), + value: U256::ZERO, + gas_limit: 1_000_000, + gas_price: 0, + chain_id: Some(LOCAL_DEV.chain_id()), + data: subcall_input, + ..Default::default() + }; + + let result = evm.transact_one(tx).expect("transact_one should succeed"); + match &result { + ExecutionResult::Success { output, .. } => { + let child_output = + ::abi_decode(output.data()) + .expect("should decode bytes wrapper"); + // echo_double(21) → 42 + let expected = U256::from(42).to_be_bytes::<32>(); + assert_eq!( + child_output.as_ref(), + &expected, + "should get echo_double(21) = 42 via delegation" + ); + } + other => panic!("expected Success, got {other:?}"), + } + } + + /// CallFrom targeting an EIP-7702 delegated account whose delegate reverts. + /// DELEGATED_EOA delegates to REVERT_CONTRACT — the revert should propagate + /// correctly through CallFrom's (success=false) return value. + #[test] + fn test_call_from_eip7702_delegation_to_reverting_contract() { + const DELEGATED_EOA: Address = address!("d000000000000000000000000000000000000001"); + const REVERT_PAYLOAD: [u8; 4] = [0xca, 0xfe, 0xba, 0xbe]; + + let contract_a_code = wrapper_call_bytecode(CALL_FROM_ADDRESS); + let revert_code = reverting_with_payload_bytecode(REVERT_PAYLOAD); + + let mut evm = setup_test_evm( + &[(EOA, U256::from(1_000_000))], + &[(WRAPPER, contract_a_code), (REVERT_CONTRACT, revert_code)], + &[WRAPPER], + ); + + // Insert EIP-7702 delegated account: DELEGATED_EOA delegates to REVERT_CONTRACT + insert_eip7702_account(&mut evm, DELEGATED_EOA, REVERT_CONTRACT); + + // EOA → WRAPPER → CallFrom(sender=EOA, target=DELEGATED_EOA, data=) + let call_from_input = encode_call_from_input(EOA, DELEGATED_EOA, &[]); + + let tx = TxEnv { + caller: EOA, + kind: TxKind::Call(WRAPPER), + value: U256::ZERO, + gas_limit: 1_000_000, + gas_price: 0, + chain_id: Some(LOCAL_DEV.chain_id()), + data: call_from_input, + ..Default::default() + }; + + let result = evm.transact_one(tx).expect("transact_one should succeed"); + match &result { + ExecutionResult::Success { output, .. } => { + let (success, return_data) = decode_call_from_output(output.data()); + assert!( + !success, + "callFrom should report child failure (delegate reverts)" + ); + assert_eq!( + return_data.as_slice(), + REVERT_PAYLOAD.as_slice(), + "callFrom should propagate the delegate's revert payload" + ); + } + other => panic!("expected Success, got {other:?}"), + } + } + + /// If EIP-7702 target resolution succeeds but the cold delegate load is OOG, + /// the delegate must stay cold. Otherwise an attacker could warm the delegate as + /// a side-effect of an insufficient-gas subcall. + #[test] + fn test_call_from_eip7702_delegate_oog_does_not_warm_delegate() { + use alloy_sol_types::SolCall; + use arc_precompiles::call_from::{abi_decode_gas, ICallFrom}; + use revm_interpreter::gas::COLD_ACCOUNT_ACCESS_COST; + + const DELEGATED_EOA: Address = address!("d000000000000000000000000000000000000001"); + + let echo_code = echo_double_bytecode(); + let mut evm = setup_test_evm( + &[(EOA, U256::from(1_000_000))], + &[(ECHO_CONTRACT, echo_code)], + &[WRAPPER], + ); + insert_eip7702_account(&mut evm, DELEGATED_EOA, ECHO_CONTRACT); + + evm.ctx_mut().journal_mut().clear(); + evm.inner.ctx.set_tx(TxEnv { + caller: EOA, + ..Default::default() + }); + + let child_data: Vec = Vec::new(); + let calldata = ICallFrom::callFromCall { + sender: EOA, + target: DELEGATED_EOA, + data: child_data.clone().into(), + } + .abi_encode(); + + let gas_limit = abi_decode_gas(child_data.len()) + .checked_add(COLD_ACCOUNT_ACCESS_COST) + .and_then(|gas| gas.checked_add(COLD_ACCOUNT_ACCESS_COST)) + .and_then(|gas| gas.checked_sub(1)) + .expect("test gas calculation should not overflow"); + let call_inputs = CallInputs { + scheme: CallScheme::Call, + target_address: CALL_FROM_ADDRESS, + bytecode_address: CALL_FROM_ADDRESS, + known_bytecode: None, + value: CallValue::Transfer(U256::ZERO), + input: CallInput::Bytes(Bytes::from(calldata)), + gas_limit, + is_static: false, + caller: WRAPPER, + return_memory_offset: 0..0, + }; + + let frame_input = FrameInit { + frame_input: FrameInput::Call(Box::new(call_inputs)), + memory: SharedMemory::default(), + depth: 1, + }; + + let precompile: Arc = + Arc::new(CallFromPrecompile); + let result = evm + .init_subcall(frame_input, precompile) + .expect("init_subcall should return OOG, not DB error"); + + match result { + ItemOrResult::Result(FrameResult::Call(outcome)) => { + assert_eq!(outcome.result.result, InstructionResult::OutOfGas); + assert_eq!( + outcome.result.gas.spent(), + gas_limit, + "OOG should consume the whole subcall gas budget" + ); + } + other => panic!("expected immediate OOG result, got {other:?}"), + } + assert!( + evm.subcall_continuations.is_empty(), + "OOG before child dispatch should not store any continuation" + ); + + let target_probe = evm + .ctx_mut() + .journal_mut() + .load_account_info_skip_cold_load(DELEGATED_EOA, true, true); + assert!( + target_probe.is_ok(), + "target account should be warm after successful target resolution" + ); + + let delegate_probe = evm + .ctx_mut() + .journal_mut() + .load_account_info_skip_cold_load(ECHO_CONTRACT, true, true); + assert!( + matches!(delegate_probe, Err(JournalLoadError::ColdLoadSkipped)), + "delegate account should remain cold when delegate resolution is OOG" + ); + } + + // ================================================================ + // tx.origin sender validation tests + // ================================================================ + + /// EOA → WRAPPER → callFrom(sender=SPOOFED_SENDER, target=ECHO, data) + /// SPOOFED_SENDER is neither tx.origin (EOA) nor the actual caller (WRAPPER), + /// so the sender validation rejects it. + #[test] + fn test_call_from_contract_sender_spoofing_rejected() { + let contract_a_code = wrapper_call_bytecode(CALL_FROM_ADDRESS); + let contract_b_code = echo_double_bytecode(); + const CONTRACT_A: Address = WRAPPER; + const CONTRACT_B: Address = ECHO_CONTRACT; + + let mut evm = setup_test_evm( + &[(EOA, U256::from(1_000_000))], + &[(CONTRACT_A, contract_a_code), (CONTRACT_B, contract_b_code)], + &[CONTRACT_A], + ); + + let inner_calldata = U256::from(42).to_be_bytes::<32>().to_vec(); + let call_from_input = + encode_call_from_input(SPOOFED_SENDER, CONTRACT_B, &inner_calldata); + + let tx = TxEnv { + caller: EOA, + kind: TxKind::Call(CONTRACT_A), + value: U256::ZERO, + gas_limit: 1_000_000, + gas_price: 0, + chain_id: Some(LOCAL_DEV.chain_id()), + data: call_from_input, + ..Default::default() + }; + + let result = evm.transact_one(tx).expect("transact_one should succeed"); + match &result { + ExecutionResult::Success { output, .. } => { + let reason = decode_revert_reason(output.data()); + assert_eq!(reason, "sender spoofing requires tx.origin as sender"); + } + other => panic!("expected Success (wrapper catches revert), got {other:?}"), + } + } + /// complete_subcall error should consume all gas allocated to the subcall. #[test] fn test_complete_subcall_error_consumes_all_gas() { @@ -5624,7 +6041,7 @@ mod tests { let spec = reth_ethereum::evm::revm_spec_by_timestamp_and_block_number(&chain_spec, 0, 0); - let hardfork_flags = chain_spec.get_hardfork_flags(0); + let hardfork_flags = chain_spec.get_hardfork_flags(0, 0); let mut cfg_env = revm::context::CfgEnv::new() .with_chain_id(chain_spec.chain_id()) .with_spec_and_mainnet_gas_params(spec); @@ -5634,7 +6051,7 @@ mod tests { .with_cfg(cfg_env) .with_block(BlockEnv::default()); let precompiles = ArcPrecompileProvider::create_precompiles_map(spec, hardfork_flags); - let instruction = EthInstructions::default(); + let instruction = EthInstructions::new_mainnet_with_spec(spec); let mut registry = SubcallRegistry::new(); registry.register( @@ -5706,1119 +6123,1724 @@ mod tests { ); } - // These tests verify that `init_subcall` correctly charges EIP-2929 account - // access costs for the child target address. + /// Bytecode: SSTORE(slot=0, value=42) then RETURN. Used to verify state revert. + fn sstore_42_bytecode() -> Bytes { + #[rustfmt::skip] + let code = vec![ + PUSH1, 42, // value = 42 + PUSH1, 0x00, // slot = 0 + SSTORE, // SSTORE(0, 42) + PUSH1, 0x00, + PUSH1, 0x00, + RETURN, // RETURN(0, 0) + ]; + Bytes::from(code) + } - /// Integration test: CallFrom targeting a cold account should cost more gas - /// than targeting a warm one (pre-warmed via access_list). + /// Bytecode: SSTORE(slot=0, value=42), burn most remaining gas, then RETURN. /// - /// Uses two separate EVM instances so each transaction starts with a fresh - /// journal — reusing one EVM would leave addresses warm from the first tx. - #[test] - fn test_call_from_cold_target_costs_more_gas_than_warm() { - let contract_a_code = wrapper_call_bytecode(CALL_FROM_ADDRESS); - let contract_b_code = echo_double_bytecode(); - const CONTRACT_A: Address = WRAPPER; - const CONTRACT_B: Address = ECHO_CONTRACT; + /// Used to force completion accounting to exceed the caller's retained gas while + /// keeping the child frame successful. + fn sstore_42_then_drain_gas_bytecode() -> Bytes { + #[rustfmt::skip] + let code = vec![ + PUSH1, 42, // value = 42 + PUSH1, 0x00, // slot = 0 + SSTORE, // SSTORE(0, 42) + JUMPDEST, // offset 5 + GAS, + PUSH2, 0x00, 0x40, // stop once gasleft <= 64 + LT, // 64 < gasleft + PUSH1, 0x05, + JUMPI, + PUSH1, 0x00, + PUSH1, 0x00, + RETURN, // RETURN(0, 0) + ]; + Bytes::from(code) + } - let inner_calldata = U256::from(42).to_be_bytes::<32>().to_vec(); - let call_from_input = encode_call_from_input(EOA, CONTRACT_B, &inner_calldata); + /// Bytecode: burn most remaining gas, then REVERT. + /// + /// Used to force completion accounting to exceed the caller's retained gas while + /// keeping the child result in the EVM revert class (not a halt). + fn drain_gas_then_revert_bytecode() -> Bytes { + #[rustfmt::skip] + let code = vec![ + JUMPDEST, // offset 0 + GAS, + PUSH2, 0x00, 0x40, // stop once gasleft <= 64 + LT, // 64 < gasleft + PUSH1, 0x00, + JUMPI, + PUSH1, 0x00, + PUSH1, 0x00, + REVERT, // REVERT(0, 0) + ]; + Bytes::from(code) + } - // Cold target: fresh EVM, no access_list - let mut evm_cold = setup_test_evm( + /// Control: the gas-draining child bytecode must be capable of persisting its + /// SSTORE when completion accounting does not OOG. + #[test] + fn test_sstore_drain_child_persists_without_excessive_completion_gas() { + let sstore_drain_code = sstore_42_then_drain_gas_bytecode(); + const STORAGE_CONTRACT: Address = address!("c000000000000000000000000000000000000021"); + + let mut evm = setup_test_evm( &[(EOA, U256::from(1_000_000))], - &[ - (CONTRACT_A, contract_a_code.clone()), - (CONTRACT_B, contract_b_code.clone()), - ], - &[CONTRACT_A], + &[(STORAGE_CONTRACT, sstore_drain_code)], + &[], ); - let tx_cold = TxEnv { - tx_type: 1, // EIP-2930 - caller: EOA, - kind: TxKind::Call(CONTRACT_A), - value: U256::ZERO, - gas_limit: 200_000, - gas_price: 0, - chain_id: Some(LOCAL_DEV.chain_id()), - data: call_from_input.clone(), - access_list: Default::default(), - ..Default::default() - }; - let result_cold = evm_cold - .transact_one(tx_cold) - .expect("cold tx should succeed"); - let gas_cold = match &result_cold { - ExecutionResult::Success { gas_used, .. } => *gas_used, - other => panic!("expected Success, got {other:?}"), - }; - // Warm target: fresh EVM, pre-warm CONTRACT_B via access_list. - // tx_type must be EIP-2930 (1) so revm processes the access_list warmup; - // the default Legacy type skips access list handling entirely. - let mut evm_warm = setup_test_evm( - &[(EOA, U256::from(1_000_000))], - &[(CONTRACT_A, contract_a_code), (CONTRACT_B, contract_b_code)], - &[CONTRACT_A], - ); - let tx_warm = TxEnv { - tx_type: 1, // EIP-2930 + let input = encode_subcall_test_input(STORAGE_CONTRACT, &[]); + evm.inner.ctx.set_tx(TxEnv { caller: EOA, - kind: TxKind::Call(CONTRACT_A), + kind: TxKind::Call(SUBCALL_TEST_ADDRESS), value: U256::ZERO, - gas_limit: 200_000, + gas_limit: 100_000, gas_price: 0, chain_id: Some(LOCAL_DEV.chain_id()), - data: call_from_input, - access_list: vec![alloy_eips::eip2930::AccessListItem { - address: CONTRACT_B, - storage_keys: vec![], - }] - .into(), + data: input, ..Default::default() - }; - let result_warm = evm_warm - .transact_one(tx_warm) - .expect("warm tx should succeed"); - let gas_warm = match &result_warm { - ExecutionResult::Success { gas_used, .. } => *gas_used, - other => panic!("expected Success, got {other:?}"), - }; + }); - // Both txs are EIP-2930; the only difference is the access_list entry. - // Delta = COLD_ACCOUNT_ACCESS_COST (2600) − ACCESS_LIST_ADDRESS_COST (2400) - // - WARM_STORAGE_READ_COST (100) = 100. + let result_and_state = evm.replay().expect("replay should succeed"); + assert!( + result_and_state.result.is_success(), + "normal completion should let the direct subcall tx succeed: {:?}", + result_and_state.result, + ); + + let storage_account = result_and_state + .state + .get(&STORAGE_CONTRACT) + .expect("storage contract should have a state diff"); + let slot_value = storage_account + .storage + .get(&U256::ZERO) + .map(|s| s.present_value) + .unwrap_or(U256::ZERO); assert_eq!( - gas_cold - gas_warm, - 100, - "cold/warm gas delta should be exactly 100 (COLD_ACCOUNT_ACCESS_COST 2600 \ - - ACCESS_LIST_ADDRESS_COST 2400 - WARM_STORAGE_READ_COST 100)" + slot_value, + U256::from(42), + "control child bytecode should persist SSTORE(0, 42)" ); } - /// Unit test: init_subcall with a cold target should set - /// init_subcall_gas_overhead = abi_decode_gas + COLD_ACCOUNT_ACCESS_COST. + /// Completion accounting that exceeds the subcall gas limit must make the + /// subcall fail with OOG and revert successful child state changes. #[test] - fn test_call_from_cold_target_recalculates_child_gas() { - use alloy_sol_types::SolCall; - use arc_precompiles::call_from::{abi_decode_gas, ICallFrom}; - use revm_interpreter::gas::COLD_ACCOUNT_ACCESS_COST; + fn test_complete_subcall_completion_oog_reverts_successful_child_state() { + use crate::subcall::AllowedCallers; + use crate::subcall_test::{ + ExcessiveCompleteSubcallPrecompile, EXCESSIVE_COMPLETE_SUBCALL_ADDRESS, + }; + + let wrapper_code = wrapper_call_bytecode(EXCESSIVE_COMPLETE_SUBCALL_ADDRESS); + let sstore_drain_code = sstore_42_then_drain_gas_bytecode(); + const CONTRACT_WRAPPER: Address = WRAPPER; + const STORAGE_CONTRACT: Address = address!("c000000000000000000000000000000000000021"); - let echo_code = echo_double_bytecode(); let mut evm = setup_test_evm( &[(EOA, U256::from(1_000_000))], - &[(ECHO_CONTRACT, echo_code)], - &[WRAPPER], + &[ + (CONTRACT_WRAPPER, wrapper_code), + (STORAGE_CONTRACT, sstore_drain_code), + ], + &[], ); - // Clear journal state and load required accounts - evm.ctx_mut().journal_mut().clear(); - evm.ctx_mut() - .journal_mut() - .load_account(NATIVE_COIN_CONTROL_ADDRESS) - .unwrap(); + let mut registry = SubcallRegistry::new(); + registry.register( + EXCESSIVE_COMPLETE_SUBCALL_ADDRESS, + Arc::new(ExcessiveCompleteSubcallPrecompile), + AllowedCallers::Unrestricted, + ); + evm.subcall_registry = Arc::new(registry); - // tx.origin must match the spoofed sender for the origin check. + let input = encode_subcall_test_input(STORAGE_CONTRACT, &[]); evm.inner.ctx.set_tx(TxEnv { caller: EOA, + kind: TxKind::Call(CONTRACT_WRAPPER), + value: U256::ZERO, + gas_limit: 100_000, + gas_price: 0, + chain_id: Some(LOCAL_DEV.chain_id()), + data: input, ..Default::default() }); - let child_data: Vec = vec![0x42]; - let calldata = ICallFrom::callFromCall { - sender: EOA, - target: ECHO_CONTRACT, - data: child_data.clone().into(), - } - .abi_encode(); - - let gas_limit: u64 = 100_000; - let call_inputs = CallInputs { - scheme: CallScheme::Call, - target_address: CALL_FROM_ADDRESS, - bytecode_address: CALL_FROM_ADDRESS, - known_bytecode: None, - value: CallValue::Transfer(U256::ZERO), - input: CallInput::Bytes(Bytes::from(calldata)), - gas_limit, - is_static: false, - caller: WRAPPER, - return_memory_offset: 0..0, - }; + let result_and_state = evm.replay().expect("replay should succeed"); + assert!( + result_and_state.result.is_success(), + "wrapper should succeed after catching the inner OOG: {:?}", + result_and_state.result, + ); - let frame_input = FrameInit { - frame_input: FrameInput::Call(Box::new(call_inputs)), - memory: SharedMemory::default(), - depth: 1, + let ExecutionResult::Success { output, .. } = &result_and_state.result else { + panic!("expected successful wrapper result"); }; - - let precompile: Arc = - Arc::new(CallFromPrecompile); - let result = evm - .init_subcall(frame_input, precompile) - .expect("init_subcall should succeed"); assert!( - matches!(result, ItemOrResult::Item(_)), - "expected child frame, got immediate result" + output.data().is_empty(), + "inner completion OOG should not return successful subcall returndata" ); - let continuation = evm - .subcall_continuations - .get(&1) - .expect("continuation should be stored at depth 1"); - - let expected_overhead = abi_decode_gas(child_data.len()) + COLD_ACCOUNT_ACCESS_COST; + let storage_account = result_and_state + .state + .get(&STORAGE_CONTRACT) + .expect("storage contract should appear in state diff (child did execute)"); + let slot_value = storage_account + .storage + .get(&U256::ZERO) + .map(|s| s.present_value) + .unwrap_or(U256::ZERO); assert_eq!( - continuation.init_subcall_gas_overhead, - expected_overhead, - "overhead should be abi_decode ({}) + cold access ({COLD_ACCOUNT_ACCESS_COST}), \ - got {}", - abi_decode_gas(child_data.len()), - continuation.init_subcall_gas_overhead + slot_value, + U256::ZERO, + "child's SSTORE(0, 42) should have been reverted after completion OOG" ); } - /// Unit test: when the target is already warm, init_subcall should use - /// WARM_STORAGE_READ_COST (100) instead of COLD_ACCOUNT_ACCESS_COST (2600). + /// Completion accounting that exceeds the subcall gas limit must OOG even + /// when the child result is a revert rather than a halt. #[test] - fn test_call_from_warm_target_uses_warm_cost() { - use alloy_sol_types::SolCall; - use arc_precompiles::call_from::{abi_decode_gas, ICallFrom}; - use revm_interpreter::gas::WARM_STORAGE_READ_COST; + fn test_complete_subcall_child_revert_excessive_completion_gas_oogs() { + use crate::subcall::AllowedCallers; + use crate::subcall_test::{ + ExcessiveCompleteSubcallPrecompile, EXCESSIVE_COMPLETE_SUBCALL_ADDRESS, + }; + + const REVERT_DRAINER: Address = address!("c000000000000000000000000000000000000022"); + let revert_drainer_code = drain_gas_then_revert_bytecode(); - let echo_code = echo_double_bytecode(); let mut evm = setup_test_evm( &[(EOA, U256::from(1_000_000))], - &[(ECHO_CONTRACT, echo_code)], - &[WRAPPER], + &[(REVERT_DRAINER, revert_drainer_code)], + &[], ); - // Clear journal state and load required accounts - evm.ctx_mut().journal_mut().clear(); - evm.ctx_mut() - .journal_mut() - .load_account(NATIVE_COIN_CONTROL_ADDRESS) - .unwrap(); - - // Pre-warm the target account - evm.ctx_mut() - .journal_mut() - .load_account(ECHO_CONTRACT) - .unwrap(); + let mut registry = SubcallRegistry::new(); + registry.register( + EXCESSIVE_COMPLETE_SUBCALL_ADDRESS, + Arc::new(ExcessiveCompleteSubcallPrecompile), + AllowedCallers::Unrestricted, + ); + evm.subcall_registry = Arc::new(registry); - evm.inner.ctx.set_tx(TxEnv { + let input = encode_subcall_test_input(REVERT_DRAINER, &[]); + let tx = TxEnv { caller: EOA, + kind: TxKind::Call(EXCESSIVE_COMPLETE_SUBCALL_ADDRESS), + value: U256::ZERO, + gas_limit: 100_000, + gas_price: 0, + chain_id: Some(LOCAL_DEV.chain_id()), + data: input, ..Default::default() - }); + }; - let child_data: Vec = vec![0x42]; - let calldata = ICallFrom::callFromCall { - sender: EOA, - target: ECHO_CONTRACT, - data: child_data.clone().into(), + let result = evm.transact_one(tx).expect("tx should execute"); + match result { + ExecutionResult::Halt { reason, .. } => { + assert!( + matches!(reason, HaltReason::OutOfGas(_)), + "excessive child-revert completion gas should OOG" + ); + } + other => panic!("expected direct subcall tx to halt OOG, got {other:?}"), } - .abi_encode(); + } - let gas_limit: u64 = 100_000; - let call_inputs = CallInputs { - scheme: CallScheme::Call, - target_address: CALL_FROM_ADDRESS, - bytecode_address: CALL_FROM_ADDRESS, - known_bytecode: None, - value: CallValue::Transfer(U256::ZERO), - input: CallInput::Bytes(Bytes::from(calldata)), - gas_limit, - is_static: false, - caller: WRAPPER, - return_memory_offset: 0..0, + /// Completion gas must also be accounted when the child halts. Calling the + /// subcall precompile directly lets the top-level result distinguish OOG from + /// a normal completion revert. + #[test] + fn test_complete_subcall_child_halt_excessive_completion_gas_oogs() { + use crate::subcall::AllowedCallers; + use crate::subcall_test::{ + ExcessiveCompleteSubcallPrecompile, EXCESSIVE_COMPLETE_SUBCALL_ADDRESS, }; - let frame_input = FrameInit { - frame_input: FrameInput::Call(Box::new(call_inputs)), - memory: SharedMemory::default(), - depth: 1, - }; + const GAS_BURNER: Address = address!("c000000000000000000000000000000000000006"); + let gas_burner_code = gas_burner_bytecode(); - let precompile: Arc = - Arc::new(CallFromPrecompile); - let result = evm - .init_subcall(frame_input, precompile) - .expect("init_subcall should succeed"); - assert!( - matches!(result, ItemOrResult::Item(_)), - "expected child frame, got immediate result" + let mut evm = setup_test_evm( + &[(EOA, U256::from(1_000_000))], + &[(GAS_BURNER, gas_burner_code)], + &[], ); - let continuation = evm - .subcall_continuations - .get(&1) - .expect("continuation should be stored at depth 1"); - - let expected_overhead = abi_decode_gas(child_data.len()) + WARM_STORAGE_READ_COST; - assert_eq!( - continuation.init_subcall_gas_overhead, - expected_overhead, - "overhead should be abi_decode ({}) + warm read ({WARM_STORAGE_READ_COST}), \ - got {}", - abi_decode_gas(child_data.len()), - continuation.init_subcall_gas_overhead + let mut registry = SubcallRegistry::new(); + registry.register( + EXCESSIVE_COMPLETE_SUBCALL_ADDRESS, + Arc::new(ExcessiveCompleteSubcallPrecompile), + AllowedCallers::Unrestricted, ); - } - - /// When caller == target, `load_account(caller)` warms the address before - /// `load_account(target)`, so only the warm access cost (100) is charged. - #[test] - fn test_call_from_caller_equals_target_uses_warm_cost() { - use alloy_sol_types::SolCall; - use arc_precompiles::call_from::{abi_decode_gas, ICallFrom}; - use revm_interpreter::gas::WARM_STORAGE_READ_COST; + evm.subcall_registry = Arc::new(registry); - let echo_code = echo_double_bytecode(); - // ECHO_CONTRACT is both the sender and target — give it balance and code. - let mut evm = setup_test_evm( - &[(ECHO_CONTRACT, U256::from(10_000_000))], - &[(ECHO_CONTRACT, echo_code)], - &[WRAPPER], - ); - - evm.ctx_mut().journal_mut().clear(); - evm.ctx_mut() - .journal_mut() - .load_account(NATIVE_COIN_CONTROL_ADDRESS) - .unwrap(); - - evm.inner.ctx.set_tx(TxEnv { - caller: ECHO_CONTRACT, + let input = encode_subcall_test_input(GAS_BURNER, &[]); + let tx = TxEnv { + caller: EOA, + kind: TxKind::Call(EXCESSIVE_COMPLETE_SUBCALL_ADDRESS), + value: U256::ZERO, + gas_limit: 100_000, + gas_price: 0, + chain_id: Some(LOCAL_DEV.chain_id()), + data: input, ..Default::default() - }); - - let child_data: Vec = vec![0x42]; - let calldata = ICallFrom::callFromCall { - sender: ECHO_CONTRACT, - target: ECHO_CONTRACT, - data: child_data.clone().into(), - } - .abi_encode(); - - let gas_limit: u64 = 100_000; - let call_inputs = CallInputs { - scheme: CallScheme::Call, - target_address: CALL_FROM_ADDRESS, - bytecode_address: CALL_FROM_ADDRESS, - known_bytecode: None, - value: CallValue::Transfer(U256::ZERO), - input: CallInput::Bytes(Bytes::from(calldata)), - gas_limit, - is_static: false, - caller: WRAPPER, - return_memory_offset: 0..0, - }; - - let frame_input = FrameInit { - frame_input: FrameInput::Call(Box::new(call_inputs)), - memory: SharedMemory::default(), - depth: 1, }; - let precompile: Arc = - Arc::new(CallFromPrecompile); - let result = evm - .init_subcall(frame_input, precompile) - .expect("init_subcall should succeed"); - assert!( - matches!(result, ItemOrResult::Item(_)), - "expected child frame, got immediate result" - ); - - let continuation = evm - .subcall_continuations - .get(&1) - .expect("continuation should be stored at depth 1"); - - let expected_overhead = abi_decode_gas(child_data.len()) + WARM_STORAGE_READ_COST; - assert_eq!( - continuation.init_subcall_gas_overhead, - expected_overhead, - "caller==target: overhead should be abi_decode ({}) + warm read \ - ({WARM_STORAGE_READ_COST}), got {}", - abi_decode_gas(child_data.len()), - continuation.init_subcall_gas_overhead - ); + let result = evm.transact_one(tx).expect("tx should execute"); + match result { + ExecutionResult::Halt { reason, .. } => { + assert!( + matches!(reason, HaltReason::OutOfGas(_)), + "excessive child-halt completion gas should OOG" + ); + } + other => panic!("expected direct subcall tx to halt OOG, got {other:?}"), + } } - /// Unit test: when gas_limit is just below abi_decode + COLD_ACCOUNT_ACCESS_COST, - /// init_subcall should OOG. + /// Nonzero completion gas on a halted child should preserve the existing + /// full-allocation burn behavior when the completion gas fits in retained gas. #[test] - fn test_call_from_oog_with_cold_account_access() { - use alloy_sol_types::SolCall; - use arc_precompiles::call_from::{abi_decode_gas, ICallFrom}; - use revm_interpreter::gas::COLD_ACCOUNT_ACCESS_COST; + fn test_complete_subcall_child_halt_completion_gas_fits_reverts() { + use crate::subcall::AllowedCallers; + use crate::subcall_test::{ + CostlyCompleteSubcallPrecompile, COSTLY_COMPLETE_SUBCALL_ADDRESS, + }; + + const GAS_BURNER: Address = address!("c000000000000000000000000000000000000006"); + let gas_burner_code = gas_burner_bytecode(); - let echo_code = echo_double_bytecode(); let mut evm = setup_test_evm( &[(EOA, U256::from(1_000_000))], - &[(ECHO_CONTRACT, echo_code)], - &[WRAPPER], + &[(GAS_BURNER, gas_burner_code)], + &[], ); - evm.ctx_mut().journal_mut().clear(); - evm.ctx_mut() - .journal_mut() - .load_account(NATIVE_COIN_CONTROL_ADDRESS) - .unwrap(); + let mut registry = SubcallRegistry::new(); + registry.register( + COSTLY_COMPLETE_SUBCALL_ADDRESS, + Arc::new(CostlyCompleteSubcallPrecompile), + AllowedCallers::Unrestricted, + ); + evm.subcall_registry = Arc::new(registry); - evm.inner.ctx.set_tx(TxEnv { + let input = encode_subcall_test_input(GAS_BURNER, &[]); + let gas_limit = 100_000; + let tx = TxEnv { caller: EOA, + kind: TxKind::Call(COSTLY_COMPLETE_SUBCALL_ADDRESS), + value: U256::ZERO, + gas_limit, + gas_price: 0, + chain_id: Some(LOCAL_DEV.chain_id()), + data: input, ..Default::default() - }); - - let child_data: Vec = vec![0x42]; - let calldata = ICallFrom::callFromCall { - sender: EOA, - target: ECHO_CONTRACT, - data: child_data.clone().into(), - } - .abi_encode(); - - // Gas is enough for ABI decode but NOT enough for ABI decode + cold access - let insufficient_gas = abi_decode_gas(child_data.len()) + COLD_ACCOUNT_ACCESS_COST - 1; - let call_inputs = CallInputs { - scheme: CallScheme::Call, - target_address: CALL_FROM_ADDRESS, - bytecode_address: CALL_FROM_ADDRESS, - known_bytecode: None, - value: CallValue::Transfer(U256::ZERO), - input: CallInput::Bytes(Bytes::from(calldata)), - gas_limit: insufficient_gas, - is_static: false, - caller: WRAPPER, - return_memory_offset: 0..0, - }; - - let frame_input = FrameInit { - frame_input: FrameInput::Call(Box::new(call_inputs)), - memory: SharedMemory::default(), - depth: 1, }; - let precompile: Arc = - Arc::new(CallFromPrecompile); - let result = evm - .init_subcall(frame_input, precompile) - .expect("init_subcall should not return db error"); - + let result = evm.transact_one(tx).expect("tx should execute"); match result { - ItemOrResult::Result(FrameResult::Call(outcome)) => { - assert_eq!( - outcome.result.result, - InstructionResult::OutOfGas, - "should OOG when gas is insufficient for account access cost" - ); + ExecutionResult::Revert { gas_used, output } => { assert_eq!( - outcome.result.gas.spent(), - insufficient_gas, - "OOG should consume all allocated gas" + gas_used, gas_limit, + "halted child with fitting completion gas should burn the full subcall allocation" ); assert!( - !evm.subcall_continuations.contains_key(&1), - "continuation should be removed after OOG" - ); - } - ItemOrResult::Result(other) => { - panic!("expected Call result, got {other:?}"); - } - ItemOrResult::Item(_) => { - panic!( - "expected OutOfGas when gas_limit ({insufficient_gas}) < \ - abi_decode ({}) + cold access ({COLD_ACCOUNT_ACCESS_COST})", - abi_decode_gas(child_data.len()) + output.is_empty(), + "halted child should return empty revert data" ); } + other => panic!("expected direct subcall tx to revert, got {other:?}"), } } - /// Boundary: gas_limit == abi_decode + COLD_ACCOUNT_ACCESS_COST should succeed - /// with child_gas_limit = 0 (child will OOG when it runs, but init_subcall itself - /// should not reject it). + /// Exact gas accounting boundary: gas_used == gas_limit succeeds, but + /// gas_used == gas_limit + 1 OOGs. #[test] - fn test_call_from_exact_overhead_gas_succeeds_with_zero_child_gas() { - use alloy_sol_types::SolCall; - use arc_precompiles::call_from::{abi_decode_gas, ICallFrom}; - use revm_interpreter::gas::COLD_ACCOUNT_ACCESS_COST; + fn test_complete_subcall_completion_gas_exact_boundary() { + use arc_precompiles::subcall::{ + SubcallCompletionResult, SubcallContinuationData, SubcallError, SubcallInitResult, + SubcallPrecompile, + }; - let echo_code = echo_double_bytecode(); - let mut evm = setup_test_evm( - &[(EOA, U256::from(1_000_000))], - &[(ECHO_CONTRACT, echo_code)], - &[WRAPPER], - ); + #[derive(Debug)] + struct FixedCompleteSubcallPrecompile { + completion_gas: u64, + success: bool, + } - evm.ctx_mut().journal_mut().clear(); - evm.ctx_mut() - .journal_mut() - .load_account(NATIVE_COIN_CONTROL_ADDRESS) - .unwrap(); + impl SubcallPrecompile for FixedCompleteSubcallPrecompile { + fn init_subcall( + &self, + _inputs: &CallInputs, + ) -> Result { + unreachable!("test calls complete_subcall directly") + } - evm.inner.ctx.set_tx(TxEnv { - caller: EOA, - ..Default::default() - }); + fn complete_subcall( + &self, + _continuation_data: SubcallContinuationData, + _child_result: &FrameResult, + ) -> Result { + Ok(SubcallCompletionResult { + output: Bytes::new(), + success: self.success, + gas_overhead: self.completion_gas, + }) + } + } - let child_data: Vec = vec![0x42]; - let calldata = ICallFrom::callFromCall { - sender: EOA, - target: ECHO_CONTRACT, - data: child_data.clone().into(), + fn run_with_gas_limit( + gas_limit: u64, + child_instruction_result: InstructionResult, + child_gas_spent: u64, + completion_success: bool, + ) -> FrameResult { + let mut evm = setup_test_evm(&[(EOA, U256::from(1_000_000))], &[], &[]); + let checkpoint = evm.inner.ctx.journal_mut().checkpoint(); + evm.inner.ctx.journal_mut().checkpoint_commit(); + + let mut child_gas = Gas::new(10); + assert!(child_gas.record_cost(child_gas_spent)); + let child_result = FrameResult::Call(CallOutcome { + result: InterpreterResult::new( + child_instruction_result, + Bytes::new(), + child_gas, + ), + memory_offset: 0..0, + was_precompile_called: false, + precompile_call_logs: Default::default(), + }); + + evm.complete_subcall( + child_result, + SubcallContinuation { + precompile: Arc::new(FixedCompleteSubcallPrecompile { + completion_gas: 5, + success: completion_success, + }), + gas_limit, + init_subcall_gas_overhead: 3, + return_memory_offset: 0..0, + continuation_data: SubcallContinuationData { + state: Box::new(()), + }, + checkpoint, + }, + ) + .expect("complete_subcall should not hit db errors") } - .abi_encode(); - let exact_gas = abi_decode_gas(child_data.len()) + COLD_ACCOUNT_ACCESS_COST; - let call_inputs = CallInputs { - scheme: CallScheme::Call, - target_address: CALL_FROM_ADDRESS, - bytecode_address: CALL_FROM_ADDRESS, - known_bytecode: None, - value: CallValue::Transfer(U256::ZERO), - input: CallInput::Bytes(Bytes::from(calldata)), - gas_limit: exact_gas, - is_static: false, - caller: WRAPPER, - return_memory_offset: 0..0, + let FrameResult::Call(exact_outcome) = + run_with_gas_limit(12, InstructionResult::Return, 4, true) + else { + panic!("expected call outcome for exact-fit case"); }; + assert_eq!( + exact_outcome.result.result, + InstructionResult::Return, + "exact gas fit should not OOG" + ); + assert_eq!( + exact_outcome.result.gas.spent(), + 12, + "exact gas fit should spend the metered gas" + ); - let frame_input = FrameInit { - frame_input: FrameInput::Call(Box::new(call_inputs)), - memory: SharedMemory::default(), - depth: 1, + let FrameResult::Call(oog_outcome) = + run_with_gas_limit(11, InstructionResult::Return, 4, true) + else { + panic!("expected call outcome for OOG case"); }; + assert_eq!( + oog_outcome.result.result, + InstructionResult::OutOfGas, + "one gas below metered cost should OOG" + ); + assert_eq!( + oog_outcome.result.gas.spent(), + 11, + "OOG should spend the full subcall gas limit" + ); - let precompile: Arc = - Arc::new(CallFromPrecompile); - let result = evm - .init_subcall(frame_input, precompile) - .expect("init_subcall should not return db error"); - - assert!( - matches!(result, ItemOrResult::Item(_)), - "exact overhead gas should push a child frame, not OOG" + let FrameResult::Call(halted_exact_outcome) = + run_with_gas_limit(18, InstructionResult::OutOfGas, 10, false) + else { + panic!("expected call outcome for halted exact-fit case"); + }; + assert_eq!( + halted_exact_outcome.result.result, + InstructionResult::Revert, + "halted child with exact gas fit should not OOG" + ); + assert_eq!( + halted_exact_outcome.result.gas.spent(), + 18, + "halted exact gas fit should spend the metered gas" ); - let continuation = evm - .subcall_continuations - .get(&1) - .expect("continuation should exist at depth 1"); + let FrameResult::Call(halted_oog_outcome) = + run_with_gas_limit(17, InstructionResult::OutOfGas, 10, false) + else { + panic!("expected call outcome for halted OOG case"); + }; assert_eq!( - continuation.init_subcall_gas_overhead, exact_gas, - "overhead should equal the full gas budget" + halted_oog_outcome.result.result, + InstructionResult::OutOfGas, + "halted child one gas below metered cost should OOG" + ); + assert_eq!( + halted_oog_outcome.result.gas.spent(), + 17, + "halted OOG should spend the full subcall gas limit" ); } - } - fn create_test_evm_with_spec( - db: InMemoryDB, - hardfork_flags: ArcHardforkFlags, - spec: SpecId, - ) -> ArcEvm< - EthEvmContext, - NoOpInspector, - EthInstructions>, - PrecompilesMap, - > { - let ctx = Context::new(db, spec); - let instruction = EthInstructions::default(); - let precompiles = ArcPrecompileProvider::create_precompiles_map(spec, hardfork_flags); - ArcEvm::new( - ctx, - NoOpInspector {}, - precompiles, - instruction, - false, - hardfork_flags, - Arc::new(SubcallRegistry::default()), - ) - } - #[test] - fn test_zero5_emits_eip7708_transfer_log() { - use revm::handler::SYSTEM_ADDRESS; + /// CS-ARC-PC-026: When `complete_subcall` rejects a successful child (returns + /// `success: false`), the child's committed state changes must be reverted. + #[test] + fn test_complete_subcall_rejects_successful_child_reverts_state() { + use crate::subcall::AllowedCallers; + use crate::subcall_test::{ + RejectingCompleteSubcallPrecompile, REJECTING_COMPLETE_SUBCALL_ADDRESS, + }; - let db = CacheDB::new(EmptyDB::default()); - let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero5]); - let mut evm = create_test_evm(db, flags); + let wrapper_code = wrapper_call_bytecode(REJECTING_COMPLETE_SUBCALL_ADDRESS); + let sstore_code = sstore_42_bytecode(); + const CONTRACT_WRAPPER: Address = WRAPPER; + const STORAGE_CONTRACT: Address = address!("c000000000000000000000000000000000000020"); - evm.ctx_mut() - .journal_mut() - .load_account(NATIVE_COIN_CONTROL_ADDRESS) - .unwrap(); + let chain_spec = LOCAL_DEV.clone(); + let mut db = InMemoryDB::default(); + db.insert_account_info( + EOA, + AccountInfo { + balance: U256::from(1_000_000), + nonce: 0, + code_hash: alloy_primitives::KECCAK256_EMPTY, + code: None, + account_id: None, + }, + ); + for (addr, code) in [ + (CONTRACT_WRAPPER, wrapper_code), + (STORAGE_CONTRACT, sstore_code), + ] { + db.insert_account_info( + addr, + AccountInfo { + balance: U256::ZERO, + nonce: 1, + code_hash: keccak256(&code), + code: Some(Bytecode::new_raw(code)), + account_id: None, + }, + ); + } - let frame = FrameInit { - frame_input: FrameInput::Call(call_input( - CallScheme::Call, - U256::from(100), - ADDRESS_A, - ADDRESS_B, - )), - memory: SharedMemory::default(), - depth: 1, - }; + let spec = + reth_ethereum::evm::revm_spec_by_timestamp_and_block_number(&chain_spec, 0, 0); + let hardfork_flags = chain_spec.get_hardfork_flags(0, 0); + let mut cfg_env = revm::context::CfgEnv::new() + .with_chain_id(chain_spec.chain_id()) + .with_spec_and_mainnet_gas_params(spec); + cfg_env.tx_gas_limit_cap = Some(u64::MAX); + let ctx = EthEvmContext::new(db, spec) + .with_cfg(cfg_env) + .with_block(BlockEnv::default()); + let precompiles = ArcPrecompileProvider::create_precompiles_map(spec, hardfork_flags); + let instruction = EthInstructions::new_mainnet_with_spec(spec); - let result = evm.before_frame_init(&frame).unwrap(); - match result { - BeforeFrameInitResult::Log(log, gas) => { - assert!(gas > 0, "Should have SLOAD gas cost"); + let mut registry = SubcallRegistry::new(); + registry.register( + REJECTING_COMPLETE_SUBCALL_ADDRESS, + Arc::new(RejectingCompleteSubcallPrecompile), + AllowedCallers::Unrestricted, + ); + + let mut evm = ArcEvm::new( + ctx, + revm::inspector::NoOpInspector {}, + precompiles, + instruction, + false, + hardfork_flags, + Arc::new(registry), + ); + + // Call wrapper → RejectingCompleteSubcallPrecompile → STORAGE_CONTRACT + // The child (STORAGE_CONTRACT) writes SSTORE(0, 42) and succeeds. + // But complete_subcall returns success: false, so the child's state must revert. + let input = encode_subcall_test_input(STORAGE_CONTRACT, &[]); + evm.inner.ctx.set_tx(TxEnv { + caller: EOA, + kind: TxKind::Call(CONTRACT_WRAPPER), + value: U256::ZERO, + gas_limit: 1_000_000, + gas_price: 0, + chain_id: Some(LOCAL_DEV.chain_id()), + data: input, + ..Default::default() + }); + + let result_and_state = evm.replay().expect("replay should succeed"); + + // The wrapper catches the revert from the precompile, so outer tx succeeds. + assert!( + result_and_state.result.is_success(), + "wrapper should succeed (it catches inner revert): {:?}", + result_and_state.result, + ); + + // The child's SSTORE(0, 42) must have been reverted. Check that STORAGE_CONTRACT + // either has no entry in the state diff or has slot 0 unchanged. + let storage_account = result_and_state.state.get(&STORAGE_CONTRACT); + if let Some(account) = storage_account { + let slot_value = account + .storage + .get(&U256::ZERO) + .map(|s| s.present_value) + .unwrap_or(U256::ZERO); assert_eq!( - log.address, SYSTEM_ADDRESS, - "Zero5 should emit EIP-7708 Transfer log from system address" + slot_value, + U256::ZERO, + "child's SSTORE(0, 42) should have been reverted, but slot 0 = {slot_value}" ); } - other => panic!( - "Expected Log result with EIP-7708 Transfer under Zero5, got {:?}", - other - ), + // If STORAGE_CONTRACT isn't in state at all, that's also correct (no writes persisted) } - } - - #[test] - fn test_zero5_self_transfer_no_log() { - let db = CacheDB::new(EmptyDB::default()); - let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero5]); - let mut evm = create_test_evm(db, flags); - evm.ctx_mut() - .journal_mut() - .load_account(NATIVE_COIN_CONTROL_ADDRESS) - .unwrap(); + /// CS-ARC-PC-026: Same as above but for the `Err` path from `complete_subcall`. + #[test] + fn test_complete_subcall_error_reverts_successful_child_state() { + use crate::subcall::AllowedCallers; + use crate::subcall_test::{ + FailingCompleteSubcallPrecompile, FAILING_COMPLETE_SUBCALL_ADDRESS, + }; - // Self-transfer: from == to - let frame = FrameInit { - frame_input: FrameInput::Call(call_input( - CallScheme::Call, - U256::from(100), - ADDRESS_A, - ADDRESS_A, - )), - memory: SharedMemory::default(), - depth: 1, - }; + let wrapper_code = wrapper_call_bytecode(FAILING_COMPLETE_SUBCALL_ADDRESS); + let sstore_code = sstore_42_bytecode(); + const CONTRACT_WRAPPER: Address = WRAPPER; + const STORAGE_CONTRACT: Address = address!("c000000000000000000000000000000000000020"); - let result = evm.before_frame_init(&frame).unwrap(); - match result { - BeforeFrameInitResult::Checked(gas) => { - assert!(gas > 0, "Should have SLOAD gas cost"); + let chain_spec = LOCAL_DEV.clone(); + let mut db = InMemoryDB::default(); + db.insert_account_info( + EOA, + AccountInfo { + balance: U256::from(1_000_000), + nonce: 0, + code_hash: alloy_primitives::KECCAK256_EMPTY, + code: None, + account_id: None, + }, + ); + for (addr, code) in [ + (CONTRACT_WRAPPER, wrapper_code), + (STORAGE_CONTRACT, sstore_code), + ] { + db.insert_account_info( + addr, + AccountInfo { + balance: U256::ZERO, + nonce: 1, + code_hash: keccak256(&code), + code: Some(Bytecode::new_raw(code)), + account_id: None, + }, + ); } - other => panic!( - "Expected Checked result for self-transfer under Zero5, got {:?}", - other - ), - } - } - #[test] - fn test_pre_zero5_still_emits_custom_log() { - let db = CacheDB::new(EmptyDB::default()); - let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero4]); - let mut evm = create_test_evm(db, flags); + let spec = + reth_ethereum::evm::revm_spec_by_timestamp_and_block_number(&chain_spec, 0, 0); + let hardfork_flags = chain_spec.get_hardfork_flags(0, 0); + let mut cfg_env = revm::context::CfgEnv::new() + .with_chain_id(chain_spec.chain_id()) + .with_spec_and_mainnet_gas_params(spec); + cfg_env.tx_gas_limit_cap = Some(u64::MAX); + let ctx = EthEvmContext::new(db, spec) + .with_cfg(cfg_env) + .with_block(BlockEnv::default()); + let precompiles = ArcPrecompileProvider::create_precompiles_map(spec, hardfork_flags); + let instruction = EthInstructions::new_mainnet_with_spec(spec); - evm.ctx_mut() - .journal_mut() - .load_account(NATIVE_COIN_CONTROL_ADDRESS) - .unwrap(); + let mut registry = SubcallRegistry::new(); + registry.register( + FAILING_COMPLETE_SUBCALL_ADDRESS, + Arc::new(FailingCompleteSubcallPrecompile), + AllowedCallers::Unrestricted, + ); - let frame = FrameInit { - frame_input: FrameInput::Call(call_input( - CallScheme::Call, - U256::from(100), - ADDRESS_A, - ADDRESS_B, - )), - memory: SharedMemory::default(), - depth: 1, - }; + let mut evm = ArcEvm::new( + ctx, + revm::inspector::NoOpInspector {}, + precompiles, + instruction, + false, + hardfork_flags, + Arc::new(registry), + ); - let result = evm.before_frame_init(&frame).unwrap(); - match result { - BeforeFrameInitResult::Log(log, _gas) => { - assert_eq!(log.address, NATIVE_COIN_AUTHORITY_ADDRESS); + let input = encode_subcall_test_input(STORAGE_CONTRACT, &[]); + evm.inner.ctx.set_tx(TxEnv { + caller: EOA, + kind: TxKind::Call(CONTRACT_WRAPPER), + value: U256::ZERO, + gas_limit: 1_000_000, + gas_price: 0, + chain_id: Some(LOCAL_DEV.chain_id()), + data: input, + ..Default::default() + }); + + let result_and_state = evm.replay().expect("replay should succeed"); + + assert!( + result_and_state.result.is_success(), + "wrapper should succeed (it catches inner revert): {:?}", + result_and_state.result, + ); + + let storage_account = result_and_state.state.get(&STORAGE_CONTRACT); + if let Some(account) = storage_account { + let slot_value = account + .storage + .get(&U256::ZERO) + .map(|s| s.present_value) + .unwrap_or(U256::ZERO); + assert_eq!( + slot_value, + U256::ZERO, + "child's SSTORE(0, 42) should have been reverted, but slot 0 = {slot_value}" + ); } - other => panic!("Expected Log result pre-Zero5, got {:?}", other), } - } - /// Verifies that AMSTERDAM SpecId enables EIP-7708 (is_enabled_in returns true). - /// Once REVM is upgraded to a version with EIP-7708 journal support, the journal's - /// `transfer` method will emit Transfer logs when SpecId >= AMSTERDAM. - #[test] - fn test_amsterdam_spec_enables_eip7708() { - // AMSTERDAM is after PRAGUE in the SpecId ordering - assert!( - SpecId::AMSTERDAM.is_enabled_in(SpecId::AMSTERDAM), - "AMSTERDAM should be enabled in AMSTERDAM" - ); - assert!( - !SpecId::PRAGUE.is_enabled_in(SpecId::AMSTERDAM), - "PRAGUE should NOT be enabled in AMSTERDAM (AMSTERDAM comes after PRAGUE)" - ); + /// Regression test: after a rejected subcall completes (including the revert path), + /// the child target address remains warm. A second subcall to the same target in the + /// same transaction should use WARM_STORAGE_READ_COST, not COLD_ACCOUNT_ACCESS_COST. + /// + /// Uses a double-call wrapper that invokes the rejecting precompile twice. Compares + /// two runs: one targeting the same contract both times (second call warm), one + /// targeting two different contracts (both calls cold). The gas delta should equal + /// COLD_ACCOUNT_ACCESS_COST - WARM_STORAGE_READ_COST = 2500. + #[test] + fn test_rejected_subcall_target_stays_warm() { + use crate::subcall::AllowedCallers; + use crate::subcall_test::{ + RejectingCompleteSubcallPrecompile, REJECTING_COMPLETE_SUBCALL_ADDRESS, + }; + use revm_interpreter::gas::{COLD_ACCOUNT_ACCESS_COST, WARM_STORAGE_READ_COST}; - // Verify that an EVM can be created with AMSTERDAM spec (for future use) - let db = create_db(&[(ADDRESS_A, 1000)]); - let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero5]); - let evm = create_test_evm_with_spec(db, flags, SpecId::AMSTERDAM); - assert_eq!( - evm.inner.ctx.cfg.spec, - SpecId::AMSTERDAM, - "EVM should be configured with AMSTERDAM spec" - ); - } + let double_wrapper = double_call_wrapper_bytecode(REJECTING_COMPLETE_SUBCALL_ADDRESS); + let sstore_code = sstore_42_bytecode(); + const DOUBLE_WRAPPER: Address = address!("c000000000000000000000000000000000000030"); + const STORAGE_A: Address = address!("c000000000000000000000000000000000000020"); + const STORAGE_B: Address = address!("c000000000000000000000000000000000000021"); - /// Verifies that Zero5 emits EIP-7708 Transfer logs regardless of SpecId. - /// Arc self-implements EIP-7708 log emission, so PRAGUE vs AMSTERDAM doesn't matter. - #[test] - fn test_zero5_emits_eip7708_regardless_of_spec() { - use revm::handler::SYSTEM_ADDRESS; + let chain_spec = LOCAL_DEV.clone(); + let spec = + reth_ethereum::evm::revm_spec_by_timestamp_and_block_number(&chain_spec, 0, 0); + let hardfork_flags = chain_spec.get_hardfork_flags(0, 0); - // Zero5 + PRAGUE: Arc emits EIP-7708 Transfer logs itself - let db = CacheDB::new(EmptyDB::default()); - let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero5]); - let mut evm = create_test_evm_with_spec(db, flags, SpecId::PRAGUE); + let input_a = encode_subcall_test_input(STORAGE_A, &[]); + let input_b = encode_subcall_test_input(STORAGE_B, &[]); - evm.ctx_mut() - .journal_mut() - .load_account(NATIVE_COIN_CONTROL_ADDRESS) - .unwrap(); + let run_double = |payload1: &[u8], payload2: &[u8]| -> u64 { + let mut db = InMemoryDB::default(); + db.insert_account_info( + EOA, + AccountInfo { + balance: U256::from(1_000_000), + nonce: 0, + code_hash: alloy_primitives::KECCAK256_EMPTY, + code: None, + account_id: None, + }, + ); + for (addr, code) in [ + (DOUBLE_WRAPPER, double_wrapper.clone()), + (STORAGE_A, sstore_code.clone()), + (STORAGE_B, sstore_code.clone()), + ] { + db.insert_account_info( + addr, + AccountInfo { + balance: U256::ZERO, + nonce: 1, + code_hash: keccak256(&code), + code: Some(Bytecode::new_raw(code)), + account_id: None, + }, + ); + } + let mut cfg_env = revm::context::CfgEnv::new() + .with_chain_id(chain_spec.chain_id()) + .with_spec_and_mainnet_gas_params(spec); + cfg_env.tx_gas_limit_cap = Some(u64::MAX); + let ctx = EthEvmContext::new(db, spec) + .with_cfg(cfg_env) + .with_block(BlockEnv::default()); + let mut registry = SubcallRegistry::new(); + registry.register( + REJECTING_COMPLETE_SUBCALL_ADDRESS, + Arc::new(RejectingCompleteSubcallPrecompile), + AllowedCallers::Unrestricted, + ); + let mut evm = ArcEvm::new( + ctx, + revm::inspector::NoOpInspector {}, + ArcPrecompileProvider::create_precompiles_map(spec, hardfork_flags), + EthInstructions::new_mainnet_with_spec(spec), + false, + hardfork_flags, + Arc::new(registry), + ); - let frame = FrameInit { - frame_input: FrameInput::Call(call_input( - CallScheme::Call, - U256::from(100), - ADDRESS_A, - ADDRESS_B, - )), - memory: SharedMemory::default(), - depth: 1, - }; + let mut calldata = Vec::new(); + calldata.extend_from_slice(&U256::from(payload1.len()).to_be_bytes::<32>()); + calldata.extend_from_slice(payload1); + calldata.extend_from_slice(payload2); + + evm.inner.ctx.set_tx(TxEnv { + caller: EOA, + kind: TxKind::Call(DOUBLE_WRAPPER), + value: U256::ZERO, + gas_limit: 1_000_000, + gas_price: 0, + chain_id: Some(chain_spec.chain_id()), + data: Bytes::from(calldata), + ..Default::default() + }); + let result = evm.replay().expect("replay should succeed"); + assert!(result.result.is_success(), "double wrapper should succeed"); + result.result.gas_used() + }; - let result = evm.before_frame_init(&frame).unwrap(); - match result { - BeforeFrameInitResult::Log(log, _gas) => { - assert_eq!( - log.address, SYSTEM_ADDRESS, - "Zero5 + PRAGUE: should emit EIP-7708 Transfer log" - ); - } - other => panic!( - "Expected Log result with EIP-7708 Transfer, got {:?}", - other - ), + // Both-cold: target A then target B (different addresses, both cold) + let gas_both_cold = run_double(&input_a, &input_b); + // Same-target: target A then target A (second access is warm) + let gas_second_warm = run_double(&input_a, &input_a); + + // The only difference between the two runs is that in the second case, + // STORAGE_A is already warm from the first subcall. Delta = COLD - WARM. + let savings = gas_both_cold - gas_second_warm; + let expected_savings = COLD_ACCOUNT_ACCESS_COST - WARM_STORAGE_READ_COST; + assert_eq!( + savings, expected_savings, + "second subcall to same target should save exactly {expected_savings} gas \ + (cold={COLD_ACCOUNT_ACCESS_COST} - warm={WARM_STORAGE_READ_COST}), \ + got {savings} (gas_both_cold={gas_both_cold}, gas_second_warm={gas_second_warm})" + ); } - } - /// Zero5: CALL with value to Address::ZERO should revert. - #[test] - fn test_zero5_call_to_zero_address_reverts() { - let db = CacheDB::new(EmptyDB::default()); - let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero5]); - let mut evm = create_test_evm(db, flags); + /// complete_subcall gas overhead (ABI encoding) should be included in total gas consumed. + /// + /// Proves the framework charges the reported `gas_overhead` from `complete_subcall`. + /// + /// Compares SubcallTestPrecompile (gas_overhead=0) against CostlyCompleteSubcallPrecompile + /// (gas_overhead=500). Both have identical init_subcall logic, so the gas delta is + /// exactly the difference in reported completion gas. + #[test] + fn test_complete_subcall_gas_overhead_is_charged() { + use crate::subcall::AllowedCallers; + use crate::subcall_test::{ + CostlyCompleteSubcallPrecompile, SubcallTestPrecompile, + COSTLY_COMPLETE_GAS_OVERHEAD, COSTLY_COMPLETE_SUBCALL_ADDRESS, + SUBCALL_TEST_ADDRESS, + }; - evm.ctx_mut() - .journal_mut() - .load_account(NATIVE_COIN_CONTROL_ADDRESS) - .unwrap(); + let zero_wrapper_code = wrapper_call_bytecode(SUBCALL_TEST_ADDRESS); + let costly_wrapper_code = wrapper_call_bytecode(COSTLY_COMPLETE_SUBCALL_ADDRESS); + let echo_code = echo_double_bytecode(); + const ZERO_WRAPPER: Address = WRAPPER; + const COSTLY_WRAPPER: Address = address!("c000000000000000000000000000000000000011"); - let frame = FrameInit { - frame_input: FrameInput::Call(call_input( - CallScheme::Call, - U256::from(100), - ADDRESS_A, - Address::ZERO, - )), - memory: SharedMemory::default(), - depth: 1, - }; + let chain_spec = LOCAL_DEV.clone(); + let mut db = InMemoryDB::default(); + db.insert_account_info( + EOA, + AccountInfo { + balance: U256::from(1_000_000), + nonce: 0, + code_hash: alloy_primitives::KECCAK256_EMPTY, + code: None, + account_id: None, + }, + ); + for (addr, code) in [ + (ZERO_WRAPPER, zero_wrapper_code), + (COSTLY_WRAPPER, costly_wrapper_code), + (ECHO_CONTRACT, echo_code), + ] { + db.insert_account_info( + addr, + AccountInfo { + balance: U256::ZERO, + nonce: 1, + code_hash: keccak256(&code), + code: Some(Bytecode::new_raw(code)), + account_id: None, + }, + ); + } - let result = evm.before_frame_init(&frame).unwrap(); - assert!( - matches!(result, BeforeFrameInitResult::Reverted(_)), - "Zero5 should revert CALL with value to zero address, got {:?}", - result, - ); - } + let spec = + reth_ethereum::evm::revm_spec_by_timestamp_and_block_number(&chain_spec, 0, 0); + let hardfork_flags = chain_spec.get_hardfork_flags(0, 0); + let mut cfg_env = revm::context::CfgEnv::new() + .with_chain_id(chain_spec.chain_id()) + .with_spec_and_mainnet_gas_params(spec); + cfg_env.tx_gas_limit_cap = Some(u64::MAX); + let ctx = EthEvmContext::new(db, spec) + .with_cfg(cfg_env) + .with_block(BlockEnv::default()); + let precompiles = ArcPrecompileProvider::create_precompiles_map(spec, hardfork_flags); + let instruction = EthInstructions::new_mainnet_with_spec(spec); - /// Zero5: CALL from Address::ZERO with value should revert. - #[test] - fn test_zero5_call_from_zero_address_reverts() { - let db = CacheDB::new(EmptyDB::default()); - let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero5]); - let mut evm = create_test_evm(db, flags); + let mut registry = SubcallRegistry::new(); + registry.register( + SUBCALL_TEST_ADDRESS, + Arc::new(SubcallTestPrecompile), + AllowedCallers::Unrestricted, + ); + registry.register( + COSTLY_COMPLETE_SUBCALL_ADDRESS, + Arc::new(CostlyCompleteSubcallPrecompile), + AllowedCallers::Unrestricted, + ); - evm.ctx_mut() - .journal_mut() - .load_account(NATIVE_COIN_CONTROL_ADDRESS) - .unwrap(); + let mut evm = ArcEvm::new( + ctx, + revm::inspector::NoOpInspector {}, + precompiles, + instruction, + false, + hardfork_flags, + Arc::new(registry), + ); - let frame = FrameInit { - frame_input: FrameInput::Call(call_input( - CallScheme::Call, - U256::from(100), - Address::ZERO, - ADDRESS_B, - )), - memory: SharedMemory::default(), - depth: 1, - }; + let inner_calldata = U256::from(42).to_be_bytes::<32>().to_vec(); - let result = evm.before_frame_init(&frame).unwrap(); - assert!( - matches!(result, BeforeFrameInitResult::Reverted(_)), - "Zero5 should revert CALL with value from zero address, got {:?}", - result, - ); - } + // Tx 1: SubcallTestPrecompile — gas_overhead = 0 + let input_zero = encode_subcall_test_input(ECHO_CONTRACT, &inner_calldata); + let tx_zero = TxEnv { + caller: EOA, + kind: TxKind::Call(ZERO_WRAPPER), + value: U256::ZERO, + gas_limit: 1_000_000, + gas_price: 0, + chain_id: Some(LOCAL_DEV.chain_id()), + data: input_zero, + ..Default::default() + }; - /// Pre-Zero5: CALL to Address::ZERO is NOT blocked (backwards compatible). - #[test] - fn test_pre_zero5_call_to_zero_address_allowed() { - let db = CacheDB::new(EmptyDB::default()); - let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero4]); - let mut evm = create_test_evm(db, flags); + let result_zero = evm.transact_one(tx_zero).expect("tx should succeed"); + let gas_used_zero = match &result_zero { + ExecutionResult::Success { gas_used, .. } => *gas_used, + other => panic!("expected Success, got {other:?}"), + }; - evm.ctx_mut() - .journal_mut() - .load_account(NATIVE_COIN_CONTROL_ADDRESS) - .unwrap(); + // Tx 2: CostlyCompleteSubcallPrecompile — gas_overhead = 500 + let input_costly = encode_subcall_test_input(ECHO_CONTRACT, &inner_calldata); + let tx_costly = TxEnv { + caller: EOA, + kind: TxKind::Call(COSTLY_WRAPPER), + value: U256::ZERO, + gas_limit: 1_000_000, + gas_price: 0, + nonce: 1, + chain_id: Some(LOCAL_DEV.chain_id()), + data: input_costly, + ..Default::default() + }; - let frame = FrameInit { - frame_input: FrameInput::Call(call_input( - CallScheme::Call, - U256::from(100), - ADDRESS_A, - Address::ZERO, - )), - memory: SharedMemory::default(), - depth: 1, - }; + let result_costly = evm.transact_one(tx_costly).expect("tx should succeed"); + let gas_used_costly = match &result_costly { + ExecutionResult::Success { gas_used, .. } => *gas_used, + other => panic!("expected Success, got {other:?}"), + }; - let result = evm.before_frame_init(&frame).unwrap(); - assert!( - matches!(result, BeforeFrameInitResult::Log(_, _)), - "Pre-Zero5 should allow CALL to zero address, got {:?}", - result, - ); - } + // The only difference between the two precompiles is gas_overhead (0 vs 500). + // The delta must be exactly COSTLY_COMPLETE_GAS_OVERHEAD. + assert_eq!( + gas_used_costly - gas_used_zero, + COSTLY_COMPLETE_GAS_OVERHEAD, + "gas delta should equal the reported completion gas_overhead ({COSTLY_COMPLETE_GAS_OVERHEAD})" + ); + } - /// Regression test for phantom EIP-7708 logs: - /// an inner value-transferring CALL that reverts must not leave a Transfer log behind. - #[test] - fn test_zero5_reverted_call_with_value_emits_no_eip7708_log() { - use arc_execution_config::{chainspec::localdev_with_hardforks, hardforks::ArcHardfork}; - use revm_primitives::TxKind; + /// Completion gas is charged even when the child reverts (not halts). + /// Uses the same CostlyCompleteSubcallPrecompile pair, targeting a reverting contract. + #[test] + fn test_complete_subcall_gas_overhead_charged_on_child_revert() { + use crate::subcall::AllowedCallers; + use crate::subcall_test::{ + CostlyCompleteSubcallPrecompile, SubcallTestPrecompile, + COSTLY_COMPLETE_GAS_OVERHEAD, COSTLY_COMPLETE_SUBCALL_ADDRESS, + SUBCALL_TEST_ADDRESS, + }; - let chain_spec = localdev_with_hardforks(&[ - (ArcHardfork::Zero3, 0), - (ArcHardfork::Zero4, 0), - (ArcHardfork::Zero5, 0), - ]); - let sender = Address::repeat_byte(0x11); - let caller_contract = Address::repeat_byte(0x22); - let reverting_contract = Address::repeat_byte(0x33); - let amount = U256::from(100); + let zero_wrapper_code = wrapper_call_bytecode(SUBCALL_TEST_ADDRESS); + let costly_wrapper_code = wrapper_call_bytecode(COSTLY_COMPLETE_SUBCALL_ADDRESS); + let revert_code = reverting_bytecode(); + const ZERO_WRAPPER: Address = WRAPPER; + const COSTLY_WRAPPER: Address = address!("c000000000000000000000000000000000000011"); + const REVERTING: Address = address!("c000000000000000000000000000000000000012"); - // Runtime bytecode: PUSH1 0x00 PUSH1 0x00 REVERT - let revert_runtime: Bytes = vec![0x60, 0x00, 0x60, 0x00, 0xfd].into(); - let caller_runtime = call_with_value_bytecode(reverting_contract, amount); + let chain_spec = LOCAL_DEV.clone(); + let mut db = InMemoryDB::default(); + db.insert_account_info( + EOA, + AccountInfo { + balance: U256::from(1_000_000), + nonce: 0, + code_hash: alloy_primitives::KECCAK256_EMPTY, + code: None, + account_id: None, + }, + ); + for (addr, code) in [ + (ZERO_WRAPPER, zero_wrapper_code), + (COSTLY_WRAPPER, costly_wrapper_code), + (REVERTING, revert_code), + ] { + db.insert_account_info( + addr, + AccountInfo { + balance: U256::ZERO, + nonce: 1, + code_hash: keccak256(&code), + code: Some(Bytecode::new_raw(code)), + account_id: None, + }, + ); + } - let mut db = create_db(&[(sender, 1000)]); - db.insert_account_info( - caller_contract, - revm::state::AccountInfo { - balance: U256::from(1000), - nonce: 1, - code_hash: keccak256(caller_runtime.bytecode()), - code: Some(caller_runtime), - account_id: None, - }, - ); - db.insert_account_info( - reverting_contract, - revm::state::AccountInfo { - balance: U256::ZERO, - nonce: 1, - code_hash: keccak256(&revert_runtime), - code: Some(Bytecode::new_raw(revert_runtime)), - account_id: None, - }, - ); + let spec = + reth_ethereum::evm::revm_spec_by_timestamp_and_block_number(&chain_spec, 0, 0); + let hardfork_flags = chain_spec.get_hardfork_flags(0, 0); + let mut cfg_env = revm::context::CfgEnv::new() + .with_chain_id(chain_spec.chain_id()) + .with_spec_and_mainnet_gas_params(spec); + cfg_env.tx_gas_limit_cap = Some(u64::MAX); + let ctx = EthEvmContext::new(db, spec) + .with_cfg(cfg_env) + .with_block(BlockEnv::default()); + let precompiles = ArcPrecompileProvider::create_precompiles_map(spec, hardfork_flags); + let instruction = EthInstructions::new_mainnet_with_spec(spec); - let mut evm = create_arc_evm(chain_spec.clone(), db); - let tx = TxEnv { - caller: sender, - kind: TxKind::Call(caller_contract), - value: U256::ZERO, - gas_limit: 100_000, - gas_price: 0, - chain_id: Some(chain_spec.chain_id()), - ..Default::default() - }; + let mut registry = SubcallRegistry::new(); + registry.register( + SUBCALL_TEST_ADDRESS, + Arc::new(SubcallTestPrecompile), + AllowedCallers::Unrestricted, + ); + registry.register( + COSTLY_COMPLETE_SUBCALL_ADDRESS, + Arc::new(CostlyCompleteSubcallPrecompile), + AllowedCallers::Unrestricted, + ); - let result = evm.transact_one(tx).expect("nested CALL should execute"); - assert!( - result.is_success(), - "Outer transaction should succeed; only the inner CALL should revert, got {:?}", - result - ); - assert_eq!( - result.logs().len(), - 0, - "Reverted inner CALL with value must not leave an EIP-7708 Transfer log behind" - ); - } + let mut evm = ArcEvm::new( + ctx, + revm::inspector::NoOpInspector {}, + precompiles, + instruction, + false, + hardfork_flags, + Arc::new(registry), + ); - /// Regression test for phantom EIP-7708 logs: - /// an inner CREATE with endowment whose initcode reverts must not leave a Transfer log behind. - #[test] - fn test_zero5_reverted_create_with_value_emits_no_eip7708_log() { - use arc_execution_config::{chainspec::localdev_with_hardforks, hardforks::ArcHardfork}; - use revm_primitives::TxKind; + // Target a reverting contract — child reverts, but does NOT halt. + let inner_calldata: Vec = vec![]; - let chain_spec = localdev_with_hardforks(&[ - (ArcHardfork::Zero3, 0), - (ArcHardfork::Zero4, 0), - (ArcHardfork::Zero5, 0), - ]); - let sender = Address::repeat_byte(0x33); - let factory_contract = Address::repeat_byte(0x44); - let amount = U256::from(100); + // Tx 1: SubcallTestPrecompile (gas_overhead=0) with reverting child + let input_zero = encode_subcall_test_input(REVERTING, &inner_calldata); + let tx_zero = TxEnv { + caller: EOA, + kind: TxKind::Call(ZERO_WRAPPER), + value: U256::ZERO, + gas_limit: 1_000_000, + gas_price: 0, + chain_id: Some(LOCAL_DEV.chain_id()), + data: input_zero, + ..Default::default() + }; - // Initcode: PUSH1 0x00 PUSH1 0x00 REVERT - let revert_initcode = vec![0x60, 0x00, 0x60, 0x00, 0xfd]; - let factory_runtime = create_with_value_bytecode(&revert_initcode, amount); + let result_zero = evm.transact_one(tx_zero).expect("tx should succeed"); + let gas_used_zero = match &result_zero { + ExecutionResult::Success { gas_used, .. } => *gas_used, + other => panic!("expected Success (wrapper catches revert), got {other:?}"), + }; - let mut db = create_db(&[(sender, 1000)]); - db.insert_account_info( - factory_contract, - revm::state::AccountInfo { - balance: U256::from(1000), + // Tx 2: CostlyCompleteSubcallPrecompile (gas_overhead=500) with reverting child + let input_costly = encode_subcall_test_input(REVERTING, &inner_calldata); + let tx_costly = TxEnv { + caller: EOA, + kind: TxKind::Call(COSTLY_WRAPPER), + value: U256::ZERO, + gas_limit: 1_000_000, + gas_price: 0, nonce: 1, - code_hash: keccak256(factory_runtime.bytecode()), - code: Some(factory_runtime), - account_id: None, - }, - ); - let mut evm = create_arc_evm(chain_spec.clone(), db); - let tx = TxEnv { - caller: sender, - kind: TxKind::Call(factory_contract), - value: U256::ZERO, - gas_limit: 120_000, - gas_price: 0, - chain_id: Some(chain_spec.chain_id()), - ..Default::default() - }; + chain_id: Some(LOCAL_DEV.chain_id()), + data: input_costly, + ..Default::default() + }; - let result = evm.transact_one(tx).expect("nested CREATE should execute"); - assert!( - result.is_success(), - "Outer transaction should succeed; only the inner CREATE should revert, got {:?}", - result - ); - assert_eq!( - result.logs().len(), - 0, - "Reverted inner CREATE with value must not leave an EIP-7708 Transfer log behind" - ); - } + let result_costly = evm.transact_one(tx_costly).expect("tx should succeed"); + let gas_used_costly = match &result_costly { + ExecutionResult::Success { gas_used, .. } => *gas_used, + other => panic!("expected Success (wrapper catches revert), got {other:?}"), + }; - /// Zero5: EIP-7708 Transfer log must precede precompile logs in the journal. - /// - /// When a CALL with value targets a precompile that emits its own logs, the - /// EIP-7708 Transfer log from the value transfer must appear before any logs - /// produced by the precompile execution. - #[test] - fn test_zero5_transfer_log_precedes_precompile_log() { - use alloy_primitives::{Log as PrimLog, LogData}; - use reth_evm::precompiles::DynPrecompile; - use revm::handler::SYSTEM_ADDRESS; - use revm::precompile::{PrecompileId, PrecompileOutput}; + // Even with a reverting child, completion gas is charged. + assert_eq!( + gas_used_costly - gas_used_zero, + COSTLY_COMPLETE_GAS_OVERHEAD, + "completion gas_overhead must be charged even when child reverts" + ); + } + + /// Unit test: CallFrom complete_subcall reports correct gas_overhead for various data sizes. + #[test] + fn test_call_from_complete_subcall_gas_overhead() { + use arc_precompiles::call_from::abi_encode_gas; + use arc_precompiles::subcall::{SubcallContinuationData, SubcallPrecompile}; + + let precompile = CallFromPrecompile; + + // Helper: create a FrameResult::Call with given output bytes + let make_child_result = |output: Bytes| -> FrameResult { + FrameResult::Call(CallOutcome { + result: InterpreterResult::new( + InstructionResult::Return, + output, + Gas::new(100_000), + ), + memory_offset: 0..0, + was_precompile_called: false, + precompile_call_logs: Default::default(), + }) + }; + + let continuation_data = SubcallContinuationData { + state: Box::new(()), + }; + + // Empty output + let result = precompile + .complete_subcall(continuation_data, &make_child_result(Bytes::new())) + .expect("complete_subcall should succeed"); + assert_eq!(result.gas_overhead, abi_encode_gas(0)); + + // 32 bytes output + let continuation_data = SubcallContinuationData { + state: Box::new(()), + }; + let result = precompile + .complete_subcall( + continuation_data, + &make_child_result(Bytes::from(vec![0u8; 32])), + ) + .expect("complete_subcall should succeed"); + assert_eq!(result.gas_overhead, abi_encode_gas(32)); + + // 33 bytes output (rounds up to 2 words) + let continuation_data = SubcallContinuationData { + state: Box::new(()), + }; + let result = precompile + .complete_subcall( + continuation_data, + &make_child_result(Bytes::from(vec![0u8; 33])), + ) + .expect("complete_subcall should succeed"); + assert_eq!(result.gas_overhead, abi_encode_gas(33)); + // 33 bytes = 2 words → base + 2*COPY + assert_eq!(result.gas_overhead, 100 + 2 * 3); + } + + // These tests verify that `init_subcall` correctly charges EIP-2929 account + // access costs for the child target address. + + /// Integration test: CallFrom targeting a cold account should cost more gas + /// than targeting a warm one (pre-warmed via access_list). + /// + /// Uses two separate EVM instances so each transaction starts with a fresh + /// journal — reusing one EVM would leave addresses warm from the first tx. + #[test] + fn test_call_from_cold_target_costs_more_gas_than_warm() { + let contract_a_code = wrapper_call_bytecode(CALL_FROM_ADDRESS); + let contract_b_code = echo_double_bytecode(); + const CONTRACT_A: Address = WRAPPER; + const CONTRACT_B: Address = ECHO_CONTRACT; + + let inner_calldata = U256::from(42).to_be_bytes::<32>().to_vec(); + let call_from_input = encode_call_from_input(EOA, CONTRACT_B, &inner_calldata); + + // Cold target: fresh EVM, no access_list + let mut evm_cold = setup_test_evm( + &[(EOA, U256::from(1_000_000))], + &[ + (CONTRACT_A, contract_a_code.clone()), + (CONTRACT_B, contract_b_code.clone()), + ], + &[CONTRACT_A], + ); + let tx_cold = TxEnv { + tx_type: 1, // EIP-2930 + caller: EOA, + kind: TxKind::Call(CONTRACT_A), + value: U256::ZERO, + gas_limit: 200_000, + gas_price: 0, + chain_id: Some(LOCAL_DEV.chain_id()), + data: call_from_input.clone(), + access_list: Default::default(), + ..Default::default() + }; + let result_cold = evm_cold + .transact_one(tx_cold) + .expect("cold tx should succeed"); + let gas_cold = match &result_cold { + ExecutionResult::Success { gas_used, .. } => *gas_used, + other => panic!("expected Success, got {other:?}"), + }; + + // Warm target: fresh EVM, pre-warm CONTRACT_B via access_list. + // tx_type must be EIP-2930 (1) so revm processes the access_list warmup; + // the default Legacy type skips access list handling entirely. + let mut evm_warm = setup_test_evm( + &[(EOA, U256::from(1_000_000))], + &[(CONTRACT_A, contract_a_code), (CONTRACT_B, contract_b_code)], + &[CONTRACT_A], + ); + let tx_warm = TxEnv { + tx_type: 1, // EIP-2930 + caller: EOA, + kind: TxKind::Call(CONTRACT_A), + value: U256::ZERO, + gas_limit: 200_000, + gas_price: 0, + chain_id: Some(LOCAL_DEV.chain_id()), + data: call_from_input, + access_list: vec![alloy_eips::eip2930::AccessListItem { + address: CONTRACT_B, + storage_keys: vec![], + }] + .into(), + ..Default::default() + }; + let result_warm = evm_warm + .transact_one(tx_warm) + .expect("warm tx should succeed"); + let gas_warm = match &result_warm { + ExecutionResult::Success { gas_used, .. } => *gas_used, + other => panic!("expected Success, got {other:?}"), + }; + + // Both txs are EIP-2930; the only difference is the access_list entry. + // Delta = COLD_ACCOUNT_ACCESS_COST (2600) − ACCESS_LIST_ADDRESS_COST (2400) + // - WARM_STORAGE_READ_COST (100) = 100. + assert_eq!( + gas_cold - gas_warm, + 100, + "cold/warm gas delta should be exactly 100 (COLD_ACCOUNT_ACCESS_COST 2600 \ + - ACCESS_LIST_ADDRESS_COST 2400 - WARM_STORAGE_READ_COST 100)" + ); + } + + /// Unit test: init_subcall with a cold target should set + /// init_subcall_gas_overhead = abi_decode_gas + COLD_ACCOUNT_ACCESS_COST. + #[test] + fn test_call_from_cold_target_recalculates_child_gas() { + use alloy_sol_types::SolCall; + use arc_precompiles::call_from::{abi_decode_gas, ICallFrom}; + use revm_interpreter::gas::COLD_ACCOUNT_ACCESS_COST; + + let echo_code = echo_double_bytecode(); + let mut evm = setup_test_evm( + &[(EOA, U256::from(1_000_000))], + &[(ECHO_CONTRACT, echo_code)], + &[WRAPPER], + ); + + // Clear journal state and load required accounts + evm.ctx_mut().journal_mut().clear(); + evm.ctx_mut() + .journal_mut() + .load_account(NATIVE_COIN_CONTROL_ADDRESS) + .unwrap(); + + // tx.origin must match the spoofed sender for the origin check. + evm.inner.ctx.set_tx(TxEnv { + caller: EOA, + ..Default::default() + }); + + let child_data: Vec = vec![0x42]; + let calldata = ICallFrom::callFromCall { + sender: EOA, + target: ECHO_CONTRACT, + data: child_data.clone().into(), + } + .abi_encode(); + + let gas_limit: u64 = 100_000; + let call_inputs = CallInputs { + scheme: CallScheme::Call, + target_address: CALL_FROM_ADDRESS, + bytecode_address: CALL_FROM_ADDRESS, + known_bytecode: None, + value: CallValue::Transfer(U256::ZERO), + input: CallInput::Bytes(Bytes::from(calldata)), + gas_limit, + is_static: false, + caller: WRAPPER, + return_memory_offset: 0..0, + }; + + let frame_input = FrameInit { + frame_input: FrameInput::Call(Box::new(call_inputs)), + memory: SharedMemory::default(), + depth: 1, + }; + + let precompile: Arc = + Arc::new(CallFromPrecompile); + let result = evm + .init_subcall(frame_input, precompile) + .expect("init_subcall should succeed"); + assert!( + matches!(result, ItemOrResult::Item(_)), + "expected child frame, got immediate result" + ); + + let continuation = evm + .subcall_continuations + .get(&1) + .expect("continuation should be stored at depth 1"); + + let expected_overhead = abi_decode_gas(child_data.len()) + COLD_ACCOUNT_ACCESS_COST; + assert_eq!( + continuation.init_subcall_gas_overhead, + expected_overhead, + "overhead should be abi_decode ({}) + cold access ({COLD_ACCOUNT_ACCESS_COST}), \ + got {}", + abi_decode_gas(child_data.len()), + continuation.init_subcall_gas_overhead + ); + } + + /// Unit test: when the target is already warm, init_subcall should use + /// WARM_STORAGE_READ_COST (100) instead of COLD_ACCOUNT_ACCESS_COST (2600). + #[test] + fn test_call_from_warm_target_uses_warm_cost() { + use alloy_sol_types::SolCall; + use arc_precompiles::call_from::{abi_decode_gas, ICallFrom}; + use revm_interpreter::gas::WARM_STORAGE_READ_COST; + + let echo_code = echo_double_bytecode(); + let mut evm = setup_test_evm( + &[(EOA, U256::from(1_000_000))], + &[(ECHO_CONTRACT, echo_code)], + &[WRAPPER], + ); + + // Clear journal state and load required accounts + evm.ctx_mut().journal_mut().clear(); + evm.ctx_mut() + .journal_mut() + .load_account(NATIVE_COIN_CONTROL_ADDRESS) + .unwrap(); + + // Pre-warm the target account + evm.ctx_mut() + .journal_mut() + .load_account(ECHO_CONTRACT) + .unwrap(); + + evm.inner.ctx.set_tx(TxEnv { + caller: EOA, + ..Default::default() + }); + + let child_data: Vec = vec![0x42]; + let calldata = ICallFrom::callFromCall { + sender: EOA, + target: ECHO_CONTRACT, + data: child_data.clone().into(), + } + .abi_encode(); + + let gas_limit: u64 = 100_000; + let call_inputs = CallInputs { + scheme: CallScheme::Call, + target_address: CALL_FROM_ADDRESS, + bytecode_address: CALL_FROM_ADDRESS, + known_bytecode: None, + value: CallValue::Transfer(U256::ZERO), + input: CallInput::Bytes(Bytes::from(calldata)), + gas_limit, + is_static: false, + caller: WRAPPER, + return_memory_offset: 0..0, + }; + + let frame_input = FrameInit { + frame_input: FrameInput::Call(Box::new(call_inputs)), + memory: SharedMemory::default(), + depth: 1, + }; + + let precompile: Arc = + Arc::new(CallFromPrecompile); + let result = evm + .init_subcall(frame_input, precompile) + .expect("init_subcall should succeed"); + assert!( + matches!(result, ItemOrResult::Item(_)), + "expected child frame, got immediate result" + ); + + let continuation = evm + .subcall_continuations + .get(&1) + .expect("continuation should be stored at depth 1"); + + let expected_overhead = abi_decode_gas(child_data.len()) + WARM_STORAGE_READ_COST; + assert_eq!( + continuation.init_subcall_gas_overhead, + expected_overhead, + "overhead should be abi_decode ({}) + warm read ({WARM_STORAGE_READ_COST}), \ + got {}", + abi_decode_gas(child_data.len()), + continuation.init_subcall_gas_overhead + ); + } + + /// When caller == target, `load_account(caller)` warms the address before + /// `load_account(target)`, so only the warm access cost (100) is charged. + #[test] + fn test_call_from_caller_equals_target_uses_warm_cost() { + use alloy_sol_types::SolCall; + use arc_precompiles::call_from::{abi_decode_gas, ICallFrom}; + use revm_interpreter::gas::WARM_STORAGE_READ_COST; + + let echo_code = echo_double_bytecode(); + // ECHO_CONTRACT is both the sender and target — give it balance and code. + let mut evm = setup_test_evm( + &[(ECHO_CONTRACT, U256::from(10_000_000))], + &[(ECHO_CONTRACT, echo_code)], + &[WRAPPER], + ); + + evm.ctx_mut().journal_mut().clear(); + evm.ctx_mut() + .journal_mut() + .load_account(NATIVE_COIN_CONTROL_ADDRESS) + .unwrap(); + + evm.inner.ctx.set_tx(TxEnv { + caller: ECHO_CONTRACT, + ..Default::default() + }); + + let child_data: Vec = vec![0x42]; + let calldata = ICallFrom::callFromCall { + sender: ECHO_CONTRACT, + target: ECHO_CONTRACT, + data: child_data.clone().into(), + } + .abi_encode(); + + let gas_limit: u64 = 100_000; + let call_inputs = CallInputs { + scheme: CallScheme::Call, + target_address: CALL_FROM_ADDRESS, + bytecode_address: CALL_FROM_ADDRESS, + known_bytecode: None, + value: CallValue::Transfer(U256::ZERO), + input: CallInput::Bytes(Bytes::from(calldata)), + gas_limit, + is_static: false, + caller: WRAPPER, + return_memory_offset: 0..0, + }; + + let frame_input = FrameInit { + frame_input: FrameInput::Call(Box::new(call_inputs)), + memory: SharedMemory::default(), + depth: 1, + }; + + let precompile: Arc = + Arc::new(CallFromPrecompile); + let result = evm + .init_subcall(frame_input, precompile) + .expect("init_subcall should succeed"); + assert!( + matches!(result, ItemOrResult::Item(_)), + "expected child frame, got immediate result" + ); + + let continuation = evm + .subcall_continuations + .get(&1) + .expect("continuation should be stored at depth 1"); + + let expected_overhead = abi_decode_gas(child_data.len()) + WARM_STORAGE_READ_COST; + assert_eq!( + continuation.init_subcall_gas_overhead, + expected_overhead, + "caller==target: overhead should be abi_decode ({}) + warm read \ + ({WARM_STORAGE_READ_COST}), got {}", + abi_decode_gas(child_data.len()), + continuation.init_subcall_gas_overhead + ); + } + + /// Unit test: when gas_limit is just below abi_decode + COLD_ACCOUNT_ACCESS_COST, + /// init_subcall should OOG. + #[test] + fn test_call_from_oog_with_cold_account_access() { + use alloy_sol_types::SolCall; + use arc_precompiles::call_from::{abi_decode_gas, ICallFrom}; + use revm_interpreter::gas::COLD_ACCOUNT_ACCESS_COST; + + let echo_code = echo_double_bytecode(); + let mut evm = setup_test_evm( + &[(EOA, U256::from(1_000_000))], + &[(ECHO_CONTRACT, echo_code)], + &[WRAPPER], + ); + + evm.ctx_mut().journal_mut().clear(); + evm.ctx_mut() + .journal_mut() + .load_account(NATIVE_COIN_CONTROL_ADDRESS) + .unwrap(); + + evm.inner.ctx.set_tx(TxEnv { + caller: EOA, + ..Default::default() + }); + + let child_data: Vec = vec![0x42]; + let calldata = ICallFrom::callFromCall { + sender: EOA, + target: ECHO_CONTRACT, + data: child_data.clone().into(), + } + .abi_encode(); + + // Gas is enough for ABI decode but NOT enough for ABI decode + cold access + let insufficient_gas = abi_decode_gas(child_data.len()) + COLD_ACCOUNT_ACCESS_COST - 1; + let call_inputs = CallInputs { + scheme: CallScheme::Call, + target_address: CALL_FROM_ADDRESS, + bytecode_address: CALL_FROM_ADDRESS, + known_bytecode: None, + value: CallValue::Transfer(U256::ZERO), + input: CallInput::Bytes(Bytes::from(calldata)), + gas_limit: insufficient_gas, + is_static: false, + caller: WRAPPER, + return_memory_offset: 0..0, + }; + + let frame_input = FrameInit { + frame_input: FrameInput::Call(Box::new(call_inputs)), + memory: SharedMemory::default(), + depth: 1, + }; + + let precompile: Arc = + Arc::new(CallFromPrecompile); + let result = evm + .init_subcall(frame_input, precompile) + .expect("init_subcall should not return db error"); + + match result { + ItemOrResult::Result(FrameResult::Call(outcome)) => { + assert_eq!( + outcome.result.result, + InstructionResult::OutOfGas, + "should OOG when gas is insufficient for account access cost" + ); + assert_eq!( + outcome.result.gas.spent(), + insufficient_gas, + "OOG should consume all allocated gas" + ); + assert!( + !evm.subcall_continuations.contains_key(&1), + "continuation should be removed after OOG" + ); + } + ItemOrResult::Result(other) => { + panic!("expected Call result, got {other:?}"); + } + ItemOrResult::Item(_) => { + panic!( + "expected OutOfGas when gas_limit ({insufficient_gas}) < \ + abi_decode ({}) + cold access ({COLD_ACCOUNT_ACCESS_COST})", + abi_decode_gas(child_data.len()) + ); + } + } + } - // A distinctive address for the mock precompile, not overlapping with real ones. - const MOCK_PRECOMPILE: Address = address!("ff00000000000000000000000000000000000099"); + /// Boundary: gas_limit == abi_decode + COLD_ACCOUNT_ACCESS_COST should succeed + /// with child_gas_limit = 0 (child will OOG when it runs, but init_subcall itself + /// should not reject it). + #[test] + fn test_call_from_exact_overhead_gas_succeeds_with_zero_child_gas() { + use alloy_sol_types::SolCall; + use arc_precompiles::call_from::{abi_decode_gas, ICallFrom}; + use revm_interpreter::gas::COLD_ACCOUNT_ACCESS_COST; - // A distinctive log address so we can tell precompile logs from transfer logs. - const MOCK_LOG_ADDRESS: Address = address!("aa00000000000000000000000000000000000001"); + let echo_code = echo_double_bytecode(); + let mut evm = setup_test_evm( + &[(EOA, U256::from(1_000_000))], + &[(ECHO_CONTRACT, echo_code)], + &[WRAPPER], + ); - // Build a PrecompilesMap that includes the mock precompile. - let spec = SpecId::PRAGUE; - let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero5]); - let mut precompile_map = ArcPrecompileProvider::create_precompiles_map(spec, flags); - precompile_map.set_precompile_lookup(move |address: &Address| { - if *address == MOCK_PRECOMPILE { - Some(DynPrecompile::new_stateful( - PrecompileId::Custom("MOCK_LOG_EMITTER".into()), - move |mut input| { - // Emit a log via the journal so it appears in the journal log list. - input.internals.log(PrimLog { - address: MOCK_LOG_ADDRESS, - data: LogData::new_unchecked(vec![], Bytes::new()), - }); - Ok(PrecompileOutput::new(0, Bytes::new())) - }, - )) - } else { - None - } - }); + evm.ctx_mut().journal_mut().clear(); + evm.ctx_mut() + .journal_mut() + .load_account(NATIVE_COIN_CONTROL_ADDRESS) + .unwrap(); - // Build the ArcEvm with our custom precompile map. - let db = create_db(&[(ADDRESS_A, 10_000)]); - let mut evm = create_test_evm_with_precompiles(db, flags, precompile_map); + evm.inner.ctx.set_tx(TxEnv { + caller: EOA, + ..Default::default() + }); - // Warm-load accounts so journal state is populated for transfer_loaded. - evm.ctx_mut() - .journal_mut() - .load_account(NATIVE_COIN_CONTROL_ADDRESS) - .unwrap(); - evm.ctx_mut().journal_mut().load_account(ADDRESS_A).unwrap(); - evm.ctx_mut() - .journal_mut() - .load_account(MOCK_PRECOMPILE) - .unwrap(); + let child_data: Vec = vec![0x42]; + let calldata = ICallFrom::callFromCall { + sender: EOA, + target: ECHO_CONTRACT, + data: child_data.clone().into(), + } + .abi_encode(); - // CALL with value from ADDRESS_A to MOCK_PRECOMPILE. - // bytecode_address must equal the precompile address so PrecompilesMap::run finds it. - let frame = FrameInit { - frame_input: FrameInput::Call(Box::new(CallInputs { + let exact_gas = abi_decode_gas(child_data.len()) + COLD_ACCOUNT_ACCESS_COST; + let call_inputs = CallInputs { scheme: CallScheme::Call, - target_address: MOCK_PRECOMPILE, - bytecode_address: MOCK_PRECOMPILE, + target_address: CALL_FROM_ADDRESS, + bytecode_address: CALL_FROM_ADDRESS, known_bytecode: None, - value: CallValue::Transfer(U256::from(100)), - input: CallInput::Bytes(Bytes::new()), - gas_limit: 500_000, + value: CallValue::Transfer(U256::ZERO), + input: CallInput::Bytes(Bytes::from(calldata)), + gas_limit: exact_gas, is_static: false, - caller: ADDRESS_A, + caller: WRAPPER, return_memory_offset: 0..0, - })), - memory: SharedMemory::default(), - depth: 1, - }; - - // Record the log count before frame_init. - let logs_before = evm.ctx().journal().logs().len(); - - let result = evm.frame_init(frame); - assert!(result.is_ok(), "frame_init should succeed"); + }; - let ctx = evm.ctx(); - let logs = ctx.journal().logs(); - let new_logs = &logs[logs_before..]; + let frame_input = FrameInit { + frame_input: FrameInput::Call(Box::new(call_inputs)), + memory: SharedMemory::default(), + depth: 1, + }; - assert!( - new_logs.len() >= 2, - "Expected at least 2 logs (transfer + precompile), got {}", - new_logs.len() - ); + let precompile: Arc = + Arc::new(CallFromPrecompile); + let result = evm + .init_subcall(frame_input, precompile) + .expect("init_subcall should not return db error"); - // First log must be the EIP-7708 Transfer log from the value transfer. - assert_eq!( - new_logs[0].address, SYSTEM_ADDRESS, - "First log should be the EIP-7708 Transfer log from system address, got {:?}", - new_logs[0].address - ); + assert!( + matches!(result, ItemOrResult::Item(_)), + "exact overhead gas should push a child frame, not OOG" + ); - // Second log must be the mock precompile's log. - assert_eq!( - new_logs[1].address, MOCK_LOG_ADDRESS, - "Second log should be the mock precompile log, got {:?}", - new_logs[1].address - ); + let continuation = evm + .subcall_continuations + .get(&1) + .expect("continuation should exist at depth 1"); + assert_eq!( + continuation.init_subcall_gas_overhead, exact_gas, + "overhead should equal the full gas budget" + ); + } + } + fn create_test_evm_with_spec( + db: InMemoryDB, + hardfork_flags: ArcHardforkFlags, + spec: SpecId, + ) -> ArcEvm< + EthEvmContext, + NoOpInspector, + EthInstructions>, + PrecompilesMap, + > { + let ctx = Context::new(db, spec); + let instruction = EthInstructions::new_mainnet_with_spec(spec); + let precompiles = ArcPrecompileProvider::create_precompiles_map(spec, hardfork_flags); + ArcEvm::new( + ctx, + NoOpInspector {}, + precompiles, + instruction, + false, + hardfork_flags, + Arc::new(SubcallRegistry::default()), + ) } - /// Regression test: a CALL with value to a precompile that reverts must roll back - /// the EIP-7708 Transfer log via Arc's checkpoint_revert (the precompile path at - /// line 507, distinct from the non-precompile path tested by - /// `test_zero5_reverted_call_with_value_emits_no_eip7708_log`). #[test] - fn test_zero5_reverted_precompile_call_with_value_emits_no_eip7708_log() { - use reth_evm::precompiles::DynPrecompile; - use revm::precompile::{PrecompileError, PrecompileId}; - - const MOCK_PRECOMPILE: Address = address!("ff00000000000000000000000000000000000099"); + fn test_zero5_emits_eip7708_transfer_log() { + use revm::handler::SYSTEM_ADDRESS; - let spec = SpecId::PRAGUE; + let db = CacheDB::new(EmptyDB::default()); let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero5]); - let mut precompile_map = ArcPrecompileProvider::create_precompiles_map(spec, flags); - precompile_map.set_precompile_lookup(move |address: &Address| { - if *address == MOCK_PRECOMPILE { - Some(DynPrecompile::new_stateful( - PrecompileId::Custom("MOCK_REVERTER".into()), - move |_input| Err(PrecompileError::other("authorization failed")), - )) - } else { - None - } - }); - - let db = create_db(&[(ADDRESS_A, 10_000)]); - let mut evm = create_test_evm_with_precompiles(db, flags, precompile_map); + let mut evm = create_test_evm(db, flags); evm.ctx_mut() .journal_mut() .load_account(NATIVE_COIN_CONTROL_ADDRESS) .unwrap(); - evm.ctx_mut().journal_mut().load_account(ADDRESS_A).unwrap(); - evm.ctx_mut() - .journal_mut() - .load_account(MOCK_PRECOMPILE) - .unwrap(); - let logs_before = evm.ctx().journal().logs().len(); - - let frame = FrameInit { - frame_input: FrameInput::Call(Box::new(CallInputs { - scheme: CallScheme::Call, - target_address: MOCK_PRECOMPILE, - bytecode_address: MOCK_PRECOMPILE, - known_bytecode: None, - value: CallValue::Transfer(U256::from(100)), - input: CallInput::Bytes(Bytes::new()), - gas_limit: 500_000, - is_static: false, - caller: ADDRESS_A, - return_memory_offset: 0..0, - })), + let mut frame = FrameInit { + frame_input: FrameInput::Call(call_input( + CallScheme::Call, + U256::from(100), + ADDRESS_A, + ADDRESS_B, + )), memory: SharedMemory::default(), depth: 1, }; - let result = evm.frame_init(frame); - assert!( - result.is_ok(), - "frame_init should succeed (precompile failure is a Result, not an Err)" - ); - - let ctx = evm.ctx(); - let new_logs = &ctx.journal().logs()[logs_before..]; - assert_eq!( - new_logs.len(), - 0, - "Reverting precompile CALL with value must not leave an EIP-7708 Transfer log behind, got {} logs", - new_logs.len() - ); + let result = evm.before_frame_init(&mut frame).unwrap(); + match result { + BeforeFrameInitResult::Log(log, gas) => { + assert!(gas > 0, "Should have SLOAD gas cost"); + assert_eq!( + log.address, SYSTEM_ADDRESS, + "Zero5 should emit EIP-7708 Transfer log from system address" + ); + } + other => panic!( + "Expected Log result with EIP-7708 Transfer under Zero5, got {:?}", + other + ), + } } #[test] - fn test_zero6_nested_from_blocklisted_charges_sload_gas() { - let sender = address!("A000000000000000000000000000000000000001"); - let recipient = address!("B000000000000000000000000000000000000002"); - - let mut db = CacheDB::new(EmptyDB::default()); - let storage_slot = native_coin_control::compute_is_blocklisted_storage_slot(sender); - db.insert_account_storage( - NATIVE_COIN_CONTROL_ADDRESS, - storage_slot.into(), - U256::from(1), - ) - .unwrap(); - - let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero5, ArcHardfork::Zero6]); + fn test_zero5_self_transfer_no_log() { + let db = CacheDB::new(EmptyDB::default()); + let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero5]); let mut evm = create_test_evm(db, flags); evm.ctx_mut() @@ -6826,51 +7848,34 @@ mod tests { .load_account(NATIVE_COIN_CONTROL_ADDRESS) .unwrap(); - let frame = FrameInit { + // Self-transfer: from == to + let mut frame = FrameInit { frame_input: FrameInput::Call(call_input( CallScheme::Call, U256::from(100), - sender, - recipient, + ADDRESS_A, + ADDRESS_A, )), memory: SharedMemory::default(), depth: 1, }; - let result = evm.before_frame_init(&frame).unwrap(); - if let BeforeFrameInitResult::Reverted(reverted) = result { - assert_eq!( - reverted.gas().spent(), - revm_interpreter::gas::COLD_SLOAD_COST, - "Zero6 nested from-blocklisted revert should charge one cold SLOAD" - ); - let expected_revert = - arc_precompiles::helpers::revert_message_to_bytes(ERR_BLOCKED_ADDRESS); - assert_eq!( - reverted.interpreter_result().output, - expected_revert, - "revert reason should be ERR_BLOCKED_ADDRESS" - ); - } else { - panic!("Expected Reverted result for blocklisted sender"); + let result = evm.before_frame_init(&mut frame).unwrap(); + match result { + BeforeFrameInitResult::Checked(gas) => { + assert!(gas > 0, "Should have SLOAD gas cost"); + } + other => panic!( + "Expected Checked result for self-transfer under Zero5, got {:?}", + other + ), } } #[test] - fn test_zero6_nested_to_blocklisted_charges_sload_gas() { - let sender = address!("A000000000000000000000000000000000000001"); - let recipient = address!("B000000000000000000000000000000000000002"); - - let mut db = CacheDB::new(EmptyDB::default()); - let storage_slot = native_coin_control::compute_is_blocklisted_storage_slot(recipient); - db.insert_account_storage( - NATIVE_COIN_CONTROL_ADDRESS, - storage_slot.into(), - U256::from(1), - ) - .unwrap(); - - let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero5, ArcHardfork::Zero6]); + fn test_pre_zero5_still_emits_custom_log() { + let db = CacheDB::new(EmptyDB::default()); + let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero4]); let mut evm = create_test_evm(db, flags); evm.ctx_mut() @@ -6878,89 +7883,99 @@ mod tests { .load_account(NATIVE_COIN_CONTROL_ADDRESS) .unwrap(); - let frame = FrameInit { + let mut frame = FrameInit { frame_input: FrameInput::Call(call_input( CallScheme::Call, U256::from(100), - sender, - recipient, + ADDRESS_A, + ADDRESS_B, )), memory: SharedMemory::default(), depth: 1, }; - let result = evm.before_frame_init(&frame).unwrap(); - if let BeforeFrameInitResult::Reverted(reverted) = result { - assert_eq!( - reverted.gas().spent(), - 2 * revm_interpreter::gas::COLD_SLOAD_COST, - "Zero6 nested to-blocklisted revert should charge two cold SLOADs" - ); - } else { - panic!("Expected Reverted result for blocklisted recipient"); + let result = evm.before_frame_init(&mut frame).unwrap(); + match result { + BeforeFrameInitResult::Log(log, _gas) => { + assert_eq!(log.address, NATIVE_COIN_AUTHORITY_ADDRESS); + } + other => panic!("Expected Log result pre-Zero5, got {:?}", other), } } + /// Verifies that AMSTERDAM SpecId enables EIP-7708 (is_enabled_in returns true). + /// Once REVM is upgraded to a version with EIP-7708 journal support, the journal's + /// `transfer` method will emit Transfer logs when SpecId >= AMSTERDAM. #[test] - fn test_pre_zero6_nested_blocklisted_charges_zero_gas() { - let sender = address!("A000000000000000000000000000000000000001"); - let recipient = address!("B000000000000000000000000000000000000002"); + fn test_amsterdam_spec_enables_eip7708() { + // AMSTERDAM is after PRAGUE in the SpecId ordering + assert!( + SpecId::AMSTERDAM.is_enabled_in(SpecId::AMSTERDAM), + "AMSTERDAM should be enabled in AMSTERDAM" + ); + assert!( + !SpecId::PRAGUE.is_enabled_in(SpecId::AMSTERDAM), + "PRAGUE should NOT be enabled in AMSTERDAM (AMSTERDAM comes after PRAGUE)" + ); - let mut db = CacheDB::new(EmptyDB::default()); - let storage_slot = native_coin_control::compute_is_blocklisted_storage_slot(sender); - db.insert_account_storage( - NATIVE_COIN_CONTROL_ADDRESS, - storage_slot.into(), - U256::from(1), - ) - .unwrap(); + // Verify that an EVM can be created with AMSTERDAM spec (for future use) + let db = create_db(&[(ADDRESS_A, 1000)]); + let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero5]); + let evm = create_test_evm_with_spec(db, flags, SpecId::AMSTERDAM); + assert_eq!( + evm.inner.ctx.cfg.spec, + SpecId::AMSTERDAM, + "EVM should be configured with AMSTERDAM spec" + ); + } + /// Verifies that Zero5 emits EIP-7708 Transfer logs regardless of SpecId. + /// Arc self-implements EIP-7708 log emission, so PRAGUE vs AMSTERDAM doesn't matter. + #[test] + fn test_zero5_emits_eip7708_regardless_of_spec() { + use revm::handler::SYSTEM_ADDRESS; + + // Zero5 + PRAGUE: Arc emits EIP-7708 Transfer logs itself + let db = CacheDB::new(EmptyDB::default()); let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero5]); - let mut evm = create_test_evm(db, flags); + let mut evm = create_test_evm_with_spec(db, flags, SpecId::PRAGUE); evm.ctx_mut() .journal_mut() .load_account(NATIVE_COIN_CONTROL_ADDRESS) .unwrap(); - let frame = FrameInit { + let mut frame = FrameInit { frame_input: FrameInput::Call(call_input( CallScheme::Call, U256::from(100), - sender, - recipient, + ADDRESS_A, + ADDRESS_B, )), memory: SharedMemory::default(), depth: 1, }; - let result = evm.before_frame_init(&frame).unwrap(); - if let BeforeFrameInitResult::Reverted(reverted) = result { - assert_eq!( - reverted.gas().spent(), - 0, - "Pre-Zero6 nested revert should charge zero gas (unchanged behavior)" - ); - } else { - panic!("Expected Reverted result for blocklisted sender"); + let result = evm.before_frame_init(&mut frame).unwrap(); + match result { + BeforeFrameInitResult::Log(log, _gas) => { + assert_eq!( + log.address, SYSTEM_ADDRESS, + "Zero5 + PRAGUE: should emit EIP-7708 Transfer log" + ); + } + other => panic!( + "Expected Log result with EIP-7708 Transfer, got {:?}", + other + ), } } + /// Zero5: CALL with value to Address::ZERO should revert. #[test] - fn test_zero6_depth0_blocklisted_charges_zero_gas() { - let sender = address!("A000000000000000000000000000000000000001"); - let recipient = address!("B000000000000000000000000000000000000002"); - - let mut db = CacheDB::new(EmptyDB::default()); - let storage_slot = native_coin_control::compute_is_blocklisted_storage_slot(sender); - db.insert_account_storage( - NATIVE_COIN_CONTROL_ADDRESS, - storage_slot.into(), - U256::from(1), - ) - .unwrap(); - - let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero5, ArcHardfork::Zero6]); + fn test_zero5_call_to_zero_address_reverts() { + let db = CacheDB::new(EmptyDB::default()); + let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero5]); let mut evm = create_test_evm(db, flags); evm.ctx_mut() @@ -6968,44 +7983,30 @@ mod tests { .load_account(NATIVE_COIN_CONTROL_ADDRESS) .unwrap(); - let frame = FrameInit { + let mut frame = FrameInit { frame_input: FrameInput::Call(call_input( CallScheme::Call, U256::from(100), - sender, - recipient, + ADDRESS_A, + Address::ZERO, )), memory: SharedMemory::default(), - depth: 0, + depth: 1, }; - let result = evm.before_frame_init(&frame).unwrap(); - if let BeforeFrameInitResult::Reverted(reverted) = result { - assert_eq!( - reverted.gas().spent(), - 0, - "Depth-0 Zero6 revert should charge zero gas (covered by validate_initial_tx_gas)" - ); - } else { - panic!("Expected Reverted result for blocklisted sender"); - } + let result = evm.before_frame_init(&mut frame).unwrap(); + assert!( + matches!(result, BeforeFrameInitResult::Reverted(_)), + "Zero5 should revert CALL with value to zero address, got {:?}", + result, + ); } + /// Zero5: CALL from Address::ZERO with value should revert. #[test] - fn test_zero6_nested_from_blocklisted_warm_sload_gas() { - let sender = address!("A000000000000000000000000000000000000001"); - let recipient = address!("B000000000000000000000000000000000000002"); - - let mut db = CacheDB::new(EmptyDB::default()); - let storage_slot = native_coin_control::compute_is_blocklisted_storage_slot(sender); - db.insert_account_storage( - NATIVE_COIN_CONTROL_ADDRESS, - storage_slot.into(), - U256::from(1), - ) - .unwrap(); - - let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero5, ArcHardfork::Zero6]); + fn test_zero5_call_from_zero_address_reverts() { + let db = CacheDB::new(EmptyDB::default()); + let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero5]); let mut evm = create_test_evm(db, flags); evm.ctx_mut() @@ -7013,276 +8014,354 @@ mod tests { .load_account(NATIVE_COIN_CONTROL_ADDRESS) .unwrap(); - // Warm the slot by reading it first - evm.inner - .ctx + let mut frame = FrameInit { + frame_input: FrameInput::Call(call_input( + CallScheme::Call, + U256::from(100), + Address::ZERO, + ADDRESS_B, + )), + memory: SharedMemory::default(), + depth: 1, + }; + + let result = evm.before_frame_init(&mut frame).unwrap(); + assert!( + matches!(result, BeforeFrameInitResult::Reverted(_)), + "Zero5 should revert CALL with value from zero address, got {:?}", + result, + ); + } + + /// Pre-Zero5: CALL to Address::ZERO is NOT blocked (backwards compatible). + #[test] + fn test_pre_zero5_call_to_zero_address_allowed() { + let db = CacheDB::new(EmptyDB::default()); + let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero4]); + let mut evm = create_test_evm(db, flags); + + evm.ctx_mut() .journal_mut() - .sload(NATIVE_COIN_CONTROL_ADDRESS, storage_slot.into()) + .load_account(NATIVE_COIN_CONTROL_ADDRESS) .unwrap(); - let frame = FrameInit { + let mut frame = FrameInit { frame_input: FrameInput::Call(call_input( CallScheme::Call, U256::from(100), - sender, - recipient, + ADDRESS_A, + Address::ZERO, )), memory: SharedMemory::default(), depth: 1, }; - let result = evm.before_frame_init(&frame).unwrap(); - if let BeforeFrameInitResult::Reverted(reverted) = result { - assert_eq!( - reverted.gas().spent(), - revm_interpreter::gas::WARM_STORAGE_READ_COST, - "Zero6 nested from-blocklisted warm revert should charge one warm SLOAD" - ); - } else { - panic!("Expected Reverted result for blocklisted sender"); - } + let result = evm.before_frame_init(&mut frame).unwrap(); + assert!( + matches!(result, BeforeFrameInitResult::Log(_, _)), + "Pre-Zero5 should allow CALL to zero address, got {:?}", + result, + ); + } + + /// Regression test for phantom EIP-7708 logs: + /// an inner value-transferring CALL that reverts must not leave a Transfer log behind. + #[test] + fn test_zero5_reverted_call_with_value_emits_no_eip7708_log() { + use arc_execution_config::{chainspec::localdev_with_hardforks, hardforks::ArcHardfork}; + use revm_primitives::TxKind; + + let chain_spec = localdev_with_hardforks(&[ + (ArcHardfork::Zero3, ForkCondition::Block(0)), + (ArcHardfork::Zero4, ForkCondition::Block(0)), + (ArcHardfork::Zero5, ForkCondition::Block(0)), + ]); + let sender = Address::repeat_byte(0x11); + let caller_contract = Address::repeat_byte(0x22); + let reverting_contract = Address::repeat_byte(0x33); + let amount = U256::from(100); + + // Runtime bytecode: PUSH1 0x00 PUSH1 0x00 REVERT + let revert_runtime: Bytes = vec![0x60, 0x00, 0x60, 0x00, 0xfd].into(); + let caller_runtime = call_with_value_bytecode(reverting_contract, amount); + + let mut db = create_db(&[(sender, 1000)]); + db.insert_account_info( + caller_contract, + revm::state::AccountInfo { + balance: U256::from(1000), + nonce: 1, + code_hash: keccak256(caller_runtime.bytecode()), + code: Some(caller_runtime), + account_id: None, + }, + ); + db.insert_account_info( + reverting_contract, + revm::state::AccountInfo { + balance: U256::ZERO, + nonce: 1, + code_hash: keccak256(&revert_runtime), + code: Some(Bytecode::new_raw(revert_runtime)), + account_id: None, + }, + ); + + let mut evm = create_arc_evm(chain_spec.clone(), db); + let tx = TxEnv { + caller: sender, + kind: TxKind::Call(caller_contract), + value: U256::ZERO, + gas_limit: 100_000, + gas_price: 0, + chain_id: Some(chain_spec.chain_id()), + ..Default::default() + }; + + let result = evm.transact_one(tx).expect("nested CALL should execute"); + assert!( + result.is_success(), + "Outer transaction should succeed; only the inner CALL should revert, got {:?}", + result + ); + assert_eq!( + result.logs().len(), + 0, + "Reverted inner CALL with value must not leave an EIP-7708 Transfer log behind" + ); + } + + /// Regression test for phantom EIP-7708 logs: + /// an inner CREATE with endowment whose initcode reverts must not leave a Transfer log behind. + #[test] + fn test_zero5_reverted_create_with_value_emits_no_eip7708_log() { + use arc_execution_config::{chainspec::localdev_with_hardforks, hardforks::ArcHardfork}; + use revm_primitives::TxKind; + + let chain_spec = localdev_with_hardforks(&[ + (ArcHardfork::Zero3, ForkCondition::Block(0)), + (ArcHardfork::Zero4, ForkCondition::Block(0)), + (ArcHardfork::Zero5, ForkCondition::Block(0)), + ]); + let sender = Address::repeat_byte(0x33); + let factory_contract = Address::repeat_byte(0x44); + let amount = U256::from(100); + + // Initcode: PUSH1 0x00 PUSH1 0x00 REVERT + let revert_initcode = vec![0x60, 0x00, 0x60, 0x00, 0xfd]; + let factory_runtime = create_with_value_bytecode(&revert_initcode, amount); + + let mut db = create_db(&[(sender, 1000)]); + db.insert_account_info( + factory_contract, + revm::state::AccountInfo { + balance: U256::from(1000), + nonce: 1, + code_hash: keccak256(factory_runtime.bytecode()), + code: Some(factory_runtime), + account_id: None, + }, + ); + let mut evm = create_arc_evm(chain_spec.clone(), db); + let tx = TxEnv { + caller: sender, + kind: TxKind::Call(factory_contract), + value: U256::ZERO, + gas_limit: 120_000, + gas_price: 0, + chain_id: Some(chain_spec.chain_id()), + ..Default::default() + }; + + let result = evm.transact_one(tx).expect("nested CREATE should execute"); + assert!( + result.is_success(), + "Outer transaction should succeed; only the inner CREATE should revert, got {:?}", + result + ); + assert_eq!( + result.logs().len(), + 0, + "Reverted inner CREATE with value must not leave an EIP-7708 Transfer log behind" + ); } + /// Zero5: EIP-7708 Transfer log must precede precompile logs in the journal. + /// + /// When a CALL with value targets a precompile that emits its own logs, the + /// EIP-7708 Transfer log from the value transfer must appear before any logs + /// produced by the precompile execution. #[test] - fn test_zero6_nested_to_blocklisted_mixed_warm_cold_sload_gas() { - let sender = address!("A000000000000000000000000000000000000001"); - let recipient = address!("B000000000000000000000000000000000000002"); + fn test_zero5_transfer_log_precedes_precompile_log() { + use alloy_primitives::{Log as PrimLog, LogData}; + use reth_evm::precompiles::DynPrecompile; + use revm::handler::SYSTEM_ADDRESS; + use revm::precompile::{PrecompileId, PrecompileOutput}; - let mut db = CacheDB::new(EmptyDB::default()); - let recipient_slot = native_coin_control::compute_is_blocklisted_storage_slot(recipient); - db.insert_account_storage( - NATIVE_COIN_CONTROL_ADDRESS, - recipient_slot.into(), - U256::from(1), - ) - .unwrap(); + // A distinctive address for the mock precompile, not overlapping with real ones. + const MOCK_PRECOMPILE: Address = address!("ff00000000000000000000000000000000000099"); - let sender_slot = native_coin_control::compute_is_blocklisted_storage_slot(sender); - db.insert_account_storage(NATIVE_COIN_CONTROL_ADDRESS, sender_slot.into(), U256::ZERO) - .unwrap(); + // A distinctive log address so we can tell precompile logs from transfer logs. + const MOCK_LOG_ADDRESS: Address = address!("aa00000000000000000000000000000000000001"); - let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero5, ArcHardfork::Zero6]); - let mut evm = create_test_evm(db, flags); + // Build a PrecompilesMap that includes the mock precompile. + let spec = SpecId::PRAGUE; + let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero5]); + let mut precompile_map = ArcPrecompileProvider::create_precompiles_map(spec, flags); + precompile_map.set_precompile_lookup(move |address: &Address| { + if *address == MOCK_PRECOMPILE { + Some(DynPrecompile::new_stateful( + PrecompileId::Custom("MOCK_LOG_EMITTER".into()), + move |mut input| { + // Emit a log via the journal so it appears in the journal log list. + input.internals.log(PrimLog { + address: MOCK_LOG_ADDRESS, + data: LogData::new_unchecked(vec![], Bytes::new()), + }); + Ok(PrecompileOutput::new(0, Bytes::new())) + }, + )) + } else { + None + } + }); + + // Build the ArcEvm with our custom precompile map. + let db = create_db(&[(ADDRESS_A, 10_000)]); + let mut evm = create_test_evm_with_precompiles(db, flags, precompile_map); + // Warm-load accounts so journal state is populated for transfer_loaded. evm.ctx_mut() .journal_mut() .load_account(NATIVE_COIN_CONTROL_ADDRESS) .unwrap(); - - // Warm the sender's slot by reading it - evm.inner - .ctx + evm.ctx_mut().journal_mut().load_account(ADDRESS_A).unwrap(); + evm.ctx_mut() .journal_mut() - .sload(NATIVE_COIN_CONTROL_ADDRESS, sender_slot.into()) - .unwrap(); - - // Recipient's slot stays cold - - let frame = FrameInit { - frame_input: FrameInput::Call(call_input( - CallScheme::Call, - U256::from(100), - sender, - recipient, - )), - memory: SharedMemory::default(), - depth: 1, - }; - - let result = evm.before_frame_init(&frame).unwrap(); - if let BeforeFrameInitResult::Reverted(reverted) = result { - // Warm from-SLOAD (100) + cold to-SLOAD (2100) = 2200 - assert_eq!( - reverted.gas().spent(), - revm_interpreter::gas::WARM_STORAGE_READ_COST - + revm_interpreter::gas::COLD_SLOAD_COST, - "Mixed warm/cold: warm from-SLOAD + cold to-SLOAD should be 2200" - ); - } else { - panic!("Expected Reverted result for blocklisted recipient"); - } - } - - #[test] - fn test_zero6_nested_selfdestructed_target_charges_sload_gas() { - let db = CacheDB::new(EmptyDB::default()); - let flags = - ArcHardforkFlags::with(&[ArcHardfork::Zero4, ArcHardfork::Zero5, ArcHardfork::Zero6]); - let mut evm = create_test_evm(db, flags); - - let spec_id = evm.ctx().cfg.spec; - let journal = evm.ctx_mut().journal_mut(); - - journal.load_account(NATIVE_COIN_CONTROL_ADDRESS).unwrap(); - - journal - .load_account_mut_optional_code(ADDRESS_A, false) - .expect("load ADDRESS_A") - .set_balance(U256::from(100)); - - journal.load_account(ADDRESS_B).expect("load ADDRESS_B"); - journal - .create_account_checkpoint(ADDRESS_A, ADDRESS_B, U256::from(100), spec_id) + .load_account(MOCK_PRECOMPILE) .unwrap(); - journal - .selfdestruct(ADDRESS_B, ADDRESS_A, false) - .expect("selfdestruct"); + // CALL with value from ADDRESS_A to MOCK_PRECOMPILE. + // bytecode_address must equal the precompile address so PrecompilesMap::run finds it. let frame = FrameInit { frame_input: FrameInput::Call(Box::new(CallInputs { scheme: CallScheme::Call, - target_address: ADDRESS_B, - bytecode_address: ADDRESS_A, + target_address: MOCK_PRECOMPILE, + bytecode_address: MOCK_PRECOMPILE, known_bytecode: None, value: CallValue::Transfer(U256::from(100)), input: CallInput::Bytes(Bytes::new()), - gas_limit: 100_000, - caller: ADDRESS_A, + gas_limit: 500_000, is_static: false, + caller: ADDRESS_A, return_memory_offset: 0..0, })), memory: SharedMemory::default(), depth: 1, }; - let result = evm.before_frame_init(&frame); - match result { - Ok(BeforeFrameInitResult::Reverted(reverted)) => { - assert_eq!( - reverted.gas().spent(), - 2 * revm_interpreter::gas::COLD_SLOAD_COST, - "Zero6 nested selfdestructed-target revert should charge two cold SLOADs" - ); - let expected_revert = arc_precompiles::helpers::revert_message_to_bytes( - ERR_SELFDESTRUCTED_BALANCE_INCREASED, - ); - assert_eq!( - reverted.interpreter_result().output, - expected_revert, - "revert reason should be ERR_SELFDESTRUCTED_BALANCE_INCREASED" - ); - } - other => panic!( - "Expected Reverted for selfdestructed target, got {:?}", - other - ), - } - } - - #[test] - fn test_zero6_nested_blocklisted_oog_when_gas_below_sload_cost() { - let sender = address!("A000000000000000000000000000000000000001"); - let recipient = address!("B000000000000000000000000000000000000002"); + // Record the log count before frame_init. + let logs_before = evm.ctx().journal().logs().len(); - let mut db = CacheDB::new(EmptyDB::default()); - let storage_slot = native_coin_control::compute_is_blocklisted_storage_slot(sender); - db.insert_account_storage( - NATIVE_COIN_CONTROL_ADDRESS, - storage_slot.into(), - U256::from(1), - ) - .unwrap(); + let result = evm.frame_init(frame); + assert!(result.is_ok(), "frame_init should succeed"); - let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero5, ArcHardfork::Zero6]); - let mut evm = create_test_evm(db, flags); + let ctx = evm.ctx(); + let logs = ctx.journal().logs(); + let new_logs = &logs[logs_before..]; - evm.ctx_mut() - .journal_mut() - .load_account(NATIVE_COIN_CONTROL_ADDRESS) - .unwrap(); + assert!( + new_logs.len() >= 2, + "Expected at least 2 logs (transfer + precompile), got {}", + new_logs.len() + ); - // gas_limit just below COLD_SLOAD_COST — frame can't afford the SLOAD - let frame = FrameInit { - frame_input: FrameInput::Call(Box::new(CallInputs { - scheme: CallScheme::Call, - target_address: recipient, - bytecode_address: recipient, - known_bytecode: None, - value: CallValue::Transfer(U256::from(100)), - input: CallInput::Bytes(Bytes::new()), - gas_limit: revm_interpreter::gas::COLD_SLOAD_COST - 1, - is_static: false, - caller: sender, - return_memory_offset: 0..0, - })), - memory: SharedMemory::default(), - depth: 1, - }; + // First log must be the EIP-7708 Transfer log from the value transfer. + assert_eq!( + new_logs[0].address, SYSTEM_ADDRESS, + "First log should be the EIP-7708 Transfer log from system address, got {:?}", + new_logs[0].address + ); - let result = evm.before_frame_init(&frame).unwrap(); - if let BeforeFrameInitResult::Reverted(reverted) = result { - assert_eq!( - reverted.instruction_result(), - InstructionResult::OutOfGas, - "Should OOG when gas_limit < SLOAD cost" - ); - assert_eq!( - reverted.gas().spent(), - revm_interpreter::gas::COLD_SLOAD_COST - 1, - "OOG should consume all available gas" - ); - } else { - panic!("Expected Reverted result for blocklisted sender with insufficient gas"); - } + // Second log must be the mock precompile's log. + assert_eq!( + new_logs[1].address, MOCK_LOG_ADDRESS, + "Second log should be the mock precompile log, got {:?}", + new_logs[1].address + ); } + /// Regression test: a CALL with value to a precompile that reverts must roll back + /// the EIP-7708 Transfer log via Arc's checkpoint_revert (the precompile path at + /// line 507, distinct from the non-precompile path tested by + /// `test_zero5_reverted_call_with_value_emits_no_eip7708_log`). #[test] - fn test_zero6_nested_to_blocklisted_oog_when_gas_between_one_and_two_sloads() { - let sender = address!("A000000000000000000000000000000000000001"); - let recipient = address!("B000000000000000000000000000000000000002"); + fn test_zero5_reverted_precompile_call_with_value_emits_no_eip7708_log() { + use reth_evm::precompiles::DynPrecompile; + use revm::precompile::{PrecompileError, PrecompileId}; - let mut db = CacheDB::new(EmptyDB::default()); - // Only recipient is blocklisted — requires 2 SLOADs (from check + to check) - let storage_slot = native_coin_control::compute_is_blocklisted_storage_slot(recipient); - db.insert_account_storage( - NATIVE_COIN_CONTROL_ADDRESS, - storage_slot.into(), - U256::from(1), - ) - .unwrap(); + const MOCK_PRECOMPILE: Address = address!("ff00000000000000000000000000000000000099"); - let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero5, ArcHardfork::Zero6]); - let mut evm = create_test_evm(db, flags); + let spec = SpecId::PRAGUE; + let flags = ArcHardforkFlags::with(&[ArcHardfork::Zero5]); + let mut precompile_map = ArcPrecompileProvider::create_precompiles_map(spec, flags); + precompile_map.set_precompile_lookup(move |address: &Address| { + if *address == MOCK_PRECOMPILE { + Some(DynPrecompile::new_stateful( + PrecompileId::Custom("MOCK_REVERTER".into()), + move |_input| Err(PrecompileError::other("authorization failed")), + )) + } else { + None + } + }); + + let db = create_db(&[(ADDRESS_A, 10_000)]); + let mut evm = create_test_evm_with_precompiles(db, flags, precompile_map); evm.ctx_mut() .journal_mut() .load_account(NATIVE_COIN_CONTROL_ADDRESS) .unwrap(); + evm.ctx_mut().journal_mut().load_account(ADDRESS_A).unwrap(); + evm.ctx_mut() + .journal_mut() + .load_account(MOCK_PRECOMPILE) + .unwrap(); + + let logs_before = evm.ctx().journal().logs().len(); - // gas_limit between COLD_SLOAD_COST and 2*COLD_SLOAD_COST — enough for 1 SLOAD but not 2 - let gas_limit = revm_interpreter::gas::COLD_SLOAD_COST + 100; let frame = FrameInit { frame_input: FrameInput::Call(Box::new(CallInputs { scheme: CallScheme::Call, - target_address: recipient, - bytecode_address: recipient, + target_address: MOCK_PRECOMPILE, + bytecode_address: MOCK_PRECOMPILE, known_bytecode: None, value: CallValue::Transfer(U256::from(100)), input: CallInput::Bytes(Bytes::new()), - gas_limit, + gas_limit: 500_000, is_static: false, - caller: sender, + caller: ADDRESS_A, return_memory_offset: 0..0, })), memory: SharedMemory::default(), depth: 1, }; - let result = evm.before_frame_init(&frame).unwrap(); - if let BeforeFrameInitResult::Reverted(reverted) = result { - assert_eq!( - reverted.instruction_result(), - InstructionResult::OutOfGas, - "Should OOG when gas_limit < 2 * SLOAD cost for to-blocklisted" - ); - assert_eq!( - reverted.gas().spent(), - gas_limit, - "OOG should consume all available gas" - ); - } else { - panic!("Expected Reverted result for blocklisted recipient with insufficient gas"); - } + let result = evm.frame_init(frame); + assert!( + result.is_ok(), + "frame_init should succeed (precompile failure is a Result, not an Err)" + ); + + let ctx = evm.ctx(); + let new_logs = &ctx.journal().logs()[logs_before..]; + assert_eq!( + new_logs.len(), + 0, + "Reverting precompile CALL with value must not leave an EIP-7708 Transfer log behind, got {} logs", + new_logs.len() + ); } /// ----- Revm upgrade checklist ----- diff --git a/crates/evm/src/executor.rs b/crates/evm/src/executor.rs index 82359ac..87eb283 100644 --- a/crates/evm/src/executor.rs +++ b/crates/evm/src/executor.rs @@ -52,7 +52,7 @@ use arc_execution_config::chainspec::{BaseFeeConfigProvider, BlockGasLimitProvid use arc_execution_config::gas_fee::{ self, arc_calc_next_block_base_fee, decode_base_fee_from_bytes, }; -use arc_execution_config::hardforks::ArcHardfork; +use arc_execution_config::hardforks::{is_arc_fork_active, ArcHardfork}; use arc_execution_config::native_coin_control::{ compute_is_blocklisted_storage_slot, is_blocklisted_status, }; @@ -64,6 +64,7 @@ use revm::DatabaseCommit; const ERR_BLOCKLIST_READ_FAILED: &str = "Failed to read beneficiary blocklist status"; const ERR_BLOCK_NUMBER_CONVERSION_FAILED: &str = "Failed to convert block number to u64"; +const ERR_BLOCK_TIMESTAMP_CONVERSION_FAILED: &str = "Failed to convert block timestamp to u64"; /// Result of executing an Arc transaction. #[derive(Debug)] @@ -139,6 +140,24 @@ where BlockExecutionError::msg(ERR_BLOCK_NUMBER_CONVERSION_FAILED) }) } + + /// Current block timestamp as `u64`. + /// + /// Needed alongside [`Self::block_number_u64`] because Arc hardforks may activate + /// either by block or by timestamp (e.g. testnet Zero5/Zero6, all networks' Zero7+); + /// runtime hardfork gating must consult both dimensions via + /// [`arc_execution_config::hardforks::is_arc_fork_active`]. + fn block_timestamp_u64(&self) -> Result { + let block_timestamp = self.evm.block().timestamp(); + block_timestamp.try_into().map_err(|err| { + tracing::error!( + error = %err, + block_timestamp = %block_timestamp, + "Failed to convert block timestamp to u64" + ); + BlockExecutionError::msg(ERR_BLOCK_TIMESTAMP_CONVERSION_FAILED) + }) + } } fn validate_beneficiary_not_blocklisted( @@ -342,11 +361,14 @@ where // Zero5+ pre-execution checks: beneficiary blocklist, gas limit validation let block_number = self.block_number_u64()?; - - if self - .chain_spec - .is_fork_active_at_block(ArcHardfork::Zero5, block_number) - { + let block_timestamp = self.block_timestamp_u64()?; + + if is_arc_fork_active( + &self.chain_spec, + ArcHardfork::Zero5, + block_number, + block_timestamp, + ) { // EIP-2935: persist parent block hash in history storage contract. // Internally gates on Prague activation and is a no-op at block 0 (genesis). self.system_caller @@ -462,6 +484,7 @@ where let requests = Requests::default(); let block_number = self.block_number_u64()?; + let block_timestamp = self.block_timestamp_u64()?; // At the end of the block, call a system contract (precompile) to persist gas accounting // state: raw gas used, smoothed gas used, and the next block's base fee. @@ -474,9 +497,12 @@ where ); }) .ok(); - let is_zero5 = self - .chain_spec - .is_fork_active_at_block(ArcHardfork::Zero5, block_number); + let is_zero5 = is_arc_fork_active( + &self.chain_spec, + ArcHardfork::Zero5, + block_number, + block_timestamp, + ); let gas_values = if is_zero5 { // ADR-0004 implementation: compute gas values within local bounds self.compute_gas_values(block_number, fee_params)? @@ -544,7 +570,7 @@ mod tests { use alloy_primitives::map::HashMap; use alloy_primitives::B256 as AlloyB256; use alloy_primitives::KECCAK256_EMPTY; - use reth_chainspec::EthChainSpec; + use reth_chainspec::{EthChainSpec, ForkCondition}; use reth_evm::ConfigureEvm; use reth_evm::EvmEnv; @@ -808,7 +834,7 @@ mod tests { #[test] fn test_zero4_invalid_alpha_stores_zero_next_base_fee() { let block_env = get_mock_block_env(); - let chain_spec = localdev_with_hardforks(&[(ArcHardfork::Zero3, 0)]); + let chain_spec = localdev_with_hardforks(&[(ArcHardfork::Zero3, ForkCondition::Block(0))]); let mut db = InMemoryDB::default(); insert_alloc_into_db(&mut db, chain_spec.genesis()); @@ -1016,7 +1042,7 @@ mod tests { #[test] fn test_beneficiary_validation_skipped_before_zero5() { // Test that beneficiary validation is skipped for blocks before Zero5 hardfork - let chain_spec = localdev_with_hardforks(&[(ArcHardfork::Zero4, 0)]); + let chain_spec = localdev_with_hardforks(&[(ArcHardfork::Zero4, ForkCondition::Block(0))]); let mut db = InMemoryDB::default(); insert_alloc_into_db(&mut db, chain_spec.genesis()); diff --git a/crates/evm/src/handler.rs b/crates/evm/src/handler.rs index cb506a9..1bb5869 100644 --- a/crates/evm/src/handler.rs +++ b/crates/evm/src/handler.rs @@ -15,11 +15,11 @@ // limitations under the License. use alloy_primitives::{Address, U256}; -use arc_execution_config::hardforks::{ArcHardfork, ArcHardforkFlags}; +use arc_execution_config::hardforks::ArcHardforkFlags; use arc_execution_config::native_coin_control::{ compute_is_blocklisted_storage_slot, is_blocklisted_status, }; -use arc_precompiles::helpers::{ERR_BLOCKED_ADDRESS, PRECOMPILE_SLOAD_GAS_COST}; +use arc_precompiles::helpers::ERR_BLOCKED_ADDRESS; use arc_precompiles::NATIVE_COIN_CONTROL_ADDRESS; use revm::inspector::{Inspector, InspectorEvmTr, InspectorHandler}; use revm::{ @@ -37,6 +37,8 @@ use revm_primitives::TxKind; pub struct ArcEvmHandler { mainnet: MainnetHandler>, /// Feature flags for Arc hardforks active at the current block. + /// Retained for future hardfork-gated handler behavior (e.g. new precompiles). + #[allow(dead_code)] hardfork_flags: ArcHardforkFlags, } @@ -102,59 +104,15 @@ where .map_err(From::from) } - /// Calculates initial gas costs including blocklist SLOAD costs for Zero6+. + /// Returns base intrinsic gas from the mainnet handler. /// - /// For Zero6 hardfork and later: - /// - Always adds 1 SLOAD cost (2,100 gas) for caller blocklist check - /// - Adds 1 additional SLOAD cost (2,100 gas) for recipient blocklist check if value > 0 - /// - /// This ensures gas accounting matches the actual blocklist checks performed in pre_execution. + /// Blocklist SLOADs are unmetered — no extra gas is added for blocklist checks. #[inline] fn validate_initial_tx_gas( &self, evm: &mut Self::Evm, ) -> Result { - // Get base intrinsic gas from mainnet handler - let mut init_and_floor_gas = self.mainnet.validate_initial_tx_gas(evm)?; - - // Add blocklist SLOAD costs if Zero6 is active - if self.hardfork_flags.is_active(ArcHardfork::Zero6) { - let ctx = evm.ctx_ref(); - let tx = ctx.tx(); - - // Always charge for caller blocklist check - let mut extra_gas = PRECOMPILE_SLOAD_GAS_COST; - - // Charge for recipient blocklist check if value > 0 - // This applies to both Call and Create transactions with value - if !tx.value().is_zero() { - // 2,100 + 2,100 = 4,200; fits in u64 - #[allow(clippy::arithmetic_side_effects)] - { - extra_gas += PRECOMPILE_SLOAD_GAS_COST; - } - } - - // Add extra gas to initial gas - init_and_floor_gas.initial_gas = init_and_floor_gas - .initial_gas - .checked_add(extra_gas) - .ok_or(InvalidTransaction::CallGasCostMoreThanGasLimit { - gas_limit: tx.gas_limit(), - initial_gas: u64::MAX, - })?; - - // Verify total doesn't exceed gas limit - if init_and_floor_gas.initial_gas > tx.gas_limit() { - return Err(InvalidTransaction::CallGasCostMoreThanGasLimit { - gas_limit: tx.gas_limit(), - initial_gas: init_and_floor_gas.initial_gas, - } - .into()); - } - } - - Ok(init_and_floor_gas) + self.mainnet.validate_initial_tx_gas(evm) } } @@ -186,35 +144,24 @@ where tx_kind: &TxKind, tx_value: U256, ) -> Result<(), ERROR> { - let (caller_blocklisted, _) = self.is_address_blocklisted(evm, caller)?; - if caller_blocklisted { + if self.is_address_blocklisted(evm, caller)? { return Err(InvalidTransaction::Str(ERR_BLOCKED_ADDRESS.into()).into()); } if let TxKind::Call(to_address) = tx_kind { - if !tx_value.is_zero() { - let (to_blocklisted, _) = self.is_address_blocklisted(evm, *to_address)?; - if to_blocklisted { - return Err(InvalidTransaction::Str(ERR_BLOCKED_ADDRESS.into()).into()); - } + if !tx_value.is_zero() && self.is_address_blocklisted(evm, *to_address)? { + return Err(InvalidTransaction::Str(ERR_BLOCKED_ADDRESS.into()).into()); } } Ok(()) } /// Checks if an address is blocklisted by reading from the native coin control precompile storage. - /// - /// Returns `(is_blocklisted, is_cold)`. The `is_cold` flag is available for gas accounting - /// but is not used at depth 0 (where `validate_initial_tx_gas` charges fixed cold SLOAD costs). - fn is_address_blocklisted( - &self, - evm: &mut EVM, - address: Address, - ) -> Result<(bool, bool), ERROR> { + fn is_address_blocklisted(&self, evm: &mut EVM, address: Address) -> Result { let storage_slot = compute_is_blocklisted_storage_slot(address).into(); let journal = evm.ctx_mut().journal_mut(); let state_load = journal.sload(NATIVE_COIN_CONTROL_ADDRESS, storage_slot)?; - Ok((is_blocklisted_status(state_load.data), state_load.is_cold)) + Ok(is_blocklisted_status(state_load.data)) } } @@ -222,6 +169,7 @@ where mod tests { use super::*; use alloy_primitives::{address, U256}; + use arc_execution_config::hardforks::ArcHardfork; use arc_precompiles::NATIVE_COIN_CONTROL_ADDRESS; use reth_ethereum::evm::revm::db::{CacheDB, EmptyDB}; use revm::{ @@ -762,163 +710,30 @@ mod tests { } #[test] - fn test_validate_initial_tx_gas_pre_zero6_no_extra_gas() { - // Pre-Zero6: No extra gas should be charged for blocklist checks + fn test_validate_initial_tx_gas_no_blocklist_surcharge() { + // Blocklist SLOADs are unmetered — native value transfer costs exactly 21,000 gas let caller = address!("1000000000000000000000000000000000000001"); let recipient = address!("2000000000000000000000000000000000000002"); let db: CacheDB> = CacheDB::new(EmptyDB::default()); let mut evm = Context::mainnet().with_db(db).build_mainnet(); - // Set up transaction with value - evm.tx.caller = caller; - evm.tx.kind = TxKind::Call(recipient); - evm.tx.value = U256::from(1000); - evm.tx.gas_limit = 100_000u64; - evm.tx.gas_price = 1; - - // Pre-Zero6 handler (Zero5 active but Zero6 not yet active) - let handler: ArcEvmHandler<_, EVMError> = - ArcEvmHandler::new(ArcHardforkFlags::with(&[ArcHardfork::Zero5])); - let result = handler.validate_initial_tx_gas(&mut evm); - - assert!(result.is_ok(), "validate_initial_tx_gas should succeed"); - let init_gas = result.unwrap(); - - // Base intrinsic gas for a simple call is 21000 - // Pre-Zero6: no extra SLOAD costs added - assert_eq!( - init_gas.initial_gas, 21000, - "Pre-Zero6: Should use standard intrinsic gas without blocklist SLOAD costs" - ); - } - - #[test] - fn test_validate_initial_tx_gas_zero6_value_transfer() { - // Zero6: Value transfer should charge 2 SLOADs (caller + recipient) - let caller = address!("3000000000000000000000000000000000000003"); - let recipient = address!("4000000000000000000000000000000000000004"); - - let db: CacheDB> = CacheDB::new(EmptyDB::default()); - let mut evm = Context::mainnet().with_db(db).build_mainnet(); - - // Set up transaction with value - evm.tx.caller = caller; - evm.tx.kind = TxKind::Call(recipient); - evm.tx.value = U256::from(1000); - evm.tx.gas_limit = 100_000u64; - evm.tx.gas_price = 1; - - // Zero6 active handler - let handler: ArcEvmHandler<_, EVMError> = - ArcEvmHandler::new(ArcHardforkFlags::with(&[ArcHardfork::Zero6])); - let result = handler.validate_initial_tx_gas(&mut evm); - - assert!(result.is_ok(), "validate_initial_tx_gas should succeed"); - let init_gas = result.unwrap(); - - // Base intrinsic gas (21000) + 2 SLOADs (2 * 2100 = 4200) = 25200 - let expected_gas = 21000 + 2 * PRECOMPILE_SLOAD_GAS_COST; - assert_eq!( - init_gas.initial_gas, expected_gas, - "Zero6 value transfer: Should add 2 SLOAD costs for blocklist checks" - ); - } - - #[test] - fn test_validate_initial_tx_gas_zero6_zero_value_call() { - // Zero6: Zero-value call should charge 1 SLOAD (caller only) - let caller = address!("5000000000000000000000000000000000000005"); - let recipient = address!("6000000000000000000000000000000000000006"); - - let db: CacheDB> = CacheDB::new(EmptyDB::default()); - let mut evm = Context::mainnet().with_db(db).build_mainnet(); - - // Set up transaction without value evm.tx.caller = caller; evm.tx.kind = TxKind::Call(recipient); - evm.tx.value = U256::ZERO; - evm.tx.gas_limit = 100_000u64; - evm.tx.gas_price = 1; - - // Zero6 active handler - let handler: ArcEvmHandler<_, EVMError> = - ArcEvmHandler::new(ArcHardforkFlags::with(&[ArcHardfork::Zero6])); - let result = handler.validate_initial_tx_gas(&mut evm); - - assert!(result.is_ok(), "validate_initial_tx_gas should succeed"); - let init_gas = result.unwrap(); - - // Base intrinsic gas (21000) + 1 SLOAD (2100) = 23100 - let expected_gas = 21000 + PRECOMPILE_SLOAD_GAS_COST; - assert_eq!( - init_gas.initial_gas, expected_gas, - "Zero6 zero-value call: Should add 1 SLOAD cost for caller blocklist check only" - ); - } - - #[test] - fn test_validate_initial_tx_gas_zero6_create_with_value() { - // Zero6: CREATE with value should charge 2 SLOADs (caller + recipient) - let caller = address!("7000000000000000000000000000000000000007"); - - let db: CacheDB> = CacheDB::new(EmptyDB::default()); - let mut evm = Context::mainnet().with_db(db).build_mainnet(); - - // Set up CREATE transaction with value - evm.tx.caller = caller; - evm.tx.kind = TxKind::Create; evm.tx.value = U256::from(1000); evm.tx.gas_limit = 100_000u64; evm.tx.gas_price = 1; - // Zero6 active handler + // Zero6 active — still no extra gas for blocklist checks let handler: ArcEvmHandler<_, EVMError> = ArcEvmHandler::new(ArcHardforkFlags::with(&[ArcHardfork::Zero6])); let result = handler.validate_initial_tx_gas(&mut evm); assert!(result.is_ok(), "validate_initial_tx_gas should succeed"); - let init_gas = result.unwrap(); - - // Base intrinsic gas for CREATE (53000) + 2 SLOADs (4200) = 57200 - let expected_gas = 53000 + 2 * PRECOMPILE_SLOAD_GAS_COST; assert_eq!( - init_gas.initial_gas, expected_gas, - "Zero6 CREATE with value: Should add 2 SLOAD costs for blocklist checks" + result.unwrap().initial_gas, + 21000, + "Native value transfer should cost exactly 21,000 gas (no blocklist surcharge)" ); } - - #[test] - fn test_validate_initial_tx_gas_zero6_gas_limit_exceeded() { - // Zero6: Should fail if gas limit is too low for blocklist checks - let caller = address!("8000000000000000000000000000000000000008"); - let recipient = address!("9000000000000000000000000000000000000009"); - - let db: CacheDB> = CacheDB::new(EmptyDB::default()); - let mut evm = Context::mainnet().with_db(db).build_mainnet(); - - // Set up transaction with value but gas limit just below required - // Base (21000) + 2 SLOADs (4200) = 25200 required - evm.tx.caller = caller; - evm.tx.kind = TxKind::Call(recipient); - evm.tx.value = U256::from(1000); - evm.tx.gas_limit = 25199u64; // Just below 25200 - evm.tx.gas_price = 1; - - // Zero6 active handler - let handler: ArcEvmHandler<_, EVMError> = - ArcEvmHandler::new(ArcHardforkFlags::with(&[ArcHardfork::Zero6])); - let result = handler.validate_initial_tx_gas(&mut evm); - - assert!( - result.is_err(), - "validate_initial_tx_gas should fail with insufficient gas" - ); - match result.unwrap_err() { - EVMError::Transaction(InvalidTransaction::CallGasCostMoreThanGasLimit { .. }) => { - // Expected error - } - err => panic!("Expected CallGasCostMoreThanGasLimit error, got: {:?}", err), - } - } } diff --git a/crates/evm/src/subcall.rs b/crates/evm/src/subcall.rs index 321046f..ccea548 100644 --- a/crates/evm/src/subcall.rs +++ b/crates/evm/src/subcall.rs @@ -22,6 +22,7 @@ use alloy_primitives::Address; use arc_precompiles::subcall::{SubcallContinuationData, SubcallPrecompile}; +use revm_context_interface::journaled_state::JournalCheckpoint; use std::collections::{HashMap, HashSet}; use std::sync::Arc; @@ -40,6 +41,9 @@ pub struct SubcallContinuation { pub(crate) return_memory_offset: std::ops::Range, /// Opaque state carried from `init_subcall` to `complete_subcall`. pub(crate) continuation_data: SubcallContinuationData, + /// Journal checkpoint taken before child dispatch. Used to revert the child's + /// committed state if `complete_subcall` rejects a successful child. + pub(crate) checkpoint: JournalCheckpoint, } /// Specifies which addresses are authorized to call a subcall precompile. diff --git a/crates/evm/src/subcall_test.rs b/crates/evm/src/subcall_test.rs index 6bc6ee9..ff6684e 100644 --- a/crates/evm/src/subcall_test.rs +++ b/crates/evm/src/subcall_test.rs @@ -106,6 +106,7 @@ impl SubcallPrecompile for SubcallTestPrecompile { Bytes::new() }, success: false, + gas_overhead: 0, }); } @@ -115,10 +116,67 @@ impl SubcallPrecompile for SubcallTestPrecompile { Ok(SubcallCompletionResult { output: encoded_output, success: true, + gas_overhead: 0, }) } } +/// Address for the CostlyCompleteSubcallPrecompile test precompile. +pub const COSTLY_COMPLETE_SUBCALL_ADDRESS: Address = + address!("1800000000000000000000000000000000000096"); + +/// Known gas_overhead reported by CostlyCompleteSubcallPrecompile for testing. +pub const COSTLY_COMPLETE_GAS_OVERHEAD: u64 = 500; + +/// Test precompile identical to SubcallTestPrecompile but reports a known nonzero gas_overhead. +/// Used to verify the framework actually charges the reported completion gas. +#[derive(Debug)] +pub struct CostlyCompleteSubcallPrecompile; + +impl SubcallPrecompile for CostlyCompleteSubcallPrecompile { + fn init_subcall(&self, inputs: &CallInputs) -> Result { + SubcallTestPrecompile.init_subcall(inputs) + } + + fn complete_subcall( + &self, + continuation_data: SubcallContinuationData, + child_result: &FrameResult, + ) -> Result { + let mut result = SubcallTestPrecompile.complete_subcall(continuation_data, child_result)?; + result.gas_overhead = COSTLY_COMPLETE_GAS_OVERHEAD; + Ok(result) + } +} + +/// Address for the ExcessiveCompleteSubcallPrecompile test precompile. +pub const EXCESSIVE_COMPLETE_SUBCALL_ADDRESS: Address = + address!("1800000000000000000000000000000000000095"); + +/// Large gas_overhead reported by ExcessiveCompleteSubcallPrecompile for OOG tests. +pub const EXCESSIVE_COMPLETE_GAS_OVERHEAD: u64 = 5_000; + +/// Test precompile that behaves like SubcallTestPrecompile but reports a large +/// completion gas overhead. +#[derive(Debug)] +pub struct ExcessiveCompleteSubcallPrecompile; + +impl SubcallPrecompile for ExcessiveCompleteSubcallPrecompile { + fn init_subcall(&self, inputs: &CallInputs) -> Result { + SubcallTestPrecompile.init_subcall(inputs) + } + + fn complete_subcall( + &self, + continuation_data: SubcallContinuationData, + child_result: &FrameResult, + ) -> Result { + let mut result = SubcallTestPrecompile.complete_subcall(continuation_data, child_result)?; + result.gas_overhead = EXCESSIVE_COMPLETE_GAS_OVERHEAD; + Ok(result) + } +} + /// Address for the FailingCompleteSubcallPrecompile test precompile. pub const FAILING_COMPLETE_SUBCALL_ADDRESS: Address = address!("1800000000000000000000000000000000000098"); @@ -165,10 +223,74 @@ impl SubcallPrecompile for FailingCompleteSubcallPrecompile { fn complete_subcall( &self, _continuation_data: SubcallContinuationData, - _child_result: &FrameResult, + child_result: &FrameResult, ) -> Result { + // Assert child succeeded + let child_ok = matches!(child_result, FrameResult::Call(o) if o.result.result.is_ok()); + assert!(child_ok, "expected successful child, got: {child_result:?}"); Err(SubcallError::InternalError( "intentional complete_subcall failure".into(), )) } } + +/// Address for the RejectingCompleteSubcallPrecompile test precompile. +pub const REJECTING_COMPLETE_SUBCALL_ADDRESS: Address = + address!("1800000000000000000000000000000000000097"); + +/// Test precompile whose complete_subcall returns `success: false` when the child succeeds. +/// Used to verify that the child's committed state is reverted. +#[derive(Debug)] +pub struct RejectingCompleteSubcallPrecompile; + +impl SubcallPrecompile for RejectingCompleteSubcallPrecompile { + fn init_subcall(&self, inputs: &CallInputs) -> Result { + let input_bytes = match &inputs.input { + CallInput::Bytes(b) => b.clone(), + CallInput::SharedBuffer(_) => { + return Err(SubcallError::AbiDecodeError( + "unexpected shared buffer".into(), + )); + } + }; + + let decoded = ::abi_decode(&input_bytes).map_err(|e| { + SubcallError::AbiDecodeError(format!("rejecting_complete_subcall: {e}")) + })?; + let (target, calldata) = decoded; + + Ok(SubcallInitResult { + child_inputs: Box::new(CallInputs { + scheme: CallScheme::Call, + target_address: target, + bytecode_address: target, + known_bytecode: None, + value: CallValue::Transfer(alloy_primitives::U256::ZERO), + input: CallInput::Bytes(calldata), + gas_limit: 50_000, + is_static: false, + caller: inputs.caller, + return_memory_offset: 0..0, + }), + continuation_data: SubcallContinuationData { + state: Box::new(()), + }, + gas_overhead: 0, + }) + } + + fn complete_subcall( + &self, + _continuation_data: SubcallContinuationData, + child_result: &FrameResult, + ) -> Result { + // Assert child succeeded + let child_ok = matches!(child_result, FrameResult::Call(o) if o.result.result.is_ok()); + assert!(child_ok, "expected successful child, got: {child_result:?}"); + Ok(SubcallCompletionResult { + output: Bytes::new(), + success: false, + gas_overhead: 0, + }) + } +} diff --git a/crates/execution-config/Cargo.toml b/crates/execution-config/Cargo.toml index 82728c4..e88daef 100644 --- a/crates/execution-config/Cargo.toml +++ b/crates/execution-config/Cargo.toml @@ -38,6 +38,7 @@ reth-ethereum-forks.workspace = true reth-ethereum-primitives.workspace = true reth-evm.workspace = true reth-network-peers.workspace = true +reth-node-core.workspace = true reth-primitives-traits.workspace = true # revm @@ -48,6 +49,7 @@ serde_json = { workspace = true, default-features = false, features = ["alloc"] thiserror.workspace = true [dev-dependencies] +clap = { workspace = true, features = ["derive"] } reth-ethereum = { workspace = true, features = ["evm"] } [lints] diff --git a/crates/execution-config/src/addresses_denylist.rs b/crates/execution-config/src/addresses_denylist.rs index dc63df2..365726e 100644 --- a/crates/execution-config/src/addresses_denylist.rs +++ b/crates/execution-config/src/addresses_denylist.rs @@ -33,7 +33,7 @@ pub const ERR_DENYLISTED_ADDRESS: &str = "Address is denylisted"; /// Address derived via deterministic CREATE2 salt search: cast create2 with --seed keccak256("Denylist.v1"), /// first match with prefix 0x360. Reproduce: `make mine-denylist-salt INIT_CODE_HASH=` pub const DEFAULT_DENYLIST_ADDRESS: Address = - address!("0x360Eb67EDbA456Bbe01512679f36c2717AA65121"); + address!("0x36059b615370eB999e8eC0c9401835B407834221"); /// ERC-7201 base storage slot for the Denylist contract (arc.storage.Denylist.v1). /// Matches the slot used by the genesis builder (`scripts/genesis/Denylist.ts`). diff --git a/crates/execution-config/src/call_from.rs b/crates/execution-config/src/call_from.rs index c2d33d9..a0878db 100644 --- a/crates/execution-config/src/call_from.rs +++ b/crates/execution-config/src/call_from.rs @@ -24,4 +24,4 @@ use alloy_primitives::{address, Address}; pub const MEMO_ADDRESS: Address = address!("5294E9927c3306DcBaDb03fe70b92e01cCede505"); /// Address of the `Multicall3From` contract (CREATE2-deployed, zero salt). -pub const MULTICALL3_FROM_ADDRESS: Address = address!("A3E6c63b16321E39a61551Dc1A38689b04d62E42"); +pub const MULTICALL3_FROM_ADDRESS: Address = address!("522fAf9A91c41c443c66765030741e4AaCe147D0"); diff --git a/crates/execution-config/src/chainspec.rs b/crates/execution-config/src/chainspec.rs index 15a38c7..1fa5c58 100644 --- a/crates/execution-config/src/chainspec.rs +++ b/crates/execution-config/src/chainspec.rs @@ -42,13 +42,13 @@ use crate::{ gas_fee::decode_base_fee_from_bytes, hardforks::{ ArcGenesisInfo, ArcHardforkFlags, ARC_DEVNET_HARDFORKS, ARC_LOCALDEV_HARDFORKS, - ARC_TESTNET_HARDFORKS, + ARC_MAINNET_HARDFORKS, ARC_TESTNET_HARDFORKS, }, }; use crate::chain_ids::*; -const ARC_SUPPORTED: &[&str] = &["arc-testnet", "arc-localdev", "arc-devnet"]; +const ARC_SUPPORTED: &[&str] = &["arc-mainnet", "arc-testnet", "arc-localdev", "arc-devnet"]; const ARC_BASE_FEE_MAX_CHANGE_DENOMINATOR: u128 = 50; // 1/50 = 2% #[derive(Debug, Clone, Default)] @@ -65,6 +65,7 @@ impl ChainSpecParser for ArcChainSpecParser { "arc-localdev" => Ok(LOCAL_DEV.clone()), "arc-devnet" => Ok(DEVNET.clone()), "arc-testnet" => Ok(TESTNET.clone()), + "arc-mainnet" => Ok(MAINNET.clone()), _ => { let genesis = parse_genesis(s)?; Ok(Arc::new(ArcChainSpec::from(genesis))) @@ -276,18 +277,20 @@ impl ArcChainSpec { Self { inner } } - /// Get the hardfork flags for a given block height. + /// Get the hardfork flags for a given (block height, timestamp). /// - /// Returns feature flags indicating which Arc hardforks are active at the given block. - /// Each hardfork is independently queryable without implying other hardforks. - pub fn get_hardfork_flags(&self, height: u64) -> ArcHardforkFlags { - ArcHardforkFlags::from_chain_hardforks(&self.inner.hardforks, height) + /// Returns feature flags indicating which Arc hardforks are active at the given + /// head. Both inputs are required because Arc hardfork schedules are + /// network-specific and may use either block heights or timestamps. + pub fn get_hardfork_flags(&self, height: u64, timestamp: u64) -> ArcHardforkFlags { + ArcHardforkFlags::from_chain_hardforks(&self.inner.hardforks, height, timestamp) } } impl BlockGasLimitProvider for ArcChainSpec { fn block_gas_limit_config(&self, _block_height: u64) -> BlockGasLimitConfig { let (min, max) = match self.chain().id() { + MAINNET_CHAIN_ID => (10_000_000, 200_000_000), TESTNET_CHAIN_ID => (10_000_000, 200_000_000), _ => (1_000_000, 1_000_000_000), }; @@ -295,6 +298,14 @@ impl BlockGasLimitProvider for ArcChainSpec { } } +const BASE_FEE_CONFIG_MAINNET: BaseFeeConfig = BaseFeeConfig::new( + BoundedParam::new(1, 20, 100), + BoundedParam::new(1, 200, 1_000), + BoundedParam::new(1, 5000, 9_000), + 1, + 20_000_000_000_000, // 20,000 gwei +); + const BASE_FEE_CONFIG_TESTNET: BaseFeeConfig = BaseFeeConfig::new( BoundedParam::new(1, 20, 100), BoundedParam::new(1, 200, 1_000), @@ -308,13 +319,14 @@ const BASE_FEE_CONFIG_DEFAULT: BaseFeeConfig = BaseFeeConfig::new( BoundedParam::new(1, 200, 10_000), BoundedParam::new(1, 5000, 10_000), 1, - u64::MAX - 1, // helpful to test bounds conditions + u64::MAX - 1, ); impl BaseFeeConfigProvider for ArcChainSpec { // While the same config is used for all blockheights, it is available to ease future hardfork transitions fn base_fee_config(&self, _block_height: u64) -> BaseFeeConfig { match self.chain().id() { + MAINNET_CHAIN_ID => BASE_FEE_CONFIG_MAINNET, TESTNET_CHAIN_ID => BASE_FEE_CONFIG_TESTNET, _ => BASE_FEE_CONFIG_DEFAULT, } @@ -334,29 +346,30 @@ const PROXY_IMPLEMENTATION_SLOT: B256 = /// Creates a custom localdev chain spec for testing with specific hardfork activations. /// /// This starts with base Ethereum forks and adds only the specified Arc hardforks. -/// This is useful for testing hardfork transitions without creating static constants -/// for every combination. +/// Each entry pairs a hardfork with the [`ForkCondition`] that activates it — use +/// `ForkCondition::Block(n)` for block-gated forks and `ForkCondition::Timestamp(t)` +/// for timestamp-gated ones. /// /// # Example /// ```ignore /// use arc_execution_config::chainspec::localdev_with_hardforks; /// use arc_execution_config::hardforks::ArcHardfork; +/// use reth_chainspec::ForkCondition; /// /// // Create a chain spec with Zero3 and Zero4 active at genesis /// let spec = localdev_with_hardforks(&[ -/// (ArcHardfork::Zero3, 0), -/// (ArcHardfork::Zero4, 0), +/// (ArcHardfork::Zero3, ForkCondition::Block(0)), +/// (ArcHardfork::Zero4, ForkCondition::Block(0)), /// ]); /// -/// // Test Zero5 activation at block 5 +/// // Test Zero7 activating at a future timestamp /// let spec = localdev_with_hardforks(&[ -/// (ArcHardfork::Zero3, 0), -/// (ArcHardfork::Zero4, 0), -/// (ArcHardfork::Zero5, 5), +/// (ArcHardfork::Zero3, ForkCondition::Block(0)), +/// (ArcHardfork::Zero7, ForkCondition::Timestamp(1_800_000_000)), /// ]); /// ``` #[cfg(any(feature = "test-utils", test))] -pub fn localdev_with_hardforks(hardforks: &[(ArcHardfork, u64)]) -> Arc { +pub fn localdev_with_hardforks(hardforks: &[(ArcHardfork, ForkCondition)]) -> Arc { use crate::hardforks::BASE_FORKS; let genesis: Genesis = @@ -364,26 +377,26 @@ pub fn localdev_with_hardforks(hardforks: &[(ArcHardfork, u64)]) -> Arc inner .hardforks - .insert(ArcHardfork::Zero3.boxed(), ForkCondition::Block(block)), + .insert(ArcHardfork::Zero3.boxed(), condition), ArcHardfork::Zero4 => inner .hardforks - .insert(ArcHardfork::Zero4.boxed(), ForkCondition::Block(block)), + .insert(ArcHardfork::Zero4.boxed(), condition), ArcHardfork::Zero5 => inner .hardforks - .insert(ArcHardfork::Zero5.boxed(), ForkCondition::Block(block)), + .insert(ArcHardfork::Zero5.boxed(), condition), ArcHardfork::Zero6 => inner .hardforks - .insert(ArcHardfork::Zero6.boxed(), ForkCondition::Block(block)), + .insert(ArcHardfork::Zero6.boxed(), condition), + ArcHardfork::Zero7 => inner + .hardforks + .insert(ArcHardfork::Zero7.boxed(), condition), }; } @@ -525,6 +538,27 @@ pub static TESTNET: LazyLock> = LazyLock::new(|| { ArcChainSpec::new(inner).into() }); +pub static MAINNET: LazyLock> = LazyLock::new(|| { + let genesis: Genesis = + serde_json::from_str(include_str!("../../../assets/mainnet/genesis.json")) + .expect("Can't deserialize Mainnet genesis json"); + let mut inner = ChainSpec::from_genesis(genesis); + inner.hardforks = ARC_MAINNET_HARDFORKS.clone(); + ArcChainSpec::new(inner).into() +}); + +/// Returns the bundled chainspec for a known Arc chain ID, or `None` if the +/// chain ID is unknown. +pub fn bundled_chainspec_for_chain_id(chain_id: u64) -> Option> { + match chain_id { + LOCALDEV_CHAIN_ID => Some(LOCAL_DEV.clone()), + MAINNET_CHAIN_ID => Some(MAINNET.clone()), + DEVNET_CHAIN_ID => Some(DEVNET.clone()), + TESTNET_CHAIN_ID => Some(TESTNET.clone()), + _ => None, + } +} + impl From for ArcChainSpec { fn from(inner: ChainSpec) -> Self { Self::new(inner) @@ -535,14 +569,17 @@ impl From for ArcChainSpec { fn from(genesis: Genesis) -> Self { let mut inner = ChainSpec::from_genesis(genesis); - // For devnet and testnet, we don't read the fork configuration from genesis. - // Patch the hardfork table from the predefined value instead. + // For mainnet, devnet, and testnet, we don't read the fork configuration from + // genesis. Patch the hardfork table from the predefined value instead. // // Localdev is intentionally NOT hardcoded here so that genesis.json controls // hardfork activation — the nightly-upgrade test patches genesis.json with jq // and relies on the node reading those values. The named network "arc-localdev" // (LOCAL_DEV static) still uses ARC_LOCALDEV_HARDFORKS directly. match inner.chain().id() { + MAINNET_CHAIN_ID => { + inner.hardforks = ARC_MAINNET_HARDFORKS.clone(); + } DEVNET_CHAIN_ID => { inner.hardforks = ARC_DEVNET_HARDFORKS.clone(); } @@ -784,11 +821,18 @@ impl EthExecutorSpec for ArcChainSpec { mod tests { use super::*; - use crate::chain_ids::{DEVNET_CHAIN_ID, LOCALDEV_CHAIN_ID, TESTNET_CHAIN_ID}; + use crate::chain_ids::{ + DEVNET_CHAIN_ID, LOCALDEV_CHAIN_ID, MAINNET_CHAIN_ID, TESTNET_CHAIN_ID, + }; use crate::hardforks::{ - ARC_OSAKA_HARDFORK_TIMESTAMP_ACTIVATION_DEVNET, ARC_ZERO3_HARDFORK_BLOCK_ACTIVATION_DEVNET, - ARC_ZERO3_HARDFORK_BLOCK_ACTIVATION_TESTNET, ARC_ZERO4_HARDFORK_BLOCK_ACTIVATION_DEVNET, - ARC_ZERO4_HARDFORK_BLOCK_ACTIVATION_TESTNET, ARC_ZERO5_HARDFORK_BLOCK_ACTIVATION_DEVNET, + ARC_OSAKA_HARDFORK_TIMESTAMP_ACTIVATION_DEVNET, + ARC_OSAKA_HARDFORK_TIMESTAMP_ACTIVATION_TESTNET, + ARC_ZERO3_HARDFORK_BLOCK_ACTIVATION_DEVNET, ARC_ZERO3_HARDFORK_BLOCK_ACTIVATION_TESTNET, + ARC_ZERO4_HARDFORK_BLOCK_ACTIVATION_DEVNET, ARC_ZERO4_HARDFORK_BLOCK_ACTIVATION_TESTNET, + ARC_ZERO5_HARDFORK_BLOCK_ACTIVATION_DEVNET, + ARC_ZERO5_HARDFORK_TIMESTAMP_ACTIVATION_TESTNET, + ARC_ZERO6_HARDFORK_BLOCK_ACTIVATION_DEVNET, + ARC_ZERO6_HARDFORK_TIMESTAMP_ACTIVATION_TESTNET, BASE_FORKS, }; fn assert_arc_chainspec_evm_hardforks(spec: &ArcChainSpec) { @@ -907,7 +951,7 @@ mod tests { .expect("Failed to parse arc-localdev"); assert_eq!(spec.chain().id(), LOCALDEV_CHAIN_ID); assert_arc_chainspec_evm_hardforks(&spec); - assert_eq!(spec.forks_iter().count(), 22); + assert_eq!(spec.forks_iter().count(), 23); assert!(spec.is_osaka_active_at_timestamp(0)); // verify zero3 hardfork block @@ -931,11 +975,17 @@ mod tests { spec.is_fork_active_at_block(ArcHardfork::Zero6, 0), "Zero6 should be active at block 0 in hardfork.rs, and load by chainspec" ); - let flags = spec.get_hardfork_flags(0); + // Zero7 activates by timestamp (Arc convention from Zero7 onward). + assert!( + spec.is_fork_active_at_timestamp(ArcHardfork::Zero7, 0), + "Zero7 should be active at timestamp 0 in hardfork.rs, and load by chainspec" + ); + let flags = spec.get_hardfork_flags(0, 0); assert!(flags.is_active(ArcHardfork::Zero3)); assert!(flags.is_active(ArcHardfork::Zero4)); assert!(flags.is_active(ArcHardfork::Zero5)); assert!(flags.is_active(ArcHardfork::Zero6)); + assert!(flags.is_active(ArcHardfork::Zero7)); } #[test] @@ -944,7 +994,7 @@ mod tests { assert_eq!(spec.chain().id(), LOCALDEV_CHAIN_ID); assert_arc_chainspec_evm_hardforks(&spec); assert!(spec.is_osaka_active_at_timestamp(0)); - assert_eq!(spec.forks_iter().count(), 22); + assert_eq!(spec.forks_iter().count(), 23); // verify zero3 hardfork block assert!(!spec.is_fork_active_at_timestamp(ArcHardfork::Zero3, 1762732800)); @@ -958,11 +1008,72 @@ mod tests { // verify zero6 hardfork block assert!(!spec.is_fork_active_at_timestamp(ArcHardfork::Zero6, 1762732800)); assert!(spec.is_fork_active_at_block(ArcHardfork::Zero6, 0)); - let flags = spec.get_hardfork_flags(0); + // Zero7 activates by timestamp (Arc convention from Zero7 onward). + assert!(spec.is_fork_active_at_timestamp(ArcHardfork::Zero7, 0)); + assert!(!spec.is_fork_active_at_block(ArcHardfork::Zero7, 0)); + let flags = spec.get_hardfork_flags(0, 0); assert!(flags.is_active(ArcHardfork::Zero3)); assert!(flags.is_active(ArcHardfork::Zero4)); assert!(flags.is_active(ArcHardfork::Zero5)); assert!(flags.is_active(ArcHardfork::Zero6)); + assert!(flags.is_active(ArcHardfork::Zero7)); + assert_eq!( + spec.display_hardforks().to_string(), + r#"Pre-merge hard forks (block based): +- Frontier @0 +- Homestead @0 +- Tangerine @0 +- SpuriousDragon @0 +- Byzantium @0 +- Constantinople @0 +- Petersburg @0 +- Istanbul @0 +- MuirGlacier @0 +- Berlin @0 +- London @0 +- ArrowGlacier @0 +- GrayGlacier @0 +- Zero3 @0 +- Zero4 @0 +- Zero5 @0 +- Zero6 @0 +Merge hard forks: +- Paris @0 (network is known to be merged) +Post-merge hard forks (timestamp based): +- Shanghai @0 blob: (target: 6, max: 9, fraction: 5007716) +- Cancun @0 blob: (target: 6, max: 9, fraction: 5007716) +- Prague @0 blob: (target: 6, max: 9, fraction: 5007716) +- Osaka @0 blob: (target: 6, max: 9, fraction: 5007716) +- Zero7 @0 blob: (target: 6, max: 9, fraction: 5007716)"# + ); + } + + #[test] + fn test_arc_mainnet_chainspec() { + let spec = ArcChainSpecParser::parse("arc-mainnet").expect("Failed to parse arc-mainnet"); + assert_eq!(spec.chain().id(), MAINNET_CHAIN_ID); + + // Pin the genesis hash to catch any unintended drift in + // assets/mainnet/genesis.json. Update only when a deliberate respin + // happens (e.g. revised admin set, additional prefund, hardfork shift). + assert_eq!( + spec.genesis_hash().to_string(), + "0x09944e07412986bb417fd0006c89ffb71ee523d68ce2017ec2dabc944c42edad", + "the genesis hash of assets/mainnet/genesis.json changed unexpectedly" + ); + + assert_arc_chainspec_evm_hardforks(&spec); + assert!(spec.is_osaka_active_at_timestamp(0)); + assert_eq!(spec.forks_iter().count(), 22); + + // Mainnet launches at Zero6: Zero3..Zero6 active at block 0, Zero7 not. + let flags = spec.get_hardfork_flags(0, 0); + assert!(flags.is_active(ArcHardfork::Zero3)); + assert!(flags.is_active(ArcHardfork::Zero4)); + assert!(flags.is_active(ArcHardfork::Zero5)); + assert!(flags.is_active(ArcHardfork::Zero6)); + assert!(!flags.is_active(ArcHardfork::Zero7)); + assert_eq!( spec.display_hardforks().to_string(), r#"Pre-merge hard forks (block based): @@ -993,6 +1104,90 @@ Post-merge hard forks (timestamp based): ); } + #[test] + fn test_bundled_chainspec_for_chain_id() { + // Round-trip: looking up a chain ID must return the matching spec — + // guards against a regression like the helper returning Some(LOCAL_DEV) + // for DEVNET_CHAIN_ID. + assert_eq!( + bundled_chainspec_for_chain_id(LOCALDEV_CHAIN_ID) + .expect("localdev bundled") + .chain() + .id(), + LOCALDEV_CHAIN_ID + ); + assert_eq!( + bundled_chainspec_for_chain_id(DEVNET_CHAIN_ID) + .expect("devnet bundled") + .chain() + .id(), + DEVNET_CHAIN_ID + ); + assert_eq!( + bundled_chainspec_for_chain_id(TESTNET_CHAIN_ID) + .expect("testnet bundled") + .chain() + .id(), + TESTNET_CHAIN_ID + ); + assert!(bundled_chainspec_for_chain_id(999_999).is_none()); + + assert_eq!( + bundled_chainspec_for_chain_id(MAINNET_CHAIN_ID) + .expect("mainnet bundled") + .chain() + .id(), + MAINNET_CHAIN_ID + ); + } + + /// Expected activations are pinned here. + #[test] + fn test_mainnet_chainspec_paths_agree() { + use alloy_genesis::Genesis; + + let from_parser = ArcChainSpecParser::parse("arc-mainnet").expect("named parser path"); + let from_helper = bundled_chainspec_for_chain_id(MAINNET_CHAIN_ID).expect("helper path"); + let from_genesis = ArcChainSpec::from( + serde_json::from_str::(&format!( + r#"{{ "config": {{ "chainId": {} }}, "alloc": {{}} }}"#, + MAINNET_CHAIN_ID + )) + .expect("synthetic mainnet genesis parses"), + ); + + assert!( + Arc::ptr_eq(&from_parser, &from_helper), + "parser and helper must return the same MAINNET Arc" + ); + + let expected_active: &[(ArcHardfork, bool)] = &[ + (ArcHardfork::Zero3, true), + (ArcHardfork::Zero4, true), + (ArcHardfork::Zero5, true), + (ArcHardfork::Zero6, true), + (ArcHardfork::Zero7, false), // deferred — not active at launch + ]; + let paths: [(&str, &ArcChainSpec); 3] = [ + ("parser", &from_parser), + ("helper", &from_helper), + ("From", &from_genesis), + ]; + for (label, spec) in paths { + for &(fork, want) in expected_active { + assert_eq!( + spec.get_hardfork_flags(0, 0).is_active(fork), + want, + "{label}: {fork:?} active={want}" + ); + } + assert!( + spec.is_osaka_active_at_timestamp(0), + "{label}: Osaka must be active at timestamp 0" + ); + } + } + #[test] fn test_arc_devnet_chainspec() { let spec = ArcChainSpecParser::parse("arc-devnet").expect("Failed to parse arc-devnet"); @@ -1005,39 +1200,54 @@ Post-merge hard forks (timestamp based): "0x41c417868fee948f58602b01a84ce0ddb5ffe2184f7e9ab43b9c8d7e5eb47067", "the genesis hash of assets/devnet/genesis.json changed unexpectedly" ); - assert_eq!(spec.forks_iter().count(), 21); + assert_eq!(spec.forks_iter().count(), 22); assert_arc_chainspec_evm_hardforks(&spec); assert!(!spec.is_osaka_active_at_timestamp(0)); assert!(spec.is_osaka_active_at_timestamp(ARC_OSAKA_HARDFORK_TIMESTAMP_ACTIVATION_DEVNET)); - let flags_before = spec.get_hardfork_flags(ARC_ZERO3_HARDFORK_BLOCK_ACTIVATION_DEVNET - 1); + let flags_before = + spec.get_hardfork_flags(ARC_ZERO3_HARDFORK_BLOCK_ACTIVATION_DEVNET - 1, 0); assert!(!flags_before.is_active(ArcHardfork::Zero3)); assert!(!flags_before.is_active(ArcHardfork::Zero4)); - let flags_at = spec.get_hardfork_flags(ARC_ZERO3_HARDFORK_BLOCK_ACTIVATION_DEVNET); + let flags_at = spec.get_hardfork_flags(ARC_ZERO3_HARDFORK_BLOCK_ACTIVATION_DEVNET, 0); assert!(flags_at.is_active(ArcHardfork::Zero3)); assert!(!flags_at.is_active(ArcHardfork::Zero4)); let flags_before_zero4 = - spec.get_hardfork_flags(ARC_ZERO4_HARDFORK_BLOCK_ACTIVATION_DEVNET - 1); + spec.get_hardfork_flags(ARC_ZERO4_HARDFORK_BLOCK_ACTIVATION_DEVNET - 1, 0); assert!(flags_before_zero4.is_active(ArcHardfork::Zero3)); assert!(!flags_before_zero4.is_active(ArcHardfork::Zero4)); - let flags_at_zero4 = spec.get_hardfork_flags(ARC_ZERO4_HARDFORK_BLOCK_ACTIVATION_DEVNET); + let flags_at_zero4 = spec.get_hardfork_flags(ARC_ZERO4_HARDFORK_BLOCK_ACTIVATION_DEVNET, 0); assert!(flags_at_zero4.is_active(ArcHardfork::Zero3)); assert!(flags_at_zero4.is_active(ArcHardfork::Zero4)); assert!(!flags_at_zero4.is_active(ArcHardfork::Zero5)); let flags_before_zero5 = - spec.get_hardfork_flags(ARC_ZERO5_HARDFORK_BLOCK_ACTIVATION_DEVNET - 1); + spec.get_hardfork_flags(ARC_ZERO5_HARDFORK_BLOCK_ACTIVATION_DEVNET - 1, 0); assert!(flags_before_zero5.is_active(ArcHardfork::Zero3)); assert!(flags_before_zero5.is_active(ArcHardfork::Zero4)); assert!(!flags_before_zero5.is_active(ArcHardfork::Zero5)); - let flags_at_zero5 = spec.get_hardfork_flags(ARC_ZERO5_HARDFORK_BLOCK_ACTIVATION_DEVNET); + let flags_at_zero5 = spec.get_hardfork_flags(ARC_ZERO5_HARDFORK_BLOCK_ACTIVATION_DEVNET, 0); assert!(flags_at_zero5.is_active(ArcHardfork::Zero3)); assert!(flags_at_zero5.is_active(ArcHardfork::Zero4)); assert!(flags_at_zero5.is_active(ArcHardfork::Zero5)); + assert!(!flags_at_zero5.is_active(ArcHardfork::Zero6)); + + let flags_before_zero6 = + spec.get_hardfork_flags(ARC_ZERO6_HARDFORK_BLOCK_ACTIVATION_DEVNET - 1, 0); + assert!(flags_before_zero6.is_active(ArcHardfork::Zero3)); + assert!(flags_before_zero6.is_active(ArcHardfork::Zero4)); + assert!(flags_before_zero6.is_active(ArcHardfork::Zero5)); + assert!(!flags_before_zero6.is_active(ArcHardfork::Zero6)); + + let flags_at_zero6 = spec.get_hardfork_flags(ARC_ZERO6_HARDFORK_BLOCK_ACTIVATION_DEVNET, 0); + assert!(flags_at_zero6.is_active(ArcHardfork::Zero3)); + assert!(flags_at_zero6.is_active(ArcHardfork::Zero4)); + assert!(flags_at_zero6.is_active(ArcHardfork::Zero5)); + assert!(flags_at_zero6.is_active(ArcHardfork::Zero6)); assert_eq!( spec.display_hardforks().to_string(), @@ -1058,6 +1268,7 @@ Post-merge hard forks (timestamp based): - Zero3 @7437594 - Zero4 @19491165 - Zero5 @32371192 +- Zero6 @40033853 Merge hard forks: - Paris @0 (network is known to be merged) Post-merge hard forks (timestamp based): @@ -1078,6 +1289,10 @@ Post-merge hard forks (timestamp based): spec.fork(ArcHardfork::Zero5), ForkCondition::Block(ARC_ZERO5_HARDFORK_BLOCK_ACTIVATION_DEVNET) ); + assert_eq!( + spec.fork(ArcHardfork::Zero6), + ForkCondition::Block(ARC_ZERO6_HARDFORK_BLOCK_ACTIVATION_DEVNET) + ); } #[test] @@ -1093,15 +1308,17 @@ Post-merge hard forks (timestamp based): ); assert_arc_chainspec_evm_hardforks(&spec); assert!(!spec.is_osaka_active_at_timestamp(0)); - assert_eq!(spec.forks_iter().count(), 19); + assert!(spec.is_osaka_active_at_timestamp(ARC_OSAKA_HARDFORK_TIMESTAMP_ACTIVATION_TESTNET)); + assert_eq!(spec.forks_iter().count(), 22); // Zero3 let flags_before_zero3 = - spec.get_hardfork_flags(ARC_ZERO3_HARDFORK_BLOCK_ACTIVATION_TESTNET - 1); + spec.get_hardfork_flags(ARC_ZERO3_HARDFORK_BLOCK_ACTIVATION_TESTNET - 1, 0); assert!(!flags_before_zero3.is_active(ArcHardfork::Zero3)); assert!(!flags_before_zero3.is_active(ArcHardfork::Zero4)); - let flags_at_zero3 = spec.get_hardfork_flags(ARC_ZERO3_HARDFORK_BLOCK_ACTIVATION_TESTNET); + let flags_at_zero3 = + spec.get_hardfork_flags(ARC_ZERO3_HARDFORK_BLOCK_ACTIVATION_TESTNET, 0); assert!(flags_at_zero3.is_active(ArcHardfork::Zero3)); assert!(!flags_at_zero3.is_active(ArcHardfork::Zero4)); assert_eq!( @@ -1111,16 +1328,61 @@ Post-merge hard forks (timestamp based): // Zero4 let flags_before_zero4 = - spec.get_hardfork_flags(ARC_ZERO4_HARDFORK_BLOCK_ACTIVATION_TESTNET - 1); + spec.get_hardfork_flags(ARC_ZERO4_HARDFORK_BLOCK_ACTIVATION_TESTNET - 1, 0); assert!(!flags_before_zero4.is_active(ArcHardfork::Zero4)); - let flags_at_zero4 = spec.get_hardfork_flags(ARC_ZERO4_HARDFORK_BLOCK_ACTIVATION_TESTNET); + let flags_at_zero4 = + spec.get_hardfork_flags(ARC_ZERO4_HARDFORK_BLOCK_ACTIVATION_TESTNET, 0); assert!(flags_at_zero4.is_active(ArcHardfork::Zero4)); assert_eq!( spec.fork(ArcHardfork::Zero4), ForkCondition::Block(ARC_ZERO4_HARDFORK_BLOCK_ACTIVATION_TESTNET) ); + // Zero5 — activates by timestamp on testnet. Use a block past Zero4's activation + // so Zero4 still reads as active in the snapshot. + let post_zero4_block = ARC_ZERO4_HARDFORK_BLOCK_ACTIVATION_TESTNET + 1; + let flags_before_zero5 = spec.get_hardfork_flags( + post_zero4_block, + ARC_ZERO5_HARDFORK_TIMESTAMP_ACTIVATION_TESTNET - 1, + ); + assert!(flags_before_zero5.is_active(ArcHardfork::Zero4)); + assert!(!flags_before_zero5.is_active(ArcHardfork::Zero5)); + + let flags_at_zero5 = spec.get_hardfork_flags( + post_zero4_block, + ARC_ZERO5_HARDFORK_TIMESTAMP_ACTIVATION_TESTNET, + ); + assert!(flags_at_zero5.is_active(ArcHardfork::Zero4)); + assert!(flags_at_zero5.is_active(ArcHardfork::Zero5)); + assert_eq!( + spec.fork(ArcHardfork::Zero5), + ForkCondition::Timestamp(ARC_ZERO5_HARDFORK_TIMESTAMP_ACTIVATION_TESTNET) + ); + + // Zero6 — activates by timestamp on testnet (same timestamp as Zero5 by current schedule). + let flags_before_zero6 = spec.get_hardfork_flags( + post_zero4_block, + ARC_ZERO6_HARDFORK_TIMESTAMP_ACTIVATION_TESTNET - 1, + ); + assert!(!flags_before_zero6.is_active(ArcHardfork::Zero6)); + + let flags_at_zero6 = spec.get_hardfork_flags( + post_zero4_block, + ARC_ZERO6_HARDFORK_TIMESTAMP_ACTIVATION_TESTNET, + ); + assert!(flags_at_zero6.is_active(ArcHardfork::Zero5)); + assert!(flags_at_zero6.is_active(ArcHardfork::Zero6)); + assert_eq!( + spec.fork(ArcHardfork::Zero6), + ForkCondition::Timestamp(ARC_ZERO6_HARDFORK_TIMESTAMP_ACTIVATION_TESTNET) + ); + + assert_eq!( + spec.fork(EthereumHardfork::Osaka), + ForkCondition::Timestamp(ARC_OSAKA_HARDFORK_TIMESTAMP_ACTIVATION_TESTNET) + ); + assert_eq!( spec.display_hardforks().to_string(), r#"Pre-merge hard forks (block based): @@ -1144,7 +1406,10 @@ Merge hard forks: Post-merge hard forks (timestamp based): - Shanghai @0 blob: (target: 6, max: 9, fraction: 5007716) - Cancun @0 blob: (target: 6, max: 9, fraction: 5007716) -- Prague @0 blob: (target: 6, max: 9, fraction: 5007716)"# +- Prague @0 blob: (target: 6, max: 9, fraction: 5007716) +- Osaka @1779890400 blob: (target: 6, max: 9, fraction: 5007716) +- Zero5 @1779894517 blob: (target: 6, max: 9, fraction: 5007716) +- Zero6 @1779894517 blob: (target: 6, max: 9, fraction: 5007716)"# ); } @@ -1178,6 +1443,77 @@ Post-merge hard forks (timestamp based): ); } + #[test] + fn test_gas_limit_config_mainnet() { + // Mainnet has no parseable chainspec name (genesis.json is gitignored), so build + // a synthetic spec by cloning localdev and overriding the chain id. + let mut spec = + (*ArcChainSpecParser::parse("arc-localdev").expect("localdev parses")).clone(); + spec.inner.chain = Chain::from_id(MAINNET_CHAIN_ID); + let config = spec.block_gas_limit_config(0); + assert_eq!( + config, + BlockGasLimitConfig::new(10_000_000, 200_000_000, 30_000_000) + ); + } + + #[test] + fn test_base_fee_config_mainnet() { + let mut spec = + (*ArcChainSpecParser::parse("arc-localdev").expect("localdev parses")).clone(); + spec.inner.chain = Chain::from_id(MAINNET_CHAIN_ID); + let cfg = spec.base_fee_config(0); + + assert_eq!(cfg.absolute_min_base_fee, 1); + assert_eq!(cfg.absolute_max_base_fee, 20_000_000_000_000); // 20,000 gwei + assert_eq!(cfg.alpha, BoundedParam::new(1, 20, 100)); + assert_eq!(cfg.k_rate, BoundedParam::new(1, 200, 1_000)); + assert_eq!( + cfg.inverse_elasticity_multiplier, + BoundedParam::new(1, 5000, 9_000) + ); + } + + /// Exercises the named-chain arms of the match (the + /// alternative path to the `arc-mainnet` / `arc-devnet` / `arc-testnet` + /// parser, used when someone passes `--chain `). + #[test] + fn test_from_genesis_named_chain_ids_apply_predefined_hardforks() { + use alloy_genesis::Genesis; + fn parse_with_chain_id(chain_id: u64) -> ArcChainSpec { + let s = format!( + r#"{{ "config": {{ "chainId": {} }}, "alloc": {{}} }}"#, + chain_id + ); + let genesis: Genesis = serde_json::from_str(&s).expect("parse genesis"); + ArcChainSpec::from(genesis) + } + + // Mainnet + let spec = parse_with_chain_id(MAINNET_CHAIN_ID); + let flags = spec.get_hardfork_flags(0, 0); + assert!(flags.is_active(ArcHardfork::Zero3)); + assert!(flags.is_active(ArcHardfork::Zero4)); + assert!(flags.is_active(ArcHardfork::Zero5)); + assert!(flags.is_active(ArcHardfork::Zero6)); + assert!(!flags.is_active(ArcHardfork::Zero7)); + assert!(spec.is_osaka_active_at_timestamp(0)); + + // Devnet + let spec = parse_with_chain_id(DEVNET_CHAIN_ID); + assert!(spec + .get_hardfork_flags(ARC_ZERO3_HARDFORK_BLOCK_ACTIVATION_DEVNET, 0) + .is_active(ArcHardfork::Zero3)); + assert!(!spec.get_hardfork_flags(0, 0).is_active(ArcHardfork::Zero3)); + + // Testnet + let spec = parse_with_chain_id(TESTNET_CHAIN_ID); + assert!(spec + .get_hardfork_flags(ARC_ZERO3_HARDFORK_BLOCK_ACTIVATION_TESTNET, 0) + .is_active(ArcHardfork::Zero3)); + assert!(!spec.get_hardfork_flags(0, 0).is_active(ArcHardfork::Zero3)); + } + /// Simulates the nightly-upgrade scenario: genesis.json with a future osakaTime. /// Verifies that From correctly reads osakaTime and activates Osaka /// only at the specified timestamp. @@ -1441,4 +1777,155 @@ Post-merge hard forks (timestamp based): fn test_bounded_param_inverted_bounds_panics() { BoundedParam::new(100u64, 20, 50); } + + static MOCK_ARC_HARDFORKS: LazyLock<[(Box, ForkCondition); 6]> = + LazyLock::new(|| { + [ + (ArcHardfork::Zero3.boxed(), ForkCondition::Block(0)), + (ArcHardfork::Zero4.boxed(), ForkCondition::Block(10)), + ( + EthereumHardfork::Osaka.boxed(), + ForkCondition::Timestamp(1779244750), + ), + ( + ArcHardfork::Zero5.boxed(), + ForkCondition::Timestamp(1779244760), + ), + ( + ArcHardfork::Zero6.boxed(), + ForkCondition::Timestamp(1779244770), + ), + ( + ArcHardfork::Zero7.boxed(), + ForkCondition::Timestamp(1779244780), + ), + ] + }); + + #[test] + fn test_arc_hardfork_ids() { + use reth_chainspec::Hardforks; + + let genesis: Genesis = + serde_json::from_str(include_str!("../../../assets/devnet/genesis.json")) + .expect("Can't deserialize Devnet genesis json"); + + let mut prev_head = Head { + number: 0, + timestamp: 0, + ..Default::default() + }; + let make_spec = |i: usize| -> ArcChainSpec { + let mut inner = ChainSpec::from_genesis(genesis.clone()); + inner.hardforks = BASE_FORKS.clone(); + for (hardfork, cond) in MOCK_ARC_HARDFORKS[0..i + 1].iter() { + inner.hardforks.insert(hardfork, *cond); + } + ArcChainSpec::new(inner) + }; + let mut prev_spec = make_spec(0); + let mut prev_hardfork = MOCK_ARC_HARDFORKS[0].0.clone(); + + for i in 1..MOCK_ARC_HARDFORKS.len() { + let hardfork = MOCK_ARC_HARDFORKS[i].0.clone(); + let spec = make_spec(i); + // simulate the next head according to the current fork condition + let head = match MOCK_ARC_HARDFORKS[i].1 { + ForkCondition::Block(block) => Head { + timestamp: prev_head.timestamp, + number: block, + ..Default::default() + }, + ForkCondition::TTD { + fork_block: Some(block), + .. + } => Head { + number: block, + ..Default::default() + }, + ForkCondition::Timestamp(timestamp) => Head { + number: prev_head.number, + timestamp, + ..Default::default() + }, + _ => panic!("unexpected fork condition"), + }; + let msg = format!( + "[iter={i}, {hardfork:?}, prev_head=({},{}), head=({},{})]", + prev_head.number, prev_head.timestamp, head.number, head.timestamp + ); + println!("{}", msg); + + let prev_filter = prev_spec.fork_filter(prev_head); + let mut filter = spec.fork_filter(prev_head); + + // make sure when we add the next hardfork, it could still valid for previos version. + let prev_fork_id = prev_filter.current(); + let fork_id = filter.current(); + + assert_eq!( + prev_fork_id.hash, fork_id.hash, + "[{msg}] fork hash should be the same when add a new hardfork", + ); + assert_eq!( + filter.validate(prev_fork_id), + Ok(()), + "[{msg}] fork id for the prev verions should validate by new version" + ); + assert_eq!( + prev_filter.validate(fork_id), + Ok(()), + "[{msg}] fork id for the new verions should validate by previous version" + ); + + // fork_id() use a different compute path, verify the value is the same as filter + assert_eq!( + prev_spec.fork_id(&prev_head), + prev_fork_id, + "[{msg}] computed fork id should be the same as it from previous filter" + ); + assert_eq!( + spec.fork_id(&prev_head), + fork_id, + "[{msg}] spec.fork_id(&prev_head) mismatched" + ); + let next_fork_id = spec.fork_id(&head); + + // Verify the fork ID by hardfork + assert_eq!( + prev_spec.inner.hardfork_fork_id(prev_hardfork.clone()), + Some(prev_fork_id), + "[{msg}] computed fork id by hardfork mismatched for previous spec" + ); + assert_eq!( + spec.inner.hardfork_fork_id(prev_hardfork.clone()), + Some(fork_id) + ); + assert_eq!( + spec.inner.hardfork_fork_id(hardfork.clone()), + Some(next_fork_id) + ); + + // Set the new head. + let transition: Option = filter.set_head(head); + assert!( + transition.is_some(), + "[{msg}] transition should be happened on next head" + ); + + // Verify the fork ID is the same as filter on next head + assert_eq!( + spec.fork_id(&head), + filter.current(), + "[{msg}] spec.fork_id(&head) mismatched on next head" + ); + assert_eq!( + next_fork_id, + filter.current(), + "[{msg}] computed fork ID mismatched on next head" + ); + + (prev_head, prev_spec, prev_hardfork) = (head, spec, hardfork); + } + } } diff --git a/crates/execution-config/src/defaults.rs b/crates/execution-config/src/defaults.rs index 408ccb3..bbed0d3 100644 --- a/crates/execution-config/src/defaults.rs +++ b/crates/execution-config/src/defaults.rs @@ -17,19 +17,22 @@ //! Default configuration for Arc Network node. //! //! This module provides default values for various node components including -//! snapshot download URLs for quick node bootstrapping. +//! snapshot download URLs for quick node bootstrapping, and RPC connection limits. use reth_cli_commands::download::DownloadDefaults; +use reth_node_core::args::DefaultRpcServerArgs; use std::borrow::Cow; // FIXME: Update this to the actual snapshot URL. /// Default snapshot URL for Arc Network testnet (chain ID 5042002). pub(crate) const DEFAULT_DOWNLOAD_URL: &str = "https://snapshots.arc.network/5042002"; -/// Initialize download URL defaults for snapshot-based node bootstrapping. -/// -/// This registers snapshot URLs for Arc Network chains (testnet and devnet) -/// which can be used with the `arc-node-execution download` command. +/// Max simultaneous RPC connections (HTTP + WS pooled). Bounds WS subscription fan-out memory. +pub const RPC_MAX_CONNECTIONS: u32 = 250; + +/// Max subscriptions per RPC connection. Real-world clients multiplex ~5 over one WS socket. +pub const RPC_MAX_SUBSCRIPTIONS_PER_CONNECTION: u32 = 32; + fn init_download_urls() { let download_defaults = DownloadDefaults { available_snapshots: vec![ @@ -41,19 +44,88 @@ fn init_download_urls() { default_chain_aware_base_url: None, long_help: None, }; + let _ = download_defaults.try_init(); +} - download_defaults - .try_init() - .expect("failed to initialize download URLs"); +fn init_rpc_defaults() { + let _ = DefaultRpcServerArgs::default() + .with_rpc_max_connections(RPC_MAX_CONNECTIONS.into()) + .with_rpc_max_subscriptions_per_connection(RPC_MAX_SUBSCRIPTIONS_PER_CONNECTION.into()) + .try_init(); } -/// Initialize all Arc Network node defaults. -/// -/// This function must be called before parsing CLI arguments to ensure -/// defaults are registered with Reth's command infrastructure. -/// -/// Currently initializes: -/// - Download URLs for snapshot-based bootstrapping +/// Register Arc defaults with Reth. Must run before CLI parsing. pub fn init_defaults() { init_download_urls(); + init_rpc_defaults(); +} + +#[cfg(test)] +mod tests { + use super::*; + use clap::{Args, Parser}; + use reth_node_core::args::RpcServerArgs; + + #[derive(Parser)] + struct CommandParser { + #[command(flatten)] + args: T, + } + + // Reth's RpcServerArgs read defaults from a process-global OnceLock, so + // every test in this binary must init first to be order-independent. + fn ensure_initialized() { + init_defaults(); + } + + #[test] + fn rpc_defaults_match_arc_constants_when_no_override() { + ensure_initialized(); + let args = CommandParser::::parse_from(["arc-node-execution"]).args; + assert_eq!(args.rpc_max_connections.get(), RPC_MAX_CONNECTIONS); + assert_eq!( + args.rpc_max_subscriptions_per_connection.get(), + RPC_MAX_SUBSCRIPTIONS_PER_CONNECTION, + ); + } + + #[test] + fn rpc_max_connections_cli_override_wins() { + ensure_initialized(); + let args = CommandParser::::parse_from([ + "arc-node-execution", + "--rpc.max-connections", + "777", + ]) + .args; + assert_eq!(args.rpc_max_connections.get(), 777); + } + + #[test] + fn rpc_max_subscriptions_per_connection_cli_override_wins() { + ensure_initialized(); + let args = CommandParser::::parse_from([ + "arc-node-execution", + "--rpc.max-subscriptions-per-connection", + "1024", + ]) + .args; + assert_eq!(args.rpc_max_subscriptions_per_connection.get(), 1024); + } + + #[test] + fn rpc_overrides_are_independent() { + ensure_initialized(); + let args = CommandParser::::parse_from([ + "arc-node-execution", + "--rpc.max-connections", + "500", + ]) + .args; + assert_eq!(args.rpc_max_connections.get(), 500); + assert_eq!( + args.rpc_max_subscriptions_per_connection.get(), + RPC_MAX_SUBSCRIPTIONS_PER_CONNECTION, + ); + } } diff --git a/crates/execution-config/src/hardforks.rs b/crates/execution-config/src/hardforks.rs index 3f42d3e..567b898 100644 --- a/crates/execution-config/src/hardforks.rs +++ b/crates/execution-config/src/hardforks.rs @@ -32,6 +32,7 @@ hardfork!( Zero5, // v0.5 hardfork, align to Ethereum Prague #[default] Zero6, // v0.6 hardfork + Zero7, // v0.7 hardfork — batch (Multicall3From) and memo contracts } ); @@ -48,6 +49,11 @@ pub struct ArcGenesisInfo { pub zero_5_block: Option, /// v0.6 hardfork block pub zero_6_block: Option, + /// v0.7 hardfork timestamp for genesis-configured chains. + /// + /// Built-in network schedules are defined in `ARC_*_HARDFORKS` and may activate + /// earlier Arc hardforks by timestamp, e.g. testnet Zero5/Zero6. + pub zero_7_time: Option, } impl ArcGenesisInfo { @@ -68,6 +74,10 @@ impl ArcGenesisInfo { hardforks.push((hardfork, ForkCondition::Block(fork_block))); } } + // Zero7+ activate by timestamp (see field doc on ArcGenesisInfo). + if let Some(time) = self.zero_7_time { + hardforks.push((ArcHardfork::Zero7, ForkCondition::Timestamp(time))); + } hardforks } } @@ -90,6 +100,7 @@ pub struct ArcHardforkFlags { zero4: bool, zero5: bool, zero6: bool, + zero7: bool, } impl ArcHardforkFlags { @@ -99,6 +110,7 @@ impl ArcHardforkFlags { ArcHardfork::Zero4, ArcHardfork::Zero5, ArcHardfork::Zero6, + ArcHardfork::Zero7, ]; /// Check if a specific hardfork is active. @@ -108,6 +120,7 @@ impl ArcHardforkFlags { ArcHardfork::Zero4 => self.zero4, ArcHardfork::Zero5 => self.zero5, ArcHardfork::Zero6 => self.zero6, + ArcHardfork::Zero7 => self.zero7, } } @@ -118,14 +131,21 @@ impl ArcHardforkFlags { ArcHardfork::Zero4 => self.zero4 = value, ArcHardfork::Zero5 => self.zero5 = value, ArcHardfork::Zero6 => self.zero6 = value, + ArcHardfork::Zero7 => self.zero7 = value, } } - /// Create flags from chain hardforks at a given block height. - pub fn from_chain_hardforks(hardforks: &ChainHardforks, block: u64) -> Self { + /// Create flags from chain hardforks at a given (block, timestamp). + /// + /// Arc hardfork activation is network-specific: some forks activate by block and + /// others by timestamp. Evaluate both dimensions so timestamp-based testnet + /// Zero5/Zero6 and block-based devnet Zero5/Zero6 are both handled correctly. + pub fn from_chain_hardforks(hardforks: &ChainHardforks, block: u64, timestamp: u64) -> Self { let mut flags = Self::default(); for &hf in Self::ALL_HARDFORKS { - if hardforks.is_fork_active_at_block(hf, block) { + if hardforks.is_fork_active_at_block(hf, block) + || hardforks.is_fork_active_at_timestamp(hf, timestamp) + { flags.set(hf, true); } } @@ -163,6 +183,29 @@ impl ArcHardforkFlags { } } +/// Checks whether an Arc hardfork is active at the given `(block_number, block_timestamp)`, +/// covering both block-based and timestamp-based activation. +/// +/// **Use this for any runtime gating of Arc hardforks.** Zero7+ activates by timestamp on +/// every network, and Zero5/Zero6 activate by timestamp on testnet. A bare +/// `is_fork_active_at_block(hf, n)` silently returns `false` for timestamp-activated forks, +/// so the corresponding EVM/validation behaviour would never trigger. +/// +/// The OR is safe by construction: `ForkCondition::active_at_block` and `active_at_timestamp` +/// both pattern-match the variant before comparing values, so a `Timestamp(_)` fork never +/// matches the block branch and vice versa — the two checks are mutually exclusive per fork. +/// +/// Mirrors the dual-check pattern in [`ArcHardforkFlags::from_chain_hardforks`]. +pub fn is_arc_fork_active( + chain_spec: &CS, + fork: ArcHardfork, + block_number: u64, + block_timestamp: u64, +) -> bool { + chain_spec.is_fork_active_at_block(fork, block_number) + || chain_spec.is_fork_active_at_timestamp(fork, block_timestamp) +} + // Reference Ethereum forks // - https://github.com/ethereum/execution-specs/blob/forks/osaka/README.md // - https://github.com/paradigmxyz/reth/blob/91defb2f9c9522007436ba6f41098d73e41cc34c/crates/ethereum/hardforks/src/hardforks/dev.rs#L14 @@ -224,6 +267,22 @@ pub(crate) static BASE_FORKS: LazyLock = LazyLock::new(|| { /// Arc Local Dev network (1337) hardforks. pub static ARC_LOCALDEV_HARDFORKS: LazyLock = LazyLock::new(|| { + let mut forks = BASE_FORKS.clone(); + forks.insert(ArcHardfork::Zero3.boxed(), ForkCondition::Block(0)); + forks.insert(ArcHardfork::Zero4.boxed(), ForkCondition::Block(0)); + // Zero5 : Osaka — paired per convention above + forks.insert(EthereumHardfork::Osaka.boxed(), ForkCondition::Timestamp(0)); + forks.insert(ArcHardfork::Zero5.boxed(), ForkCondition::Block(0)); + forks.insert(ArcHardfork::Zero6.boxed(), ForkCondition::Block(0)); + // Zero7+ activate by timestamp to keep the EIP-2124 fork-id stable across + // mixed-version peers (block-based forks declared after a timestamp fork break + // ForkFilter's BTreeMap ordering). + forks.insert(ArcHardfork::Zero7.boxed(), ForkCondition::Timestamp(0)); + forks +}); + +/// Arc Mainnet network (5042) hardforks. +pub static ARC_MAINNET_HARDFORKS: LazyLock = LazyLock::new(|| { let mut forks = BASE_FORKS.clone(); forks.insert(ArcHardfork::Zero3.boxed(), ForkCondition::Block(0)); forks.insert(ArcHardfork::Zero4.boxed(), ForkCondition::Block(0)); @@ -253,6 +312,10 @@ pub static ARC_DEVNET_HARDFORKS: LazyLock = LazyLock::new(|| { ArcHardfork::Zero5.boxed(), ForkCondition::Block(ARC_ZERO5_HARDFORK_BLOCK_ACTIVATION_DEVNET), ); + forks.insert( + ArcHardfork::Zero6.boxed(), + ForkCondition::Block(ARC_ZERO6_HARDFORK_BLOCK_ACTIVATION_DEVNET), + ); forks }); @@ -267,8 +330,22 @@ pub static ARC_TESTNET_HARDFORKS: LazyLock = LazyLock::new(|| { ArcHardfork::Zero4.boxed(), ForkCondition::Block(ARC_ZERO4_HARDFORK_BLOCK_ACTIVATION_TESTNET), ); - // TODO: When Zero5 is activated on testnet, add Osaka at the same activation point: - // forks.insert(EthereumHardfork::Osaka.boxed(), ForkCondition::Timestamp()); + forks.insert( + EthereumHardfork::Osaka.boxed(), + ForkCondition::Timestamp(ARC_OSAKA_HARDFORK_TIMESTAMP_ACTIVATION_TESTNET), + ); + // Zero5/Zero6 must activate by timestamp + // (not block) — declaring them as Block-after-Osaka would trigger the EIP-2124 + // BTreeMap-ordering bug for any peer that doesn't have them declared yet. + forks.insert( + ArcHardfork::Zero5.boxed(), + ForkCondition::Timestamp(ARC_ZERO5_HARDFORK_TIMESTAMP_ACTIVATION_TESTNET), + ); + forks.insert( + ArcHardfork::Zero6.boxed(), + ForkCondition::Timestamp(ARC_ZERO6_HARDFORK_TIMESTAMP_ACTIVATION_TESTNET), + ); + forks }); @@ -281,8 +358,13 @@ pub const ARC_ZERO4_HARDFORK_BLOCK_ACTIVATION_DEVNET: u64 = 19491165; pub const ARC_ZERO4_HARDFORK_BLOCK_ACTIVATION_TESTNET: u64 = 26148086; /// Zero5 pub const ARC_ZERO5_HARDFORK_BLOCK_ACTIVATION_DEVNET: u64 = 32371192; +pub const ARC_ZERO5_HARDFORK_TIMESTAMP_ACTIVATION_TESTNET: u64 = 1779894517; +/// Zero6 +pub const ARC_ZERO6_HARDFORK_BLOCK_ACTIVATION_DEVNET: u64 = 40033853; +pub const ARC_ZERO6_HARDFORK_TIMESTAMP_ACTIVATION_TESTNET: u64 = 1779894517; /// Osaka (paired with Zero5) pub const ARC_OSAKA_HARDFORK_TIMESTAMP_ACTIVATION_DEVNET: u64 = 1775483400; +pub const ARC_OSAKA_HARDFORK_TIMESTAMP_ACTIVATION_TESTNET: u64 = 1779890400; #[cfg(test)] mod tests { @@ -295,6 +377,7 @@ mod tests { assert_eq!(ArcHardfork::Zero4.name(), "Zero4"); assert_eq!(ArcHardfork::Zero5.name(), "Zero5"); assert_eq!(ArcHardfork::Zero6.name(), "Zero6"); + assert_eq!(ArcHardfork::Zero7.name(), "Zero7"); } #[test] @@ -305,28 +388,33 @@ mod tests { assert!(!flags.is_active(ArcHardfork::Zero4)); assert!(!flags.is_active(ArcHardfork::Zero5)); assert!(!flags.is_active(ArcHardfork::Zero6)); + assert!(!flags.is_active(ArcHardfork::Zero7)); - // Test from chain hardforks - localdev has Zero3-Zero5 and PQC active at block 0 - let flags = ArcHardforkFlags::from_chain_hardforks(&ARC_LOCALDEV_HARDFORKS, 0); + // Test from chain hardforks - localdev has all Arc hardforks active at genesis + let flags = ArcHardforkFlags::from_chain_hardforks(&ARC_LOCALDEV_HARDFORKS, 0, 0); assert!(flags.is_active(ArcHardfork::Zero3)); assert!(flags.is_active(ArcHardfork::Zero4)); assert!(flags.is_active(ArcHardfork::Zero5)); assert!(flags.is_active(ArcHardfork::Zero6)); + assert!(flags.is_active(ArcHardfork::Zero7)); // Test from chain hardforks - devnet has Zero3 and Zero4 active after their activation blocks let flags = ArcHardforkFlags::from_chain_hardforks( &ARC_DEVNET_HARDFORKS, ARC_ZERO4_HARDFORK_BLOCK_ACTIVATION_DEVNET, + 0, ); assert!(flags.is_active(ArcHardfork::Zero3)); assert!(flags.is_active(ArcHardfork::Zero4)); assert!(!flags.is_active(ArcHardfork::Zero5)); assert!(!flags.is_active(ArcHardfork::Zero6)); + assert!(!flags.is_active(ArcHardfork::Zero7)); // Test from chain hardforks - devnet before Zero4 activation let flags = ArcHardforkFlags::from_chain_hardforks( &ARC_DEVNET_HARDFORKS, ARC_ZERO4_HARDFORK_BLOCK_ACTIVATION_DEVNET - 1, + 0, ); assert!(flags.is_active(ArcHardfork::Zero3)); assert!(!flags.is_active(ArcHardfork::Zero4)); @@ -335,6 +423,7 @@ mod tests { let flags = ArcHardforkFlags::from_chain_hardforks( &ARC_DEVNET_HARDFORKS, ARC_ZERO3_HARDFORK_BLOCK_ACTIVATION_DEVNET - 1, + 0, ); assert!(!flags.is_active(ArcHardfork::Zero3)); assert!(!flags.is_active(ArcHardfork::Zero4)); @@ -356,9 +445,9 @@ mod tests { assert!(!flags.is_active(ArcHardfork::Zero3)); assert!(!flags.is_active(ArcHardfork::Zero4)); - // Test all_combinations() helper - should yield 16 combinations (2^4) + // Test all_combinations() helper - should yield 32 combinations (2^5) let combinations: Vec<_> = ArcHardforkFlags::all_combinations().collect(); - assert_eq!(combinations.len(), 16); + assert_eq!(combinations.len(), 32); // Verify some key combinations are present assert!(combinations.contains(&ArcHardforkFlags::with(&[]))); @@ -373,12 +462,13 @@ mod tests { ArcHardfork::Zero4, ArcHardfork::Zero5, ArcHardfork::Zero6, + ArcHardfork::Zero7, ]))); } #[test] fn test_parse_arc_hardfork_from_genesis() { - let s = r#"{ "config": { "zero3Block": 123123, "zero4Block": 223881, "zero5Block": 323496, "zero6Block": 423000 } }"#; + let s = r#"{ "config": { "zero3Block": 123123, "zero4Block": 223881, "zero5Block": 323496, "zero6Block": 423000, "zero7Time": 1800000000 } }"#; let genesis = serde_json::from_str::(s).expect("Failed to parse genesis"); let info = ArcGenesisInfo::extract_from(&genesis.config.extra_fields) @@ -387,6 +477,7 @@ mod tests { assert_eq!(info.zero_4_block, Some(223881)); assert_eq!(info.zero_5_block, Some(323496)); assert_eq!(info.zero_6_block, Some(423000)); + assert_eq!(info.zero_7_time, Some(1800000000)); } // Verify ethereum hardforks are supported for all networks. @@ -429,7 +520,7 @@ mod tests { fn test_arc_localdev_forks() { let forks = ARC_LOCALDEV_HARDFORKS.clone(); assert_base_hardforks(&forks); - assert_eq!(forks.len(), 22); + assert_eq!(forks.len(), 23); // verify hardfork zero3 block assert!(!forks.is_fork_active_at_timestamp(ArcHardfork::Zero3, 0)); @@ -446,13 +537,17 @@ mod tests { // verify hardfork zero6 block assert!(!forks.is_fork_active_at_timestamp(ArcHardfork::Zero6, 0)); assert!(forks.is_fork_active_at_block(ArcHardfork::Zero6, 0)); + + // Zero7 activates by timestamp (Arc convention from Zero7 onward). + assert!(forks.is_fork_active_at_timestamp(ArcHardfork::Zero7, 0)); + assert!(!forks.is_fork_active_at_block(ArcHardfork::Zero7, 0)); } #[test] fn test_arc_devnet_forks() { let forks = ARC_DEVNET_HARDFORKS.clone(); assert_base_hardforks(&forks); - assert_eq!(forks.len(), 21); + assert_eq!(forks.len(), 22); // verify hardfork zero3 block assert_eq!( @@ -517,13 +612,29 @@ mod tests { EthereumHardfork::Osaka, ARC_OSAKA_HARDFORK_TIMESTAMP_ACTIVATION_DEVNET )); + + // verify hardfork zero6 block + assert_eq!( + forks.get(ArcHardfork::Zero6), + Some(ForkCondition::Block( + ARC_ZERO6_HARDFORK_BLOCK_ACTIVATION_DEVNET + )) + ); + assert!(!forks.is_fork_active_at_block( + ArcHardfork::Zero6, + ARC_ZERO6_HARDFORK_BLOCK_ACTIVATION_DEVNET - 1 + )); + assert!(forks.is_fork_active_at_block( + ArcHardfork::Zero6, + ARC_ZERO6_HARDFORK_BLOCK_ACTIVATION_DEVNET + )); } #[test] fn test_arc_testnet_forks() { let forks = ARC_TESTNET_HARDFORKS.clone(); assert_base_hardforks(&forks); - assert_eq!(forks.len(), 19); + assert_eq!(forks.len(), 22); // verify hardfork zero3 block assert_eq!( @@ -556,5 +667,150 @@ mod tests { ArcHardfork::Zero4, ARC_ZERO4_HARDFORK_BLOCK_ACTIVATION_TESTNET )); + + // verify osaka timestamp + assert_eq!( + forks.get(EthereumHardfork::Osaka), + Some(ForkCondition::Timestamp( + ARC_OSAKA_HARDFORK_TIMESTAMP_ACTIVATION_TESTNET + )) + ); + assert!(!forks.is_fork_active_at_timestamp( + EthereumHardfork::Osaka, + ARC_OSAKA_HARDFORK_TIMESTAMP_ACTIVATION_TESTNET - 1 + )); + assert!(forks.is_fork_active_at_timestamp( + EthereumHardfork::Osaka, + ARC_OSAKA_HARDFORK_TIMESTAMP_ACTIVATION_TESTNET + )); + + // verify hardfork zero5 timestamp (testnet activates Zero5 by timestamp) + assert_eq!( + forks.get(ArcHardfork::Zero5), + Some(ForkCondition::Timestamp( + ARC_ZERO5_HARDFORK_TIMESTAMP_ACTIVATION_TESTNET + )) + ); + assert!(!forks.is_fork_active_at_timestamp( + ArcHardfork::Zero5, + ARC_ZERO5_HARDFORK_TIMESTAMP_ACTIVATION_TESTNET - 1 + )); + assert!(forks.is_fork_active_at_timestamp( + ArcHardfork::Zero5, + ARC_ZERO5_HARDFORK_TIMESTAMP_ACTIVATION_TESTNET + )); + + // verify hardfork zero6 timestamp (testnet activates Zero6 by timestamp) + assert_eq!( + forks.get(ArcHardfork::Zero6), + Some(ForkCondition::Timestamp( + ARC_ZERO6_HARDFORK_TIMESTAMP_ACTIVATION_TESTNET + )) + ); + assert!(!forks.is_fork_active_at_timestamp( + ArcHardfork::Zero6, + ARC_ZERO6_HARDFORK_TIMESTAMP_ACTIVATION_TESTNET - 1 + )); + assert!(forks.is_fork_active_at_timestamp( + ArcHardfork::Zero6, + ARC_ZERO6_HARDFORK_TIMESTAMP_ACTIVATION_TESTNET + )); + } + + /// Per-network policy: from a given cutoff hardfork (inclusive) onward, all + /// declarations MUST activate by timestamp. + /// + /// Background — once any timestamp-based fork is in a network's `ChainHardforks`, + /// adding a future `ForkCondition::Block(n>0)` to the same table breaks EIP-2124 + /// peering with binaries that don't have the new fork declared (the BTreeMap + /// ordering in alloy-eip2124 folds the new Block key into the existing Time + /// entry's cumulative hash). The cutoff is per-network: + /// + /// - mainnet: Zero7+ (Zero3-Zero6 are at Block(0), filtered out by alloy + /// before the cumulative hash is built — they don't trigger the bug). + /// - devnet: Zero7+. Zero5/Zero6 were declared as Block before this invariant + /// was understood; the v0.6.1↔v0.7.1 mismatch was the operational cost. + /// Going forward, Zero7+ MUST be Timestamp to prevent recurrence. + /// - testnet: Zero5+. Testnet has external validators we cannot coordinate + /// a chainspec change with, so the cutoff is stricter — every fork after + /// Osaka must be Timestamp. + /// + /// Localdev is intentionally omitted: it activates everything at genesis, and + /// `Block(0)` / `Timestamp(0)` keys are filtered out by alloy-eip2124, so no + /// future-block-fork shape can arise. + #[test] + fn test_no_future_block_forks_per_network() { + // `ArcHardfork::VARIANTS` is the canonical ordering (Zero3, Zero4, ..., Zero7, + // and any future variants appended to the enum). The cutoff names where the + // timestamp-only invariant starts taking effect. + let policy: &[(&str, &ChainHardforks, ArcHardfork)] = &[ + ("devnet", &*ARC_DEVNET_HARDFORKS, ArcHardfork::Zero7), + ("testnet", &*ARC_TESTNET_HARDFORKS, ArcHardfork::Zero5), + ("mainnet", &*ARC_MAINNET_HARDFORKS, ArcHardfork::Zero7), + ]; + + for &(name, forks, cutoff) in policy { + let cutoff_idx = ArcHardfork::VARIANTS + .iter() + .position(|hf| *hf == cutoff) + .expect("cutoff hardfork must be in ArcHardfork::VARIANTS"); + + for hf in ArcHardfork::VARIANTS[cutoff_idx..].iter().copied() { + let Some(cond) = forks.get(hf) else { continue }; + match cond { + ForkCondition::Timestamp(_) => {} + ForkCondition::Block(0) => {} // genesis-equivalent, filtered by alloy-eip2124 + other => panic!( + "{name}: hardfork {hf:?} must activate by timestamp \ + (or be Block(0)), got {other:?}. Cutoff for this network \ + is {cutoff:?}+. See test_arc_hardfork_ids for the EIP-2124 \ + invariant.", + ), + } + } + } + } + + /// `is_arc_fork_active` must dispatch on the fork's condition variant — a block-based + /// fork only cares about `block_number`, a timestamp-based fork only cares about + /// `block_timestamp`. The OR cannot cross-talk between dimensions. + #[test] + fn test_is_arc_fork_active_dispatches_by_variant() { + // Build a minimal chainspec with Zero3 as Block(100) and Zero5 as Timestamp(2_000). + let mut forks = BASE_FORKS.clone(); + forks.insert(ArcHardfork::Zero3.boxed(), ForkCondition::Block(100)); + forks.insert(ArcHardfork::Zero5.boxed(), ForkCondition::Timestamp(2_000)); + let spec = crate::chainspec::ArcChainSpec::new(reth_chainspec::ChainSpec { + hardforks: forks, + ..reth_chainspec::ChainSpec::from_genesis(alloy_genesis::Genesis::default()) + }); + + // Zero3 is Block(100). Only block_number matters. + assert!(!is_arc_fork_active(&spec, ArcHardfork::Zero3, 99, 0)); + assert!(is_arc_fork_active(&spec, ArcHardfork::Zero3, 100, 0)); + // A block_timestamp huge enough to exceed Zero5's Timestamp(2_000) must NOT + // accidentally activate Zero3 — Zero3's variant is Block, so the timestamp branch + // returns false for it regardless of value. + assert!(!is_arc_fork_active(&spec, ArcHardfork::Zero3, 99, u64::MAX)); + + // Zero5 is Timestamp(2_000). Only block_timestamp matters. + assert!(!is_arc_fork_active(&spec, ArcHardfork::Zero5, 0, 1_999)); + assert!(is_arc_fork_active(&spec, ArcHardfork::Zero5, 0, 2_000)); + // Likewise a block_number that coincidentally exceeds Zero5's u64 value (2_000) + // must NOT activate Zero5 — Zero5's variant is Timestamp, the block branch is false. + assert!(!is_arc_fork_active( + &spec, + ArcHardfork::Zero5, + u64::MAX, + 1_999 + )); + + // Undeclared fork: both branches false. + assert!(!is_arc_fork_active( + &spec, + ArcHardfork::Zero7, + u64::MAX, + u64::MAX + )); } } diff --git a/crates/execution-config/src/protocol_config.rs b/crates/execution-config/src/protocol_config.rs index 3d7b35e..feb61fe 100644 --- a/crates/execution-config/src/protocol_config.rs +++ b/crates/execution-config/src/protocol_config.rs @@ -23,6 +23,7 @@ use alloy_primitives::Bytes; use alloy_sol_types::sol; use alloy_sol_types::SolCall; use reth_evm::Evm; +use revm::handler::SYSTEM_ADDRESS; use revm::DatabaseCommit; use revm_primitives::Address; @@ -106,8 +107,8 @@ where let result_and_state = evm .transact_system_call( - Address::ZERO, // caller (use zero address to avoid RejectCallerWithCode) - PROTOCOL_CONFIG_ADDRESS, // contract address + SYSTEM_ADDRESS, + PROTOCOL_CONFIG_ADDRESS, Bytes::from(call_data), ) .map_err(|e| ProtocolConfigError::EvmError(format!("{e:?}")))?; diff --git a/crates/execution-e2e/src/actions/assertions.rs b/crates/execution-e2e/src/actions/assertions.rs index 324be09..d3dd344 100644 --- a/crates/execution-e2e/src/actions/assertions.rs +++ b/crates/execution-e2e/src/actions/assertions.rs @@ -21,9 +21,9 @@ use alloy_consensus::TxReceipt; use alloy_eips::Encodable2718; use alloy_primitives::{Address, U256}; use alloy_rpc_types_eth::BlockNumberOrTag; -use arc_execution_config::hardforks::ArcHardfork; +use arc_execution_config::hardforks::{is_arc_fork_active, ArcHardfork}; use futures_util::future::BoxFuture; -use reth_chainspec::{ChainSpecProvider, EthereumHardfork, EthereumHardforks, Hardforks}; +use reth_chainspec::{ChainSpecProvider, EthereumHardfork, EthereumHardforks}; use reth_node_api::Block; use reth_provider::{BlockReaderIdExt, ReceiptProvider}; use reth_rpc_api::EthApiClient; @@ -103,13 +103,24 @@ impl AssertHardfork { impl Action for AssertHardfork { fn execute<'a>(&'a mut self, env: &'a mut ArcEnvironment) -> BoxFuture<'a, eyre::Result<()>> { Box::pin(async move { - let block_number = env.block_number(); + let block = env.current_block(); + let block_number = block.number; + let block_timestamp = block.timestamp; let chain_spec = env.node().inner.provider().chain_spec(); - let is_active = chain_spec.is_fork_active_at_block(self.hardfork, block_number); + // An Arc hardfork can be configured as either Block or Timestamp, + // so check both — the underlying `is_fork_active_at_*` returns false + // for the wrong condition type, and we OR them together. + let is_active = is_arc_fork_active( + chain_spec.as_ref(), + self.hardfork, + block_number, + block_timestamp, + ); info!( hardfork = ?self.hardfork, block_number, + block_timestamp, is_active, expected_active = self.expected_active, "Asserting hardfork status" @@ -117,9 +128,10 @@ impl Action for AssertHardfork { if is_active != self.expected_active { return Err(eyre::eyre!( - "Hardfork {:?} at block {}: expected active={}, got active={}", + "Hardfork {:?} at block {} (ts {}): expected active={}, got active={}", self.hardfork, block_number, + block_timestamp, self.expected_active, is_active )); @@ -222,6 +234,8 @@ pub struct AssertTxIncluded { name: String, /// Expected execution status (success or reverted). expected_status: TxStatus, + /// Expected gas used. + expected_gas_used: Option, /// Specific block number to check. If None, uses current block. block_number: Option, } @@ -234,6 +248,7 @@ impl AssertTxIncluded { Self { name: name.into(), expected_status: TxStatus::default(), + expected_gas_used: None, block_number: None, } } @@ -244,6 +259,12 @@ impl AssertTxIncluded { self } + /// Sets the expected transaction gas used. + pub fn expect_gas_used(mut self, expected_gas_used: u64) -> Self { + self.expected_gas_used = Some(expected_gas_used); + self + } + /// Sets a specific block number to check instead of using the current block. pub fn in_block(mut self, block_number: u64) -> Self { self.block_number = Some(block_number); @@ -285,16 +306,19 @@ impl Action for AssertTxIncluded { .map(|tx| tx.trie_hash()) .collect(); - if !tx_hashes.contains(&tx_hash) { - return Err(eyre::eyre!( - "Transaction '{}' ({}) not found in block {}. Block contains {} transactions: {:?}", - self.name, - tx_hash, - block_number, - tx_hashes.len(), - tx_hashes - )); - } + let tx_index = tx_hashes + .iter() + .position(|h| *h == tx_hash) + .ok_or_else(|| { + eyre::eyre!( + "Transaction '{}' ({}) not found in block {}, Block contains {} transactions: {:?}", + self.name, + tx_hash, + block_number, + tx_hashes.len(), + tx_hashes + ) + })?; // Get the receipt to check execution status let receipt = node @@ -324,6 +348,49 @@ impl Action for AssertTxIncluded { actual_status )); } + if let Some(expected_gas_used) = self.expected_gas_used { + // Consensus receipts only store cumulative gas in the block. Per-tx gas (what + // JSON-RPC reports as `gasUsed`) is the delta from the previous tx in the same + // block, not `cumulative_gas_used` on the receipt alone. + let prev_cumulative_gas_used = if tx_index == 0 { + 0 + } else { + let prev_tx_hash = tx_hashes[tx_index - 1]; + node.inner + .provider() + .receipt_by_hash(prev_tx_hash)? + .ok_or_else(|| { + eyre::eyre!( + "Previous receipt not found for transaction '{}' ({})", + self.name, + prev_tx_hash + ) + })? + .cumulative_gas_used() + }; + let cumulative_gas_used = receipt.cumulative_gas_used(); + let actual_gas_used = cumulative_gas_used + .checked_sub(prev_cumulative_gas_used) + .ok_or_else(|| { + eyre::eyre!( + "Cumulative gas regression for tx '{}' ({}): \ + prev_cumulative={}, current_cumulative={}", + self.name, + tx_hash, + prev_cumulative_gas_used, + cumulative_gas_used, + ) + })?; + if actual_gas_used != expected_gas_used { + return Err(eyre::eyre!( + "Transaction '{}' ({}) gas used mismatch: expected {}, got {}", + self.name, + tx_hash, + expected_gas_used, + actual_gas_used + )); + } + } info!( name = %self.name, diff --git a/crates/execution-e2e/src/actions/send_transaction.rs b/crates/execution-e2e/src/actions/send_transaction.rs index 5e8ae39..f6baf85 100644 --- a/crates/execution-e2e/src/actions/send_transaction.rs +++ b/crates/execution-e2e/src/actions/send_transaction.rs @@ -96,7 +96,7 @@ impl SendTransaction { create: false, data: None, data_resolver: None, - gas_limit: 26000, + gas_limit: 21000, wallet_index: 0, } } diff --git a/crates/execution-e2e/src/chainspec.rs b/crates/execution-e2e/src/chainspec.rs index 19f9963..9d212f8 100644 --- a/crates/execution-e2e/src/chainspec.rs +++ b/crates/execution-e2e/src/chainspec.rs @@ -28,11 +28,14 @@ pub use arc_execution_config::chainspec::{ mod tests { use super::*; use arc_execution_config::hardforks::ArcHardfork; - use reth_chainspec::Hardforks; + use reth_chainspec::{ForkCondition, Hardforks}; #[test] fn test_localdev_with_hardforks_creates_valid_spec() { - let spec = localdev_with_hardforks(&[(ArcHardfork::Zero3, 0), (ArcHardfork::Zero4, 5)]); + let spec = localdev_with_hardforks(&[ + (ArcHardfork::Zero3, ForkCondition::Block(0)), + (ArcHardfork::Zero4, ForkCondition::Block(5)), + ]); // Zero3 should be active at block 0 assert!(spec.is_fork_active_at_block(ArcHardfork::Zero3, 0)); @@ -47,7 +50,10 @@ mod tests { #[test] fn test_zero4_at_block_3() { - let spec = localdev_with_hardforks(&[(ArcHardfork::Zero3, 0), (ArcHardfork::Zero4, 3)]); + let spec = localdev_with_hardforks(&[ + (ArcHardfork::Zero3, ForkCondition::Block(0)), + (ArcHardfork::Zero4, ForkCondition::Block(3)), + ]); assert!(spec.is_fork_active_at_block(ArcHardfork::Zero3, 0)); assert!(!spec.is_fork_active_at_block(ArcHardfork::Zero4, 2)); @@ -57,9 +63,9 @@ mod tests { #[test] fn test_zero5_at_block_5() { let spec = localdev_with_hardforks(&[ - (ArcHardfork::Zero3, 0), - (ArcHardfork::Zero4, 0), - (ArcHardfork::Zero5, 5), + (ArcHardfork::Zero3, ForkCondition::Block(0)), + (ArcHardfork::Zero4, ForkCondition::Block(0)), + (ArcHardfork::Zero5, ForkCondition::Block(5)), ]); assert!(spec.is_fork_active_at_block(ArcHardfork::Zero3, 0)); @@ -71,10 +77,10 @@ mod tests { #[test] fn test_zero6_at_block_5() { let spec = localdev_with_hardforks(&[ - (ArcHardfork::Zero3, 0), - (ArcHardfork::Zero4, 0), - (ArcHardfork::Zero5, 0), - (ArcHardfork::Zero6, 5), + (ArcHardfork::Zero3, ForkCondition::Block(0)), + (ArcHardfork::Zero4, ForkCondition::Block(0)), + (ArcHardfork::Zero5, ForkCondition::Block(0)), + (ArcHardfork::Zero6, ForkCondition::Block(5)), ]); assert!(spec.is_fork_active_at_block(ArcHardfork::Zero3, 0)); diff --git a/crates/execution-e2e/tests/base_fee.rs b/crates/execution-e2e/tests/base_fee.rs index 0f37241..aee49b5 100644 --- a/crates/execution-e2e/tests/base_fee.rs +++ b/crates/execution-e2e/tests/base_fee.rs @@ -28,6 +28,7 @@ use arc_execution_e2e::{ Action, ArcEnvironment, ArcSetup, }; use eyre::Result; +use reth_chainspec::ForkCondition; // ADR-0004 encodes the next block's required base fee in parent's `extra_data` (8 bytes). // Two independent checks enforce this on every new block: @@ -144,9 +145,9 @@ async fn test_base_fee_absolute_bounds_enforced_only_after_zero5() -> Result<()> // Zero5 not yet active: the bounds check is skipped — "block base fee mismatch" must not appear. let pre_zero5_spec = localdev_with_hardforks(&[ - (ArcHardfork::Zero3, 0), - (ArcHardfork::Zero4, 0), - (ArcHardfork::Zero5, 10), + (ArcHardfork::Zero3, ForkCondition::Block(0)), + (ArcHardfork::Zero4, ForkCondition::Block(0)), + (ArcHardfork::Zero5, ForkCondition::Block(10)), ]); let status = submit_with_base_fee(ArcSetup::new().with_chain_spec(pre_zero5_spec), U256::ZERO).await?; diff --git a/crates/execution-e2e/tests/block_hash_history.rs b/crates/execution-e2e/tests/block_hash_history.rs index e9d301e..2025b18 100644 --- a/crates/execution-e2e/tests/block_hash_history.rs +++ b/crates/execution-e2e/tests/block_hash_history.rs @@ -30,6 +30,7 @@ use arc_execution_e2e::{ ArcSetup, ArcTestBuilder, }; use eyre::Result; +use reth_chainspec::ForkCondition; /// EIP-2935 History Storage Contract address. const HISTORY_STORAGE_ADDRESS: Address = address!("0000F90827F1C53a10cb7A02335B175320002935"); @@ -104,10 +105,10 @@ async fn test_block_hash_history_starts_at_zero5_activation() -> Result<()> { reth_tracing::init_test_tracing(); let chain_spec = localdev_with_hardforks(&[ - (ArcHardfork::Zero3, 0), - (ArcHardfork::Zero4, 0), - (ArcHardfork::Zero5, 3), - (ArcHardfork::Zero6, 3), + (ArcHardfork::Zero3, ForkCondition::Block(0)), + (ArcHardfork::Zero4, ForkCondition::Block(0)), + (ArcHardfork::Zero5, ForkCondition::Block(3)), + (ArcHardfork::Zero6, ForkCondition::Block(3)), ]); ArcTestBuilder::new() diff --git a/crates/execution-e2e/tests/blocklist_gas.rs b/crates/execution-e2e/tests/blocklist_gas.rs deleted file mode 100644 index 5bfea56..0000000 --- a/crates/execution-e2e/tests/blocklist_gas.rs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2026 Circle Internet Group, Inc. All rights reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! E2E tests for NativeCoinControl blocklist 2×SLOAD gas (Zero6 hardfork). -//! -//! The Zero6 hardfork adds extra intrinsic gas for blocklist SLOAD checks: -//! - Value transfer (tx.value > 0): +4,200 gas (2 SLOADs: caller + recipient) -//! - Zero-value call (tx.value == 0): +2,100 gas (1 SLOAD: caller only) -//! -//! These costs are enforced during EVM execution in `validate_initial_tx_gas`. -//! The standard pool validator does not know about Arc-specific SLOAD costs, -//! so transactions with insufficient gas enter the pool but are skipped by the -//! payload builder during block construction. - -use alloy_primitives::U256; -use arc_execution_config::hardforks::ArcHardfork; -use arc_execution_e2e::{ - actions::{AssertTxIncluded, AssertTxNotIncluded, ProduceBlocks, SendTransaction, TxStatus}, - chainspec::localdev_with_hardforks, - ArcSetup, ArcTestBuilder, -}; -use eyre::Result; -use rstest::rstest; - -/// Tests Zero6 blocklist SLOAD gas accounting through the full node stack. -/// -/// With Zero6 active (default localdev): -/// - Value transfer requires: base (21,000) + 2 SLOADs (4,200) = 25,200 gas -/// - Zero-value call requires: base (21,000) + 1 SLOAD (2,100) = 23,100 gas -/// -/// Sufficient gas → tx included in block. -/// Insufficient gas → tx enters pool but payload builder skips it. -#[rstest] -#[case::value_transfer_sufficient_gas(25_200, 1, true)] -#[case::value_transfer_insufficient_gas(25_199, 1, false)] -#[case::zero_value_call_sufficient_gas(23_100, 0, true)] -#[case::zero_value_call_insufficient_gas(23_099, 0, false)] -#[tokio::test] -async fn test_zero6_blocklist_sload_gas( - #[case] gas_limit: u64, - #[case] value: u64, - #[case] should_be_included: bool, -) -> Result<()> { - reth_tracing::init_test_tracing(); - - let mut builder = ArcTestBuilder::new() - .with_setup(ArcSetup::new()) - .with_action( - SendTransaction::new("tx") - .with_gas_limit(gas_limit) - .with_value(U256::from(value)), - ) - .with_action(ProduceBlocks::new(1)); - - builder = if should_be_included { - builder.with_action(AssertTxIncluded::new("tx").expect(TxStatus::Success)) - } else { - builder.with_action(AssertTxNotIncluded::new("tx")) - }; - - builder.run().await -} - -/// Pre-Zero6: no extra SLOAD gas is charged, so 21,000 is enough for a value transfer. -/// -/// With Zero6 at block 100 (not yet active), standard intrinsic gas (21,000) suffices. -#[tokio::test] -async fn test_pre_zero6_no_extra_gas() -> Result<()> { - reth_tracing::init_test_tracing(); - - let chain_spec = localdev_with_hardforks(&[ - (ArcHardfork::Zero3, 0), - (ArcHardfork::Zero4, 0), - (ArcHardfork::Zero5, 0), - (ArcHardfork::Zero6, 100), - ]); - - ArcTestBuilder::new() - .with_setup(ArcSetup::new().with_chain_spec(chain_spec)) - .with_action( - SendTransaction::new("tx") - .with_gas_limit(21_000) - .with_value(U256::from(1)), - ) - .with_action(ProduceBlocks::new(1)) - .with_action(AssertTxIncluded::new("tx").expect(TxStatus::Success)) - .run() - .await -} diff --git a/crates/execution-e2e/tests/eip7708_gas.rs b/crates/execution-e2e/tests/eip7708_gas.rs deleted file mode 100644 index 1d2f419..0000000 --- a/crates/execution-e2e/tests/eip7708_gas.rs +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright 2026 Circle Internet Group, Inc. All rights reserved. -// -// SPDX-License-Identifier: Apache-2.0 -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! EIP-7708 gas accounting e2e tests. -//! -//! Verifies that gasUsed in receipts correctly accounts for the EIP-7708 -//! log emission cost. Tests isolate transactions in separate blocks to get -//! clean per-transaction gas measurements (cumulative_gas_used == per-tx gas -//! when the tx is alone in its block). - -mod helpers; - -use alloy_primitives::{address, U256}; -use arc_execution_e2e::{ - actions::{AssertTxIncluded, AssertTxTrace, ProduceBlocks, SendTransaction, TxStatus}, - ArcSetup, ArcTestBuilder, -}; -use reth_provider::ReceiptProvider; - -/// Mirrors `arc_precompiles::helpers::PRECOMPILE_SLOAD_GAS_COST`. -/// Under Zero6, each blocklist check costs one SLOAD at this price. -const PRECOMPILE_SLOAD_GAS_COST: u64 = 2100; - -/// Test #44: Value transfer gasUsed > zero-value transfer gasUsed. -/// -/// Isolates each tx in its own block so cumulative_gas_used == per-tx gas. -/// The value transfer incurs the EIP-7708 log emission cost (375 base + 375*3 topics -/// + 8*32 data = 1,756 gas overhead), so it must use strictly more gas. -#[tokio::test] -async fn test_value_transfer_gas_includes_log_cost() { - reth_tracing::init_test_tracing(); - - let recipient = address!("0x0000000000000000000000000000000000009999"); - let value = U256::from(1_000_000); - - let mut env = arc_execution_e2e::ArcEnvironment::new(); - arc_execution_e2e::ArcSetup::new() - .apply(&mut env) - .await - .expect("setup failed"); - - // Block 1: value transfer (isolated) - let mut tx_with_value = SendTransaction::new("with_value") - .with_to(recipient) - .with_value(value); - arc_execution_e2e::Action::execute(&mut tx_with_value, &mut env) - .await - .expect("send with value"); - - let mut produce = arc_execution_e2e::actions::ProduceBlocks::new(1); - arc_execution_e2e::Action::execute(&mut produce, &mut env) - .await - .expect("produce block 1"); - - // Block 2: zero-value transfer (isolated) - let mut tx_zero_value = SendTransaction::new("zero_value") - .with_to(recipient) - .with_value(U256::ZERO); - arc_execution_e2e::Action::execute(&mut tx_zero_value, &mut env) - .await - .expect("send zero value"); - - let mut produce2 = arc_execution_e2e::actions::ProduceBlocks::new(1); - arc_execution_e2e::Action::execute(&mut produce2, &mut env) - .await - .expect("produce block 2"); - - // Get receipts — each tx is alone in its block, so cumulative_gas_used == per-tx gas - let with_value_hash = *env.get_tx_hash("with_value").expect("with_value hash"); - let zero_value_hash = *env.get_tx_hash("zero_value").expect("zero_value hash"); - - let receipt_with = env - .node() - .inner - .provider() - .receipt_by_hash(with_value_hash) - .expect("receipt query") - .expect("receipt not found"); - - let receipt_zero = env - .node() - .inner - .provider() - .receipt_by_hash(zero_value_hash) - .expect("receipt query") - .expect("receipt not found"); - - let gas_with_value = receipt_with.cumulative_gas_used; - let gas_zero_value = receipt_zero.cumulative_gas_used; - - // Value transfer must use strictly more gas due to EIP-7708 log emission - assert!( - gas_with_value > gas_zero_value, - "Value transfer gas ({}) should be greater than zero-value transfer gas ({}). \ - The difference should be ~1756 gas for the EIP-7708 Transfer log.", - gas_with_value, - gas_zero_value - ); - - // The overhead should be approximately 1,756 gas (LOG3 cost for Transfer event) - let overhead = gas_with_value - .checked_sub(gas_zero_value) - .expect("gas_with_value < gas_zero_value"); - assert!( - overhead > 1000, - "Gas overhead ({}) is suspiciously low — expected ~1756 for LOG3", - overhead - ); -} - -/// Test #45: Value transfer succeeds within default gas limit. -/// -/// The default gas limit (26,000) should be sufficient for a simple value transfer -/// with EIP-7708 log emission overhead. -#[tokio::test] -async fn test_value_transfer_within_gas_limit() { - reth_tracing::init_test_tracing(); - - let recipient = address!("0x0000000000000000000000000000000000008888"); - let value = U256::from(1_000_000); - - ArcTestBuilder::new() - .with_setup(ArcSetup::new()) - .with_action( - SendTransaction::new("transfer") - .with_to(recipient) - .with_value(value), - ) - .with_action(ProduceBlocks::new(1)) - .with_action(AssertTxIncluded::new("transfer").expect(TxStatus::Success)) - .with_action(AssertTxTrace::new("transfer")) - .run() - .await - .expect("test_value_transfer_within_gas_limit failed"); -} - -/// Test #46: Value transfer with explicit low gas succeeds. -/// -/// The intrinsic gas for a value transfer is 21,000 + EIP-7708 overhead (~1,756). -/// A gas limit of 26,000 should be sufficient. -#[tokio::test] -async fn test_value_transfer_explicit_gas_succeeds() { - reth_tracing::init_test_tracing(); - - let recipient = address!("0x0000000000000000000000000000000000007777"); - let value = U256::from(100); - - let mut env = arc_execution_e2e::ArcEnvironment::new(); - arc_execution_e2e::ArcSetup::new() - .apply(&mut env) - .await - .expect("setup failed"); - - let mut send = SendTransaction::new("transfer") - .with_to(recipient) - .with_value(value) - .with_gas_limit(26_000); - arc_execution_e2e::Action::execute(&mut send, &mut env) - .await - .expect("send tx"); - - let mut produce = arc_execution_e2e::actions::ProduceBlocks::new(1); - arc_execution_e2e::Action::execute(&mut produce, &mut env) - .await - .expect("produce"); - - let tx_hash = *env.get_tx_hash("transfer").expect("tx hash"); - let receipt = env - .node() - .inner - .provider() - .receipt_by_hash(tx_hash) - .expect("receipt query") - .expect("receipt not found"); - - assert!( - receipt.success, - "Value transfer with 26,000 gas should succeed; got reverted. Gas used: {}", - receipt.cumulative_gas_used, - ); - - // Verify the gas used is reasonable (21,000 intrinsic + log overhead + value transfer cost) - assert!( - receipt.cumulative_gas_used > 21_000, - "Gas used ({}) should exceed intrinsic gas (21,000)", - receipt.cumulative_gas_used, - ); -} - -/// Test #47: Zero value transfer uses baseline gas (no log emission overhead). -/// -/// Isolates a zero-value transfer in its own block and verifies it uses exactly -/// the baseline gas (21,000 intrinsic, no EIP-7708 log overhead). -#[tokio::test] -async fn test_zero_value_transfer_baseline_gas() { - reth_tracing::init_test_tracing(); - - let recipient = address!("0x0000000000000000000000000000000000006666"); - - let mut env = arc_execution_e2e::ArcEnvironment::new(); - arc_execution_e2e::ArcSetup::new() - .apply(&mut env) - .await - .expect("setup failed"); - - let mut send = SendTransaction::new("transfer") - .with_to(recipient) - .with_value(U256::ZERO); - arc_execution_e2e::Action::execute(&mut send, &mut env) - .await - .expect("send tx"); - - let mut produce = arc_execution_e2e::actions::ProduceBlocks::new(1); - arc_execution_e2e::Action::execute(&mut produce, &mut env) - .await - .expect("produce"); - - let tx_hash = *env.get_tx_hash("transfer").expect("tx hash"); - let receipt = env - .node() - .inner - .provider() - .receipt_by_hash(tx_hash) - .expect("receipt query") - .expect("receipt not found"); - - assert!(receipt.success, "Zero-value transfer should succeed"); - - // Under Zero6 (active in localdev), a zero-value call pays intrinsic gas + 1 SLOAD - // for the caller blocklist check (recipient check is skipped when value is zero). - let expected_gas = 21_000 + PRECOMPILE_SLOAD_GAS_COST; - assert_eq!( - receipt.cumulative_gas_used, expected_gas, - "Zero-value transfer gas mismatch: expected {} (21k intrinsic + {} blocklist SLOAD), got {}", - expected_gas, PRECOMPILE_SLOAD_GAS_COST, receipt.cumulative_gas_used, - ); -} diff --git a/crates/execution-e2e/tests/eip7708_hardfork_transition.rs b/crates/execution-e2e/tests/eip7708_hardfork_transition.rs index 6456b6a..2fc4fc2 100644 --- a/crates/execution-e2e/tests/eip7708_hardfork_transition.rs +++ b/crates/execution-e2e/tests/eip7708_hardfork_transition.rs @@ -33,6 +33,7 @@ use arc_execution_e2e::{ ArcSetup, ArcTestBuilder, }; use helpers::constants::{NATIVE_COIN_AUTHORITY_ADDRESS, SYSTEM_ADDRESS, WALLET_FIRST_ADDRESS}; +use reth_chainspec::ForkCondition; /// Test #20: Pre-Zero5 value transfer emits NativeCoinTransferred from NativeCoinAuthority. /// @@ -43,10 +44,10 @@ async fn test_pre_zero5_emits_native_coin_transferred() { reth_tracing::init_test_tracing(); let chain_spec = localdev_with_hardforks(&[ - (ArcHardfork::Zero3, 0), - (ArcHardfork::Zero4, 0), - (ArcHardfork::Zero5, 100), // far in the future - (ArcHardfork::Zero6, 100), + (ArcHardfork::Zero3, ForkCondition::Block(0)), + (ArcHardfork::Zero4, ForkCondition::Block(0)), + (ArcHardfork::Zero5, ForkCondition::Block(100)), // far in the future + (ArcHardfork::Zero6, ForkCondition::Block(100)), ]); let recipient = address!("0x000000000000000000000000000000000000bEEF"); @@ -86,10 +87,10 @@ async fn test_zero5_activation_boundary() { // Zero5 activates at block 3 let chain_spec = localdev_with_hardforks(&[ - (ArcHardfork::Zero3, 0), - (ArcHardfork::Zero4, 0), - (ArcHardfork::Zero5, 3), - (ArcHardfork::Zero6, 100), + (ArcHardfork::Zero3, ForkCondition::Block(0)), + (ArcHardfork::Zero4, ForkCondition::Block(0)), + (ArcHardfork::Zero5, ForkCondition::Block(3)), + (ArcHardfork::Zero6, ForkCondition::Block(100)), ]); let recipient = address!("0x000000000000000000000000000000000000bEEF"); diff --git a/crates/execution-e2e/tests/eip7708_native_transfer.rs b/crates/execution-e2e/tests/eip7708_native_transfer.rs index 06c5e39..1381b05 100644 --- a/crates/execution-e2e/tests/eip7708_native_transfer.rs +++ b/crates/execution-e2e/tests/eip7708_native_transfer.rs @@ -53,7 +53,11 @@ async fn test_call_eoa_with_value_emits_eip7708_log() { .with_value(value), ) .with_action(ProduceBlocks::new(1)) - .with_action(AssertTxIncluded::new("transfer").expect(TxStatus::Success)) + .with_action( + AssertTxIncluded::new("transfer") + .expect(TxStatus::Success) + .expect_gas_used(21_000), + ) .with_action( AssertTxLogs::new("transfer") .expect_log_count(1) @@ -81,7 +85,11 @@ async fn test_call_eoa_zero_value_no_log() { .with_value(U256::ZERO), ) .with_action(ProduceBlocks::new(1)) - .with_action(AssertTxIncluded::new("transfer").expect(TxStatus::Success)) + .with_action( + AssertTxIncluded::new("transfer") + .expect(TxStatus::Success) + .expect_gas_used(21_000), + ) .with_action(AssertTxLogs::new("transfer").expect_no_logs()) .with_action(AssertTxTrace::new("transfer")) .run() @@ -104,7 +112,11 @@ async fn test_call_eoa_self_transfer_no_log() { .with_value(value), ) .with_action(ProduceBlocks::new(1)) - .with_action(AssertTxIncluded::new("transfer").expect(TxStatus::Success)) + .with_action( + AssertTxIncluded::new("transfer") + .expect(TxStatus::Success) + .expect_gas_used(21_000), + ) .with_action(AssertTxLogs::new("transfer").expect_no_logs()) .with_action(AssertTxTrace::new("transfer")) .run() @@ -679,8 +691,16 @@ async fn test_multiple_transfers_in_block() { .with_value(value_b), ) .with_action(ProduceBlocks::new(1)) - .with_action(AssertTxIncluded::new("tx_a").expect(TxStatus::Success)) - .with_action(AssertTxIncluded::new("tx_b").expect(TxStatus::Success)) + .with_action( + AssertTxIncluded::new("tx_a") + .expect(TxStatus::Success) + .expect_gas_used(21_000), + ) + .with_action( + AssertTxIncluded::new("tx_b") + .expect(TxStatus::Success) + .expect_gas_used(21_000), + ) .with_action( AssertTxLogs::new("tx_a") .expect_log_count(1) diff --git a/crates/execution-e2e/tests/eip7708_zero_address.rs b/crates/execution-e2e/tests/eip7708_zero_address.rs index 9bc16c3..54fb913 100644 --- a/crates/execution-e2e/tests/eip7708_zero_address.rs +++ b/crates/execution-e2e/tests/eip7708_zero_address.rs @@ -29,6 +29,7 @@ use arc_execution_e2e::{ chainspec::localdev_with_hardforks, ArcSetup, ArcTestBuilder, }; +use reth_chainspec::ForkCondition; /// Test #24: Send value to Address::ZERO under Zero5 — tx reverts. #[tokio::test] @@ -82,10 +83,10 @@ async fn test_pre_zero5_zero_address_allowed() { reth_tracing::init_test_tracing(); let chain_spec = localdev_with_hardforks(&[ - (ArcHardfork::Zero3, 0), - (ArcHardfork::Zero4, 0), - (ArcHardfork::Zero5, 100), - (ArcHardfork::Zero6, 100), + (ArcHardfork::Zero3, ForkCondition::Block(0)), + (ArcHardfork::Zero4, ForkCondition::Block(0)), + (ArcHardfork::Zero5, ForkCondition::Block(100)), + (ArcHardfork::Zero6, ForkCondition::Block(100)), ]); let value = U256::from(1_000_000); diff --git a/crates/execution-e2e/tests/hardfork_transition.rs b/crates/execution-e2e/tests/hardfork_transition.rs index 5a296fb..576dbd4 100644 --- a/crates/execution-e2e/tests/hardfork_transition.rs +++ b/crates/execution-e2e/tests/hardfork_transition.rs @@ -17,7 +17,7 @@ //! Hardfork transition e2e tests for Arc Chain. //! //! These tests verify that block production works correctly across -//! hardfork boundaries for Zero4, Zero5, and Zero6 hardforks. +//! hardfork boundaries for Zero4, Zero5, Zero6, and Zero7 hardforks. use arc_execution_config::hardforks::ArcHardfork; use arc_execution_e2e::{ @@ -26,7 +26,7 @@ use arc_execution_e2e::{ ArcSetup, ArcTestBuilder, }; use eyre::Result; -use reth_chainspec::EthereumHardfork; +use reth_chainspec::{EthereumHardfork, ForkCondition}; #[tokio::test] async fn test_hardfork_active_at_genesis() -> Result<()> { @@ -38,6 +38,7 @@ async fn test_hardfork_active_at_genesis() -> Result<()> { .with_action(AssertHardfork::is_active(ArcHardfork::Zero4)) .with_action(AssertHardfork::is_active(ArcHardfork::Zero5)) .with_action(AssertHardfork::is_active(ArcHardfork::Zero6)) + .with_action(AssertHardfork::is_active(ArcHardfork::Zero7)) .run() .await } @@ -48,10 +49,11 @@ async fn test_sequential_hardfork_transitions() -> Result<()> { reth_tracing::init_test_tracing(); let chain_spec = localdev_with_hardforks(&[ - (ArcHardfork::Zero3, 2), - (ArcHardfork::Zero4, 4), - (ArcHardfork::Zero5, 6), - (ArcHardfork::Zero6, 8), + (ArcHardfork::Zero3, ForkCondition::Block(2)), + (ArcHardfork::Zero4, ForkCondition::Block(4)), + (ArcHardfork::Zero5, ForkCondition::Block(6)), + (ArcHardfork::Zero6, ForkCondition::Block(8)), + (ArcHardfork::Zero7, ForkCondition::Block(10)), ]); ArcTestBuilder::new() @@ -61,6 +63,7 @@ async fn test_sequential_hardfork_transitions() -> Result<()> { .with_action(AssertHardfork::is_not_active(ArcHardfork::Zero4)) .with_action(AssertHardfork::is_not_active(ArcHardfork::Zero5)) .with_action(AssertHardfork::is_not_active(ArcHardfork::Zero6)) + .with_action(AssertHardfork::is_not_active(ArcHardfork::Zero7)) // Produce block 1-2 - Zero3 activates .with_action(ProduceBlocks::new(2)) .with_action(AssertBlockNumber::new(2)) @@ -77,6 +80,10 @@ async fn test_sequential_hardfork_transitions() -> Result<()> { .with_action(ProduceBlocks::new(2)) .with_action(AssertBlockNumber::new(8)) .with_action(AssertHardfork::is_active(ArcHardfork::Zero6)) + // Produce block 9-10 - Zero7 activates + .with_action(ProduceBlocks::new(2)) + .with_action(AssertBlockNumber::new(10)) + .with_action(AssertHardfork::is_active(ArcHardfork::Zero7)) .run() .await } diff --git a/crates/execution-e2e/tests/native_transfer_balance.rs b/crates/execution-e2e/tests/native_transfer_balance.rs index 1f656ee..2b4a9b4 100644 --- a/crates/execution-e2e/tests/native_transfer_balance.rs +++ b/crates/execution-e2e/tests/native_transfer_balance.rs @@ -40,9 +40,9 @@ fn transfer_value() -> U256 { U256::from(100u64) * U256::from(10u64).pow(U256::from(18u64)) } -/// Max gas cost per tx: gas_limit(26_000) * max_fee_per_gas(1000e9). +/// Max gas cost per tx: gas_limit(21_000) * max_fee_per_gas(1000e9). fn max_gas_cost() -> U256 { - U256::from(26_000u64) * U256::from(1_000_000_000_000u64) + U256::from(21_000u64) * U256::from(1_000_000_000_000u64) } /// Recipient balance goes from 0 to the transferred value. diff --git a/crates/execution-e2e/tests/pq_precompile.rs b/crates/execution-e2e/tests/pq_precompile.rs index 51ca49a..a27121e 100644 --- a/crates/execution-e2e/tests/pq_precompile.rs +++ b/crates/execution-e2e/tests/pq_precompile.rs @@ -60,7 +60,7 @@ enum PqExpected { #[derive(Clone, Debug)] struct PqVerifyVector { call_label: &'static str, - msg: Bytes, + message: Bytes, vk: Bytes, sig: Bytes, expected: PqExpected, @@ -79,7 +79,7 @@ fn build_vectors() -> Vec<(&'static str, PqVerifyVector)> { "valid_signature", PqVerifyVector { call_label: "pq_valid_sig", - msg: Bytes::copy_from_slice(MSG_HELLO_WORLD), + message: Bytes::copy_from_slice(MSG_HELLO_WORLD), vk: vk.clone(), sig: sig_hello.clone(), expected: PqExpected::ReturnTrue, @@ -89,7 +89,7 @@ fn build_vectors() -> Vec<(&'static str, PqVerifyVector)> { "valid_empty_message", PqVerifyVector { call_label: "pq_valid_empty_msg", - msg: Bytes::copy_from_slice(MSG_EMPTY), + message: Bytes::copy_from_slice(MSG_EMPTY), vk: vk.clone(), sig: sig_empty, expected: PqExpected::ReturnTrue, @@ -99,7 +99,7 @@ fn build_vectors() -> Vec<(&'static str, PqVerifyVector)> { "invalid_wrong_message", PqVerifyVector { call_label: "pq_invalid_sig", - msg: Bytes::copy_from_slice(MSG_GOODBYE_WORLD), + message: Bytes::copy_from_slice(MSG_GOODBYE_WORLD), vk: vk.clone(), sig: sig_hello.clone(), expected: PqExpected::ReturnFalse, @@ -109,7 +109,7 @@ fn build_vectors() -> Vec<(&'static str, PqVerifyVector)> { "wrong_verifying_key_value", PqVerifyVector { call_label: "pq_wrong_vk_value", - msg: Bytes::copy_from_slice(MSG_HELLO_WORLD), + message: Bytes::copy_from_slice(MSG_HELLO_WORLD), vk: wrong_vk, sig: sig_hello.clone(), expected: PqExpected::ReturnFalse, @@ -119,7 +119,7 @@ fn build_vectors() -> Vec<(&'static str, PqVerifyVector)> { "bad_verifying_key_len", PqVerifyVector { call_label: "pq_bad_vk_len", - msg: Bytes::copy_from_slice(MSG_HELLO_WORLD), + message: Bytes::copy_from_slice(MSG_HELLO_WORLD), vk: Bytes::copy_from_slice(&MALFORMED_100), sig: sig_hello.clone(), expected: PqExpected::Revert, @@ -129,7 +129,7 @@ fn build_vectors() -> Vec<(&'static str, PqVerifyVector)> { "bad_signature_len", PqVerifyVector { call_label: "pq_bad_sig_len", - msg: Bytes::copy_from_slice(MSG_HELLO_WORLD), + message: Bytes::copy_from_slice(MSG_HELLO_WORLD), vk, sig: Bytes::copy_from_slice(&MALFORMED_100), expected: PqExpected::Revert, @@ -156,7 +156,7 @@ async fn test_pq_precompile(#[case] index: usize) -> Result<()> { let data: Bytes = IPQ::verifySlhDsaSha2128sCall { vk: vector.vk.clone(), - msg: vector.msg.clone(), + message: vector.message.clone(), sig: vector.sig.clone(), } .abi_encode() diff --git a/crates/execution-e2e/tests/transaction.rs b/crates/execution-e2e/tests/transaction.rs index 2892568..155894f 100644 --- a/crates/execution-e2e/tests/transaction.rs +++ b/crates/execution-e2e/tests/transaction.rs @@ -16,7 +16,7 @@ //! Transaction sending e2e tests for Arc Chain. -use alloy_primitives::{address, bytes}; +use alloy_primitives::{address, bytes, U256}; use arc_execution_e2e::{ actions::{AssertTxIncluded, ProduceBlocks, SendTransaction, TxStatus}, ArcSetup, ArcTestBuilder, @@ -64,6 +64,7 @@ async fn test_reverted_transaction() -> Result<()> { .with_action( SendTransaction::new("tx1") .with_to(address!("0x3600000000000000000000000000000000000000")) + .with_value(U256::ZERO) // Value must be 0 — FiatTokenProxy (0x3600…0000) is pre-blocklisted in NativeCoinControl .with_data(bytes!("0x1234abcd")) .with_gas_limit(100_000), ) diff --git a/crates/execution-payload/src/payload.rs b/crates/execution-payload/src/payload.rs index b276575..ebd6bb3 100644 --- a/crates/execution-payload/src/payload.rs +++ b/crates/execution-payload/src/payload.rs @@ -21,7 +21,6 @@ //! Panics during individual transaction execution are caught inline in //! `arc_ethereum_payload` and converted to `UnprocessableTransactionError`. -use alloy_consensus::Transaction; use alloy_primitives::U256; use alloy_primitives::{hex, TxHash}; use alloy_rlp::Encodable; @@ -475,6 +474,21 @@ where } } +/// Proposer revenue contributed by a single transaction on Arc. +/// +/// On Arc, Proposer revenue equals `effective_gas_price * gas_used`, not +/// `effective_tip_per_gas * gas_used` (the upstream-reth formula, which +/// assumes base fees are burned). +fn proposer_revenue(tx: &T, gas_used: u64, base_fee: u64) -> U256 { + let effective_gas_price = tx.effective_gas_price(Some(base_fee)); + // u128 * u64 fits in U256 (max 192 bits); + // bounded by block_gas_limit * max_fee_per_gas. + #[allow(clippy::arithmetic_side_effects)] + { + U256::from(effective_gas_price) * U256::from(gas_used) + } +} + /// Constructs an transaction payload using the best transactions from the pool. /// It follows the upstream Ethereum payload building logic with a Arc-specific deadline for the main loop. /// @@ -657,14 +671,9 @@ where block_transactions_rlp_length = block_transactions_rlp_length.saturating_add(tx_rlp_len); - // update and add to total fees - let miner_fee = tx - .effective_tip_per_gas(base_fee) - .expect("fee is always valid; execution succeeded"); - // u128 * u64 fits in U256 (max 192 bits); total_fees is bounded by block gas limit * max fee. #[allow(clippy::arithmetic_side_effects)] { - total_fees += U256::from(miner_fee) * U256::from(gas_used); + total_fees += proposer_revenue(tx.inner(), gas_used, base_fee); } cumulative_gas_used = cumulative_gas_used .checked_add(gas_used) @@ -1118,4 +1127,105 @@ mod tests { ); let _ = builder.build_empty_payload(empty_payload_config()); } + + // --- Regression tests for `proposer_revenue` --- + // + // Arc redirects base fees to the beneficiary instead of burning them (see + // `ArcEvmHandler::reward_beneficiary`), so proposer revenue must be + // computed from `effective_gas_price`, not `effective_tip_per_gas`. These + // tests pin that formula against a reth bump accidentally reintroducing + // the upstream tip-only pattern. + #[test] + fn proposer_revenue_dynamic_fee_tx_includes_base_fee_plus_tip() { + use alloy_consensus::TxEip1559; + use alloy_primitives::{Address, TxKind}; + + let base_fee: u64 = 100; + let priority_fee: u128 = 50; + let max_fee: u128 = 200; + let gas_used: u64 = 21_000; + + let tx = TxEip1559 { + chain_id: 1, + nonce: 0, + gas_limit: 100_000, + max_fee_per_gas: max_fee, + max_priority_fee_per_gas: priority_fee, + to: TxKind::Call(Address::ZERO), + value: U256::ZERO, + access_list: Default::default(), + input: Default::default(), + }; + + // effective_gas_price = min(max_fee, base_fee + priority_fee) = 150 + let expected = U256::from(150u128) * U256::from(gas_used); + assert_eq!(proposer_revenue(&tx, gas_used, base_fee), expected); + + // Regression guard: full fee is strictly more than tip-only. If a reth + // bump reintroduces `effective_tip_per_gas`, this assert fails. + let tip_only = U256::from(priority_fee) * U256::from(gas_used); + assert!(proposer_revenue(&tx, gas_used, base_fee) > tip_only); + + // Zero-tip trip-wire: proposer revenue is `base_fee * gas_used`, not 0. + // `effective_tip_per_gas` would return 0 here. + let zero_tip_tx = TxEip1559 { + max_priority_fee_per_gas: 0, + ..tx + }; + assert_eq!( + proposer_revenue(&zero_tip_tx, gas_used, base_fee), + U256::from(base_fee) * U256::from(gas_used), + ); + } + + #[test] + fn proposer_revenue_dynamic_fee_tx_capped_at_max_fee() { + use alloy_consensus::TxEip1559; + use alloy_primitives::{Address, TxKind}; + + let base_fee: u64 = 100; + let priority_fee: u128 = 200; // base + tip would exceed max + let max_fee: u128 = 250; + let gas_used: u64 = 21_000; + + let tx = TxEip1559 { + chain_id: 1, + nonce: 0, + gas_limit: 100_000, + max_fee_per_gas: max_fee, + max_priority_fee_per_gas: priority_fee, + to: TxKind::Call(Address::ZERO), + value: U256::ZERO, + access_list: Default::default(), + input: Default::default(), + }; + + // effective_gas_price = min(250, 100 + 200) = 250 + let expected = U256::from(max_fee) * U256::from(gas_used); + assert_eq!(proposer_revenue(&tx, gas_used, base_fee), expected); + } + + #[test] + fn proposer_revenue_legacy_equals_gas_price_times_gas() { + use alloy_consensus::TxLegacy; + use alloy_primitives::{Address, TxKind}; + + let gas_price: u128 = 75; + let gas_used: u64 = 21_000; + // Base fee is irrelevant for legacy txs. + let base_fee: u64 = 100; + + let tx = TxLegacy { + chain_id: Some(1), + nonce: 0, + gas_price, + gas_limit: 100_000, + to: TxKind::Call(Address::ZERO), + value: U256::ZERO, + input: Default::default(), + }; + + let expected = U256::from(gas_price) * U256::from(gas_used); + assert_eq!(proposer_revenue(&tx, gas_used, base_fee), expected); + } } diff --git a/crates/execution-txpool/src/pool.rs b/crates/execution-txpool/src/pool.rs index 606fd0f..15df698 100644 --- a/crates/execution-txpool/src/pool.rs +++ b/crates/execution-txpool/src/pool.rs @@ -125,7 +125,7 @@ mod tests { /// Helper function to create a test transaction fn create_test_transaction() -> MockTransaction { MockTransaction::legacy() - .with_gas_limit(26_000) + .with_gas_limit(21_000) .with_gas_price(1_000_000_000) // 1 gwei .with_value(U256::from(1000)) } diff --git a/crates/execution-txpool/src/validator.rs b/crates/execution-txpool/src/validator.rs index 931b444..d03bdad 100644 --- a/crates/execution-txpool/src/validator.rs +++ b/crates/execution-txpool/src/validator.rs @@ -452,7 +452,7 @@ mod tests { BlocklistTestCase { name: "valid_sender_and_recipient", tx: MockTransaction::legacy() - .with_gas_limit(26_000) + .with_gas_limit(21_000) .with_gas_price(1_000_000_000) .with_value(U256::from(1000)), sender_blocklisted: false, @@ -462,7 +462,7 @@ mod tests { BlocklistTestCase { name: "blocklisted_sender", tx: MockTransaction::legacy() - .with_gas_limit(26_000) + .with_gas_limit(21_000) .with_gas_price(1_000_000_000) .with_value(U256::from(1000)), sender_blocklisted: true, @@ -472,7 +472,7 @@ mod tests { BlocklistTestCase { name: "blocklisted_to_with_value", tx: MockTransaction::legacy() - .with_gas_limit(26_000) + .with_gas_limit(21_000) .with_gas_price(1_000_000_000) .with_value(U256::from(1000)), sender_blocklisted: false, @@ -482,7 +482,7 @@ mod tests { BlocklistTestCase { name: "blocklisted_to_with_zero_value", tx: MockTransaction::legacy() - .with_gas_limit(26_000) + .with_gas_limit(21_000) .with_gas_price(1_000_000_000) .with_value(U256::ZERO), sender_blocklisted: false, @@ -544,7 +544,7 @@ mod tests { async fn invalid_tx_list_disabled_allows_tx() { // Build a validator with invalid_tx_list None and ensure tx is not rejected for invalid_tx reason. let tx = MockTransaction::legacy() - .with_gas_limit(26_000) + .with_gas_limit(21_000) .with_gas_price(1_000_000_000) .with_value(U256::from(777)); let provider = MockEthProvider::default(); @@ -575,7 +575,7 @@ mod tests { #[tokio::test] async fn invalid_tx_hash_is_rejected() { let tx = MockTransaction::legacy() - .with_gas_limit(26_000) + .with_gas_limit(21_000) .with_gas_price(1_000_000_000) .with_value(U256::from(1234)); @@ -619,7 +619,7 @@ mod tests { async fn addresses_denylist_config_none_accepts_tx() { // When addresses_denylist_config is None, no address denylist check; tx is accepted. let tx = MockTransaction::legacy() - .with_gas_limit(26_000) + .with_gas_limit(21_000) .with_gas_price(1_000_000_000) .with_value(U256::from(100)); let provider = MockEthProvider::default(); @@ -647,7 +647,7 @@ mod tests { async fn addresses_denylist_enabled_sender_excluded_accepts_tx() { // When denylist is enabled but sender is in address exclusions, tx is accepted. let tx = MockTransaction::legacy() - .with_gas_limit(26_000) + .with_gas_limit(21_000) .with_gas_price(1_000_000_000) .with_value(U256::from(200)); let sender = tx.sender(); @@ -683,7 +683,7 @@ mod tests { async fn addresses_denylist_denylisted_address_rejected() { // When denylist is enabled and sender or recipient is denylisted in contract storage, tx is rejected. let tx = MockTransaction::legacy() - .with_gas_limit(26_000) + .with_gas_limit(21_000) .with_gas_price(1_000_000_000) .with_value(U256::from(300)); let contract = Address::from([0x36u8; 20]); diff --git a/crates/execution-validation/src/consensus.rs b/crates/execution-validation/src/consensus.rs index 3719140..9bafc95 100644 --- a/crates/execution-validation/src/consensus.rs +++ b/crates/execution-validation/src/consensus.rs @@ -29,7 +29,7 @@ use std::sync::Arc; use arc_execution_config::chainspec::{BaseFeeConfigProvider, BlockGasLimitProvider}; use arc_execution_config::gas_fee::decode_base_fee_from_bytes; -use arc_execution_config::hardforks::ArcHardfork; +use arc_execution_config::hardforks::{is_arc_fork_active, ArcHardfork}; /// Arc Network custom consensus implementation #[derive(Debug, Clone)] @@ -146,7 +146,12 @@ where let Some(expected_base_fee) = decode_base_fee_from_bytes(parent.extra_data()) else { // Post-Zero5 this branch should be unreachable: `arc_validate_extra_data_format` // enforces that extra_data is exactly 8 bytes. - if chain_spec.is_fork_active_at_block(ArcHardfork::Zero5, parent.number()) { + if is_arc_fork_active( + chain_spec, + ArcHardfork::Zero5, + parent.number(), + parent.timestamp(), + ) { tracing::error!( parent_number = parent.number(), extra_data_len = parent.extra_data().len(), @@ -181,7 +186,12 @@ fn arc_validate_extra_data_format( header: &H, chain_spec: &CS, ) -> Result<(), ConsensusError> { - if !chain_spec.is_fork_active_at_block(ArcHardfork::Zero5, header.number()) { + if !is_arc_fork_active( + chain_spec, + ArcHardfork::Zero5, + header.number(), + header.timestamp(), + ) { return Ok(()); } @@ -212,7 +222,12 @@ fn arc_validate_header_base_fee< validate_header_base_fee(header, chain_spec)?; // Post-Zero5: enforce absolute bounds. - if !chain_spec.is_fork_active_at_block(ArcHardfork::Zero5, header.number()) { + if !is_arc_fork_active( + chain_spec, + ArcHardfork::Zero5, + header.number(), + header.timestamp(), + ) { return Ok(()); } @@ -241,7 +256,12 @@ fn arc_validate_gas_limit_bounds Result<(), ConsensusError> { - if !chain_spec.is_fork_active_at_block(ArcHardfork::Zero5, header.number()) { + if !is_arc_fork_active( + chain_spec, + ArcHardfork::Zero5, + header.number(), + header.timestamp(), + ) { return Ok(()); } @@ -269,7 +289,12 @@ fn arc_validate_beneficiary_nonzero( header: &H, chain_spec: &CS, ) -> Result<(), ConsensusError> { - if !chain_spec.is_fork_active_at_block(ArcHardfork::Zero6, header.number()) { + if !is_arc_fork_active( + chain_spec, + ArcHardfork::Zero6, + header.number(), + header.timestamp(), + ) { return Ok(()); } @@ -931,7 +956,7 @@ mod tests { } // Pre-Zero5: bounds check is skipped entirely. - let pre_zero5 = localdev_with_hardforks(&[(ArcHardfork::Zero4, 0)]); + let pre_zero5 = localdev_with_hardforks(&[(ArcHardfork::Zero4, ForkCondition::Block(0))]); let header = Header { number: 1, base_fee_per_gas: Some(config.absolute_min_base_fee - 1), @@ -975,7 +1000,7 @@ mod tests { )); // Pre-Zero5: length check is skipped entirely. - let pre_zero5 = localdev_with_hardforks(&[(ArcHardfork::Zero4, 0)]); + let pre_zero5 = localdev_with_hardforks(&[(ArcHardfork::Zero4, ForkCondition::Block(0))]); let header = Header { number: 1, extra_data: Bytes::from([0u8; 7].as_slice()), diff --git a/crates/malachite-app/src/app.rs b/crates/malachite-app/src/app.rs index 58b4bb2..a8193af 100644 --- a/crates/malachite-app/src/app.rs +++ b/crates/malachite-app/src/app.rs @@ -183,7 +183,16 @@ async fn handle_consensus( } // Notification that consensus has decided a value. - AppMsg::Decided { certificate, .. } => { + // + // The reply acknowledges that the certificate has been durably stored so the sync + // actor can advertise the new tip height. Without it, peers never learn we advanced + // beyond startup and late joiners cannot value-sync from us. `decided::handle` + // consumes the channel at the durability point. + AppMsg::Decided { + certificate, + extensions: _, + reply, + } => { let _guard = state.metrics.start_msg_process_timer("Decided"); let height = certificate.height; @@ -193,7 +202,7 @@ async fn handle_consensus( info!(%height, %round, %value_id, %signatures, "🎉 Consensus has decided on value"); - decided::handle(state, engine, certificate).await?; + decided::handle(state, engine, certificate, reply).await?; } // Notification that a height has been finalized. diff --git a/crates/malachite-app/src/handlers/decided.rs b/crates/malachite-app/src/handlers/decided.rs index 73fbe15..e1903b4 100644 --- a/crates/malachite-app/src/handlers/decided.rs +++ b/crates/malachite-app/src/handlers/decided.rs @@ -17,6 +17,7 @@ use eyre::{eyre, Context}; use tracing::{debug, error, info, warn}; +use malachitebft_app_channel::Reply; use malachitebft_core_types::CommitCertificate; use arc_consensus_types::{ArcContext, Height}; @@ -40,6 +41,10 @@ use crate::utils::sync_state::{sync_state, SyncState}; /// /// The `Finalized` message that will follow this message sends the appropriate `Next` message to /// consensus to start the next height, or in case of failure, restart the current height. +/// +/// The `commit_ack` channel is consumed once the certificate is durably stored, so the sync actor +/// can advertise the new tip height. If the commit fails before the store completes (block lookup +/// or storage error), the channel is dropped and no acknowledgement is sent. #[tracing::instrument( name = "decided", skip_all, @@ -52,6 +57,7 @@ pub async fn handle( state: &mut State, engine: &Engine, certificate: CommitCertificate, + commit_ack: Reply<()>, ) -> eyre::Result<()> { let decided_height = certificate.height; let decided_value_id = certificate.value_id; @@ -71,6 +77,7 @@ pub async fn handle( certificate, stats, metrics, + commit_ack, ) .await; @@ -127,6 +134,11 @@ async fn store_proposal_monitor_on_decision( /// Commits a value with the given certificate, finalizes the block, /// updates internal state and moves to the next height. +/// +/// The `commit_ack` channel is consumed inside `commit` once the certificate has been durably +/// stored. If we error out before reaching the store (block lookup), the channel is dropped here +/// without firing. +#[allow(clippy::too_many_arguments)] async fn decide( block_finalizer: impl BlockFinalizer, undecided_blocks: impl UndecidedBlocksRepository, @@ -135,6 +147,7 @@ async fn decide( certificate: CommitCertificate, stats: &Stats, metrics: &AppMetrics, + commit_ack: Reply<()>, ) -> eyre::Result { let height = certificate.height; let round = certificate.round; @@ -173,6 +186,7 @@ async fn decide( pruning_service, certificate, &block, + commit_ack, ) .await .wrap_err_with(|| { @@ -191,13 +205,18 @@ async fn decide( Ok(new_latest_block) } -/// Commits a value with the given certificate, cleanup stale consensus data and prune historical data +/// Commits a value with the given certificate, cleanup stale consensus data and prune historical data. +/// +/// The `commit_ack` channel is consumed immediately after the certificate is durably stored — +/// before any post-store work (cleanup, finalize, pruning) — so the sync actor learns the new tip +/// even if a later step fails. If the store itself fails, the channel is dropped without firing. async fn commit( block_finalizer: impl BlockFinalizer, decided_blocks: impl DecidedBlocksRepository, pruning_service: impl PruningService, certificate: CommitCertificate, block: &ConsensusBlock, + commit_ack: Reply<()>, ) -> eyre::Result { let certificate_height = certificate.height; let certificate_round = certificate.round; @@ -210,6 +229,13 @@ async fn commit( format!("Failed to store decided block at height={certificate_height}, round={certificate_round}, value_id={value_id}") })?; + if commit_ack.send(()).is_err() { + error!( + %certificate_height, + "Decided: Failed to send commit acknowledgement (sync actor may not be notified)" + ); + } + // Clean up stale consensus data (undecided blocks and pending proposals up to the certificate height) if let Err(e) = pruning_service .clean_stale_consensus_data(certificate_height) @@ -401,6 +427,14 @@ mod tests { Stats::default() } + /// A dummy commit-ack channel for tests that don't assert ack delivery. + /// The receiver is dropped, so a successful `commit_ack.send(())` will + /// return `Err`, which `commit` logs and ignores. + fn dummy_commit_ack() -> Reply<()> { + let (tx, _rx) = tokio::sync::oneshot::channel(); + tx + } + // Tests for decide() function // Successful decision with valid block found @@ -461,6 +495,7 @@ mod tests { certificate, &stats, &metrics, + dummy_commit_ack(), ) .await; @@ -497,6 +532,7 @@ mod tests { certificate, &stats, &metrics, + dummy_commit_ack(), ) .await; @@ -532,6 +568,7 @@ mod tests { certificate, &stats, &metrics, + dummy_commit_ack(), ) .await; @@ -573,6 +610,7 @@ mod tests { certificate, &stats, &metrics, + dummy_commit_ack(), ) .await; @@ -631,6 +669,7 @@ mod tests { pruning_service, certificate, &consensus_block, + dummy_commit_ack(), ) .await; @@ -663,6 +702,7 @@ mod tests { pruning_service, certificate, &consensus_block, + dummy_commit_ack(), ) .await; @@ -699,6 +739,7 @@ mod tests { pruning_service, certificate, &consensus_block, + dummy_commit_ack(), ) .await; @@ -748,6 +789,7 @@ mod tests { pruning_service, certificate, &consensus_block, + dummy_commit_ack(), ) .await; @@ -755,4 +797,87 @@ mod tests { let block = result.unwrap(); assert_eq!(block.block_number, height); } + + /// commit_ack must fire after the certificate is durably stored, even if a later + /// post-store step (finalize) fails — sync should still learn the new tip. + #[tokio::test] + async fn test_commit_acks_when_store_succeeds_but_finalize_fails() { + let height = 5u64; + let round = 2u32; + let timestamp = 1000u64; + let block_hash = B256::repeat_byte((height % 256) as u8); + let certificate = test_commit_certificate(height, round, block_hash); + let consensus_block = test_consensus_block(height, round, timestamp); + + let mut decided_blocks = MockDecidedBlocksRepository::new(); + decided_blocks.expect_store().return_once(|_, _, _| Ok(())); + + let mut block_finalizer = MockBlockFinalizer::new(); + block_finalizer + .expect_finalize_decided_block() + .return_once(|_, _| Err(eyre!("Finalization failed"))); + + let mut pruning_service = MockPruningService::new(); + pruning_service + .expect_clean_stale_consensus_data() + .return_once(|_| Ok(())); + + let (ack_tx, ack_rx) = tokio::sync::oneshot::channel(); + + let result = commit( + block_finalizer, + decided_blocks, + pruning_service, + certificate, + &consensus_block, + ack_tx, + ) + .await; + + assert!(result.is_err(), "expected finalize failure"); + assert_eq!( + ack_rx.await, + Ok(()), + "ack must be sent when store succeeds, even if finalize later fails" + ); + } + + /// commit_ack must NOT fire if the durable store fails — otherwise sync + /// would advertise a height we cannot serve. + #[tokio::test] + async fn test_commit_does_not_ack_when_store_fails() { + let height = 5u64; + let round = 2u32; + let timestamp = 1000u64; + let block_hash = B256::repeat_byte((height % 256) as u8); + let certificate = test_commit_certificate(height, round, block_hash); + let consensus_block = test_consensus_block(height, round, timestamp); + + let mut decided_blocks = MockDecidedBlocksRepository::new(); + decided_blocks + .expect_store() + .return_once(|_, _, _| Err(std::io::Error::other("Store failed"))); + + let block_finalizer = MockBlockFinalizer::new(); + let pruning_service = MockPruningService::new(); + + let (ack_tx, ack_rx) = tokio::sync::oneshot::channel(); + + let result = commit( + block_finalizer, + decided_blocks, + pruning_service, + certificate, + &consensus_block, + ack_tx, + ) + .await; + + assert!(result.is_err(), "expected store failure"); + // Sender dropped without sending → recv resolves to Err(RecvError) + assert!( + ack_rx.await.is_err(), + "ack channel must be dropped (not sent) when store fails" + ); + } } diff --git a/crates/malachite-app/src/handlers/get_value.rs b/crates/malachite-app/src/handlers/get_value.rs index 58d3874..8f8569f 100644 --- a/crates/malachite-app/src/handlers/get_value.rs +++ b/crates/malachite-app/src/handlers/get_value.rs @@ -235,7 +235,7 @@ async fn build_and_validate_block( .await?; let validator = EnginePayloadValidator::new(engine, metrics); - let validity = validate_consensus_block(&validator, &block, store) + let validity = validate_consensus_block(&validator, &block, store, metrics) .await .wrap_err_with(|| { format!( diff --git a/crates/malachite-app/src/handlers/mod.rs b/crates/malachite-app/src/handlers/mod.rs index 8115e6e..2b2215a 100644 --- a/crates/malachite-app/src/handlers/mod.rs +++ b/crates/malachite-app/src/handlers/mod.rs @@ -29,3 +29,6 @@ pub mod process_synced_value; pub mod received_proposal_part; pub mod restream_proposal; pub mod started_round; + +#[cfg(test)] +mod test_utils; diff --git a/crates/malachite-app/src/handlers/process_synced_value.rs b/crates/malachite-app/src/handlers/process_synced_value.rs index 38fb206..f56125d 100644 --- a/crates/malachite-app/src/handlers/process_synced_value.rs +++ b/crates/malachite-app/src/handlers/process_synced_value.rs @@ -19,7 +19,7 @@ use std::time::Duration; use bytes::Bytes; use eyre::Context; use ssz::Decode; -use tracing::error; +use tracing::{error, warn}; use malachitebft_app_channel::app::types::core::Round; use malachitebft_app_channel::app::types::ProposedValue; @@ -33,6 +33,7 @@ use arc_eth_engine::persistence_meter::PersistenceMeter; use malachitebft_app_channel::app::types::core::Validity; use crate::block::ConsensusBlock; +use crate::metrics::{AppMetrics, InvalidPayloadSource}; use crate::payload::{validate_consensus_block, EnginePayloadValidator, PayloadValidator}; use crate::state::State; use crate::store::repositories::{InvalidPayloadsRepository, UndecidedBlocksRepository}; @@ -63,6 +64,7 @@ pub async fn handle( state.store(), state.store(), state.persistence_meter(), + state.metrics(), height, round, proposer, @@ -98,7 +100,7 @@ pub async fn handle( /// /// Decodes the raw bytes into an [`ExecutionPayloadV3`], validates it via /// [`validate_consensus_block`], and stores the resulting [`ConsensusBlock`] as an -/// undecided block. If the engine rejects the payload, an [`InvalidPayload`] +/// undecided block. If the engine rejects the payload, an /// [`InvalidPayload`](crate::invalid_payloads::InvalidPayload) record is persisted /// through `store` and the block is kept with [`Validity::Invalid`] so that /// consensus can proceed with the correct validity information. @@ -111,6 +113,7 @@ async fn on_process_synced_value( undecided_blocks_repo: impl UndecidedBlocksRepository, invalid_payloads_repo: impl InvalidPayloadsRepository, persistence_meter: impl PersistenceMeter, + metrics: &AppMetrics, height: Height, round: Round, proposer: Address, @@ -119,6 +122,12 @@ async fn on_process_synced_value( let payload = match ExecutionPayloadV3::from_ssz_bytes(&value_bytes) { Ok(payload) => payload, Err(e) => { + warn!( + %height, %round, %proposer, + "Failed to decode synced value into an execution payload: {e:?}", + ); + metrics.inc_invalid_payloads_count(InvalidPayloadSource::SyncDecode); + let invalid = InvalidPayload::new_without_payload(height, round, proposer, &format!("{e:?}")); @@ -128,11 +137,6 @@ async fn on_process_synced_value( ) })?; - error!( - %height, %round, %proposer, - "Failed to decode synced value into an execution payload: {e:?}", - ); - return Ok(None); } }; @@ -151,7 +155,7 @@ async fn on_process_synced_value( }; let validity = validate_consensus_block( - &engine, &block, &invalid_payloads_repo, + &engine, &block, &invalid_payloads_repo, metrics, ) .await .wrap_err_with(|| { @@ -169,6 +173,27 @@ async fn on_process_synced_value( error!(%height, %round, %proposer, %block_hash, "❌ Received invalid payload via sync"); } + // If a undecided block for the sync value height round and hash exists then skip `wait_for_persisted_block` + // so consensus path is not blocked on EL persistence. + if let Some(existing) = undecided_blocks_repo + .get_by_round_and_hash(height, round, block_hash) + .await + .wrap_err_with(|| { + format!( + "Failed to query undecided blocks repo for dedup at \ + height={height}, round={round}, block_hash={block_hash}" + ) + })? + { + debug_assert_eq!( + existing.validity, validity, + "dedup hit at height={height}, round={round}, block_hash={block_hash}: \ + existing.validity ({:?}) != freshly-computed validity ({validity:?})", + existing.validity, + ); + return Ok(Some(ProposedValue::from(&existing))); + } + let proposal = ProposedValue::from(&block); undecided_blocks_repo.store_undecided_block(block).await.wrap_err_with(|| { @@ -204,6 +229,7 @@ mod tests { }; use arbitrary::{Arbitrary, Unstructured}; + use arc_consensus_types::Value; use arc_eth_engine::mocks::MockPersistenceMeter; use arc_eth_engine::persistence_meter::NoopPersistenceMeter; use bytes::Bytes; @@ -212,6 +238,15 @@ mod tests { use ssz::Encode; use std::io; + /// Sets up the dedup query (`get_by_round_and_hash`) on an + /// `UndecidedBlocksRepository` mock to return `None` so the main path + /// flows through to `store_undecided_block`. Use in tests that are not + /// specifically exercising the dedup race. + fn expect_no_undecided_dedup_hit(mock: &mut MockUndecidedBlocksRepository) { + mock.expect_get_by_round_and_hash() + .returning(|_, _, _| Ok(None)); + } + async fn test_on_process_synced_value_validity( result: PayloadValidationResult, expected: Validity, @@ -231,6 +266,7 @@ mod tests { .returning(move |_| Ok(result.clone())); let mut undecided = MockUndecidedBlocksRepository::new(); + expect_no_undecided_dedup_hit(&mut undecided); undecided .expect_store_undecided_block() .withf(move |block| { @@ -246,11 +282,13 @@ mod tests { .times(if is_invalid { 1 } else { 0 }) .returning(|_| Ok(())); + let metrics = AppMetrics::default(); let Some(proposal) = on_process_synced_value( engine, undecided, invalid, NoopPersistenceMeter, + &metrics, height, round, proposer, @@ -262,6 +300,8 @@ mod tests { }; assert_eq!(proposal.validity, expected); + let expected_count = if expected.is_valid() { 0 } else { 1 }; + assert_eq!(metrics.get_invalid_payloads_count(), expected_count); } #[tokio::test] @@ -298,11 +338,13 @@ mod tests { let proposer = Address::new([0u8; 20]); let value_bytes = Bytes::from(vec![0u8; 10]); + let metrics = AppMetrics::default(); let proposal = on_process_synced_value( engine, undecided, invalid, NoopPersistenceMeter, + &metrics, height, round, proposer, @@ -312,6 +354,7 @@ mod tests { .expect("Failed to process synced value"); assert!(proposal.is_none()); + assert_eq!(metrics.get_invalid_payloads_count(), 1); } // These two tests cover error paths in `on_process_synced_value` that were @@ -339,11 +382,13 @@ mod tests { let mut invalid = MockInvalidPayloadsRepository::new(); invalid.expect_append().times(0); + let metrics = AppMetrics::default(); let result = on_process_synced_value( engine, undecided, invalid, NoopPersistenceMeter, + &metrics, height, round, proposer, @@ -352,6 +397,7 @@ mod tests { .await; assert!(result.is_err()); + assert_eq!(metrics.get_invalid_payloads_count(), 0); } #[tokio::test] @@ -373,11 +419,13 @@ mod tests { .times(1) .returning(|_| Err(io::Error::other("Simulated invalid payload store error"))); + let metrics = AppMetrics::default(); let result = on_process_synced_value( engine, undecided, invalid, NoopPersistenceMeter, + &metrics, height, round, proposer, @@ -386,6 +434,7 @@ mod tests { .await; assert!(result.is_err()); + assert_eq!(metrics.get_invalid_payloads_count(), 1); } #[tokio::test] @@ -404,6 +453,7 @@ mod tests { .returning(|_| Ok(PayloadValidationResult::Valid)); let mut undecided = MockUndecidedBlocksRepository::new(); + expect_no_undecided_dedup_hit(&mut undecided); undecided .expect_store_undecided_block() .times(1) @@ -412,11 +462,13 @@ mod tests { let mut invalid = MockInvalidPayloadsRepository::new(); invalid.expect_append().times(0); + let metrics = AppMetrics::default(); let result = on_process_synced_value( engine, undecided, invalid, NoopPersistenceMeter, + &metrics, height, round, proposer, @@ -426,6 +478,7 @@ mod tests { assert!(result.is_err()); assert!(result.unwrap_err().downcast_ref::().is_some()); + assert_eq!(metrics.get_invalid_payloads_count(), 0); } #[tokio::test] @@ -444,6 +497,7 @@ mod tests { .returning(|_| Ok(PayloadValidationResult::Valid)); let mut undecided = MockUndecidedBlocksRepository::new(); + expect_no_undecided_dedup_hit(&mut undecided); undecided .expect_store_undecided_block() .times(1) @@ -459,11 +513,13 @@ mod tests { .times(1) .return_once(|_, _| Ok(())); + let metrics = AppMetrics::default(); let proposal = on_process_synced_value( engine, undecided, invalid, persistence_meter, + &metrics, height, round, proposer, @@ -474,6 +530,7 @@ mod tests { assert!(proposal.is_some()); assert_eq!(proposal.unwrap().validity, Validity::Valid); + assert_eq!(metrics.get_invalid_payloads_count(), 0); } #[tokio::test] @@ -494,6 +551,7 @@ mod tests { }); let mut undecided = MockUndecidedBlocksRepository::new(); + expect_no_undecided_dedup_hit(&mut undecided); undecided .expect_store_undecided_block() .times(1) @@ -505,11 +563,13 @@ mod tests { let mut persistence_meter = MockPersistenceMeter::new(); persistence_meter.expect_wait_for_persisted_block().times(0); + let metrics = AppMetrics::default(); let proposal = on_process_synced_value( engine, undecided, invalid, persistence_meter, + &metrics, height, round, proposer, @@ -520,6 +580,7 @@ mod tests { assert!(proposal.is_some()); assert_eq!(proposal.unwrap().validity, Validity::Invalid); + assert_eq!(metrics.get_invalid_payloads_count(), 1); } #[tokio::test] @@ -538,6 +599,7 @@ mod tests { .returning(|_| Ok(PayloadValidationResult::Valid)); let mut undecided = MockUndecidedBlocksRepository::new(); + expect_no_undecided_dedup_hit(&mut undecided); undecided .expect_store_undecided_block() .times(1) @@ -553,11 +615,13 @@ mod tests { .times(1) .return_once(|_, _| Err(eyre::eyre!("persistence meter timeout"))); + let metrics = AppMetrics::default(); let proposal = on_process_synced_value( engine, undecided, invalid, persistence_meter, + &metrics, height, round, proposer, @@ -568,5 +632,85 @@ mod tests { assert!(proposal.is_some()); assert_eq!(proposal.unwrap().validity, Validity::Valid); + assert_eq!(metrics.get_invalid_payloads_count(), 0); + } + + /// Race: the proposer's gossiped proposal arrived first and was already + /// stored as `UndecidedBlock(height, round, block_hash)` (and the EL has + /// it persisted) by the time the in-flight ProcessSyncedValue with + /// identical bytes runs. We must: + /// - still run engine validation (defense in depth: the synced bytes + /// are from a peer we don't trust implicitly), but + /// - skip the redundant `store_undecided_block` upsert and the + /// `wait_for_persisted_block` call, + /// and return a `ProposedValue` carrying the existing block's validity + /// (which equals the freshly-validated one, since engine validation is + /// deterministic). + #[tokio::test] + async fn on_process_synced_value_dedups_against_existing_undecided_block() { + let mut u = Unstructured::new(&[7u8; 512]); + + let height = Height::new(42); + let round = Round::new(0); + let proposer = Address::new([1u8; 20]); + let payload = ExecutionPayloadV3::arbitrary(&mut u).unwrap(); + let block_hash = payload.payload_inner.payload_inner.block_hash; + let value_bytes = Bytes::from(payload.as_ssz_bytes()); + + let existing_block = ConsensusBlock { + height, + round, + valid_round: Round::Nil, + proposer, + execution_payload: payload, + validity: Validity::Valid, + signature: None, + }; + + // Engine validation still runs once (defense in depth on the synced + // bytes), and accepts. + let mut engine = MockPayloadValidator::new(); + engine + .expect_validate_payload() + .times(1) + .returning(|_| Ok(PayloadValidationResult::Valid)); + + let mut undecided = MockUndecidedBlocksRepository::new(); + undecided + .expect_get_by_round_and_hash() + .with(eq(height), eq(round), eq(block_hash)) + .times(1) + .return_once(move |_, _, _| Ok(Some(existing_block))); + // No redundant upsert. + undecided.expect_store_undecided_block().times(0); + + let mut invalid = MockInvalidPayloadsRepository::new(); + invalid.expect_append().times(0); + + // No persistence wait — the proposer's path already satisfied it. + let mut persistence_meter = MockPersistenceMeter::new(); + persistence_meter.expect_wait_for_persisted_block().times(0); + + let metrics = AppMetrics::default(); + let proposal = on_process_synced_value( + engine, + undecided, + invalid, + persistence_meter, + &metrics, + height, + round, + proposer, + value_bytes, + ) + .await + .expect("should succeed via dedup short-circuit"); + + let proposal = proposal.expect("expected Some(proposal) on dedup hit"); + assert_eq!(proposal.height, height); + assert_eq!(proposal.round, round); + assert_eq!(proposal.proposer, proposer); + assert_eq!(proposal.validity, Validity::Valid); + assert_eq!(proposal.value, Value::new(block_hash)); } } diff --git a/crates/malachite-app/src/handlers/received_proposal_part.rs b/crates/malachite-app/src/handlers/received_proposal_part.rs index c7481d6..9df26d2 100644 --- a/crates/malachite-app/src/handlers/received_proposal_part.rs +++ b/crates/malachite-app/src/handlers/received_proposal_part.rs @@ -29,7 +29,7 @@ use arc_eth_engine::engine::Engine; use arc_signer::ArcSigningProvider; use crate::block::ConsensusBlock; -use crate::metrics::AppMetrics; +use crate::metrics::{AppMetrics, InvalidPayloadSource}; use crate::payload::{validate_consensus_block, EnginePayloadValidator}; use crate::proposal_parts::{ assemble_block_from_parts, resolve_expected_proposer, validate_proposal_parts, @@ -220,7 +220,7 @@ async fn validate_block( from: PeerId, ) -> eyre::Result<()> { let validator = EnginePayloadValidator::new(engine, metrics); - let validity = validate_consensus_block(&validator, block, store) + let validity = validate_consensus_block(&validator, block, store, metrics) .await .wrap_err_with(|| { format!( @@ -329,6 +329,14 @@ async fn process_proposal_parts( let block = match assemble_block_from_parts(&parts) { Ok(block) => block, Err(e) => { + warn!( + height = %parts_height, + round = %parts_round, + proposer = %parts_proposer, + "Failed to assemble block from parts: {e}", + ); + ctx.metrics + .inc_invalid_payloads_count(InvalidPayloadSource::AssemblyFailure); let invalid = InvalidPayload::new_from_parts(&parts, &e.to_string()); ctx.store.append_invalid_payload(invalid).await.wrap_err_with(|| { format!( @@ -398,3 +406,58 @@ async fn maybe_store_pending_proposal( Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + use arc_consensus_db::{DbMetrics, DbUpgrade}; + use arc_consensus_types::proposer::RoundRobin; + use arc_consensus_types::Validator; + use arc_signer::local::{LocalSigningProvider, PrivateKey}; + use bytesize::ByteSize; + use tempfile::tempdir; + + use crate::handlers::test_utils::signed_parts_without_data; + + #[tokio::test] + async fn process_proposal_parts_increments_on_assembly_failure() { + let dir = tempdir().unwrap(); + let store = Store::open( + dir.path().join("db"), + DbMetrics::default(), + DbUpgrade::Skip, + ByteSize::mib(64), + ) + .await + .unwrap(); + + let signing_key = PrivateKey::generate(rand::rngs::OsRng); + let validator = Validator::new(signing_key.public_key(), 1); + let validator_set = ValidatorSet::new(vec![validator]); + + let height = Height::new(1); + let round = Round::new(0); + let parts = signed_parts_without_data(height, round, &signing_key).await; + + let selector = RoundRobin; + let provider = ArcSigningProvider::Local(LocalSigningProvider::new(signing_key)); + let metrics = AppMetrics::default(); + + let ctx = ProcessingContext { + store: &store, + metrics: &metrics, + signing_provider: &provider, + current_height: height, + current_round: round, + current_validator_set: &validator_set, + proposer_selector: &selector, + max_pending_proposals: 10, + }; + + let result = process_proposal_parts(ctx, parts).await; + + assert!(result.is_err()); + assert_eq!(metrics.get_invalid_payloads_count(), 1); + } +} diff --git a/crates/malachite-app/src/handlers/started_round.rs b/crates/malachite-app/src/handlers/started_round.rs index f15905e..76e7730 100644 --- a/crates/malachite-app/src/handlers/started_round.rs +++ b/crates/malachite-app/src/handlers/started_round.rs @@ -18,6 +18,7 @@ use eyre::Context; use tracing::{error, info, warn}; use malachitebft_app_channel::app::consensus::Role; +use malachitebft_app_channel::app::types::core::Validity; use malachitebft_app_channel::app::types::ProposedValue; use malachitebft_app_channel::Reply; @@ -27,7 +28,7 @@ use arc_eth_engine::engine::Engine; use arc_signer::ArcSigningProvider; use crate::block::ConsensusBlock; -use crate::metrics::AppMetrics; +use crate::metrics::{AppMetrics, InvalidPayloadSource}; use crate::payload::{validate_consensus_block, EnginePayloadValidator, PayloadValidator}; use crate::proposal_parts::{ assemble_block_from_parts, resolve_expected_proposer, validate_proposal_parts, @@ -140,6 +141,7 @@ async fn fetch_and_process_pending_proposals( validator_set, proposer_selector, signing_provider, + metrics, ) .await .wrap_err("Failed to validate pending proposal parts")?; @@ -150,6 +152,7 @@ async fn fetch_and_process_pending_proposals( store, &EnginePayloadValidator::new(engine, metrics), store, + metrics, ) .await .wrap_err("failed to validate undecided blocks")?; @@ -171,6 +174,7 @@ async fn process_pending_proposal_parts( validator_set: &ValidatorSet, proposer_selector: &dyn ProposerSelector, signing_provider: &ArcSigningProvider, + metrics: &AppMetrics, ) -> eyre::Result<()> { for parts in pending_parts { let (height, round, proposer) = (parts.height(), parts.round(), parts.proposer()); @@ -202,13 +206,14 @@ async fn process_pending_proposal_parts( remove_pending_parts_and_store_undecided_block(store, parts, block).await?; } Err(e) => { + warn!(%height, %round, %proposer, "Failed to assemble block from pending parts: {e}"); + metrics.inc_invalid_payloads_count(InvalidPayloadSource::AssemblyFailure); let invalid_payload = InvalidPayload::new_from_parts(&parts, &e.to_string()); store.append_invalid_payload(invalid_payload).await.wrap_err_with(|| { format!( "Failed to store invalid payload after assembling block from pending parts (height={height}, round={round}, proposer={proposer})", ) })?; - warn!(%height, %round, %proposer, "Failed to assemble block from pending parts: {e}"); } } } @@ -235,6 +240,7 @@ async fn validate_undecided_blocks( undecided_blocks: &impl UndecidedBlocksRepository, payload_validator: &impl PayloadValidator, invalid_payloads: &impl InvalidPayloadsRepository, + metrics: &AppMetrics, ) -> eyre::Result> { let blocks = undecided_blocks .get_by_round(height, round) @@ -254,14 +260,20 @@ async fn validate_undecided_blocks( info!(%height, %round, %block_hash, "Validating undecided block"); - let validity = - match validate_consensus_block(payload_validator, &block, invalid_payloads).await { - Ok(validity) => validity, - Err(e) => { - error!(%height, %round, %block_hash, "Failed to validate undecided block: {e}"); - continue; - } - }; + let validity = match validate_consensus_block( + payload_validator, + &block, + invalid_payloads, + metrics, + ) + .await + { + Ok(validity) => validity, + Err(e) => { + error!(%height, %round, %block_hash, "Failed to validate undecided block, marking Invalid: {e}"); + Validity::Invalid + } + }; block.validity = validity; @@ -323,7 +335,15 @@ mod tests { use alloy_rpc_types_engine::ExecutionPayloadV3; use arbitrary::{Arbitrary, Unstructured}; + use arc_consensus_db::{DbMetrics, DbUpgrade}; + use arc_consensus_types::proposer::RoundRobin; + use arc_consensus_types::Validator; + use arc_signer::local::{LocalSigningProvider, PrivateKey}; + use bytesize::ByteSize; use malachitebft_core_types::Validity; + use tempfile::tempdir; + + use crate::handlers::test_utils::signed_parts_without_data; fn create_dummy_block(height: Height, round: Round, seed: u8) -> ConsensusBlock { let bytes = [seed; 1024]; @@ -340,6 +360,19 @@ mod tests { } } + async fn test_store() -> (Store, tempfile::TempDir) { + let dir = tempdir().unwrap(); + let store = Store::open( + dir.path().join("db"), + DbMetrics::default(), + DbUpgrade::Skip, + ByteSize::mib(64), + ) + .await + .unwrap(); + (store, dir) + } + #[tokio::test] async fn validate_undecided_blocks_all_valid() { let height = Height::new(1); @@ -368,12 +401,15 @@ mod tests { let mut invalid = MockInvalidPayloadsRepository::new(); invalid.expect_append().times(0); - let result = validate_undecided_blocks(height, round, &undecided, &validator, &invalid) - .await - .expect("should succeed"); + let metrics = AppMetrics::default(); + let result = + validate_undecided_blocks(height, round, &undecided, &validator, &invalid, &metrics) + .await + .expect("should succeed"); assert_eq!(result.len(), 2); assert!(result.iter().all(|b| b.validity == Validity::Valid)); + assert_eq!(metrics.get_invalid_payloads_count(), 0); } #[tokio::test] @@ -420,9 +456,11 @@ mod tests { let mut invalid = MockInvalidPayloadsRepository::new(); invalid.expect_append().times(1).returning(|_| Ok(())); - let result = validate_undecided_blocks(height, round, &undecided, &validator, &invalid) - .await - .expect("should succeed"); + let metrics = AppMetrics::default(); + let result = + validate_undecided_blocks(height, round, &undecided, &validator, &invalid, &metrics) + .await + .expect("should succeed"); assert_eq!(result.len(), 2); assert_eq!(result[0].validity, Validity::Valid); @@ -432,6 +470,7 @@ mod tests { vec![Validity::Valid, Validity::Invalid], "persisted validity should match engine verdict, not the placeholder" ); + assert_eq!(metrics.get_invalid_payloads_count(), 1); } #[tokio::test] @@ -445,11 +484,14 @@ mod tests { let validator = MockPayloadValidator::new(); let invalid = MockInvalidPayloadsRepository::new(); - let result = validate_undecided_blocks(height, round, &undecided, &validator, &invalid) - .await - .expect("should succeed"); + let metrics = AppMetrics::default(); + let result = + validate_undecided_blocks(height, round, &undecided, &validator, &invalid, &metrics) + .await + .expect("should succeed"); assert!(result.is_empty()); + assert_eq!(metrics.get_invalid_payloads_count(), 0); } #[tokio::test] @@ -465,18 +507,26 @@ mod tests { let validator = MockPayloadValidator::new(); let invalid = MockInvalidPayloadsRepository::new(); - let err = validate_undecided_blocks(height, round, &undecided, &validator, &invalid) - .await - .expect_err("should propagate repository error"); + let metrics = AppMetrics::default(); + let err = + validate_undecided_blocks(height, round, &undecided, &validator, &invalid, &metrics) + .await + .expect_err("should propagate repository error"); assert!( err.to_string().contains("Failed to fetch undecided blocks"), "error should describe the failure, got: {err}", ); + assert_eq!(metrics.get_invalid_payloads_count(), 0); } #[tokio::test] - async fn validate_undecided_blocks_validation_error_skips_block() { + async fn validate_undecided_blocks_validation_error_marks_invalid() { + // When the engine call fails (transport error, SYNCING/ACCEPTED, etc.) + // we treat the block as `Invalid` for the current round so the placeholder + // `Valid` written by `process_pending_proposal_parts` does not leak. + // Malachite's `FullProposalKeeper::handle_validity_change` rejects any + // subsequent `Valid -> Invalid` flip on the same WAL entry. let height = Height::new(1); let round = Round::new(0); @@ -488,13 +538,18 @@ mod tests { undecided .expect_get_by_round() .returning(move |_, _| Ok(blocks.clone())); - // Only the block that successfully validated should be persisted, the - // errored block must be skipped entirely. + + // Both blocks must be persisted: the errored block with `Invalid`, + // the successful one with `Valid`. Order matches the input order. + let persisted = Arc::new(Mutex::new(Vec::::new())); + let persisted_clone = Arc::clone(&persisted); undecided .expect_store_undecided_block() - .times(1) - .withf(|b| b.validity == Validity::Valid) - .returning(|_| Ok(())); + .times(2) + .returning(move |b| { + persisted_clone.lock().unwrap().push(b.validity); + Ok(()) + }); let mut call_count = 0usize; let mut validator = MockPayloadValidator::new(); @@ -510,16 +565,59 @@ mod tests { } }); + // Engine `Err` is not a verdict, so no forensic record is written. let mut invalid = MockInvalidPayloadsRepository::new(); invalid.expect_append().times(0); - let result = validate_undecided_blocks(height, round, &undecided, &validator, &invalid) - .await - .expect("should succeed despite one block erroring"); + let metrics = AppMetrics::default(); + let result = + validate_undecided_blocks(height, round, &undecided, &validator, &invalid, &metrics) + .await + .expect("should succeed despite one block erroring"); - // First block errored and was skipped, only second block returned - assert_eq!(result.len(), 1); - assert_eq!(result[0].validity, Validity::Valid); + assert_eq!(result.len(), 2, "both blocks should be returned"); + assert_eq!(result[0].validity, Validity::Invalid); + assert_eq!(result[1].validity, Validity::Valid); + assert_eq!( + *persisted.lock().unwrap(), + vec![Validity::Invalid, Validity::Valid], + "errored block must be persisted as Invalid, not left with placeholder Valid", + ); + // Engine failure is not counted as an engine rejection; the metric + // tracks `EngineReject` and `AssemblyFailure`, not transport errors. + assert_eq!(metrics.get_invalid_payloads_count(), 0); + } + + #[tokio::test] + async fn process_pending_proposal_parts_increments_on_assembly_failure() { + let (store, _dir) = test_store().await; + + let signing_key = PrivateKey::generate(rand::rngs::OsRng); + let validator = Validator::new(signing_key.public_key(), 1); + let validator_set = ValidatorSet::new(vec![validator]); + + let height = Height::new(1); + let round = Round::new(0); + let parts = signed_parts_without_data(height, round, &signing_key).await; + + let selector = RoundRobin; + let provider = ArcSigningProvider::Local(LocalSigningProvider::new(signing_key)); + let metrics = AppMetrics::default(); + + process_pending_proposal_parts( + &store, + vec![parts], + height, + round, + &validator_set, + &selector, + &provider, + &metrics, + ) + .await + .expect("should handle assembly failure gracefully"); + + assert_eq!(metrics.get_invalid_payloads_count(), 1); } #[tokio::test] @@ -547,14 +645,17 @@ mod tests { let invalid = MockInvalidPayloadsRepository::new(); - let err = validate_undecided_blocks(height, round, &undecided, &validator, &invalid) - .await - .expect_err("persist error should propagate"); + let metrics = AppMetrics::default(); + let err = + validate_undecided_blocks(height, round, &undecided, &validator, &invalid, &metrics) + .await + .expect_err("persist error should propagate"); assert!( err.to_string() .contains("Failed to persist validated undecided block"), "error should describe the persist failure, got: {err}", ); + assert_eq!(metrics.get_invalid_payloads_count(), 0); } } diff --git a/crates/malachite-app/src/handlers/test_utils.rs b/crates/malachite-app/src/handlers/test_utils.rs new file mode 100644 index 0000000..3f296e6 --- /dev/null +++ b/crates/malachite-app/src/handlers/test_utils.rs @@ -0,0 +1,52 @@ +// Copyright 2025 Circle Internet Group, Inc. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Shared helpers for handler unit tests. + +use sha3::Digest; + +use arc_consensus_types::{ + Address, Height, ProposalFin, ProposalInit, ProposalPart, ProposalParts, Round, +}; +use arc_signer::local::{LocalSigningProvider, PrivateKey}; +use arc_signer::SigningProvider; + +/// Builds signed `ProposalParts` with only `Init` and `Fin` (no data chunks). +/// Useful for exercising assembly-failure paths: the empty data makes +/// `assemble_block_from_parts` fail on SSZ decode of zero bytes, but the +/// signature over `height + round` still verifies against the signer. +pub(super) async fn signed_parts_without_data( + height: Height, + round: Round, + signing_key: &PrivateKey, +) -> ProposalParts { + let proposer = Address::from_public_key(&signing_key.public_key()); + let init = ProposalInit::new(height, round, Round::Nil, proposer); + + let mut hasher = sha3::Keccak256::new(); + hasher.update(height.as_u64().to_be_bytes()); + hasher.update(round.as_i64().to_be_bytes()); + let hash = hasher.finalize().to_vec(); + + let provider = LocalSigningProvider::new(signing_key.clone()); + let signature = provider.sign_bytes(&hash).await.unwrap(); + + ProposalParts::new(vec![ + ProposalPart::Init(init), + ProposalPart::Fin(ProposalFin::new(signature)), + ]) + .unwrap() +} diff --git a/crates/malachite-app/src/hardcoded_config.rs b/crates/malachite-app/src/hardcoded_config.rs index a2b318f..3ed389e 100644 --- a/crates/malachite-app/src/hardcoded_config.rs +++ b/crates/malachite-app/src/hardcoded_config.rs @@ -18,11 +18,13 @@ use std::time::Duration; -use arc_consensus_types::{ConsensusConfig, RemoteSigningConfig, RetryConfig, ValueSyncConfig}; +use arc_consensus_types::{ + ChainId, ConsensusConfig, RemoteSigningConfig, RetryConfig, ValueSyncConfig, +}; use arc_node_consensus_cli::config::ScoringStrategy; use malachitebft_app_channel::app::config::{ - BootstrapProtocol, DiscoveryConfig, GossipSubConfig, P2pConfig, PubSubProtocol, Selector, - ValuePayload, + BootstrapProtocol, DiscoveryConfig, GossipSubConfig, P2pConfig, ProtocolNames, PubSubProtocol, + Selector, ValuePayload, }; use malachitebft_app_channel::app::net::Multiaddr; @@ -39,6 +41,55 @@ pub mod consensus { /// /// Default: `10` pub const QUEUE_CAPACITY: usize = 16; + + /// Maximum number of buffered inputs per height in the gossip input queue. + /// + /// Default: `500` + pub const QUEUE_PER_HEIGHT_CAPACITY: usize = 500; + + /// Duration to wait before replaying the WAL on recovery. + /// + /// Default: `5s` + pub const WAL_REPLAY_DELAY: Duration = Duration::from_secs(5); + + /// Chain-specific libp2p protocol names. + pub mod p2p { + use super::*; + + pub(crate) fn protocol_names_malachite_defaults() -> ProtocolNames { + ProtocolNames { + consensus: "/malachitebft-core-consensus/v1beta1".to_string(), + discovery_kad: "/malachitebft-discovery/kad/v1beta1".to_string(), + discovery_regres: "/malachitebft-discovery/reqres/v1beta1".to_string(), + sync: "/malachitebft-sync/v1beta1".to_string(), + validator_proof: "/malachitebft-validator-proof/v1".to_string(), + } + } + + fn protocol_names_arc_v1() -> ProtocolNames { + ProtocolNames { + consensus: "/arc/consensus/v1".to_string(), + discovery_kad: "/arc/discovery/kad/v1".to_string(), + discovery_regres: "/arc/discovery/req-res/v1".to_string(), + sync: "/arc/sync/v1".to_string(), + validator_proof: "/arc/validator-proof/v1".to_string(), + } + } + + /// Chain-specific libp2p protocol names. + /// + /// Mainnet uses Arc-branded names; Testnet, Devnet, and Localdev + /// keep the Malachite defaults to avoid breaking the wire protocol + /// on already-running networks (including CI upgrade scenarios). + pub fn protocol_names(chain_id: ChainId) -> ProtocolNames { + match chain_id { + ChainId::Mainnet => protocol_names_arc_v1(), + ChainId::Localdev => protocol_names_malachite_defaults(), + ChainId::Testnet => protocol_names_malachite_defaults(), + ChainId::Devnet => protocol_names_malachite_defaults(), + } + } + } } /// Hardcoded discovery parameters. @@ -84,7 +135,7 @@ pub mod value_sync { /// Timeout duration for sync requests /// Default: `Duration::from_secs(10)` - pub const REQUEST_TIMEOUT: Duration = Duration::from_secs(10); + pub const REQUEST_TIMEOUT: Duration = Duration::from_secs(1); /// Maximum size of a request /// Default: `1 MiB` @@ -187,6 +238,8 @@ pub fn build_consensus_config( enabled: consensus_enabled, value_payload: consensus::VALUE_PAYLOAD, queue_capacity: consensus::QUEUE_CAPACITY, + queue_per_height_capacity: consensus::QUEUE_PER_HEIGHT_CAPACITY, + wal_replay_delay: consensus::WAL_REPLAY_DELAY, p2p: P2pConfig { listen_addr, persistent_peers, @@ -252,7 +305,6 @@ pub fn build_remote_signing_config( #[cfg(test)] mod tests { use super::*; - use malachitebft_app_channel::app::config::PubSubProtocol; #[test] fn generate_gossipsub_config_uses_defaults() { @@ -289,6 +341,11 @@ mod tests { assert!(config.enabled); assert_eq!(config.value_payload, consensus::VALUE_PAYLOAD); assert_eq!(config.queue_capacity, consensus::QUEUE_CAPACITY); + assert_eq!( + config.queue_per_height_capacity, + consensus::QUEUE_PER_HEIGHT_CAPACITY + ); + assert_eq!(config.wal_replay_delay, consensus::WAL_REPLAY_DELAY); assert_eq!(config.p2p.listen_addr, listen_addr); assert_eq!(config.p2p.persistent_peers, persistent_peers); } @@ -495,6 +552,8 @@ mod tests { // Verify consensus constants assert_eq!(consensus::VALUE_PAYLOAD, ValuePayload::ProposalAndParts); assert_eq!(consensus::QUEUE_CAPACITY, 16); + assert_eq!(consensus::QUEUE_PER_HEIGHT_CAPACITY, 500); + assert_eq!(consensus::WAL_REPLAY_DELAY, Duration::from_secs(5)); // Verify discovery constants assert_eq!(discovery::BOOTSTRAP_PROTOCOL, BootstrapProtocol::Full); @@ -507,4 +566,44 @@ mod tests { // Verify remote signing constants assert_eq!(remote_signing::TIMEOUT, Duration::from_secs(30)); } + + fn expected_arc_v1_names() -> ProtocolNames { + ProtocolNames { + consensus: "/arc/consensus/v1".to_string(), + discovery_kad: "/arc/discovery/kad/v1".to_string(), + discovery_regres: "/arc/discovery/req-res/v1".to_string(), + sync: "/arc/sync/v1".to_string(), + validator_proof: "/arc/validator-proof/v1".to_string(), + } + } + + #[test] + fn protocol_names_mainnet_uses_arc_brand() { + assert_eq!( + consensus::p2p::protocol_names(ChainId::Mainnet), + expected_arc_v1_names(), + ); + } + + #[test] + fn protocol_names_non_mainnet_uses_malachite_defaults() { + for chain_id in [ChainId::Testnet, ChainId::Devnet, ChainId::Localdev] { + assert_eq!( + consensus::p2p::protocol_names(chain_id), + ProtocolNames::default(), + "expected Malachite defaults for {chain_id:?}", + ); + } + } + + // Tripwire: our inlined Malachite defaults must match upstream. If Malachite + // bumps its defaults, this fails and forces a conscious decision about whether + // non-mainnet chains follow the bump or stay pinned to the inlined strings. + #[test] + fn inlined_malachite_defaults_match_upstream() { + assert_eq!( + consensus::p2p::protocol_names_malachite_defaults(), + ProtocolNames::default(), + ); + } } diff --git a/crates/malachite-app/src/metrics/app.rs b/crates/malachite-app/src/metrics/app.rs index 3ce932d..c8eca57 100644 --- a/crates/malachite-app/src/metrics/app.rs +++ b/crates/malachite-app/src/metrics/app.rs @@ -98,6 +98,10 @@ pub struct Inner { /// Number of times the node fell behind and transitioned from InSync to CatchingUp sync_fell_behind_count: Counter, + /// Number of invalid payloads observed and persisted for forensics, + /// labelled by source (engine reject, assembly failure, sync decode). + invalid_payloads_count: Family, + /// Number of pending proposal parts waiting to be processed at a future height or round pending_proposal_parts_count: Gauge, @@ -137,6 +141,7 @@ impl Inner { }), height_restart_count: Counter::default(), sync_fell_behind_count: Counter::default(), + invalid_payloads_count: Family::default(), pending_proposal_parts_count: Gauge::default(), consensus_params: Family::default(), handshake_replay_blocks: Gauge::default(), @@ -257,6 +262,12 @@ impl AppMetrics { metrics.sync_fell_behind_count.clone(), ); + registry.register( + "invalid_payloads_count", + "Number of invalid payloads observed and persisted for forensics", + metrics.invalid_payloads_count.clone(), + ); + registry.register( "pending_proposal_parts_count", "Number of pending proposal parts waiting to be processed at a future height or round", @@ -418,6 +429,30 @@ impl AppMetrics { self.sync_fell_behind_count.inc(); } + /// Increment the invalid-payload counter for the given source. + pub fn inc_invalid_payloads_count(&self, source: InvalidPayloadSource) { + self.invalid_payloads_count + .get_or_create(&InvalidPayloadSourceLabel::new(source)) + .inc(); + } + + /// Total number of invalid payloads across all sources. + #[cfg(test)] + pub fn get_invalid_payloads_count(&self) -> u64 { + InvalidPayloadSource::ALL + .iter() + .map(|source| self.get_invalid_payloads_count_by_source(*source)) + .sum() + } + + /// Number of invalid payloads recorded for a specific source. + #[cfg(test)] + pub fn get_invalid_payloads_count_by_source(&self, source: InvalidPayloadSource) -> u64 { + self.invalid_payloads_count + .get_or_create(&InvalidPayloadSourceLabel::new(source)) + .get() + } + /// Observe the number of pending proposal parts pub fn observe_pending_proposal_parts_count(&self, count: usize) { self.pending_proposal_parts_count.set(count as i64); @@ -492,6 +527,45 @@ impl EngineApiLabel { } } +/// Source of an invalid-payload record, used to label the counter. +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] +pub enum InvalidPayloadSource { + /// Execution engine rejected the payload. + EngineReject, + /// Assembling a block from proposal parts failed (malformed parts or SSZ + /// decode error on the concatenated data). + AssemblyFailure, + /// SSZ decode of a value received via sync failed. + SyncDecode, +} + +impl InvalidPayloadSource { + #[cfg(test)] + pub(super) const ALL: [InvalidPayloadSource; 3] = + [Self::EngineReject, Self::AssemblyFailure, Self::SyncDecode]; + + fn as_str(&self) -> &'static str { + match self { + Self::EngineReject => "engine_reject", + Self::AssemblyFailure => "assembly_failure", + Self::SyncDecode => "sync_decode", + } + } +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)] +struct InvalidPayloadSourceLabel { + source: &'static str, +} + +impl InvalidPayloadSourceLabel { + fn new(source: InvalidPayloadSource) -> Self { + Self { + source: source.as_str(), + } + } +} + #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)] pub struct VersionInfoLabel { pub version: &'static str, diff --git a/crates/malachite-app/src/metrics/mod.rs b/crates/malachite-app/src/metrics/mod.rs index fd55f9e..90628bd 100644 --- a/crates/malachite-app/src/metrics/mod.rs +++ b/crates/malachite-app/src/metrics/mod.rs @@ -17,7 +17,9 @@ pub mod app; pub mod db; pub mod process; +pub mod validator_set; -pub use app::AppMetrics; +pub use app::{AppMetrics, InvalidPayloadSource}; pub use db::DbMetrics; pub use process::ProcessMetrics; +pub use validator_set::ValidatorSetMetrics; diff --git a/crates/malachite-app/src/metrics/validator_set.rs b/crates/malachite-app/src/metrics/validator_set.rs new file mode 100644 index 0000000..5d6e764 --- /dev/null +++ b/crates/malachite-app/src/metrics/validator_set.rs @@ -0,0 +1,73 @@ +// Copyright 2026 Circle Internet Group, Inc. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Validator-set decoding metrics. +//! +//! Registered against the same `SharedRegistry` as `AppMetrics`/`DbMetrics`/`ProcessMetrics` +//! so the counter is exported by the consensus `/metrics` endpoint with the standard +//! `moniker` label. + +use malachitebft_app_channel::app::metrics::prometheus::metrics::counter::Counter; +use malachitebft_app_channel::app::metrics::SharedRegistry; + +/// Metrics for the `abi_decode_validator_set` decoder living in `arc-eth-engine`. +#[derive(Clone, Debug, Default)] +pub struct ValidatorSetMetrics { + /// Active validators skipped because their on-chain public key was malformed. + skipped: Counter, +} + +impl ValidatorSetMetrics { + /// Register the counter under prefix `arc_validator_set`. The exported metric name is + /// `arc_validator_set_skipped_total` (the `_total` suffix is added by `prometheus-client`). + pub fn register(registry: &SharedRegistry) -> Self { + let metrics = Self::default(); + + registry.with_prefix("arc_validator_set", |registry| { + registry.register( + "skipped", + "Active validators skipped during validator-set decoding due to malformed public keys", + metrics.skipped.clone(), + ); + }); + + metrics + } + + /// Install this counter as the global recorder used by `arc_shared::metrics::validator_set`. + /// Idempotent: subsequent calls are dropped. + pub fn install_global(&self) { + let counter = self.skipped.clone(); + arc_shared::metrics::validator_set::set_recorder(Box::new(move || { + counter.inc(); + })); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn register_and_install_global_does_not_panic() { + let registry = SharedRegistry::global().with_moniker("test"); + let metrics = ValidatorSetMetrics::register(®istry); + metrics.install_global(); + + // Recording through the shared API must not panic once a recorder is installed. + arc_shared::metrics::validator_set::record_skipped_validator(); + } +} diff --git a/crates/malachite-app/src/node.rs b/crates/malachite-app/src/node.rs index 897f08b..c5360d6 100644 --- a/crates/malachite-app/src/node.rs +++ b/crates/malachite-app/src/node.rs @@ -63,7 +63,7 @@ use arc_signer::ArcSigningProvider; use crate::env_config::EnvConfig; use crate::hardcoded_config::{GossipLoad, GossipMeshParams}; -use crate::metrics::{AppMetrics, DbMetrics, ProcessMetrics}; +use crate::metrics::{AppMetrics, DbMetrics, ProcessMetrics, ValidatorSetMetrics}; use crate::request::AppRequest; use crate::state::State; use crate::store::Store; @@ -152,12 +152,13 @@ impl ConsensusIdentity { } } -impl From for ConsensusContext { +impl From for ConsensusContext { fn from(identity: ConsensusIdentity) -> Self { - ConsensusContext { - address: identity.address, - signing_provider: identity.signing_provider, - } + ConsensusContext::new_validator( + identity.address, + Box::new(identity.signing_provider.clone()), + Box::new(identity.signing_provider), + ) } } @@ -392,6 +393,10 @@ impl App { let db_metrics = DbMetrics::register(&self.registry); let process_metrics = ProcessMetrics::register(&self.registry); + // Wire the global recorder for `arc_validator_set_skipped_total` so the counter + // emitted from `abi_decode_validator_set` lands on the consensus `/metrics` endpoint. + ValidatorSetMetrics::register(&self.registry).install_global(); + (app_metrics, db_metrics, process_metrics) } @@ -439,6 +444,19 @@ impl App { } } + /// Must run after `resolve_chain_identity` and before the engine is started, + /// so the overrides are observed when `self.config` is cloned into the engine. + fn apply_chain_specific_config(&mut self, chain_id: ChainId) { + let protocol_names = crate::hardcoded_config::consensus::p2p::protocol_names(chain_id); + info!( + ?chain_id, + consensus = %protocol_names.consensus, + sync = %protocol_names.sync, + "Applying chain-specific libp2p protocol names", + ); + self.config.consensus.p2p.protocol_names = protocol_names; + } + fn apply_state_overrides(&self, state: &mut State) { if let Some(suggested_fee_recipient) = self.start_config.suggested_fee_recipient { state.set_suggested_fee_recipient(suggested_fee_recipient); @@ -615,7 +633,7 @@ impl App { } #[tracing::instrument(name = "node", skip_all, fields(moniker = %self.config.moniker))] - async fn start(&mut self) -> eyre::Result { + pub async fn start(&mut self) -> eyre::Result { let ctx = ArcContext::new(); // Read environment-based configuration once at startup @@ -643,6 +661,8 @@ impl App { .await .wrap_err("Failed to resolve chain identity from execution engine")?; + self.apply_chain_specific_config(chain_id); + let consensus_spec = ConsensusSpec::from(chain_id); // Resolve default follow endpoints when --follow is used without explicit endpoints @@ -893,6 +913,29 @@ mod tests { use super::*; use tempfile::tempdir; + #[tokio::test] + async fn start_requires_execution_engine() { + let tmp = tempfile::tempdir().unwrap(); + let start_config = StartConfig { + rpc_sync_enabled: true, + rpc_sync_endpoints: vec!["http://unused:8545".parse().unwrap()], + ..Default::default() + }; + let mut app = App::new( + Config::default(), + tmp.path().to_path_buf(), + tmp.path().join("unused.key"), + start_config, + ); + match app.start().await { + Err(err) => assert!( + format!("{err:?}").contains("engine"), + "unexpected error: {err:?}" + ), + Ok(_) => panic!("should fail without execution engine"), + } + } + fn write_key_file(dir: &std::path::Path) -> (PathBuf, PrivateKey) { let key = PrivateKey::generate(OsRng); let path = dir.join("priv_validator_key.json"); @@ -944,7 +987,6 @@ mod tests { async fn validator_loads_consensus_identity_from_key_file() { let dir = tempdir().unwrap(); let (key_path, original_key) = write_key_file(dir.path()); - let app = test_app(key_path, true); let identity = app.setup_node_identity().await.unwrap(); diff --git a/crates/malachite-app/src/payload.rs b/crates/malachite-app/src/payload.rs index efd254b..2a4df9a 100644 --- a/crates/malachite-app/src/payload.rs +++ b/crates/malachite-app/src/payload.rs @@ -29,7 +29,7 @@ use arc_eth_engine::json_structures::ExecutionBlock; use arc_eth_engine::rpc::EngineApiRpcError; use crate::block::ConsensusBlock; -use crate::metrics::app::AppMetrics; +use crate::metrics::app::{AppMetrics, InvalidPayloadSource}; use crate::store::repositories::InvalidPayloadsRepository; use arc_consensus_db::invalid_payloads::InvalidPayload; @@ -293,10 +293,23 @@ async fn validate_payload( /// to [`PayloadValidator::validate_payload`] for the actual engine call /// and then persists an [`InvalidPayload`] record when the verdict is /// `Invalid`. +/// +/// # Return contract +/// +/// - `Ok(Validity::Valid)`: the engine accepted the payload. +/// - `Ok(Validity::Invalid)`: the engine rejected the payload. Persisting +/// the forensic [`InvalidPayload`] record is **best-effort**: a failure +/// to append is logged at `error` but does not change the verdict +/// returned to the caller. The engine's verdict is authoritative and +/// must reach the consensus layer so the corresponding undecided block +/// is marked `Invalid` rather than left with a placeholder `Valid`. +/// - `Err(_)`: no verdict was obtained (engine transport error, +/// `SYNCING`/`ACCEPTED` status, etc.). pub async fn validate_consensus_block( payload_validator: &impl PayloadValidator, block: &ConsensusBlock, store: &impl InvalidPayloadsRepository, + metrics: &AppMetrics, ) -> eyre::Result { let result = payload_validator .validate_payload(&block.execution_payload) @@ -305,8 +318,25 @@ pub async fn validate_consensus_block( match result { PayloadValidationResult::Valid => Ok(Validity::Valid), PayloadValidationResult::Invalid { reason } => { + warn!( + height = %block.height, + round = %block.round, + block_hash = %block.block_hash(), + proposer = %block.proposer, + reason = %reason, + "Engine rejected payload, storing for forensics", + ); + metrics.inc_invalid_payloads_count(InvalidPayloadSource::EngineReject); let invalid = InvalidPayload::new_from_block(block, &reason); - store.append(invalid).await?; + if let Err(e) = store.append(invalid).await { + error!( + height = %block.height, + round = %block.round, + block_hash = %block.block_hash(), + proposer = %block.proposer, + "Failed to persist invalid-payload forensic record: {e}", + ); + } Ok(Validity::Invalid) } } @@ -519,12 +549,14 @@ mod tests { let mut store = MockInvalidPayloadsRepository::new(); store.expect_append().times(0); + let metrics = AppMetrics::default(); let block = test_block(); - let result = validate_consensus_block(&validator, &block, &store) + let result = validate_consensus_block(&validator, &block, &store, &metrics) .await .expect("should succeed"); assert_eq!(result, Validity::Valid); + assert_eq!(metrics.get_invalid_payloads_count(), 0); } #[tokio::test] @@ -549,12 +581,14 @@ mod tests { }) .returning(|_| Ok(())); + let metrics = AppMetrics::default(); let block = test_block(); - let result = validate_consensus_block(&validator, &block, &store) + let result = validate_consensus_block(&validator, &block, &store, &metrics) .await .expect("should succeed"); assert_eq!(result, Validity::Invalid); + assert_eq!(metrics.get_invalid_payloads_count(), 1); } #[tokio::test] @@ -567,8 +601,9 @@ mod tests { let mut store = MockInvalidPayloadsRepository::new(); store.expect_append().times(0); + let metrics = AppMetrics::default(); let block = test_block(); - let err = validate_consensus_block(&validator, &block, &store) + let err = validate_consensus_block(&validator, &block, &store, &metrics) .await .expect_err("should propagate error"); @@ -577,10 +612,17 @@ mod tests { "error should contain the original message, \ got: {err}", ); + assert_eq!(metrics.get_invalid_payloads_count(), 0); } #[tokio::test] - async fn validate_consensus_block_propagates_store_error() { + async fn validate_consensus_block_returns_invalid_when_forensics_persist_fails() { + // When the engine returns Invalid but persisting the forensic record + // fails (e.g. transient DB issue), the engine's verdict is still the + // authoritative answer and must be returned. Otherwise the caller + // (validate_undecided_blocks) treats it as "no verdict obtained" and + // leaves the placeholder `Valid` in undecided_blocks, masking a + // rejected block. let mut validator = MockPayloadValidator::new(); validator.expect_validate_payload().returning(|_| { Ok(PayloadValidationResult::Invalid { @@ -594,16 +636,14 @@ mod tests { .times(1) .returning(|_| Err(std::io::Error::other("disk full"))); + let metrics = AppMetrics::default(); let block = test_block(); - let err = validate_consensus_block(&validator, &block, &store) + let validity = validate_consensus_block(&validator, &block, &store, &metrics) .await - .expect_err("should propagate store error"); + .expect("verdict should be returned even when forensics persist fails"); - assert!( - err.to_string().contains("disk full"), - "error should contain the store error message, \ - got: {err}", - ); + assert_eq!(validity, Validity::Invalid); + assert_eq!(metrics.get_invalid_payloads_count(), 1); } #[derive(Clone, Debug)] diff --git a/crates/malachite-app/src/proposal_parts.rs b/crates/malachite-app/src/proposal_parts.rs index 78c921c..66849d4 100644 --- a/crates/malachite-app/src/proposal_parts.rs +++ b/crates/malachite-app/src/proposal_parts.rs @@ -297,6 +297,7 @@ mod tests { use super::*; use arc_consensus_types::proposer::RoundRobin; + use arc_consensus_types::signing::SigningProvider; use arc_consensus_types::{Address, ProposalFin, ProposalInit, ValidatorSet}; use arc_signer::local::{LocalSigningProvider, PrivateKey, PublicKey}; diff --git a/crates/malachite-app/src/rpc/routes.rs b/crates/malachite-app/src/rpc/routes.rs index a51797f..16a4f8c 100644 --- a/crates/malachite-app/src/rpc/routes.rs +++ b/crates/malachite-app/src/rpc/routes.rs @@ -423,7 +423,7 @@ mod tests { last_signed_prevote: None, last_signed_precommit: None, round_certificate: None, - input_queue: dump_types::BoundedQueue::new(0), + input_queue: dump_types::BoundedQueue::new(0, 0), } } diff --git a/crates/malachite-app/src/streaming.rs b/crates/malachite-app/src/streaming.rs index c0b19b6..af530ab 100644 --- a/crates/malachite-app/src/streaming.rs +++ b/crates/malachite-app/src/streaming.rs @@ -21,9 +21,9 @@ use std::time::{Duration, Instant}; use bytes::Bytes; use schnellru::{ByLength, LruMap}; -use tracing::{error, warn}; +use tracing::warn; -use arc_consensus_types::{Height, ProposalPart, ProposalParts, Round}; +use arc_consensus_types::{Height, ProposalPart, ProposalParts, ProposalPartsError, Round}; use malachitebft_app_channel::app::streaming::{Sequence, StreamId, StreamMessage}; use malachitebft_app_channel::app::types::PeerId; @@ -84,6 +84,8 @@ pub enum InsertResult { pub enum InsertError { /// The stream_id is not exactly [`STREAM_ID_LEN`] bytes. InvalidStreamIdLength { actual: usize, expected: usize }, + /// A completed stream failed final assembly into [`ProposalParts`]. + AssemblyFailed(ProposalPartsError), } impl std::fmt::Display for InsertError { @@ -95,6 +97,9 @@ impl std::fmt::Display for InsertError { "invalid stream_id length: {actual} bytes (expected {expected})" ) } + InsertError::AssemblyFailed(err) => { + write!(f, "failed to assemble proposal parts: {err}") + } } } } @@ -428,15 +433,17 @@ impl PartStreamsMap { } }; - // StreamState guarantees Init and Fin are present and no duplicates exist, - // so ProposalParts::new should never fail on a complete stream. + // `StreamState` guarantees that an `Init` and a stream-end `Fin` are + // observed before the stream is marked complete, but it deduplicates + // only by message `sequence` — not by part *type*. A misbehaving peer + // can send multiple `ProposalPart::Init` (or `ProposalPart::Fin`) + // envelopes at distinct sequence numbers, complete the stream, and + // reach this branch with `ProposalPartsError::DuplicatePart(..)`. + // Surface the failure as `Invalid` so the caller can act on the + // misbehaviour rather than silently dropping the stream. match ProposalParts::new(parts) { Ok(proposal_parts) => InsertResult::Complete(proposal_parts), - Err(e) => { - debug_assert!(false, "unreachable: complete stream failed assembly: {e}"); - error!(%peer_id, %stream_id, "Failed to assemble proposal parts: {e}"); - InsertResult::Pending - } + Err(e) => InsertResult::Invalid(InsertError::AssemblyFailed(e)), } } @@ -719,6 +726,77 @@ mod tests { } } + #[test] + fn test_insert_returns_invalid_with_duplicate_init() { + let peer = PeerId::random(); + let stream = make_stream_id(101); + + let mut map = PartStreamsMap::new(Height::new(1), NUM_VALIDATORS); + + // First Init + assert!(map + .must_insert(peer, make_message(&stream, 0, make_init_part())) + .is_none()); + + // Second Init + assert!(map + .must_insert(peer, make_message(&stream, 1, make_init_part())) + .is_none()); + + // Complete the stream + let result = map.insert(peer, make_fin_message(&stream, 2)); + + // Insert fails with DuplicatePart("Init") + assert!(matches!( + result, + InsertResult::Invalid(InsertError::AssemblyFailed( + ProposalPartsError::DuplicatePart("Init") + )) + )); + assert!( + map.streams.is_empty(), + "completed stream must be removed even when assembly fails" + ); + } + + #[test] + fn test_insert_returns_invalid_with_duplicate_fin() { + let peer = PeerId::random(); + let stream = make_stream_id(101); + + let mut map = PartStreamsMap::new(Height::new(1), NUM_VALIDATORS); + + // Init + assert!(map + .must_insert(peer, make_message(&stream, 0, make_init_part())) + .is_none()); + + // First Fin + assert!(map + .must_insert(peer, make_message(&stream, 1, make_fin_part())) + .is_none()); + + // Second Fin + assert!(map + .must_insert(peer, make_message(&stream, 2, make_fin_part())) + .is_none()); + + // Complete the stream + let result = map.insert(peer, make_fin_message(&stream, 3)); + + // Insert fails with DuplicatePart("Fin") + assert!(matches!( + result, + InsertResult::Invalid(InsertError::AssemblyFailed( + ProposalPartsError::DuplicatePart("Fin") + )) + )); + assert!( + map.streams.is_empty(), + "completed stream must be removed even when assembly fails" + ); + } + #[test] fn test_insert_duplicate_sequence_is_ignored() { let peer_1 = PeerId::random(); diff --git a/crates/malachite-app/src/validator_proof.rs b/crates/malachite-app/src/validator_proof.rs index 24e1e48..16c2aa1 100644 --- a/crates/malachite-app/src/validator_proof.rs +++ b/crates/malachite-app/src/validator_proof.rs @@ -22,10 +22,10 @@ //! the P2P network without possessing their consensus key. use arc_consensus_types::codec::{network::NetCodec, Codec}; +use arc_consensus_types::signing::Signer; use arc_signer::ArcSigningProvider; use bytes::Bytes; use eyre::Result; -use malachitebft_app_channel::SigningProviderExt; use tracing::info; /// Create a signed validator proof binding the consensus public key to the P2P peer ID. @@ -64,6 +64,7 @@ pub async fn create_validator_proof( #[cfg(test)] mod tests { use super::*; + use arc_consensus_types::signing::Verifier; use arc_consensus_types::ArcContext; use arc_signer::local::{LocalSigningProvider, PrivateKey}; use malachitebft_app_channel::app::types::Keypair; diff --git a/crates/node/Cargo.toml b/crates/node/Cargo.toml index 13bf4a9..7ee8394 100644 --- a/crates/node/Cargo.toml +++ b/crates/node/Cargo.toml @@ -55,6 +55,7 @@ reth-node-core.workspace = true reth-node-ethereum.workspace = true reth-node-metrics = { workspace = true, optional = true, features = ["jemalloc"] } reth-prune-types.workspace = true +reth-rpc-builder.workspace = true reth-rpc-eth-types.workspace = true reth-rpc-server-types.workspace = true diff --git a/crates/node/README.md b/crates/node/README.md index 48988ff..e1adc5a 100644 --- a/crates/node/README.md +++ b/crates/node/README.md @@ -204,7 +204,8 @@ All three methods bypass the middleware. Pending-block queries additionally depe The execution layer is built on top of [Reth][reth], extending it with Arc-specific functionality: -- **Custom Precompiles** - Native implementations for Arc-specific operations (native coin control, post-quantum signatures, system accounting) +- **Custom Precompiles** - Native implementations for Arc-specific operations (native coin control, + post-quantum signatures, system accounting). - **Custom EVM Configuration** - Specialized gas calculations and execution logic - **Transaction Pool Enhancements** - Custom validation - **Block Executor** - Optimized block execution with Arc-specific features diff --git a/crates/node/src/main.rs b/crates/node/src/main.rs index df6ea9b..31b5084 100644 --- a/crates/node/src/main.rs +++ b/crates/node/src/main.rs @@ -43,6 +43,7 @@ use directories::BaseDirs; use reth_chainspec::EthChainSpec; use reth_ethereum::cli::interface::{Cli as RethCli, Commands}; use reth_node_core::version::default_extra_data; +use reth_rpc_builder::config::RethRpcServerConfig; use reth_rpc_server_types::{RethRpcModule, RpcModuleSelection}; use tracing::info; @@ -85,10 +86,9 @@ impl ArcCli { return Err("--builder.extradata is not supported"); } - // The middleware intercepts `eth_getBlockByNumber("pending")` on single - // calls only; batch calls fall through to Reth and rely on - // `--rpc.pending-block=none` to return null. Reject the asymmetric - // combination that would leak pending-block data via batch. + // The middleware intercepts pending-block and pool-based queries in both + // single and batch paths. Enforce `--rpc.pending-block=none` so reth + // replaces pending data with finalized data for all other queries. if compute_filter_pending_txs(&node_cmd.ext) && node_cmd.rpc.rpc_pending_block != reth_rpc_eth_types::builder::config::PendingBlockKind::None @@ -259,8 +259,9 @@ struct ArcExtraCli { /// /// Off by default: the middleware blocks `eth_subscribe("newPendingTransactions")`, /// `eth_newPendingTransactionFilter`, and returns null for - /// `eth_getBlockByNumber("pending")`. Set this flag on trusted / internal - /// nodes where exposing pending-tx state is desired (e.g. debugging). + /// `eth_getBlockByNumber("pending")` and `eth_getTransactionBySenderAndNonce`. + /// Set this flag on trusted / internal nodes where exposing pending-tx state + /// is desired (e.g. debugging). #[arg( long = "arc.expose-pending-txs", default_value_t = false, @@ -552,6 +553,7 @@ fn main() { let wait_for_payload = ext.wait_for_payload; let filter_pending_txs = compute_filter_pending_txs(&ext); + let max_response_body_size = builder.config().rpc.rpc_max_response_size_bytes(); let rebroadcast_interval = std::time::Duration::from_secs(ext.txpool_rebroadcast_interval); let handle = builder @@ -562,6 +564,7 @@ fn main() { payload_builder_deadline_ms, wait_for_payload, filter_pending_txs, + max_response_body_size, rebroadcast_interval, )) .launch_with_debug_capabilities() diff --git a/crates/node/tests/native_transfer.rs b/crates/node/tests/native_transfer.rs index 4ed8a1e..7eec4d5 100644 --- a/crates/node/tests/native_transfer.rs +++ b/crates/node/tests/native_transfer.rs @@ -27,7 +27,7 @@ use alloy_sol_types::SolEvent; use arc_evm::ArcEvm; use arc_precompiles::NATIVE_COIN_AUTHORITY_ADDRESS; use common::NativeCoinAuthority; -use reth_chainspec::{EthChainSpec, DEV}; +use reth_chainspec::{EthChainSpec, ForkCondition, DEV}; use reth_e2e_test_utils::wallet::Wallet; use reth_ethereum::evm::revm::{inspector::Inspector, interpreter::interpreter::EthInterpreter}; use reth_evm::Evm; @@ -61,17 +61,12 @@ where let sender = wallet.wallet_gen()[WALLET_SENDER_INDEX].clone(); let receiver = wallet.wallet_gen()[WALLET_RECEIVER_INDEX].clone(); let amount = U256::from(42); - // Zero6 hardfork requires extra gas for blocklist SLOAD checks: - // - Base intrinsic gas: 21000 - // - Caller blocklist check: 2100 - // - Recipient blocklist check (value > 0): 2100 - // Total: 25200 let tx = TxEnv { chain_id: Some(DEV.chain_id()), caller: sender.address(), kind: TxKind::Call(receiver.address()), value: amount, - gas_limit: 26_000, // Must exceed 25200 for Zero6 blocklist gas + gas_limit: 21_000, gas_price: 0, ..Default::default() }; @@ -126,7 +121,7 @@ fn inspect_evm_native_transfer() { let (tx, exec) = test_native_transfer(&mut evm, &wallet); let frame = inspector - .with_transaction_gas_limit(26_000) + .with_transaction_gas_limit(21_000) .into_geth_builder() .geth_call_traces(call_config, exec.result.gas_used()); @@ -135,7 +130,7 @@ fn inspect_evm_native_transfer() { assert_eq!(frame.value, Some(tx.value)); assert_eq!(frame.input, Bytes::new()); assert_eq!(frame.output, None); - assert_eq!(frame.gas, 26_000); + assert_eq!(frame.gas, 21_000); assert_eq!(frame.error, None); assert_eq!(frame.revert_reason, None); assert_eq!(frame.calls.len(), 0); @@ -149,9 +144,9 @@ fn inspect_evm_native_transfer() { fn chainspec_with_zero5() -> std::sync::Arc { use arc_execution_config::{chainspec::localdev_with_hardforks, hardforks::ArcHardfork}; localdev_with_hardforks(&[ - (ArcHardfork::Zero3, 0), - (ArcHardfork::Zero4, 0), - (ArcHardfork::Zero5, 0), + (ArcHardfork::Zero3, ForkCondition::Block(0)), + (ArcHardfork::Zero4, ForkCondition::Block(0)), + (ArcHardfork::Zero5, ForkCondition::Block(0)), ]) } @@ -170,7 +165,7 @@ fn evm_native_transfer_zero5_eip7708_log() { caller: sender.address(), kind: TxKind::Call(receiver.address()), value: amount, - gas_limit: 26_000, + gas_limit: 21_000, gas_price: 0, ..Default::default() }; @@ -201,7 +196,10 @@ fn evm_native_transfer_zero5_eip7708_log() { fn evm_native_transfer_pre_zero5_emits_native_coin_transferred() { use arc_execution_config::{chainspec::localdev_with_hardforks, hardforks::ArcHardfork}; // Zero4 active but NOT Zero5 - let chain_spec = localdev_with_hardforks(&[(ArcHardfork::Zero3, 0), (ArcHardfork::Zero4, 0)]); + let chain_spec = localdev_with_hardforks(&[ + (ArcHardfork::Zero3, ForkCondition::Block(0)), + (ArcHardfork::Zero4, ForkCondition::Block(0)), + ]); let (mut evm, wallet) = common::setup_evm_with_chainspec(chain_spec); let sender = wallet.wallet_gen()[WALLET_SENDER_INDEX].clone(); @@ -213,7 +211,7 @@ fn evm_native_transfer_pre_zero5_emits_native_coin_transferred() { caller: sender.address(), kind: TxKind::Call(receiver.address()), value: amount, - gas_limit: 26_000, + gas_limit: 21_000, gas_price: 0, ..Default::default() }; @@ -241,7 +239,10 @@ fn evm_native_transfer_pre_zero5_emits_native_coin_transferred() { /// Helper to create a chainspec with Zero4 active but NOT Zero5. fn chainspec_pre_zero5() -> std::sync::Arc { use arc_execution_config::{chainspec::localdev_with_hardforks, hardforks::ArcHardfork}; - localdev_with_hardforks(&[(ArcHardfork::Zero3, 0), (ArcHardfork::Zero4, 0)]) + localdev_with_hardforks(&[ + (ArcHardfork::Zero3, ForkCondition::Block(0)), + (ArcHardfork::Zero4, ForkCondition::Block(0)), + ]) } /// B1: Zero5 self-transfer (sender == receiver) with non-zero value produces no logs. @@ -260,7 +261,7 @@ fn evm_native_transfer_zero5_self_transfer_no_log() { caller: sender.address(), kind: TxKind::Call(sender.address()), value: amount, - gas_limit: 26_000, + gas_limit: 21_000, gas_price: 0, ..Default::default() }; @@ -306,7 +307,7 @@ fn evm_native_transfer_zero5_zero_value_no_log() { caller: sender.address(), kind: TxKind::Call(receiver.address()), value: U256::ZERO, - gas_limit: 26_000, + gas_limit: 21_000, gas_price: 0, ..Default::default() }; @@ -384,7 +385,7 @@ fn evm_native_transfer_zero5_to_zero_address_reverts() { caller: sender.address(), kind: TxKind::Call(alloy_primitives::Address::ZERO), value: amount, - gas_limit: 26_000, + gas_limit: 21_000, gas_price: 0, ..Default::default() }; @@ -434,7 +435,7 @@ fn evm_native_transfer_event_ordering_preserved_across_zero5() { caller: sender.address(), kind: TxKind::Call(receiver.address()), value: amount, - gas_limit: 26_000, + gas_limit: 21_000, gas_price: 0, ..Default::default() }; @@ -453,7 +454,7 @@ fn evm_native_transfer_event_ordering_preserved_across_zero5() { caller: sender_z5.address(), kind: TxKind::Call(receiver_z5.address()), value: amount, - gas_limit: 26_000, + gas_limit: 21_000, gas_price: 0, ..Default::default() }; @@ -513,7 +514,7 @@ fn evm_native_transfer_zero5_amsterdam_eip7708_log() { caller: sender.address(), kind: TxKind::Call(receiver.address()), value: amount, - gas_limit: 26_000, + gas_limit: 21_000, gas_price: 0, ..Default::default() }; @@ -554,9 +555,9 @@ fn evm_native_transfer_hardfork_boundary_zero5_activation() { use reth_evm::ConfigureEvm; let chain_spec = localdev_with_hardforks(&[ - (ArcHardfork::Zero3, 0), - (ArcHardfork::Zero4, 0), - (ArcHardfork::Zero5, 10), + (ArcHardfork::Zero3, ForkCondition::Block(0)), + (ArcHardfork::Zero4, ForkCondition::Block(0)), + (ArcHardfork::Zero5, ForkCondition::Block(10)), ]); let amount = U256::from(42); @@ -575,7 +576,7 @@ fn evm_native_transfer_hardfork_boundary_zero5_activation() { caller: sender.address(), kind: TxKind::Call(receiver.address()), value: amount, - gas_limit: 26_000, + gas_limit: 21_000, gas_price: 0, ..Default::default() }; @@ -619,7 +620,7 @@ fn evm_native_transfer_hardfork_boundary_zero5_activation() { caller: sender.address(), kind: TxKind::Call(receiver.address()), value: amount, - gas_limit: 26_000, + gas_limit: 21_000, gas_price: 0, ..Default::default() }; @@ -866,6 +867,8 @@ fn evm_native_create2_with_value_zero5_emits_eip7708_log() { }, ); + let trace_db = db.clone(); + let trace_evm_env = evm_env.clone(); let mut evm = evm_config.evm_with_env(db, evm_env); let tx = TxEnv { @@ -878,7 +881,9 @@ fn evm_native_create2_with_value_zero5_emits_eip7708_log() { ..Default::default() }; - let exec = evm.transact_raw(tx).expect("CREATE2 tx should be accepted"); + let exec = evm + .transact_raw(tx.clone()) + .expect("CREATE2 tx should be accepted"); assert!( exec.result.is_success(), "CREATE2 with value should succeed: {:?}", @@ -920,4 +925,37 @@ fn evm_native_create2_with_value_zero5_emits_eip7708_log() { decoded_create2.value, endowment, "CREATE2 Transfer amount should match the endowment" ); + + let call_config = CallConfig { + with_log: Some(true), + only_top_call: Some(false), + }; + let mut inspector = + TracingInspector::new(TracingInspectorConfig::from_geth_call_config(&call_config)); + let mut trace_evm = + evm_config.evm_with_env_and_inspector(trace_db, trace_evm_env, &mut inspector); + let trace_exec = trace_evm + .transact_raw(tx.clone()) + .expect("inspected CREATE2 tx should be accepted"); + assert!( + trace_exec.result.is_success(), + "inspected CREATE2 with value should succeed: {:?}", + trace_exec.result + ); + drop(trace_evm); + + let frame = inspector + .with_transaction_gas_limit(tx.gas_limit) + .into_geth_builder() + .geth_call_traces(call_config, trace_exec.result.gas_used()); + assert_eq!(frame.typ, "CALL"); + assert_eq!(frame.to, Some(factory)); + assert_eq!( + frame.calls.len(), + 1, + "factory should have exactly one CREATE2 child frame" + ); + let create2_frame = &frame.calls[0]; + assert_eq!(create2_frame.typ, "CREATE2"); + assert_eq!(create2_frame.to, Some(expected_create2_addr)); } diff --git a/crates/precompiles/Cargo.toml b/crates/precompiles/Cargo.toml index 2ec2074..b88e32b 100644 --- a/crates/precompiles/Cargo.toml +++ b/crates/precompiles/Cargo.toml @@ -36,7 +36,7 @@ revm-interpreter.workspace = true revm-primitives.workspace = true serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, default-features = false, features = ["alloc"] } -slh-dsa = "0.2.0-rc.4" +slh-dsa.workspace = true thiserror.workspace = true [dev-dependencies] diff --git a/crates/precompiles/src/call_from.rs b/crates/precompiles/src/call_from.rs index df40637..328c876 100644 --- a/crates/precompiles/src/call_from.rs +++ b/crates/precompiles/src/call_from.rs @@ -58,6 +58,16 @@ pub fn abi_decode_gas(data_len: usize) -> u64 { ABI_DECODE_BASE_GAS.saturating_add(words.saturating_mul(gas::COPY)) } +/// Fixed base gas for complete_subcall ABI encoding of `(bool success, bytes returnData)`. +/// Covers the fixed-size tuple header (bool + offset + length words). +pub const ABI_ENCODE_BASE_GAS: u64 = 100; + +/// Computes total complete_subcall gas: base overhead + ceil(data.len() / 32) * COPY. +pub fn abi_encode_gas(data_len: usize) -> u64 { + let words = (data_len as u64).div_ceil(32); + ABI_ENCODE_BASE_GAS.saturating_add(words.saturating_mul(gas::COPY)) +} + sol! { /// CallFrom precompile interface. interface ICallFrom { @@ -89,7 +99,7 @@ fn decode_child_call(inputs: &CallInputs) -> Result<(CallInputs, u64), SubcallEr )); } }; - let decoded = ICallFrom::callFromCall::abi_decode(input_bytes) + let decoded = ICallFrom::callFromCall::abi_decode_validate(input_bytes) .map_err(|e| SubcallError::AbiDecodeError(format!("callFrom: {e}")))?; let sender = decoded.sender; @@ -159,6 +169,7 @@ impl SubcallPrecompile for CallFromPrecompile { let child_success = outcome.result.result.is_ok(); let child_output = outcome.result.output.clone(); + let encode_gas = abi_encode_gas(child_output.len()); // ABI-encode (bool success, bytes returnData) matching the declared interface. // The precompile always succeeds; the caller inspects the bool to determine @@ -171,6 +182,7 @@ impl SubcallPrecompile for CallFromPrecompile { Ok(SubcallCompletionResult { output: encoded.into(), success: true, + gas_overhead: encode_gas, }) } } @@ -180,17 +192,31 @@ mod tests { use super::*; /// Guard against silent upstream changes to the EVM COPY gas cost. - /// An unexpected change would alter `abi_decode_gas` results, effectively - /// creating an unintentional hardfork. + /// An unexpected change would alter `abi_decode_gas` and `abi_encode_gas` results, + /// effectively creating an unintentional hardfork. #[test] fn evm_copy_gas_cost_is_3() { assert_eq!( gas::COPY, 3, - "revm COPY gas cost changed — review abi_decode_gas impact" + "revm COPY gas cost changed — review abi_decode_gas/abi_encode_gas impact" ); } + #[test] + fn abi_encode_gas_base_plus_per_word() { + // 0 bytes → base only (no per-word charge since div_ceil(0, 32) = 0) + assert_eq!(abi_encode_gas(0), ABI_ENCODE_BASE_GAS); + // 1 byte → 1 word + assert_eq!(abi_encode_gas(1), ABI_ENCODE_BASE_GAS + gas::COPY); + // 32 bytes → 1 word + assert_eq!(abi_encode_gas(32), ABI_ENCODE_BASE_GAS + gas::COPY); + // 33 bytes → 2 words + assert_eq!(abi_encode_gas(33), ABI_ENCODE_BASE_GAS + 2 * gas::COPY); + // 64 bytes → 2 words + assert_eq!(abi_encode_gas(64), ABI_ENCODE_BASE_GAS + 2 * gas::COPY); + } + /// Guard against `trace_child_call` and `init_subcall` diverging. /// /// Both methods share `decode_child_call`, but if someone bypasses it in one diff --git a/crates/precompiles/src/helpers.rs b/crates/precompiles/src/helpers.rs index 084044d..64b741b 100644 --- a/crates/precompiles/src/helpers.rs +++ b/crates/precompiles/src/helpers.rs @@ -16,11 +16,12 @@ use alloy_evm::EvmInternals; use alloy_primitives::{Address, Bytes, StorageKey, U256}; -use alloy_sol_types::{SolEvent, SolValue}; +use alloy_sol_types::{SolCall, SolEvent, SolValue}; use reth_ethereum::evm::revm::precompile::{PrecompileError, PrecompileOutput}; use reth_evm::precompiles::PrecompileInput; use revm::context_interface::journaled_state::TransferError; use revm::state::AccountInfo; +use revm_context_interface::cfg::gas::CALL_STIPEND; use revm_interpreter::Gas; use revm_primitives::address; use revm_primitives::constants::KECCAK_EMPTY; @@ -37,6 +38,8 @@ pub const REVERT_SELECTOR: [u8; 4] = [0x08, 0xc3, 0x79, 0xa0]; /// Approximate gas costs for precompile read / writes pub const PRECOMPILE_SSTORE_GAS_COST: u64 = 2900; pub const PRECOMPILE_SLOAD_GAS_COST: u64 = 2100; +/// EIP-161 account creation surcharge when crediting an empty account. +pub const PRECOMPILE_EMPTY_ACCOUNT_GAS_COST: u64 = 25_000; /// Gas costs for emitting a log pub const LOG_BASE_COST: u64 = 375; // Base cost for emitting a log @@ -69,9 +72,16 @@ pub fn revert_message_to_bytes(msg: &str) -> Bytes { Bytes::from(result) } -/// Gas penalty for ABI decode revert (invalid selector, etc) -/// In normal cases we didn't record this cost, but when reverted, add this penalty to the gas usage. -pub(crate) const PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY: u64 = 200; +/// Gas penalty added to early-path reverts so callers cannot probe precompiles +/// for free. +/// +/// Pre-Zero6: applied only to ABI decode failures (truncated input, unknown +/// selector) via `new_reverted_with_penalty`. +/// +/// Zero6+: also applied to authorization and validation reverts (unauthorized +/// caller, blocklist, zero address, zero amount, overflow) via +/// [`new_reverted_with_early_penalty`]. +pub(crate) const PRECOMPILE_EARLY_REVERT_GAS_PENALTY: u64 = 200; /// Enum to represent either a reverted precompile output or an error pub(crate) enum PrecompileErrorOrRevert { @@ -116,6 +126,19 @@ fn account_load_cost(is_cold: bool, hardfork_flags: ArcHardforkFlags) -> u64 { } } +fn record_zero6_empty_account_creation_cost( + gas_counter: &mut Gas, + account_info: &AccountInfo, + amount: U256, + hardfork_flags: ArcHardforkFlags, +) -> Result<(), PrecompileErrorOrRevert> { + if hardfork_flags.is_active(ArcHardfork::Zero6) && !amount.is_zero() && account_info.is_empty() + { + record_cost_or_out_of_gas(gas_counter, PRECOMPILE_EMPTY_ACCOUNT_GAS_COST)?; + } + Ok(()) +} + pub(crate) fn record_cost_or_out_of_gas( gas_counter: &mut Gas, cost: u64, @@ -145,6 +168,44 @@ impl From for Result } } +/// Build a revert that charges [`PRECOMPILE_EARLY_REVERT_GAS_PENALTY`] +/// when Zero6 is active, and zero gas otherwise. +/// +/// Use at early-path reverts (unauthorized caller, blocklist, zero address, +/// zero amount, overflow) to give uniform gas accounting under Zero6 and +/// prevent free probing of precompile revert paths. +pub(crate) fn new_reverted_with_early_penalty( + gas_counter: Gas, + msg: &str, + hardfork_flags: ArcHardforkFlags, +) -> PrecompileErrorOrRevert { + if hardfork_flags.is_active(ArcHardfork::Zero6) { + PrecompileErrorOrRevert::new_reverted_with_penalty( + gas_counter, + PRECOMPILE_EARLY_REVERT_GAS_PENALTY, + msg, + ) + } else { + PrecompileErrorOrRevert::new_reverted(gas_counter, msg) + } +} + +/// ABI-decodes raw precompile call arguments. +/// +/// Pre-Zero6, this preserves the legacy lenient Alloy decode behavior. Zero6 +/// switches to validated decoding, which rejects non-canonical ABI padding for +/// short static types such as `address`, `bool`, and `uint64`. +pub(crate) fn abi_decode_raw_with_zero6_validation( + input: &[u8], + hardfork_flags: ArcHardforkFlags, +) -> alloy_sol_types::Result { + if hardfork_flags.is_active(ArcHardfork::Zero6) { + C::abi_decode_raw_validate(input) + } else { + C::abi_decode_raw(input) + } +} + /// Reads a value from storage for stateful precompiles. /// /// # Parameters @@ -210,6 +271,11 @@ pub(crate) fn read( /// - Pre-Zero5: Fixed cost of 2,900 gas units /// - Zero5+: EIP-2929/EIP-2200 aware (varies based on warm/cold and value changes) /// +/// # EIP-2200 Sentry (Zero6+) +/// Mirrors revm's SSTORE opcode behavior: if the remaining gas is less than or +/// equal to [`CALL_STIPEND`] (2,300), the call frame fails with `OutOfGas` +/// before any storage mutation is journaled. +/// /// # Returns /// - `Ok(())`: Success /// - `Err(PrecompileErrorOrRevert)`: If out of gas or storage write fails @@ -234,6 +300,12 @@ pub(crate) fn write( gas_counter: &mut Gas, hardfork_flags: ArcHardforkFlags, ) -> Result<(), PrecompileErrorOrRevert> { + // EIP-2200 reentrancy sentry: refuse SSTORE when remaining gas does not + // exceed the call stipend. + if hardfork_flags.is_active(ArcHardfork::Zero6) && gas_counter.remaining() <= CALL_STIPEND { + return Err(PrecompileErrorOrRevert::Error(PrecompileError::OutOfGas)); + } + // Parse the input as a U256 value let value = U256::from_be_slice(input); @@ -303,10 +375,9 @@ pub(crate) fn transfer( // Check that the account can be decremented by the amount check_can_decr_account(&loaded_from_account.info, amount, gas_counter)?; - // Charge SLOAD + SSTORE for both accounts, mimicking the prior balance_decr + - // balance_incr sequence. Pre-Zero6 each SLOAD is flat; Zero6+ uses cold/warm pricing - // via account_load_cost. + // Mirrors prior balance_decr + balance_incr; Zero6+ uses cold/warm via account_load_cost. record_cost_or_out_of_gas(gas_counter, PRECOMPILE_SSTORE_GAS_COST)?; + let to_load = internals.load_account(to).map_err(|_| { PrecompileErrorOrRevert::Error(PrecompileError::Other(ERR_EXECUTION_REVERTED.into())) })?; @@ -314,6 +385,7 @@ pub(crate) fn transfer( gas_counter, account_load_cost(to_load.is_cold, hardfork_flags), )?; + record_cost_or_out_of_gas(gas_counter, PRECOMPILE_SSTORE_GAS_COST)?; if hardfork_flags.is_active(ArcHardfork::Zero5) && to_load.is_selfdestructed() { @@ -323,6 +395,8 @@ pub(crate) fn transfer( )); } + record_zero6_empty_account_creation_cost(gas_counter, &to_load.info, amount, hardfork_flags)?; + let transfer_result = internals.transfer(from, to, amount).map_err(|_e| { PrecompileErrorOrRevert::new_reverted(*gas_counter, ERR_EXECUTION_REVERTED) })?; @@ -381,6 +455,7 @@ pub(crate) fn balance_incr( // Update state record_cost_or_out_of_gas(gas_counter, PRECOMPILE_SSTORE_GAS_COST)?; + record_zero6_empty_account_creation_cost(gas_counter, &account.info, amount, hardfork_flags)?; internals.balance_incr(to, amount).map_err(|_| { PrecompileErrorOrRevert::Error(PrecompileError::Other(ERR_EXECUTION_REVERTED.into())) })?; @@ -511,9 +586,132 @@ pub(crate) fn emit_event( #[cfg(test)] mod tests { use super::*; - use alloy_primitives::U256; + use alloy_primitives::{address, U256}; + use alloy_sol_types::sol; use revm_primitives::B256; + sol! { + interface IAbiDecodeTest { + function takesAddress(address account) external; + function takesUint64(uint64 value) external; + } + } + + #[test] + fn abi_decode_raw_validation_is_zero6_gated_for_address_padding() { + let mut input = [0u8; 32]; + input[..12].fill(0x11); + input[12] = 0xaa; + + let pre_zero6 = abi_decode_raw_with_zero6_validation::( + &input, + ArcHardforkFlags::with(&[ArcHardfork::Zero5]), + ) + .expect("pre-Zero6 decode preserves legacy lenient padding"); + assert_eq!( + pre_zero6.account, + address!("aa00000000000000000000000000000000000000") + ); + + let zero6 = abi_decode_raw_with_zero6_validation::( + &input, + ArcHardforkFlags::with(&[ArcHardfork::Zero6]), + ); + assert!(zero6.is_err(), "Zero6 rejects non-zero address padding"); + } + + #[test] + fn abi_decode_raw_validation_is_zero6_gated_for_uint64_padding() { + let mut input = [0u8; 32]; + input[0] = 0x11; + input[31] = 42; + + let pre_zero6 = abi_decode_raw_with_zero6_validation::( + &input, + ArcHardforkFlags::with(&[ArcHardfork::Zero5]), + ) + .expect("pre-Zero6 decode preserves legacy lenient padding"); + assert_eq!(pre_zero6.value, 42); + + let zero6 = abi_decode_raw_with_zero6_validation::( + &input, + ArcHardforkFlags::with(&[ArcHardfork::Zero6]), + ); + assert!(zero6.is_err(), "Zero6 rejects non-zero uint64 padding"); + } + + #[test] + fn zero6_empty_account_creation_cost_charges_only_for_nonzero_empty_accounts() { + let zero6 = ArcHardforkFlags::with(&[ArcHardfork::Zero6]); + let pre_zero6 = ArcHardforkFlags::with(&[ArcHardfork::Zero5]); + + let mut gas_counter = Gas::new(100_000); + assert!(record_zero6_empty_account_creation_cost( + &mut gas_counter, + &AccountInfo::default(), + U256::from(1), + pre_zero6, + ) + .is_ok()); + assert_eq!(gas_counter.used(), 0); + + assert!(record_zero6_empty_account_creation_cost( + &mut gas_counter, + &AccountInfo::default(), + U256::ZERO, + zero6, + ) + .is_ok()); + assert_eq!(gas_counter.used(), 0); + + assert!(record_zero6_empty_account_creation_cost( + &mut gas_counter, + &AccountInfo::default(), + U256::from(1), + zero6, + ) + .is_ok()); + assert_eq!(gas_counter.used(), PRECOMPILE_EMPTY_ACCOUNT_GAS_COST); + + for non_empty_account in [ + AccountInfo { + balance: U256::from(1), + ..Default::default() + }, + AccountInfo { + nonce: 1, + ..Default::default() + }, + AccountInfo { + code_hash: B256::from([1u8; 32]), + ..Default::default() + }, + ] { + assert!(record_zero6_empty_account_creation_cost( + &mut gas_counter, + &non_empty_account, + U256::from(1), + zero6, + ) + .is_ok()); + assert_eq!(gas_counter.used(), PRECOMPILE_EMPTY_ACCOUNT_GAS_COST); + } + } + + #[test] + fn zero6_empty_account_creation_cost_errors_when_out_of_gas() { + let mut gas_counter = Gas::new(PRECOMPILE_EMPTY_ACCOUNT_GAS_COST.saturating_sub(1)); + assert!(matches!( + record_zero6_empty_account_creation_cost( + &mut gas_counter, + &AccountInfo::default(), + U256::from(1), + ArcHardforkFlags::with(&[ArcHardfork::Zero6]), + ), + Err(PrecompileErrorOrRevert::Error(PrecompileError::OutOfGas)) + )); + } + // Generated 11/30/2025 with AI assistance #[test] fn test_check_can_decr_account() { diff --git a/crates/precompiles/src/lib.rs b/crates/precompiles/src/lib.rs index 311c415..9ffa5b1 100644 --- a/crates/precompiles/src/lib.rs +++ b/crates/precompiles/src/lib.rs @@ -74,6 +74,13 @@ //! }); //! ``` //! +//! ### Subcall Precompiles +//! Subcall precompiles need to run a child EVM call and then post-process the +//! child result. They are not registered through `ArcPrecompileProvider`. +//! Implement [`crate::subcall::SubcallPrecompile`] instead, then register the +//! address, implementation, and allowed callers in `ArcEvmFactory::build_subcall_registry`. +//! [`crate::call_from::CallFromPrecompile`] is the production example. +//! //! ## Creating a New Precompile //! //! ### Step 1: Choose an Address @@ -101,10 +108,13 @@ //! let mut gas_counter = Gas::new(precompile_input.gas); //! let mut precompile_input = precompile_input; //! -//! let args = IMyPrecompile::myFunctionCall::abi_decode_raw(input) +//! let args = abi_decode_raw_with_zero6_validation::( +//! input, +//! hardfork_flags, +//! ) //! .map_err(|_| PrecompileErrorOrRevert::new_reverted_with_penalty( //! gas_counter, -//! PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY, +//! PRECOMPILE_EARLY_REVERT_GAS_PENALTY, //! ERR_EXECUTION_REVERTED, //! ))?; //! @@ -123,6 +133,7 @@ //! ``` //! //! ### Step 4: Register the Precompile +//! Provider-managed precompiles should be registered in the precompile map. //! Add a match arm to `ArcPrecompileProvider::create_precompiles_map` in //! `precompile_provider.rs`: //! ```rust,ignore @@ -132,13 +143,18 @@ //! )), //! ``` //! +//! For a subcall precompile, skip `create_precompiles_map`. Register it in +//! `ArcEvmFactory::build_subcall_registry` with an `AllowedCallers` policy so +//! `ArcEvm::frame_init` can intercept the call and drive the two-phase +//! `init_subcall` / `complete_subcall` flow. +//! //! ## Gas Accounting //! //! The `precompile!` macro does not track gas on its own — each arm constructs a `Gas` //! counter from `precompile_input.gas` and threads `&mut gas_counter` through the helpers //! (`read`, `write`, `emit_event`, `balance_incr`, …). Helpers mutate the counter in place //! and return `PrecompileErrorOrRevert::Error(OutOfGas)` when the remaining gas is -//! insufficient. The macro adds `PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY` when the +//! insufficient. The macro adds `PRECOMPILE_EARLY_REVERT_GAS_PENALTY` when the //! selector is unknown or the input is shorter than 4 bytes; arms should use the same //! penalty when ABI decoding fails. //! diff --git a/crates/precompiles/src/macros.rs b/crates/precompiles/src/macros.rs index 03e3067..1d5c29d 100644 --- a/crates/precompiles/src/macros.rs +++ b/crates/precompiles/src/macros.rs @@ -33,7 +33,7 @@ /// `PrecompileErrorOrRevert::Revert(...)` are converted into an `Ok(PrecompileOutput)` /// carrying the revert payload; `PrecompileErrorOrRevert::Error(...)` becomes `Err`. /// If the calldata is shorter than 4 bytes or the selector is unknown, the macro -/// charges `PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY` and returns a revert. +/// charges `PRECOMPILE_EARLY_REVERT_GAS_PENALTY` and returns a revert. /// /// # Example /// ```rust,ignore @@ -88,9 +88,9 @@ /// - Fallback revert (with gas penalty) for unknown selectors or truncated input /// - Conversion of `PrecompileErrorOrRevert` into the final `Result` /// -/// ABI decoding, gas accounting, and output encoding remain the arm body's job; call -/// `<$fn_call>::abi_decode_raw` on the supplied calldata bytes when you need the -/// decoded arguments. +/// ABI decoding, gas accounting, and output encoding remain the arm body's job; use +/// `helpers::abi_decode_raw_with_zero6_validation` on the supplied calldata bytes when +/// you need the decoded arguments. #[macro_export] macro_rules! precompile { ($fn_name:ident, $precompile_input:ident, $hardfork_flags:ident; { @@ -107,7 +107,7 @@ macro_rules! precompile { if input_bytes.len() < 4 { return $crate::helpers::PrecompileErrorOrRevert::new_reverted_with_penalty( - gas_counter, PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY, "Input too short").into(); + gas_counter, PRECOMPILE_EARLY_REVERT_GAS_PENALTY, "Input too short").into(); } let selector: [u8; 4] = input_bytes[0..4].try_into().unwrap(); @@ -121,7 +121,7 @@ macro_rules! precompile { ),* _ => { return $crate::helpers::PrecompileErrorOrRevert::new_reverted_with_penalty( - gas_counter, PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY, "Invalid selector").into(); + gas_counter, PRECOMPILE_EARLY_REVERT_GAS_PENALTY, "Invalid selector").into(); }, }; diff --git a/crates/precompiles/src/native_coin_authority.rs b/crates/precompiles/src/native_coin_authority.rs index b828164..879350d 100644 --- a/crates/precompiles/src/native_coin_authority.rs +++ b/crates/precompiles/src/native_coin_authority.rs @@ -20,11 +20,11 @@ //! transfer, and total supply management. use crate::helpers::{ - balance_decr, balance_incr, check_delegatecall, check_gas_remaining, check_staticcall, - emit_event, read, transfer, write, PrecompileErrorOrRevert, ERR_BLOCKED_ADDRESS, - ERR_EXECUTION_REVERTED, LOG_BASE_COST, LOG_DATA_COST, LOG_TOPIC_COST, - NATIVE_FIAT_TOKEN_ADDRESS, PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY, PRECOMPILE_SLOAD_GAS_COST, - PRECOMPILE_SSTORE_GAS_COST, + abi_decode_raw_with_zero6_validation, balance_decr, balance_incr, check_delegatecall, + check_gas_remaining, check_staticcall, emit_event, new_reverted_with_early_penalty, read, + transfer, write, PrecompileErrorOrRevert, ERR_BLOCKED_ADDRESS, ERR_EXECUTION_REVERTED, + LOG_BASE_COST, LOG_DATA_COST, LOG_TOPIC_COST, NATIVE_FIAT_TOKEN_ADDRESS, + PRECOMPILE_EARLY_REVERT_GAS_PENALTY, PRECOMPILE_SLOAD_GAS_COST, PRECOMPILE_SSTORE_GAS_COST, }; use crate::native_coin_control::{compute_is_blocklisted_storage_slot, UNBLOCKLISTED_STATUS}; use crate::precompile; @@ -208,20 +208,23 @@ precompile!(run_native_coin_authority, precompile_input, hardfork_flags; { )?; // Decode arguments passed to mint function - let args = INativeCoinAuthority::mintCall::abi_decode_raw( - input) + let args = abi_decode_raw_with_zero6_validation::( + input, + hardfork_flags, + ) .map_err(|_| PrecompileErrorOrRevert::new_reverted_with_penalty( - gas_counter, PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY, ERR_EXECUTION_REVERTED) + gas_counter, PRECOMPILE_EARLY_REVERT_GAS_PENALTY, ERR_EXECUTION_REVERTED) )?; if hardfork_flags.is_active(ArcHardfork::Zero5) { // Zero5+: Skip early gas check - warm/cold pricing makes upfront calculation unreliable // Check authorization if precompile_input.caller != ALLOWED_CALLER_ADDRESS { - return Err(PrecompileErrorOrRevert::new_reverted( + return Err(new_reverted_with_early_penalty( gas_counter, ERR_CANNOT_MINT, + hardfork_flags, )); } } else { @@ -235,7 +238,7 @@ precompile!(run_native_coin_authority, precompile_input, hardfork_flags; { &mut gas_counter, hardfork_flags, )? { - return Err(PrecompileErrorOrRevert::new_reverted(gas_counter, ERR_CANNOT_MINT)); + return Err(new_reverted_with_early_penalty(gas_counter, ERR_CANNOT_MINT, hardfork_flags)); } } @@ -248,17 +251,17 @@ precompile!(run_native_coin_authority, precompile_input, hardfork_flags; { // Reject minting to zero address (Zero5+) if hardfork_flags.is_active(ArcHardfork::Zero5) && args.to == Address::ZERO { - return Err(PrecompileErrorOrRevert::new_reverted(gas_counter, ERR_ZERO_ADDRESS)); + return Err(new_reverted_with_early_penalty(gas_counter, ERR_ZERO_ADDRESS, hardfork_flags)); } // Check blocklist if is_blocklisted(&mut precompile_input.internals, args.to, &mut gas_counter, hardfork_flags)? { - return Err(PrecompileErrorOrRevert::new_reverted(gas_counter, ERR_BLOCKED_ADDRESS)); + return Err(new_reverted_with_early_penalty(gas_counter, ERR_BLOCKED_ADDRESS, hardfork_flags)); } // Validate amount if args.amount == U256::ZERO { - return Err(PrecompileErrorOrRevert::new_reverted(gas_counter, ERR_ZERO_AMOUNT)); + return Err(new_reverted_with_early_penalty(gas_counter, ERR_ZERO_AMOUNT, hardfork_flags)); } // Read current total supply @@ -274,7 +277,7 @@ precompile!(run_native_coin_authority, precompile_input, hardfork_flags; { // Check for overflow let new_total_supply = match current_total_supply.checked_add(args.amount) { Some(new_total_supply) => new_total_supply, - None => return Err(PrecompileErrorOrRevert::new_reverted(gas_counter, ERR_OVERFLOW)), + None => return Err(new_reverted_with_early_penalty(gas_counter, ERR_OVERFLOW, hardfork_flags)), }; // Write new total supply @@ -323,10 +326,13 @@ precompile!(run_native_coin_authority, precompile_input, hardfork_flags; { )?; // Decode arguments passed to burn function - let args = INativeCoinAuthority::burnCall::abi_decode_raw(input) + let args = abi_decode_raw_with_zero6_validation::( + input, + hardfork_flags, + ) .map_err(|_| PrecompileErrorOrRevert::new_reverted_with_penalty( - gas_counter, PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY, ERR_EXECUTION_REVERTED, + gas_counter, PRECOMPILE_EARLY_REVERT_GAS_PENALTY, ERR_EXECUTION_REVERTED, ) )?; @@ -334,9 +340,10 @@ precompile!(run_native_coin_authority, precompile_input, hardfork_flags; { if hardfork_flags.is_active(ArcHardfork::Zero5) { // Zero5+: Skip early gas check - warm/cold pricing makes upfront calculation unreliable if precompile_input.caller != ALLOWED_CALLER_ADDRESS { - return Err(PrecompileErrorOrRevert::new_reverted( + return Err(new_reverted_with_early_penalty( gas_counter, ERR_CANNOT_BURN, + hardfork_flags, )); } } else { @@ -344,7 +351,7 @@ precompile!(run_native_coin_authority, precompile_input, hardfork_flags; { check_gas_remaining(&gas_counter, BURN_GAS_COST)?; if !(is_authorized(&mut precompile_input.internals, precompile_input.caller, &mut gas_counter, hardfork_flags)?) { - return Err(PrecompileErrorOrRevert::new_reverted(gas_counter, ERR_CANNOT_BURN)); + return Err(new_reverted_with_early_penalty(gas_counter, ERR_CANNOT_BURN, hardfork_flags)); } } @@ -357,17 +364,17 @@ precompile!(run_native_coin_authority, precompile_input, hardfork_flags; { // Reject burning from zero address (Zero5+) if hardfork_flags.is_active(ArcHardfork::Zero5) && args.from == Address::ZERO { - return Err(PrecompileErrorOrRevert::new_reverted(gas_counter, ERR_ZERO_ADDRESS)); + return Err(new_reverted_with_early_penalty(gas_counter, ERR_ZERO_ADDRESS, hardfork_flags)); } // Check blocklist if is_blocklisted(&mut precompile_input.internals, args.from, &mut gas_counter, hardfork_flags)? { - return Err(PrecompileErrorOrRevert::new_reverted(gas_counter, ERR_BLOCKED_ADDRESS)); + return Err(new_reverted_with_early_penalty(gas_counter, ERR_BLOCKED_ADDRESS, hardfork_flags)); } // Validate amount if args.amount == U256::ZERO { - return Err(PrecompileErrorOrRevert::new_reverted(gas_counter, ERR_ZERO_AMOUNT)); + return Err(new_reverted_with_early_penalty(gas_counter, ERR_ZERO_AMOUNT, hardfork_flags)); } // Check balance and burn tokens @@ -428,10 +435,13 @@ precompile!(run_native_coin_authority, precompile_input, hardfork_flags; { )?; // Decode arguments passed to transfer function - let args = INativeCoinAuthority::transferCall::abi_decode_raw(input) + let args = abi_decode_raw_with_zero6_validation::( + input, + hardfork_flags, + ) .map_err(|_| PrecompileErrorOrRevert::new_reverted_with_penalty( - gas_counter, PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY, ERR_EXECUTION_REVERTED, + gas_counter, PRECOMPILE_EARLY_REVERT_GAS_PENALTY, ERR_EXECUTION_REVERTED, ) )?; @@ -439,9 +449,10 @@ precompile!(run_native_coin_authority, precompile_input, hardfork_flags; { if hardfork_flags.is_active(ArcHardfork::Zero5) { // Zero5+: Skip early gas check - warm/cold pricing makes upfront calculation unreliable if precompile_input.caller != ALLOWED_CALLER_ADDRESS { - return Err(PrecompileErrorOrRevert::new_reverted( + return Err(new_reverted_with_early_penalty( gas_counter, ERR_CANNOT_TRANSFER, + hardfork_flags, )); } } else { @@ -456,7 +467,7 @@ precompile!(run_native_coin_authority, precompile_input, hardfork_flags; { check_gas_remaining(&gas_counter, expect_gas_cost)?; if !(is_authorized(&mut precompile_input.internals, precompile_input.caller, &mut gas_counter, hardfork_flags)?) { - return Err(PrecompileErrorOrRevert::new_reverted(gas_counter, ERR_CANNOT_TRANSFER)); + return Err(new_reverted_with_early_penalty(gas_counter, ERR_CANNOT_TRANSFER, hardfork_flags)); } } @@ -471,15 +482,15 @@ precompile!(run_native_coin_authority, precompile_input, hardfork_flags; { if hardfork_flags.is_active(ArcHardfork::Zero5) && (args.from == Address::ZERO || args.to == Address::ZERO) { - return Err(PrecompileErrorOrRevert::new_reverted(gas_counter, ERR_ZERO_ADDRESS)); + return Err(new_reverted_with_early_penalty(gas_counter, ERR_ZERO_ADDRESS, hardfork_flags)); } // Check blocklist if is_blocklisted(&mut precompile_input.internals, args.from, &mut gas_counter, hardfork_flags)? { - return Err(PrecompileErrorOrRevert::new_reverted(gas_counter, ERR_BLOCKED_ADDRESS)); + return Err(new_reverted_with_early_penalty(gas_counter, ERR_BLOCKED_ADDRESS, hardfork_flags)); } if is_blocklisted(&mut precompile_input.internals, args.to, &mut gas_counter, hardfork_flags)? { - return Err(PrecompileErrorOrRevert::new_reverted(gas_counter, ERR_BLOCKED_ADDRESS)); + return Err(new_reverted_with_early_penalty(gas_counter, ERR_BLOCKED_ADDRESS, hardfork_flags)); } // Zero amount transfers are allowed, but do not emit an event @@ -523,7 +534,7 @@ precompile!(run_native_coin_authority, precompile_input, hardfork_flags; { if !hardfork_flags.is_active(ArcHardfork::Zero6) && !input.is_empty() { return Err(PrecompileErrorOrRevert::new_reverted_with_penalty( - gas_counter, PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY, ERR_EXECUTION_REVERTED)); + gas_counter, PRECOMPILE_EARLY_REVERT_GAS_PENALTY, ERR_EXECUTION_REVERTED)); } // Early return if not enough gas @@ -567,13 +578,12 @@ mod tests { }; use reth_evm::precompiles::{DynPrecompile, PrecompilesMap}; use revm::{ - bytecode::{Bytecode, JumpTable, LegacyAnalyzedBytecode}, + bytecode::Bytecode, handler::PrecompileProvider, interpreter::InterpreterResult, precompile::{PrecompileId, Precompiles}, }; use revm_context_interface::journaled_state::account::JournaledAccountTr; - use revm_primitives::b256; use std::collections::HashSet; fn mock_context(hardfork_flags: ArcHardforkFlags) -> revm::Context { @@ -693,6 +703,7 @@ mod tests { const ADDRESS_B: Address = address!("2000000000000000000000000000000000000002"); const ADDRESS_C: Address = address!("300000D000000000000000000000000000000003"); const NON_EMPTY_ADDRESS: Address = address!("400000D000000000000000000000000000000004"); + const ZERO6_EMPTY_ACCOUNT_GAS_DELTA: u64 = crate::helpers::PRECOMPILE_EMPTY_ACCOUNT_GAS_COST; fn assert_precompile_result( precompile_res: Result, String>, @@ -816,14 +827,9 @@ mod tests { ctx.journal_mut() .load_account(NON_EMPTY_ADDRESS) .expect("Cannot load account"); - ctx.journal_mut().set_code_with_hash( + ctx.journal_mut().set_code( NON_EMPTY_ADDRESS, - Bytecode::LegacyAnalyzed(std::sync::Arc::new(LegacyAnalyzedBytecode::new( - Bytes::from_static(&[0x60, 0x00, 0x60, 0x00, 0x56]), // PUSH1 0x00 PUSH1 0x00 JUMP - JumpTable::default().len(), - JumpTable::default(), - ))), - b256!("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b422"), + Bytecode::new_legacy(Bytes::from_static(&[0x60, 0x00, 0x60, 0x00, 0x56])), ); ctx.journal_mut() .balance_incr(NON_EMPTY_ADDRESS, mock_initial_supply) @@ -885,6 +891,7 @@ mod tests { blocklisted_addresses: None, gas_used: 0, pre_zero5_gas_used: Some(PRECOMPILE_SLOAD_GAS_COST), + zero6_gas_used: Some(PRECOMPILE_EARLY_REVERT_GAS_PENALTY), target_address: NATIVE_COIN_AUTHORITY_ADDRESS, bytecode_address: NATIVE_COIN_AUTHORITY_ADDRESS, ..Default::default() @@ -907,6 +914,7 @@ mod tests { blocklisted_addresses: None, gas_used: 2100, // blocklist check cold SLOAD only pre_zero5_gas_used: Some(PRECOMPILE_SLOAD_GAS_COST * 2), + zero6_gas_used: Some(2100 + PRECOMPILE_EARLY_REVERT_GAS_PENALTY), target_address: NATIVE_COIN_AUTHORITY_ADDRESS, bytecode_address: NATIVE_COIN_AUTHORITY_ADDRESS, ..Default::default() @@ -929,6 +937,7 @@ mod tests { blocklisted_addresses: None, gas_used: 2200, // blocklist cold (2100) + total_supply warm (100) pre_zero5_gas_used: Some(PRECOMPILE_SLOAD_GAS_COST * 3), + zero6_gas_used: Some(2200 + PRECOMPILE_EARLY_REVERT_GAS_PENALTY), target_address: NATIVE_COIN_AUTHORITY_ADDRESS, bytecode_address: NATIVE_COIN_AUTHORITY_ADDRESS, ..Default::default() @@ -967,7 +976,7 @@ mod tests { expected_result: InstructionResult::Revert, return_data: None, blocklisted_addresses: None, - gas_used: PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY, + gas_used: PRECOMPILE_EARLY_REVERT_GAS_PENALTY, pre_zero5_gas_used: None, target_address: NATIVE_COIN_AUTHORITY_ADDRESS, bytecode_address: NATIVE_COIN_AUTHORITY_ADDRESS, @@ -983,7 +992,7 @@ mod tests { expected_result: InstructionResult::Revert, return_data: None, blocklisted_addresses: None, - gas_used: PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY, + gas_used: PRECOMPILE_EARLY_REVERT_GAS_PENALTY, pre_zero5_gas_used: None, target_address: ADDRESS_B, bytecode_address: NATIVE_COIN_AUTHORITY_ADDRESS, @@ -1033,9 +1042,8 @@ mod tests { bytecode_address: NATIVE_COIN_AUTHORITY_ADDRESS, ..Default::default() }, - // blocklist cold (2100) + total_supply warm read (100) + total_supply warm write (100) - // + balance_incr fixed (5000) + event (1381) = 8681 - // EIP-7708 (Zero5): Transfer event (3 topics) replaces NativeCoinMinted (2 topics) + // Zero5: blocklist cold (2100) + total_supply warm read/write (200) + // + balance_incr fixed (5000) + Transfer event (1756) = 9056. NativeCoinAuthorityTest { name: "mint() success and returns true", caller: ALLOWED_CALLER_ADDRESS, @@ -1048,6 +1056,7 @@ mod tests { gas_limit: MINT_GAS_COST, pre_zero5_gas_limit: None, eip7708_gas_limit: Some(MINT_GAS_COST_EIP7708), + zero6_gas_limit: Some(MINT_GAS_COST_EIP7708 + ZERO6_EMPTY_ACCOUNT_GAS_DELTA), expected_revert_str: None, expected_result: InstructionResult::Return, return_data: Some(true.abi_encode().into()), @@ -1055,7 +1064,33 @@ mod tests { gas_used: 8681, pre_zero5_gas_used: Some(MINT_GAS_COST), eip7708_gas_used: Some(9056), - zero6_gas_used: Some(9556), + zero6_gas_used: Some(9556 + ZERO6_EMPTY_ACCOUNT_GAS_DELTA), + target_address: NATIVE_COIN_AUTHORITY_ADDRESS, + bytecode_address: NATIVE_COIN_AUTHORITY_ADDRESS, + ..Default::default() + }, + // Zero6: NON_EMPTY_ADDRESS is initialized in test setup, so balance_incr() + // must not charge the empty-account creation surcharge. + NativeCoinAuthorityTest { + name: "mint() to non-empty account succeeds without empty account surcharge", + caller: ALLOWED_CALLER_ADDRESS, + calldata: INativeCoinAuthority::mintCall { + to: NON_EMPTY_ADDRESS, + amount: U256::from(1), + } + .abi_encode() + .into(), + gas_limit: MINT_GAS_COST, + pre_zero5_gas_limit: None, + eip7708_gas_limit: Some(MINT_GAS_COST_EIP7708), + expected_revert_str: None, + expected_result: InstructionResult::Return, + return_data: Some(true.abi_encode().into()), + blocklisted_addresses: None, + gas_used: 8681, + pre_zero5_gas_used: Some(MINT_GAS_COST), + eip7708_gas_used: Some(9056), + zero6_gas_used: Some(7056), target_address: NATIVE_COIN_AUTHORITY_ADDRESS, bytecode_address: NATIVE_COIN_AUTHORITY_ADDRESS, ..Default::default() @@ -1078,6 +1113,7 @@ mod tests { blocklisted_addresses: None, gas_used: 0, pre_zero5_gas_used: None, + zero6_gas_used: Some(PRECOMPILE_EARLY_REVERT_GAS_PENALTY), target_address: NATIVE_COIN_AUTHORITY_ADDRESS, bytecode_address: NATIVE_COIN_AUTHORITY_ADDRESS, eip7708_only: true, @@ -1101,6 +1137,7 @@ mod tests { blocklisted_addresses: None, gas_used: 0, pre_zero5_gas_used: Some(PRECOMPILE_SLOAD_GAS_COST), + zero6_gas_used: Some(PRECOMPILE_EARLY_REVERT_GAS_PENALTY), target_address: NATIVE_COIN_AUTHORITY_ADDRESS, bytecode_address: NATIVE_COIN_AUTHORITY_ADDRESS, ..Default::default() @@ -1123,6 +1160,7 @@ mod tests { blocklisted_addresses: None, gas_used: 2100, // blocklist cold SLOAD only pre_zero5_gas_used: Some(PRECOMPILE_SLOAD_GAS_COST * 2), + zero6_gas_used: Some(2100 + PRECOMPILE_EARLY_REVERT_GAS_PENALTY), target_address: NATIVE_COIN_AUTHORITY_ADDRESS, bytecode_address: NATIVE_COIN_AUTHORITY_ADDRESS, ..Default::default() @@ -1184,7 +1222,7 @@ mod tests { expected_result: InstructionResult::Revert, return_data: None, blocklisted_addresses: None, - gas_used: PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY, + gas_used: PRECOMPILE_EARLY_REVERT_GAS_PENALTY, pre_zero5_gas_used: None, target_address: NATIVE_COIN_AUTHORITY_ADDRESS, bytecode_address: NATIVE_COIN_AUTHORITY_ADDRESS, @@ -1235,8 +1273,8 @@ mod tests { ..Default::default() }, // blocklist cold (2100) + balance_decr fixed (5000) + total_supply warm read (100) - // + total_supply warm write (100) + event (1381) = 8681 - // EIP-7708 (Zero5): Transfer event (3 topics) replaces NativeCoinBurned (2 topics) + // Zero5: blocklist cold (2100) + balance_decr fixed (5000) + // + total_supply warm read/write (200) + Transfer event (1756) = 9056. NativeCoinAuthorityTest { name: "burn() succeeds and returns true", caller: ALLOWED_CALLER_ADDRESS, @@ -1279,6 +1317,7 @@ mod tests { blocklisted_addresses: None, gas_used: 0, pre_zero5_gas_used: None, + zero6_gas_used: Some(PRECOMPILE_EARLY_REVERT_GAS_PENALTY), target_address: NATIVE_COIN_AUTHORITY_ADDRESS, bytecode_address: NATIVE_COIN_AUTHORITY_ADDRESS, eip7708_only: true, @@ -1303,6 +1342,7 @@ mod tests { blocklisted_addresses: None, gas_used: 0, pre_zero5_gas_used: Some(PRECOMPILE_SLOAD_GAS_COST), + zero6_gas_used: Some(PRECOMPILE_EARLY_REVERT_GAS_PENALTY), target_address: NATIVE_COIN_AUTHORITY_ADDRESS, bytecode_address: NATIVE_COIN_AUTHORITY_ADDRESS, ..Default::default() @@ -1368,7 +1408,7 @@ mod tests { expected_result: InstructionResult::Revert, return_data: None, blocklisted_addresses: None, - gas_used: PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY, + gas_used: PRECOMPILE_EARLY_REVERT_GAS_PENALTY, pre_zero5_gas_used: None, target_address: NATIVE_COIN_AUTHORITY_ADDRESS, bytecode_address: NATIVE_COIN_AUTHORITY_ADDRESS, @@ -1439,6 +1479,7 @@ mod tests { blocklisted_addresses: None, gas_used: 0, pre_zero5_gas_used: None, + zero6_gas_used: Some(PRECOMPILE_EARLY_REVERT_GAS_PENALTY), target_address: NATIVE_COIN_AUTHORITY_ADDRESS, bytecode_address: NATIVE_COIN_AUTHORITY_ADDRESS, eip7708_only: true, @@ -1462,6 +1503,7 @@ mod tests { blocklisted_addresses: None, gas_used: 0, pre_zero5_gas_used: None, + zero6_gas_used: Some(PRECOMPILE_EARLY_REVERT_GAS_PENALTY), target_address: NATIVE_COIN_AUTHORITY_ADDRESS, bytecode_address: NATIVE_COIN_AUTHORITY_ADDRESS, eip7708_only: true, @@ -1485,6 +1527,7 @@ mod tests { blocklisted_addresses: None, gas_used: 0, pre_zero5_gas_used: None, + zero6_gas_used: Some(PRECOMPILE_EARLY_REVERT_GAS_PENALTY), target_address: NATIVE_COIN_AUTHORITY_ADDRESS, bytecode_address: NATIVE_COIN_AUTHORITY_ADDRESS, eip7708_only: true, @@ -1504,13 +1547,39 @@ mod tests { .into(), gas_limit: TRANSFER_GAS_COST, pre_zero5_gas_limit: None, + zero6_gas_limit: Some(TRANSFER_GAS_COST + ZERO6_EMPTY_ACCOUNT_GAS_DELTA), + expected_revert_str: None, + expected_result: InstructionResult::Return, + return_data: Some(true.abi_encode().into()), + blocklisted_addresses: None, + gas_used: 15956, + pre_zero5_gas_used: Some(TRANSFER_GAS_COST), + zero6_gas_used: Some(14456 + ZERO6_EMPTY_ACCOUNT_GAS_DELTA), + target_address: NATIVE_COIN_AUTHORITY_ADDRESS, + bytecode_address: NATIVE_COIN_AUTHORITY_ADDRESS, + ..Default::default() + }, + // Zero6: NON_EMPTY_ADDRESS is initialized in test setup, so transfer() + // must not charge the empty-account creation surcharge. + NativeCoinAuthorityTest { + name: "transfer() to non-empty account succeeds without empty account surcharge", + caller: ALLOWED_CALLER_ADDRESS, + calldata: INativeCoinAuthority::transferCall { + from: ADDRESS_A, + to: NON_EMPTY_ADDRESS, + amount: U256::from(1), + } + .abi_encode() + .into(), + gas_limit: TRANSFER_GAS_COST, + pre_zero5_gas_limit: None, expected_revert_str: None, expected_result: InstructionResult::Return, return_data: Some(true.abi_encode().into()), blocklisted_addresses: None, gas_used: 15956, pre_zero5_gas_used: Some(TRANSFER_GAS_COST), - zero6_gas_used: Some(14456), + zero6_gas_used: Some(11956), target_address: NATIVE_COIN_AUTHORITY_ADDRESS, bytecode_address: NATIVE_COIN_AUTHORITY_ADDRESS, ..Default::default() @@ -1555,13 +1624,14 @@ mod tests { .into(), gas_limit: TRANSFER_GAS_COST, pre_zero5_gas_limit: None, + zero6_gas_limit: Some(TRANSFER_GAS_COST + ZERO6_EMPTY_ACCOUNT_GAS_DELTA), expected_revert_str: None, expected_result: InstructionResult::Return, return_data: Some(true.abi_encode().into()), blocklisted_addresses: None, gas_used: 15956, pre_zero5_gas_used: Some(TRANSFER_GAS_COST), - zero6_gas_used: Some(14456), + zero6_gas_used: Some(14456 + ZERO6_EMPTY_ACCOUNT_GAS_DELTA), target_address: NATIVE_COIN_AUTHORITY_ADDRESS, bytecode_address: NATIVE_COIN_AUTHORITY_ADDRESS, ..Default::default() @@ -1641,13 +1711,13 @@ mod tests { blocklisted_addresses: Some(HashSet::from([ADDRESS_B])), gas_used: 100, // blocklist warm (test setup wrote it) pre_zero5_gas_used: Some(PRECOMPILE_SLOAD_GAS_COST * 2), + zero6_gas_used: Some(100 + PRECOMPILE_EARLY_REVERT_GAS_PENALTY), target_address: NATIVE_COIN_AUTHORITY_ADDRESS, bytecode_address: NATIVE_COIN_AUTHORITY_ADDRESS, ..Default::default() }, - // blocklist cold (2100) + total_supply warm read (100) + total_supply warm write (100) - // + balance_incr fixed (5000) + event (1381) = 8681 - // EIP-7708 (Zero5): Transfer event (3 topics) replaces NativeCoinMinted (2 topics) + // Zero5: blocklist cold (2100) + total_supply warm read/write (200) + // + balance_incr fixed (5000) + Transfer event (1756) = 9056. NativeCoinAuthorityTest { name: "mint() to non-blocklisted address succeeds", caller: ALLOWED_CALLER_ADDRESS, @@ -1660,6 +1730,7 @@ mod tests { gas_limit: MINT_GAS_COST, pre_zero5_gas_limit: None, eip7708_gas_limit: Some(MINT_GAS_COST_EIP7708), + zero6_gas_limit: Some(MINT_GAS_COST_EIP7708 + ZERO6_EMPTY_ACCOUNT_GAS_DELTA), expected_revert_str: None, expected_result: InstructionResult::Return, return_data: Some(true.abi_encode().into()), @@ -1667,7 +1738,7 @@ mod tests { gas_used: 8681, pre_zero5_gas_used: Some(MINT_GAS_COST), eip7708_gas_used: Some(9056), - zero6_gas_used: Some(9556), + zero6_gas_used: Some(9556 + ZERO6_EMPTY_ACCOUNT_GAS_DELTA), target_address: NATIVE_COIN_AUTHORITY_ADDRESS, bytecode_address: NATIVE_COIN_AUTHORITY_ADDRESS, ..Default::default() @@ -1690,6 +1761,7 @@ mod tests { blocklisted_addresses: Some(HashSet::from([ADDRESS_B])), gas_used: 100, // blocklist warm (test setup wrote it) pre_zero5_gas_used: Some(PRECOMPILE_SLOAD_GAS_COST * 2), + zero6_gas_used: Some(100 + PRECOMPILE_EARLY_REVERT_GAS_PENALTY), target_address: NATIVE_COIN_AUTHORITY_ADDRESS, bytecode_address: NATIVE_COIN_AUTHORITY_ADDRESS, ..Default::default() @@ -1713,6 +1785,7 @@ mod tests { blocklisted_addresses: Some(HashSet::from([ADDRESS_A])), gas_used: 100, // from blocklist warm (test setup wrote it) pre_zero5_gas_used: Some(PRECOMPILE_SLOAD_GAS_COST * 2), + zero6_gas_used: Some(100 + PRECOMPILE_EARLY_REVERT_GAS_PENALTY), target_address: NATIVE_COIN_AUTHORITY_ADDRESS, bytecode_address: NATIVE_COIN_AUTHORITY_ADDRESS, ..Default::default() @@ -1736,6 +1809,7 @@ mod tests { blocklisted_addresses: Some(HashSet::from([ADDRESS_B])), gas_used: 2200, // from blocklist cold (2100) + to blocklist warm (100) pre_zero5_gas_used: Some(PRECOMPILE_SLOAD_GAS_COST * 3), + zero6_gas_used: Some(2200 + PRECOMPILE_EARLY_REVERT_GAS_PENALTY), target_address: NATIVE_COIN_AUTHORITY_ADDRESS, bytecode_address: NATIVE_COIN_AUTHORITY_ADDRESS, ..Default::default() @@ -2220,21 +2294,7 @@ mod tests { let transfer_amount = U256::from(1000); let mut ctx = mock_context(hardfork_flags); - - ctx.journal_mut() - .sstore( - NATIVE_COIN_AUTHORITY_ADDRESS, - TOTAL_SUPPLY_STORAGE_KEY.into(), - initial_supply, - ) - .expect("Unable to write initial total supply"); - - ctx.journal_mut() - .load_account(ADDRESS_A) - .expect("Cannot load account"); - ctx.journal_mut() - .balance_incr(ADDRESS_A, initial_supply) - .expect("Unable to write initial balance for ADDRESS_A"); + setup_initial_state(&mut ctx, initial_supply); // Self-transfer: from == to == ADDRESS_A let inputs = CallInputs { @@ -2336,6 +2396,67 @@ mod tests { } } + #[test] + fn transfer_recipient_overflow_preserves_pre_zero6_gas() { + for hardfork_flags in ArcHardforkFlags::all_combinations() { + if hardfork_flags.is_active(ArcHardfork::Zero6) { + continue; + } + + let mut ctx = mock_context(hardfork_flags); + setup_initial_state(&mut ctx, U256::from(1_000_000_000)); + ctx.journal_mut() + .load_account(ADDRESS_B) + .expect("Cannot load recipient account"); + ctx.journal_mut() + .balance_incr(ADDRESS_B, U256::MAX) + .expect("Unable to set recipient max balance"); + + let inputs = CallInputs { + scheme: CallScheme::Call, + target_address: NATIVE_COIN_AUTHORITY_ADDRESS, + bytecode_address: NATIVE_COIN_AUTHORITY_ADDRESS, + known_bytecode: None, + caller: ALLOWED_CALLER_ADDRESS, + value: CallValue::Transfer(U256::ZERO), + input: CallInput::Bytes( + INativeCoinAuthority::transferCall { + from: ADDRESS_A, + to: ADDRESS_B, + amount: U256::from(1), + } + .abi_encode() + .into(), + ), + gas_limit: 100_000, + is_static: false, + return_memory_offset: 0..0, + }; + + let result = call_native_coin_authority(&mut ctx, &inputs, hardfork_flags) + .expect("call should not error") + .expect("call should return interpreter result"); + + assert_eq!(result.result, InstructionResult::Revert); + assert_eq!( + bytes_to_revert_message(result.output.as_ref()).as_deref(), + Some(ERR_OVERFLOW), + ); + + let expected_gas = if hardfork_flags.is_active(ArcHardfork::Zero5) { + 2 * PRECOMPILE_SLOAD_GAS_COST + + 2 * PRECOMPILE_SLOAD_GAS_COST + + 2 * PRECOMPILE_SSTORE_GAS_COST + } else { + 3 * PRECOMPILE_SLOAD_GAS_COST + + 2 * PRECOMPILE_SLOAD_GAS_COST + + 2 * PRECOMPILE_SSTORE_GAS_COST + }; + assert_eq!(result.gas.used(), expected_gas); + assert_eq!(result.gas.refunded(), 0); + } + } + // Helper to convert bytes to a revert error string fn bytes_to_revert_message(input: &[u8]) -> Option { // Expect at least 4 bytes for the selector. diff --git a/crates/precompiles/src/native_coin_control.rs b/crates/precompiles/src/native_coin_control.rs index df20442..211ff27 100644 --- a/crates/precompiles/src/native_coin_control.rs +++ b/crates/precompiles/src/native_coin_control.rs @@ -20,9 +20,10 @@ //! blocklisting and unblocklisting addresses from receiving native coin transfers. use crate::helpers::{ - check_delegatecall, check_gas_remaining, check_staticcall, emit_event, read, write, + abi_decode_raw_with_zero6_validation, check_delegatecall, check_gas_remaining, + check_staticcall, emit_event, new_reverted_with_early_penalty, read, write, PrecompileErrorOrRevert, ERR_EXECUTION_REVERTED, LOG_BASE_COST, LOG_TOPIC_COST, - NATIVE_FIAT_TOKEN_ADDRESS, PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY, PRECOMPILE_SLOAD_GAS_COST, + NATIVE_FIAT_TOKEN_ADDRESS, PRECOMPILE_EARLY_REVERT_GAS_PENALTY, PRECOMPILE_SLOAD_GAS_COST, PRECOMPILE_SSTORE_GAS_COST, }; use crate::precompile; @@ -128,7 +129,7 @@ fn is_authorized( /// /// Delegates to the execution-config canonical implementation. pub fn compute_is_blocklisted_storage_slot(key: Address) -> StorageKey { - StorageKey::new(native_coin_control_config::compute_is_blocklisted_storage_slot(key).0) + native_coin_control_config::compute_is_blocklisted_storage_slot(key) } precompile!(run_native_coin_control, precompile_input, hardfork_flags; { @@ -144,23 +145,46 @@ precompile!(run_native_coin_control, precompile_input, hardfork_flags; { )?; // Decode arguments passed to blocklist function - let args = INativeCoinControl::blocklistCall::abi_decode_raw(input) + let args = abi_decode_raw_with_zero6_validation::( + input, + hardfork_flags, + ) .map_err(|_| PrecompileErrorOrRevert::new_reverted_with_penalty( - gas_counter, PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY, ERR_EXECUTION_REVERTED, + gas_counter, PRECOMPILE_EARLY_REVERT_GAS_PENALTY, ERR_EXECUTION_REVERTED, ) )?; if hardfork_flags.is_active(ArcHardfork::Zero5) { - // Early return if not enough gas - check_gas_remaining(&gas_counter, BLOCKLIST_GAS_COST - PRECOMPILE_SLOAD_GAS_COST)?; - - // Check authorization - if precompile_input.caller != ALLOWED_CALLER_ADDRESS { - return Err(PrecompileErrorOrRevert::new_reverted( - gas_counter, - ERR_CANNOT_BLOCKLIST, - )); + if hardfork_flags.is_active(ArcHardfork::Zero6) { + // Auth first so the Zero6 early-revert penalty is reachable + // regardless of remaining gas; otherwise the success-path + // floor below OOGs callers in the 200..4024 gas window. + if precompile_input.caller != ALLOWED_CALLER_ADDRESS { + return Err(new_reverted_with_early_penalty( + gas_counter, + ERR_CANNOT_BLOCKLIST, + hardfork_flags, + )); + } + check_gas_remaining( + &gas_counter, + BLOCKLIST_GAS_COST - PRECOMPILE_SLOAD_GAS_COST, + )?; + } else { + // Zero5-only: keep the original order to preserve consensus + // on networks already past the Zero5 activation block. + check_gas_remaining( + &gas_counter, + BLOCKLIST_GAS_COST - PRECOMPILE_SLOAD_GAS_COST, + )?; + if precompile_input.caller != ALLOWED_CALLER_ADDRESS { + return Err(new_reverted_with_early_penalty( + gas_counter, + ERR_CANNOT_BLOCKLIST, + hardfork_flags, + )); + } } } else { // Early return if not enough gas @@ -173,7 +197,7 @@ precompile!(run_native_coin_control, precompile_input, hardfork_flags; { &mut gas_counter, hardfork_flags, )?) { - return Err(PrecompileErrorOrRevert::new_reverted(gas_counter, ERR_CANNOT_BLOCKLIST)); + return Err(new_reverted_with_early_penalty(gas_counter, ERR_CANNOT_BLOCKLIST, hardfork_flags)); } } @@ -216,10 +240,14 @@ precompile!(run_native_coin_control, precompile_input, hardfork_flags; { let mut precompile_input = precompile_input; // Decode arguments passed to isBlocklisted function - let args = INativeCoinControl::isBlocklistedCall::abi_decode_raw(input) + let args = + abi_decode_raw_with_zero6_validation::( + input, + hardfork_flags, + ) .map_err(|_| PrecompileErrorOrRevert::new_reverted_with_penalty( - gas_counter, PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY, ERR_EXECUTION_REVERTED, + gas_counter, PRECOMPILE_EARLY_REVERT_GAS_PENALTY, ERR_EXECUTION_REVERTED, ) )?; @@ -257,23 +285,46 @@ precompile!(run_native_coin_control, precompile_input, hardfork_flags; { )?; // Decode arguments passed to unBlocklist function - let args = INativeCoinControl::unBlocklistCall::abi_decode_raw(input) + let args = abi_decode_raw_with_zero6_validation::( + input, + hardfork_flags, + ) .map_err(|_| PrecompileErrorOrRevert::new_reverted_with_penalty( - gas_counter, PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY, ERR_EXECUTION_REVERTED, + gas_counter, PRECOMPILE_EARLY_REVERT_GAS_PENALTY, ERR_EXECUTION_REVERTED, ) )?; if hardfork_flags.is_active(ArcHardfork::Zero5) { - // Early return if not enough gas - check_gas_remaining(&gas_counter, UNBLOCKLIST_GAS_COST - PRECOMPILE_SLOAD_GAS_COST)?; - - // Check authorization - if precompile_input.caller != ALLOWED_CALLER_ADDRESS { - return Err(PrecompileErrorOrRevert::new_reverted( - gas_counter, - ERR_CANNOT_UNBLOCKLIST, - )); + if hardfork_flags.is_active(ArcHardfork::Zero6) { + // Auth first so the Zero6 early-revert penalty is reachable + // regardless of remaining gas; otherwise the success-path + // floor below OOGs callers in the 200..4024 gas window. + if precompile_input.caller != ALLOWED_CALLER_ADDRESS { + return Err(new_reverted_with_early_penalty( + gas_counter, + ERR_CANNOT_UNBLOCKLIST, + hardfork_flags, + )); + } + check_gas_remaining( + &gas_counter, + UNBLOCKLIST_GAS_COST - PRECOMPILE_SLOAD_GAS_COST, + )?; + } else { + // Zero5-only: keep the original order to preserve consensus + // on networks already past the Zero5 activation block. + check_gas_remaining( + &gas_counter, + UNBLOCKLIST_GAS_COST - PRECOMPILE_SLOAD_GAS_COST, + )?; + if precompile_input.caller != ALLOWED_CALLER_ADDRESS { + return Err(new_reverted_with_early_penalty( + gas_counter, + ERR_CANNOT_UNBLOCKLIST, + hardfork_flags, + )); + } } } else { // Early return if not enough gas @@ -286,7 +337,7 @@ precompile!(run_native_coin_control, precompile_input, hardfork_flags; { &mut gas_counter, hardfork_flags, )?) { - return Err(PrecompileErrorOrRevert::new_reverted(gas_counter, ERR_CANNOT_UNBLOCKLIST)); + return Err(new_reverted_with_early_penalty(gas_counter, ERR_CANNOT_UNBLOCKLIST, hardfork_flags)); } } @@ -396,8 +447,17 @@ mod tests { gas_used: u64, /// If set, overrides gas_used for pre-Zero5 hardforks (before EIP-2929/2200 storage costs) pre_zero5_gas_used: Option, + /// If set, overrides gas_used when Zero6 is active (auth/validation reverts now + /// charge `PRECOMPILE_EARLY_REVERT_GAS_PENALTY`). + zero6_gas_used: Option, target_address: Address, bytecode_address: Address, + /// If true, skip this test case for hardfork combinations without Zero6. + /// Used for cases whose result shape differs under Zero6 (e.g. penalty + /// revert vs OOG for low-gas unauthorized calls). + zero6_only: bool, + /// If true, skip this test case for hardfork combinations with Zero6. + pre_zero6_only: bool, } // Test constants @@ -446,7 +506,9 @@ mod tests { ); } - let expected_gas_used = if hardfork_flags.is_active(ArcHardfork::Zero5) { + let expected_gas_used = if hardfork_flags.is_active(ArcHardfork::Zero6) { + tc.zero6_gas_used.unwrap_or(tc.gas_used) + } else if hardfork_flags.is_active(ArcHardfork::Zero5) { tc.gas_used } else { tc.pre_zero5_gas_used.unwrap_or(tc.gas_used) @@ -479,6 +541,9 @@ mod tests { return_data: Some(true.abi_encode().into()), gas_used: 23225, pre_zero5_gas_used: Some(BLOCKLIST_GAS_COST), + zero6_gas_used: None, + zero6_only: false, + pre_zero6_only: false, target_address: NATIVE_COIN_CONTROL_ADDRESS, bytecode_address: NATIVE_COIN_CONTROL_ADDRESS, }, @@ -495,6 +560,9 @@ mod tests { return_data: None, gas_used: 0, pre_zero5_gas_used: Some(PRECOMPILE_SLOAD_GAS_COST), + zero6_gas_used: Some(PRECOMPILE_EARLY_REVERT_GAS_PENALTY), + zero6_only: false, + pre_zero6_only: false, target_address: NATIVE_COIN_CONTROL_ADDRESS, bytecode_address: NATIVE_COIN_CONTROL_ADDRESS, }, @@ -510,6 +578,9 @@ mod tests { return_data: None, gas_used: 0, pre_zero5_gas_used: None, + zero6_gas_used: None, + zero6_only: false, + pre_zero6_only: false, target_address: NATIVE_COIN_CONTROL_ADDRESS, bytecode_address: NATIVE_COIN_CONTROL_ADDRESS, }, @@ -521,8 +592,11 @@ mod tests { expected_result: InstructionResult::Revert, expected_revert_str: Some(ERR_EXECUTION_REVERTED), return_data: None, - gas_used: PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY, + gas_used: PRECOMPILE_EARLY_REVERT_GAS_PENALTY, pre_zero5_gas_used: None, + zero6_gas_used: None, + zero6_only: false, + pre_zero6_only: false, target_address: NATIVE_COIN_CONTROL_ADDRESS, bytecode_address: NATIVE_COIN_CONTROL_ADDRESS, }, @@ -539,6 +613,9 @@ mod tests { return_data: None, gas_used: 0, pre_zero5_gas_used: Some(PRECOMPILE_SLOAD_GAS_COST), + zero6_gas_used: None, + zero6_only: false, + pre_zero6_only: false, target_address: ADDRESS_B, bytecode_address: NATIVE_COIN_CONTROL_ADDRESS, }, @@ -555,6 +632,9 @@ mod tests { return_data: None, gas_used: 0, pre_zero5_gas_used: Some(PRECOMPILE_SLOAD_GAS_COST), + zero6_gas_used: None, + zero6_only: false, + pre_zero6_only: false, target_address: NATIVE_COIN_CONTROL_ADDRESS, bytecode_address: ADDRESS_B, }, @@ -571,6 +651,9 @@ mod tests { return_data: Some(false.abi_encode().into()), gas_used: PRECOMPILE_SLOAD_GAS_COST, pre_zero5_gas_used: None, + zero6_gas_used: None, + zero6_only: false, + pre_zero6_only: false, target_address: NATIVE_COIN_CONTROL_ADDRESS, bytecode_address: NATIVE_COIN_CONTROL_ADDRESS, }, @@ -586,6 +669,9 @@ mod tests { return_data: None, gas_used: 0, pre_zero5_gas_used: None, + zero6_gas_used: None, + zero6_only: false, + pre_zero6_only: false, target_address: NATIVE_COIN_CONTROL_ADDRESS, bytecode_address: NATIVE_COIN_CONTROL_ADDRESS, }, @@ -597,8 +683,11 @@ mod tests { expected_result: InstructionResult::Revert, expected_revert_str: Some(ERR_EXECUTION_REVERTED), return_data: None, - gas_used: PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY, + gas_used: PRECOMPILE_EARLY_REVERT_GAS_PENALTY, pre_zero5_gas_used: None, + zero6_gas_used: None, + zero6_only: false, + pre_zero6_only: false, target_address: NATIVE_COIN_CONTROL_ADDRESS, bytecode_address: NATIVE_COIN_CONTROL_ADDRESS, }, @@ -615,6 +704,9 @@ mod tests { return_data: Some(true.abi_encode().into()), gas_used: 3325, pre_zero5_gas_used: Some(UNBLOCKLIST_GAS_COST), + zero6_gas_used: None, + zero6_only: false, + pre_zero6_only: false, target_address: NATIVE_COIN_CONTROL_ADDRESS, bytecode_address: NATIVE_COIN_CONTROL_ADDRESS, }, @@ -631,6 +723,9 @@ mod tests { return_data: None, gas_used: 0, pre_zero5_gas_used: Some(PRECOMPILE_SLOAD_GAS_COST), + zero6_gas_used: None, + zero6_only: false, + pre_zero6_only: false, target_address: ADDRESS_B, bytecode_address: NATIVE_COIN_CONTROL_ADDRESS, }, @@ -647,6 +742,9 @@ mod tests { return_data: None, gas_used: 0, pre_zero5_gas_used: Some(PRECOMPILE_SLOAD_GAS_COST), + zero6_gas_used: None, + zero6_only: false, + pre_zero6_only: false, target_address: NATIVE_COIN_CONTROL_ADDRESS, bytecode_address: ADDRESS_B, }, @@ -663,6 +761,9 @@ mod tests { return_data: None, gas_used: 0, pre_zero5_gas_used: Some(PRECOMPILE_SLOAD_GAS_COST), + zero6_gas_used: Some(PRECOMPILE_EARLY_REVERT_GAS_PENALTY), + zero6_only: false, + pre_zero6_only: false, target_address: NATIVE_COIN_CONTROL_ADDRESS, bytecode_address: NATIVE_COIN_CONTROL_ADDRESS, }, @@ -678,6 +779,9 @@ mod tests { return_data: None, gas_used: 0, pre_zero5_gas_used: None, + zero6_gas_used: None, + zero6_only: false, + pre_zero6_only: false, target_address: NATIVE_COIN_CONTROL_ADDRESS, bytecode_address: NATIVE_COIN_CONTROL_ADDRESS, }, @@ -689,8 +793,89 @@ mod tests { expected_result: InstructionResult::Revert, expected_revert_str: Some(ERR_EXECUTION_REVERTED), return_data: None, - gas_used: PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY, + gas_used: PRECOMPILE_EARLY_REVERT_GAS_PENALTY, + pre_zero5_gas_used: None, + zero6_gas_used: None, + zero6_only: false, + pre_zero6_only: false, + target_address: NATIVE_COIN_CONTROL_ADDRESS, + bytecode_address: NATIVE_COIN_CONTROL_ADDRESS, + }, + // Under Zero6, the auth check runs before the success-path gas + // floor, so an unauthorized caller with gas >= 200 (the penalty) + // gets a penalized revert — not an OOG. + NativeCoinControlTest { + name: "blocklist() Zero6 low-gas unauthorized reverts with penalty", + caller: ADDRESS_A, + calldata: INativeCoinControl::blocklistCall { account: ADDRESS_B } + .abi_encode() + .into(), + gas_limit: 500, + expected_result: InstructionResult::Revert, + expected_revert_str: Some(ERR_CANNOT_BLOCKLIST), + return_data: None, + gas_used: PRECOMPILE_EARLY_REVERT_GAS_PENALTY, pre_zero5_gas_used: None, + zero6_gas_used: None, + zero6_only: true, + pre_zero6_only: false, + target_address: NATIVE_COIN_CONTROL_ADDRESS, + bytecode_address: NATIVE_COIN_CONTROL_ADDRESS, + }, + // Pre-Zero6 (Zero3/Zero4/Zero5): gas floor runs before auth, so a + // low-gas unauthorized caller OOGs. Locks in the historical Zero5 + // behavior on devnet. + NativeCoinControlTest { + name: "blocklist() pre-Zero6 low-gas unauthorized OOGs", + caller: ADDRESS_A, + calldata: INativeCoinControl::blocklistCall { account: ADDRESS_B } + .abi_encode() + .into(), + gas_limit: 500, + expected_result: InstructionResult::PrecompileOOG, + expected_revert_str: None, + return_data: None, + gas_used: 0, + pre_zero5_gas_used: None, + zero6_gas_used: None, + zero6_only: false, + pre_zero6_only: true, + target_address: NATIVE_COIN_CONTROL_ADDRESS, + bytecode_address: NATIVE_COIN_CONTROL_ADDRESS, + }, + NativeCoinControlTest { + name: "unBlocklist() Zero6 low-gas unauthorized reverts with penalty", + caller: ADDRESS_A, + calldata: INativeCoinControl::unBlocklistCall { account: ADDRESS_B } + .abi_encode() + .into(), + gas_limit: 500, + expected_result: InstructionResult::Revert, + expected_revert_str: Some(ERR_CANNOT_UNBLOCKLIST), + return_data: None, + gas_used: PRECOMPILE_EARLY_REVERT_GAS_PENALTY, + pre_zero5_gas_used: None, + zero6_gas_used: None, + zero6_only: true, + pre_zero6_only: false, + target_address: NATIVE_COIN_CONTROL_ADDRESS, + bytecode_address: NATIVE_COIN_CONTROL_ADDRESS, + }, + NativeCoinControlTest { + name: "unBlocklist() pre-Zero6 low-gas unauthorized OOGs", + caller: ADDRESS_A, + calldata: INativeCoinControl::unBlocklistCall { account: ADDRESS_B } + .abi_encode() + .into(), + gas_limit: 500, + expected_result: InstructionResult::PrecompileOOG, + expected_revert_str: None, + return_data: None, + gas_used: 0, + pre_zero5_gas_used: None, + zero6_gas_used: None, + zero6_only: false, + pre_zero6_only: true, target_address: NATIVE_COIN_CONTROL_ADDRESS, bytecode_address: NATIVE_COIN_CONTROL_ADDRESS, }, @@ -698,6 +883,20 @@ mod tests { for tc in cases { for hardfork_flags in ArcHardforkFlags::all_combinations() { + // ZeroX hardforks are cumulative; Zero6 implies Zero5. + if hardfork_flags.is_active(ArcHardfork::Zero6) + && !hardfork_flags.is_active(ArcHardfork::Zero5) + { + continue; + } + + if tc.zero6_only && !hardfork_flags.is_active(ArcHardfork::Zero6) { + continue; + } + if tc.pre_zero6_only && hardfork_flags.is_active(ArcHardfork::Zero6) { + continue; + } + let tc_name = tc.name.to_string() + &format!(" (hardfork_flags: {:?})", hardfork_flags); diff --git a/crates/precompiles/src/pq.rs b/crates/precompiles/src/pq.rs index b25c393..74200a5 100644 --- a/crates/precompiles/src/pq.rs +++ b/crates/precompiles/src/pq.rs @@ -13,15 +13,9 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -// NOTE: Post-quantum signature schemes and their underlying libraries are -// relatively new. Algorithm parameters, gas costs, and the precompile interface -// may change in future hardforks as the ecosystem matures. Do not rely on -// stability of this precompile across network upgrades without checking the -// changelog. - use crate::helpers::{ record_cost_or_out_of_gas, PrecompileErrorOrRevert, ERR_EXECUTION_REVERTED, - PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY, + PRECOMPILE_EARLY_REVERT_GAS_PENALTY, }; use crate::precompile; use alloy_primitives::{address, Address}; @@ -48,11 +42,16 @@ const VERIFY_BASE_GAS: u64 = 230_000; const GAS_PER_MSG_WORD: u64 = KECCAK256WORD; sol! { - /// PQ Signature Verifier precompile interface + /// Experimental PQ Signature Verifier precompile interface. interface IPQ { - /// Verify an SLH-DSA-SHA2-128s signature + /// Verify an SLH-DSA-SHA2-128s signature. + /// + /// Since PQ signatures are still very new, we recommend not to solely + /// rely on them for authentication, but pair them with classical + /// signatures. + /// /// Gas cost: 230,000 base + 6 per 32-byte word of message (same as KECCAK256) - function verifySlhDsaSha2128s(bytes calldata vk, bytes calldata msg, bytes calldata sig) external returns (bool isValid); + function verifySlhDsaSha2128s(bytes calldata vk, bytes calldata message, bytes calldata sig) external returns (bool isValid); } } @@ -62,10 +61,10 @@ precompile!(run_pq, precompile_input, hardfork_flags; { let _ = hardfork_flags; let mut gas_counter = Gas::new(precompile_input.gas); - let args = IPQ::verifySlhDsaSha2128sCall::abi_decode_raw(input).map_err(|_| { + let args = IPQ::verifySlhDsaSha2128sCall::abi_decode_raw_validate(input).map_err(|_| { PrecompileErrorOrRevert::new_reverted_with_penalty( gas_counter, - PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY, + PRECOMPILE_EARLY_REVERT_GAS_PENALTY, ERR_EXECUTION_REVERTED, ) })?; @@ -75,7 +74,7 @@ precompile!(run_pq, precompile_input, hardfork_flags; { // GAS_PER_MSG_WORD (6) < 32, so the product cannot exceed u64::MAX #[allow(clippy::arithmetic_side_effects)] - let msg_word_gas = (args.msg.len() as u64).div_ceil(32) * GAS_PER_MSG_WORD; + let msg_word_gas = (args.message.len() as u64).div_ceil(32) * GAS_PER_MSG_WORD; record_cost_or_out_of_gas(&mut gas_counter, msg_word_gas)?; // SLH-DSA-SHA2-128s constants from FIPS 205 @@ -102,7 +101,7 @@ precompile!(run_pq, precompile_input, hardfork_flags; { let signature = Signature::::try_from(args.sig.as_ref()) .map_err(|_| PrecompileErrorOrRevert::new_reverted(gas_counter, "Failed to parse signature"))?; - let is_valid = verifying_key.verify(args.msg.as_ref(), &signature).is_ok(); + let is_valid = verifying_key.verify(args.message.as_ref(), &signature).is_ok(); Ok(PrecompileOutput::new(gas_counter.used(), is_valid.abi_encode().into())) })() @@ -162,11 +161,11 @@ mod tests { } /// Helper to call SLH-DSA-SHA2-128s verifier with given inputs. - /// Param order matches the ABI: (vk, msg, sig). + /// Param order matches the ABI: (vk, message, sig). fn transact_slh_dsa_verifier( evm: &mut TestEvm, vk: Vec, - msg: Vec, + message: Vec, sig: Vec, ) -> Result>, EVMError> { @@ -175,7 +174,7 @@ mod tests { kind: TxKind::Call(PQ_ADDRESS), data: IPQ::verifySlhDsaSha2128sCall { vk: vk.into(), - msg: msg.into(), + message: message.into(), sig: sig.into(), } .abi_encode() diff --git a/crates/precompiles/src/subcall.rs b/crates/precompiles/src/subcall.rs index edf1fa9..65f989e 100644 --- a/crates/precompiles/src/subcall.rs +++ b/crates/precompiles/src/subcall.rs @@ -46,15 +46,11 @@ use std::any::Any; /// precompile, the CALL opcode triggers a new `frame_init` cycle through `ArcEvm`, which /// consults the subcall registry as expected. /// -/// # No separate checkpoint around child execution -/// -/// The subcall framework does **not** take a separate journal checkpoint around the child -/// execution. The child frame's own checkpoint (managed by revm's `make_call_frame` / -/// `process_next_action`) handles commit/revert based on the child's success or failure. +/// # Checkpoint and revert semantics /// +/// The subcall framework takes a journal checkpoint **before** dispatching the child frame. /// If `complete_subcall` returns `success: false` or `Err` when the child succeeded, the -/// child's state changes will **not** be reverted — they are already committed. Implementors -/// should be aware of this when designing their completion logic. +/// framework reverts the child's committed state changes using that checkpoint. /// /// Returning `success: true` when the child failed is fine (e.g., `CallFromPrecompile` /// always succeeds and encodes the child's outcome in its output bytes). The child's @@ -81,8 +77,8 @@ pub trait SubcallPrecompile: Send + Sync { /// /// # Note on checkpoint semantics /// - /// Returning `success: false` or `Err` when the child succeeded will **not** revert - /// the child's state changes. See the trait-level docs for details. + /// Returning `success: false` or `Err` when the child succeeded will cause the + /// framework to revert the child's committed state changes. See the trait-level docs. fn complete_subcall( &self, continuation_data: SubcallContinuationData, @@ -133,6 +129,9 @@ pub struct SubcallCompletionResult { pub output: Bytes, /// Whether the precompile considers the call successful. pub success: bool, + /// Gas consumed by the completion phase (e.g., ABI encoding the return data). + /// Added to the total gas_used for the precompile invocation. + pub gas_overhead: u64, } /// Errors that can occur during subcall precompile execution. diff --git a/crates/precompiles/src/system_accounting.rs b/crates/precompiles/src/system_accounting.rs index 40266e8..f8f119c 100644 --- a/crates/precompiles/src/system_accounting.rs +++ b/crates/precompiles/src/system_accounting.rs @@ -15,16 +15,19 @@ // limitations under the License. use crate::helpers::{ - check_delegatecall, check_staticcall, read, record_cost_or_out_of_gas, write, + abi_decode_raw_with_zero6_validation, check_delegatecall, check_staticcall, + new_reverted_with_early_penalty, read, record_cost_or_out_of_gas, write, PrecompileErrorOrRevert, ERR_EXECUTION_REVERTED, ERR_INVALID_CALLER, - PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY, PRECOMPILE_SLOAD_GAS_COST, + PRECOMPILE_EARLY_REVERT_GAS_PENALTY, PRECOMPILE_SLOAD_GAS_COST, }; use crate::precompile; use alloy_evm::Evm; use alloy_primitives::B256; use alloy_primitives::{address, keccak256, Address, Bytes, StorageKey}; use alloy_sol_types::{sol, SolCall, SolValue}; +use arc_execution_config::hardforks::ArcHardfork; use reth_ethereum::evm::revm::precompile::PrecompileOutput; +use revm::handler::SYSTEM_ADDRESS; use revm::state::EvmState; use revm::DatabaseCommit; use revm_interpreter::Gas; @@ -58,8 +61,8 @@ const GAS_VALUES_STORAGE_KEY: StorageKey = StorageKey::new([ /// as headroom for external readers (RPC, monitoring) and is otherwise arbitrary. const GAS_VALUES_RING_BUFFER_SIZE: u64 = 64; -// Address impersonated by the system caller -const SYSTEM_CALLER: Address = address!("0x0000000000000000000000000000000000000000"); +// Arc system-accounting caller. +const ARC_SYSTEM_CALLER: Address = SYSTEM_ADDRESS; sol! { struct GasValues { @@ -73,7 +76,7 @@ sol! { interface ISystemAccounting { /// Writes `gasValues` into ring-buffer slot /// `blockNumber % GAS_VALUES_RING_BUFFER_SIZE`, overwriting whatever - /// the slot previously held. SYSTEM_CALLER-gated; no validation on + /// the slot previously held. ARC_SYSTEM_CALLER-gated; no validation on /// `blockNumber`, since writes happen once per block from the block /// executor. function storeGasValues(uint64 blockNumber, GasValues calldata gasValues) external returns (bool); @@ -144,19 +147,25 @@ precompile!(run_system_accounting, precompile_input, hardfork_flags; { )?; // Decode arguments passed to blocklist function - let args = ISystemAccounting::storeGasValuesCall::abi_decode_raw(input) + let args = abi_decode_raw_with_zero6_validation::( + input, + hardfork_flags, + ) .map_err(|_| PrecompileErrorOrRevert::new_reverted_with_penalty( - gas_counter, PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY, ERR_EXECUTION_REVERTED, + gas_counter, PRECOMPILE_EARLY_REVERT_GAS_PENALTY, ERR_EXECUTION_REVERTED, ) )?; - // Record cost - record_cost_or_out_of_gas(&mut gas_counter, PRECOMPILE_SLOAD_GAS_COST)?; + // Redundant 2100-gas charge — no SLOAD occurs here, but kept pre-Zero6 to + // preserve consensus on already-finalized blocks. + if !hardfork_flags.is_active(ArcHardfork::Zero6) { + record_cost_or_out_of_gas(&mut gas_counter, PRECOMPILE_SLOAD_GAS_COST)?; + } // Check caller - if precompile_input.caller != SYSTEM_CALLER { - return Err(PrecompileErrorOrRevert::new_reverted(gas_counter, ERR_INVALID_CALLER)); + if precompile_input.caller != ARC_SYSTEM_CALLER { + return Err(new_reverted_with_early_penalty(gas_counter, ERR_INVALID_CALLER, hardfork_flags)); } // Check delegatecall @@ -188,10 +197,13 @@ precompile!(run_system_accounting, precompile_input, hardfork_flags; { let mut precompile_input = precompile_input; // Decode arguments passed to blocklist function - let args = ISystemAccounting::getGasValuesCall::abi_decode_raw(input) + let args = abi_decode_raw_with_zero6_validation::( + input, + hardfork_flags, + ) .map_err(|_| PrecompileErrorOrRevert::new_reverted_with_penalty( - gas_counter, PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY, ERR_EXECUTION_REVERTED, + gas_counter, PRECOMPILE_EARLY_REVERT_GAS_PENALTY, ERR_EXECUTION_REVERTED, ) )?; @@ -264,7 +276,7 @@ where let result_and_state = evm .transact_system_call( - Address::ZERO, + ARC_SYSTEM_CALLER, SYSTEM_ACCOUNTING_ADDRESS, Bytes::from(call_data), ) @@ -305,7 +317,7 @@ where let result_and_state = evm .transact_system_call( - Address::ZERO, + ARC_SYSTEM_CALLER, SYSTEM_ACCOUNTING_ADDRESS, Bytes::from(call_data), ) @@ -340,8 +352,8 @@ mod tests { use super::*; use crate::helpers::{ ERR_DELEGATE_CALL_NOT_ALLOWED, ERR_EXECUTION_REVERTED, ERR_INVALID_CALLER, - PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY, PRECOMPILE_SLOAD_GAS_COST, - PRECOMPILE_SSTORE_GAS_COST, REVERT_SELECTOR, + PRECOMPILE_EARLY_REVERT_GAS_PENALTY, PRECOMPILE_SLOAD_GAS_COST, PRECOMPILE_SSTORE_GAS_COST, + REVERT_SELECTOR, }; use arc_execution_config::hardforks::{ArcHardfork, ArcHardforkFlags}; @@ -407,7 +419,7 @@ mod tests { target_address: SYSTEM_ACCOUNTING_ADDRESS, bytecode_address: SYSTEM_ACCOUNTING_ADDRESS, known_bytecode: None, - caller: Address::ZERO, + caller: ARC_SYSTEM_CALLER, value: CallValue::Transfer(U256::ZERO), input: CallInput::Bytes( ISystemAccounting::storeGasValuesCall { @@ -437,7 +449,7 @@ mod tests { target_address: SYSTEM_ACCOUNTING_ADDRESS, bytecode_address: SYSTEM_ACCOUNTING_ADDRESS, known_bytecode: None, - caller: Address::ZERO, + caller: ARC_SYSTEM_CALLER, value: CallValue::Transfer(U256::ZERO), input: CallInput::Bytes( ISystemAccounting::getGasValuesCall { @@ -537,17 +549,17 @@ mod tests { }, GetCase { name: "get() invalid params reverts", - caller: Address::ZERO, + caller: ARC_SYSTEM_CALLER, calldata: ISystemAccounting::getGasValuesCall::SELECTOR.into(), gas_limit: PRECOMPILE_SLOAD_GAS_COST, expected_result: InstructionResult::Revert, expected_revert_str: Some(ERR_EXECUTION_REVERTED), return_data: None, - gas_used: PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY, + gas_used: PRECOMPILE_EARLY_REVERT_GAS_PENALTY, }, GetCase { name: "get() OOG", - caller: Address::ZERO, + caller: ARC_SYSTEM_CALLER, calldata: ISystemAccounting::getGasValuesCall { blockNumber: 1 } .abi_encode() .into(), @@ -620,10 +632,20 @@ mod tests { caller: Address, calldata: Bytes, gas_limit: u64, + /// If set, overrides `gas_limit` when Zero6 is active. Needed when + /// the Zero6 early-revert penalty pushes required gas above the + /// Zero5 limit. + zero6_gas_limit: Option, expected_result: InstructionResult, expected_revert_str: Option<&'static str>, return_data: Option, gas_used: u64, + /// If set, overrides `gas_used` for pre-Zero5 hardforks (fixed + /// SSTORE cost vs. EIP-2929/EIP-2200 warm/cold pricing). + pre_zero5_gas_used: Option, + /// If set, overrides `gas_used` when Zero6 is active (auth reverts + /// charge `PRECOMPILE_EARLY_REVERT_GAS_PENALTY`). + zero6_gas_used: Option, target_address: Address, bytecode_address: Address, } @@ -634,13 +656,16 @@ mod tests { gasUsedSmoothed: 22, nextBaseFee: 33, }; - // Zero5: 2100 (fixed auth check) + 22100 (cold SSTORE 0→non-zero) = 24200 + // Zero5: 2100 (redundant pre-auth charge, no real SLOAD) + 22100 (cold SSTORE 0→non-zero) + // = 24200. + // Zero6: 22100 only — redundant charge dropped (see `zero6_gas_used` override below). + // Pre-Zero5 uses the fixed SSTORE path (see `pre_zero5_gas_used` override below). let expected_gas_success = PRECOMPILE_SLOAD_GAS_COST + COLD_SSTORE_ZERO_TO_NONZERO_GAS_COST; let cases: &[StoreCase] = &[ StoreCase { name: "successful insert", - caller: Address::ZERO, + caller: ARC_SYSTEM_CALLER, calldata: ISystemAccounting::storeGasValuesCall { blockNumber: bn_ok, gasValues: val_ok.clone(), @@ -648,39 +673,51 @@ mod tests { .abi_encode() .into(), gas_limit: expected_gas_success, + zero6_gas_limit: None, expected_result: InstructionResult::Return, expected_revert_str: None, return_data: Some(true.abi_encode().into()), gas_used: expected_gas_success, + pre_zero5_gas_used: Some(PRECOMPILE_SLOAD_GAS_COST + PRECOMPILE_SSTORE_GAS_COST), + zero6_gas_used: Some(COLD_SSTORE_ZERO_TO_NONZERO_GAS_COST), target_address: SYSTEM_ACCOUNTING_ADDRESS, bytecode_address: SYSTEM_ACCOUNTING_ADDRESS, }, StoreCase { name: "invalid calldata reverts", - caller: Address::ZERO, + caller: ARC_SYSTEM_CALLER, calldata: ISystemAccounting::storeGasValuesCall::SELECTOR.into(), gas_limit: PRECOMPILE_SLOAD_GAS_COST, + zero6_gas_limit: None, expected_result: InstructionResult::Revert, expected_revert_str: Some(ERR_EXECUTION_REVERTED), return_data: None, - gas_used: PRECOMPILE_ABI_DECODE_REVERT_GAS_PENALTY, + gas_used: PRECOMPILE_EARLY_REVERT_GAS_PENALTY, + pre_zero5_gas_used: None, + zero6_gas_used: None, target_address: SYSTEM_ACCOUNTING_ADDRESS, bytecode_address: SYSTEM_ACCOUNTING_ADDRESS, }, StoreCase { name: "OOG while storing value", - caller: Address::ZERO, + caller: ARC_SYSTEM_CALLER, calldata: ISystemAccounting::storeGasValuesCall { blockNumber: bn_ok, gasValues: val_ok.clone(), } .abi_encode() .into(), + // Pre-Zero6: OOGs at the redundant 2100-gas pre-auth charge. gas_limit: PRECOMPILE_SLOAD_GAS_COST - 1, + // Zero6: redundant charge dropped, so the next gas-charging point is the + // cold SSTORE inside `write()`. One gas short of that cost OOGs there. + zero6_gas_limit: Some(COLD_SSTORE_ZERO_TO_NONZERO_GAS_COST - 1), expected_result: InstructionResult::PrecompileOOG, expected_revert_str: None, return_data: None, gas_used: 0, + pre_zero5_gas_used: None, + zero6_gas_used: None, target_address: SYSTEM_ACCOUNTING_ADDRESS, bytecode_address: SYSTEM_ACCOUNTING_ADDRESS, }, @@ -694,15 +731,20 @@ mod tests { .abi_encode() .into(), gas_limit: PRECOMPILE_SLOAD_GAS_COST, + // Zero6: redundant 2100-gas charge dropped, so only the early-revert + // penalty is consumed. Limit must still cover the penalty exactly. + zero6_gas_limit: Some(PRECOMPILE_EARLY_REVERT_GAS_PENALTY), expected_result: InstructionResult::Revert, expected_revert_str: Some(ERR_INVALID_CALLER), return_data: None, gas_used: PRECOMPILE_SLOAD_GAS_COST, + pre_zero5_gas_used: None, + zero6_gas_used: Some(PRECOMPILE_EARLY_REVERT_GAS_PENALTY), target_address: SYSTEM_ACCOUNTING_ADDRESS, bytecode_address: SYSTEM_ACCOUNTING_ADDRESS, }, StoreCase { - name: "reverts if target address != precompile address", + name: "reverts from zero-address caller (legacy system caller)", caller: Address::ZERO, calldata: ISystemAccounting::storeGasValuesCall { blockNumber: bn_ok, @@ -710,17 +752,43 @@ mod tests { } .abi_encode() .into(), + gas_limit: PRECOMPILE_SLOAD_GAS_COST, + zero6_gas_limit: Some(PRECOMPILE_EARLY_REVERT_GAS_PENALTY), + expected_result: InstructionResult::Revert, + expected_revert_str: Some(ERR_INVALID_CALLER), + return_data: None, + gas_used: PRECOMPILE_SLOAD_GAS_COST, + pre_zero5_gas_used: None, + zero6_gas_used: Some(PRECOMPILE_EARLY_REVERT_GAS_PENALTY), + target_address: SYSTEM_ACCOUNTING_ADDRESS, + bytecode_address: SYSTEM_ACCOUNTING_ADDRESS, + }, + StoreCase { + name: "reverts if target address != precompile address", + caller: ARC_SYSTEM_CALLER, + calldata: ISystemAccounting::storeGasValuesCall { + blockNumber: bn_ok, + gasValues: val_ok.clone(), + } + .abi_encode() + .into(), gas_limit: expected_gas_success, + zero6_gas_limit: None, expected_result: InstructionResult::Revert, expected_revert_str: Some(ERR_DELEGATE_CALL_NOT_ALLOWED), return_data: None, gas_used: PRECOMPILE_SLOAD_GAS_COST, + pre_zero5_gas_used: None, + // Zero6: nothing is charged before check_delegatecall reverts (auth passes, + // redundant pre-auth charge dropped). System-tx callers never delegatecall in + // production, so the 0-gas exit here is unreachable on real workloads. + zero6_gas_used: Some(0), target_address: address!("0x0000000000000000000000000000000000000123"), bytecode_address: SYSTEM_ACCOUNTING_ADDRESS, }, StoreCase { name: "reverts if bytecode address != precompile address", - caller: Address::ZERO, + caller: ARC_SYSTEM_CALLER, calldata: ISystemAccounting::storeGasValuesCall { blockNumber: bn_ok, gasValues: val_ok.clone(), @@ -728,56 +796,81 @@ mod tests { .abi_encode() .into(), gas_limit: expected_gas_success, + zero6_gas_limit: None, expected_result: InstructionResult::Revert, expected_revert_str: Some(ERR_DELEGATE_CALL_NOT_ALLOWED), return_data: None, gas_used: PRECOMPILE_SLOAD_GAS_COST, + pre_zero5_gas_used: None, + zero6_gas_used: Some(0), target_address: SYSTEM_ACCOUNTING_ADDRESS, bytecode_address: address!("0x0000000000000000000000000000000000000123"), }, ]; for tc in cases { - let mut ctx = Context::mainnet(); - ctx.journal_mut() - .load_account(SYSTEM_ACCOUNTING_ADDRESS) - .expect("Unable to load system accounting account"); - - let inputs = CallInputs { - scheme: CallScheme::Call, - target_address: tc.target_address, - bytecode_address: tc.bytecode_address, - known_bytecode: None, - caller: tc.caller, - value: CallValue::Transfer(U256::ZERO), - input: CallInput::Bytes(tc.calldata.clone()), - gas_limit: tc.gas_limit, - is_static: false, - return_memory_offset: 0..0, - }; - - let res = call_system_accounting( - &mut ctx, - &inputs, - ArcHardforkFlags::with(&[ArcHardfork::Zero5]), - ) - .unwrap() - .unwrap(); - // Check result - assert_eq!(res.result, tc.expected_result, "{}", tc.name); + for hardfork_flags in ArcHardforkFlags::all_combinations() { + // ZeroX hardforks are cumulative; Zero6 implies Zero5. + if hardfork_flags.is_active(ArcHardfork::Zero6) + && !hardfork_flags.is_active(ArcHardfork::Zero5) + { + continue; + } - // Revert string - if let Some(expected_revert_str) = tc.expected_revert_str { - let reason = bytes_to_revert_message(res.output.as_ref()).expect("revert reason"); - assert_eq!(reason, expected_revert_str, "{}", tc.name); - } + let tc_name = format!("{} (hardfork_flags: {:?})", tc.name, hardfork_flags); + + let gas_limit = if hardfork_flags.is_active(ArcHardfork::Zero6) { + tc.zero6_gas_limit.unwrap_or(tc.gas_limit) + } else { + tc.gas_limit + }; + + let expected_gas_used = if hardfork_flags.is_active(ArcHardfork::Zero6) { + tc.zero6_gas_used.unwrap_or(tc.gas_used) + } else if hardfork_flags.is_active(ArcHardfork::Zero5) { + tc.gas_used + } else { + tc.pre_zero5_gas_used.unwrap_or(tc.gas_used) + }; + + let mut ctx = Context::mainnet(); + ctx.journal_mut() + .load_account(SYSTEM_ACCOUNTING_ADDRESS) + .expect("Unable to load system accounting account"); + + let inputs = CallInputs { + scheme: CallScheme::Call, + target_address: tc.target_address, + bytecode_address: tc.bytecode_address, + known_bytecode: None, + caller: tc.caller, + value: CallValue::Transfer(U256::ZERO), + input: CallInput::Bytes(tc.calldata.clone()), + gas_limit, + is_static: false, + return_memory_offset: 0..0, + }; + + let res = call_system_accounting(&mut ctx, &inputs, hardfork_flags) + .unwrap() + .unwrap(); + // Check result + assert_eq!(res.result, tc.expected_result, "{tc_name}"); + + // Revert string + if let Some(expected_revert_str) = tc.expected_revert_str { + let reason = + bytes_to_revert_message(res.output.as_ref()).expect("revert reason"); + assert_eq!(reason, expected_revert_str, "{tc_name}"); + } - // Return data - if let Some(expected_return) = &tc.return_data { - assert_eq!(res.output, *expected_return, "{}", tc.name); + // Return data + if let Some(expected_return) = &tc.return_data { + assert_eq!(res.output, *expected_return, "{tc_name}"); + } + // Gas used + assert_eq!(res.gas.used(), expected_gas_used, "{tc_name}"); } - // Gas used - assert_eq!(res.gas.used(), tc.gas_used, "{}", tc.name); } } @@ -841,6 +934,90 @@ mod tests { assert_eq!(decoded_read_original_block.gasUsedSmoothed, 5); } + /// Under Zero6+, any SSTORE through `helpers::write` must fail with + /// `PrecompileOOG` and consume zero gas when the remaining gas is at or + /// below `CALL_STIPEND` (2,300), mirroring revm's `ReentrancySentryOOG` + /// halt for the SSTORE opcode. + #[test] + fn store_gas_values_eip_2200_sentry_zero6() { + use revm_context_interface::cfg::gas::CALL_STIPEND; + + let zero6_flags = ArcHardforkFlags::with(&[ArcHardfork::Zero5, ArcHardfork::Zero6]); + + let calldata: Bytes = ISystemAccounting::storeGasValuesCall { + blockNumber: 1, + gasValues: GasValues { + gasUsed: 1, + gasUsedSmoothed: 2, + nextBaseFee: 3, + }, + } + .abi_encode() + .into(); + + let make_inputs = |gas_limit: u64| CallInputs { + scheme: CallScheme::Call, + target_address: SYSTEM_ACCOUNTING_ADDRESS, + bytecode_address: SYSTEM_ACCOUNTING_ADDRESS, + known_bytecode: None, + caller: ARC_SYSTEM_CALLER, + value: CallValue::Transfer(U256::ZERO), + input: CallInput::Bytes(calldata.clone()), + gas_limit, + is_static: false, + return_memory_offset: 0..0, + }; + + // gas_limit == CALL_STIPEND: sentry fires immediately; no auth check, + // no journal mutation, no gas consumed. + for gas_limit in [1, CALL_STIPEND - 1, CALL_STIPEND] { + let mut ctx = Context::mainnet(); + ctx.journal_mut() + .load_account(SYSTEM_ACCOUNTING_ADDRESS) + .expect("load system accounting account"); + + let res = call_system_accounting(&mut ctx, &make_inputs(gas_limit), zero6_flags) + .unwrap() + .unwrap(); + + assert_eq!( + res.result, + InstructionResult::PrecompileOOG, + "Zero6 sentry must OOG at gas_limit={gas_limit}" + ); + assert_eq!( + res.gas.used(), + 0, + "Zero6 sentry must charge zero gas at gas_limit={gas_limit}" + ); + } + + // gas_limit == CALL_STIPEND + 1: sentry passes; OOG happens later + // inside `write()` at the cold-SSTORE dynamic charge. Externally still + // PrecompileOOG, but proves the sentry boundary is exclusive. + let mut ctx = Context::mainnet(); + ctx.journal_mut() + .load_account(SYSTEM_ACCOUNTING_ADDRESS) + .expect("load system accounting account"); + let res = call_system_accounting(&mut ctx, &make_inputs(CALL_STIPEND + 1), zero6_flags) + .unwrap() + .unwrap(); + assert_eq!(res.result, InstructionResult::PrecompileOOG); + + // Sanity: with enough gas the same call succeeds and the sentry is a + // no-op on the happy path. + let happy_gas = COLD_SSTORE_ZERO_TO_NONZERO_GAS_COST; + let mut ctx = Context::mainnet(); + ctx.journal_mut() + .load_account(SYSTEM_ACCOUNTING_ADDRESS) + .expect("load system accounting account"); + let res = call_system_accounting(&mut ctx, &make_inputs(happy_gas), zero6_flags) + .unwrap() + .unwrap(); + assert_eq!(res.result, InstructionResult::Return); + assert_eq!(res.gas.used(), happy_gas); + } + #[test] fn test_compute_gas_values_storage_slot() { use super::compute_gas_values_storage_slot; @@ -963,7 +1140,7 @@ mod tests { target_address: SYSTEM_ACCOUNTING_ADDRESS, bytecode_address: SYSTEM_ACCOUNTING_ADDRESS, known_bytecode: None, - caller: Address::ZERO, + caller: ARC_SYSTEM_CALLER, value: CallValue::Transfer(U256::ZERO), input: CallInput::Bytes( ISystemAccounting::storeGasValuesCall { @@ -1004,7 +1181,7 @@ mod tests { target_address: SYSTEM_ACCOUNTING_ADDRESS, bytecode_address: SYSTEM_ACCOUNTING_ADDRESS, known_bytecode: None, - caller: Address::ZERO, + caller: ARC_SYSTEM_CALLER, value: CallValue::Transfer(U256::ZERO), input: CallInput::Bytes( ISystemAccounting::getGasValuesCall { blockNumber: 1 } diff --git a/crates/quake/Cargo.toml b/crates/quake/Cargo.toml index 90ed1dc..a855458 100644 --- a/crates/quake/Cargo.toml +++ b/crates/quake/Cargo.toml @@ -67,7 +67,7 @@ thiserror = { workspace = true } tokio = { workspace = true, features = ["full"] } toml = { workspace = true, features = ["preserve_order"] } tracing = { workspace = true } -tracing-subscriber = { workspace = true, features = ["ansi"] } +tracing-subscriber = { workspace = true, features = ["ansi", "env-filter", "fmt"] } url = { workspace = true, features = ["serde"] } [dev-dependencies] diff --git a/crates/quake/README.md b/crates/quake/README.md index 1f080c1..6a695a1 100644 --- a/crates/quake/README.md +++ b/crates/quake/README.md @@ -19,6 +19,7 @@ Quake is a tool for deploying Arc testnets and running end-to-end tests. - change the voting power of validators in the validator set of a node, - upgrade the running version of individual nodes. - Emulate network latency between nodes by assigning data-center regions to nodes and injecting artificial latency between regions. +- Web-based topology viewer for real-time visualization of nodes, connections, peer status, and network health. - MCP (Model Context Protocol) server for AI-assisted testnet management via Claude Code, Cursor, and other MCP-compatible clients. __Table of contents__ @@ -37,6 +38,7 @@ __Table of contents__ - [Upgrade](#upgrade) - [Chaos testing](#chaos-testing) - [The `valset` command](#the-valset-command) + - [The `web` command](#the-web-command) - [The `mcp` command](#the-mcp-command) - [The `generate` command](#the-generate-command) - [Manifest File Format](#manifest-file-format) @@ -62,6 +64,7 @@ __Table of contents__ - [Custom Docker images](#custom-docker-images) - [Remote commands](#remote-commands) - [Sharing a remote testnet](#sharing-a-remote-testnet) + - [Cleaning up orphaned AWS resources](#cleaning-up-orphaned-aws-resources) - [Profiling](#profiling) - [Prerequisites](#prerequisites) - [Feature environment variables](#feature-environment-variables) @@ -184,6 +187,7 @@ graph LR logs valset load + web ssh["remote ssh"] export["remote export"] import["remote import"] @@ -317,17 +321,18 @@ Remove generated files ```bash ./quake clean ``` -It will `stop` the nodes, if not done before. +It will stop the nodes first if needed. Monitoring services are managed +separately with `quake monitoring`. Clean and restart the testnet in one step: ```bash ./quake restart ``` -This is equivalent to running `clean` followed by `start`. It accepts the same -flags as both commands, e.g. `--all` (from `clean`) and `--remote` (from `start`): +This is equivalent to running `clean` followed by `start`. It accepts clean +scope flags such as `--all` and `--data`, plus the regular `start` flags: ```bash -# Clean everything (including monitoring data) and restart -./quake restart --all +# Clean everything (including monitoring data) and restart without monitoring services +./quake restart --all --monitoring=false # Clean all nodes and restart specific nodes ./quake restart validator1 validator2 @@ -616,6 +621,37 @@ Notes: do not use container names (`validator1_cl`, `validator2_cl`). - it does not accept `*` wildcards. +### The `web` command + +The `web` command starts a browser-based topology viewer that visualizes the testnet in real time and allows you to control the testnet. +Open `http://localhost:7777` in a browser to see the web application. +Currently, it only works in local mode. + +There is one tab per topology: +- **Manifest**: expected topology from manifest peers and subnets (always available) +- **CL Consensus / Liveness / Proposal Parts**: gossipsub mesh per topic (live) +- **EL Peers**: execution layer devp2p peer connections (live) + +Two views: **Graph** (force-directed layout with subnet clustering) and **Map** (world map with nodes at their AWS region coordinates). + +#### Data architecture + +- **CL data** (mesh topology, proposer, rounds): Fetched via HTTP from each node's `/network-state` and `/status` endpoints during each topology poll. +- **EL data** (block heights, peers, mempool): Collected via a single WebSocket connection per node. Block heights arrive in real-time via `eth_subscribe(newHeads)`. Peer data (`admin_peers`) and mempool status (`txpool_status`) are polled periodically on the same connection. +- **Container statuses**: Tracked by two background tasks: a `docker events` subscriber for real-time state changes (start, stop, pause, die) and a periodic `docker inspect` poller for network disconnect detection. + +For a deeper dive (server state, background tasks, topology assembly, frontend rendering pipeline), see [`docs/web-architecture.md`](docs/web-architecture.md). + +#### Options + +| Flag | Default | Description | +|------|---------|-------------| +| `--host` | `127.0.0.1` | Bind address for the web server | +| `--port` | `7777` | Web server port | +| `--refresh-ms` | `1000` | Frontend topology poll interval (ms) | +| `--el-refresh-ms` | `1000` | EL peer refresh poller interval (ms) | +| `--container-refresh-ms` | `1000` | Docker container status poller interval (ms) | + ### The `mcp` command The `mcp` command starts a [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server that exposes Quake's testnet tools to AI assistants like Claude Code, Cursor, and other MCP-compatible clients. This lets you observe, manage, and test a running testnet through natural language. @@ -750,12 +786,12 @@ PRs labeled `test-random` will also trigger this workflow. ### The `clean` command -By default, `clean` removes all node data and configuration. The following flags control what is removed: +By default, `clean` stops the testnet and removes all node data and configuration, +but leaves monitoring data alone. The following flags control what is removed: | Flag | Short | Description | |------|-------|-------------| -| `--all` | `-a` | Remove everything, including monitoring services and their data. Cannot be combined with other flags. | -| `--monitoring` | `-m` | Stop monitoring services and remove their data only. | +| `--all` | `-a` | Remove everything, including monitoring services and their data. Cannot be combined with data flags. | | `--data` | `-d` | Remove only execution and consensus layer data, preserving configuration. Cannot be combined with `--execution-data` or `--consensus-data`. | | `--execution-data` | `-x` | Remove only execution layer (Reth) data. Cannot be combined with `--data` or `--consensus-data`. | | `--consensus-data` | `-c` | Remove only consensus layer (Malachite) data. Cannot be combined with `--data` or `--execution-data`. | @@ -770,13 +806,25 @@ By default, `clean` removes all node data and configuration. The following flags # Remove only consensus layer data ./quake clean --consensus-data -# Remove node data and monitoring -./quake clean --data --monitoring - # Remove everything including monitoring ./quake clean --all ``` +### The `monitoring` command + +Monitoring services (Prometheus, Grafana, cAdvisor, Blockscout) can be controlled with the `monitoring` command: + +```bash +# Start monitoring services +./quake monitoring start + +# Stop monitoring services +./quake monitoring stop + +# Stop monitoring services and remove monitoring data +./quake monitoring clean +``` + ## Manifest File Format The manifest is a TOML file. @@ -800,6 +848,37 @@ Optional top-level settings: `${IMAGE_REGISTRY_URL}/arc-execution:`, where `IMAGE_REGISTRY_URL` is taken from the `.env` file (see [Custom Docker images](#custom-docker-images)). - **image_cl_upgrade**, **image_el_upgrade**: Docker images to use when upgrading containers with `quake perturb upgrade`. Required for upgrade scenarios; not supported in remote mode. +- **node_size**: EC2 instance type for validator/full nodes (e.g. `"m6a.4xlarge"`). Equivalent to + the `--node-size` CLI flag. See [Instance sizing](#instance-sizing) for available options. + **Remote mode only** — ignored in local mode (a warning is printed). +- **cc_size**: EC2 instance type for the Control Center. Equivalent to `--cc-size`. + **Remote mode only** — ignored in local mode. +- **node_disk_gb**: Root EBS volume size in GiB for each node. Must be ≥ 8. Equivalent to + `--node-disk-gb`. Omit to keep the AMI default. **Remote mode only** — ignored in local mode. +- **cc_disk_gb**: Root EBS volume size in GiB for the Control Center. Must be ≥ 8. Equivalent to + `--cc-disk-gb`. **Remote mode only** — ignored in local mode. +- **node_volume_type**: AWS EBS volume type for each node's root disk; tunes disk + cost/performance (e.g. match a production disk profile). General Purpose SSD + (`gp2`, `gp3`), Provisioned IOPS SSD (`io1`, `io2`), Throughput Optimized HDD (`st1`), + Cold HDD (`sc1`). Default: `gp3`. See [AWS EBS volume types][ebs-types]. Equivalent + to `--node-volume-type`. **Remote mode only**. +- **node_volume_iops**: Provisioned IOPS for the node root EBS volume; raises the I/O + ceiling above the volume type's baseline. Only valid with `gp3`, `io1`, `io2`; range + 100–256000. Default: AMI's baseline IOPS for the chosen type. Equivalent to + `--node-volume-iops`. **Remote mode only**. +- **el_cpu_limit**: Hard CPU cap for each EL container; reproduces production CPU quotas + on the testnet. Whole or fractional CPUs (e.g. `0.5`). Maps to Docker Compose + [`cpus`][compose-cpus]. Default: no limit (container uses all host CPUs). +- **el_memory_limit_gb**: Hard memory cap for each EL container in GiB; fractional values + (e.g. `2.5`) are allowed. Maps to Docker Compose [`mem_limit`][compose-mem-limit]. + Default: no limit locally; 2.5 GiB on remote. +- **cl_cpu_limit**: Hard CPU cap for each CL container; same semantics as `el_cpu_limit`. +- **cl_memory_limit_gb**: Hard memory cap for each CL container in GiB; fractional values + allowed. Default: no limit locally; 1 GiB on remote. + +[ebs-types]: https://docs.aws.amazon.com/ebs/latest/userguide/ebs-volume-types.html +[compose-cpus]: https://docs.docker.com/reference/compose-file/services/#cpus +[compose-mem-limit]: https://docs.docker.com/reference/compose-file/services/#mem_limit ### Nodes @@ -1106,7 +1185,6 @@ targets. For example: ```bash ./quake load -t 60 -r 500 --targets ALL_VALIDATORS ./quake spam -t 30 -r 1000 --targets TRUSTED -./quake remote load -- --targets FULL_NODES -r 1000 -t 60 ``` To distinguish group references from individual nodes in peer lists, by convention we use lowercase for node names and uppercase for node group names. @@ -1628,12 +1706,12 @@ stop. Send transaction load: ```sh -./quake remote load -- --targets validator1,validator2 -r 1000 -t 60 +./quake load --targets validator1,validator2 -r 1000 -t 60 ``` -Under the hood, this commands calls Spammer from CC. All Spammer options are supported. -`--targets` accepts the same comma-separated selectors as `quake load` on a -local testnet, including manifest node groups such as `ALL_VALIDATORS` or -custom `[node_groups]`. +`quake load` and `quake spam` auto-dispatch based on testnet type: local testnets +run the spammer directly, remote testnets forward to the Control Center via SSH. +All Spammer options are supported. `--targets` accepts comma-separated selectors +including manifest node groups such as `ALL_VALIDATORS` or custom `[node_groups]`. Download diagnostic artifacts from the remote testnet: ```bash @@ -1713,6 +1791,47 @@ also tear down the infrastructure when done. Bundles exported with > infrastructure. Treat it as sensitive material: transfer it securely and do not > commit it to version control. +### Cleaning up orphaned AWS resources + +`quake clean` relies on Terraform state to delete the AWS infrastructure for a +remote testnet. If that state is lost (e.g. the `.quake/` directory was deleted, +or `quake remote create` crashed before state was written), the nodes, VPC, +security groups, and related resources stay in AWS with no supported way for +`quake` to remove them. + +`crates/quake/scripts/aws-resources.sh` is the recovery path. It discovers +resources by the project name they were created with +(`arc--testnet-`) and removes them using the AWS CLI directly, +no Terraform state required. + +```bash +# Summarize every orphaned project for the current user. +./crates/quake/scripts/aws-resources.sh list + +# Show the full resource plan for a single testnet (no deletion). +./crates/quake/scripts/aws-resources.sh list + +# Delete orphaned resources for a testnet. Without --yes, the script prints the +# plan and prompts before deleting; with --yes, it proceeds non-interactively. +./crates/quake/scripts/aws-resources.sh remove +./crates/quake/scripts/aws-resources.sh remove --yes +``` + +`list` is read-only. `remove` defaults to an interactive confirmation, so a +bare invocation never deletes anything without an explicit `y`. The script +scopes to the current user (`$GITHUB_USER`, or `--user NAME`); `remove` and +`list TESTNET` refuse to touch a project belonging to another user unless +`--user` is passed explicitly, and the bare `list` summary surfaces a notice +when `--user` overrides the current `$GITHUB_USER`. Run `--help` for the full +option set. + +> [!IMPORTANT] +> Prefer `quake clean` whenever the Terraform state is still available. This +> script is a recovery tool for orphaned infrastructure only. It matches +> resources on AWS-side names and tags alone, so a mistyped testnet or the +> wrong `--region` can tear down infrastructure belonging to an active +> testnet. + ## Profiling Both the consensus (CL) and execution (EL) binaries support heap and CPU @@ -1864,11 +1983,17 @@ TODO: more testing scenarios to come ### Running tests -Run all tests in all groups: +Run all tests (except excluded groups: `validation`, `health`, `validator_set`, `perf`): ```bash ./quake test ``` +Run an excluded group explicitly: +```bash +./quake test perf:block_time +./quake test validation:basic +``` + Run all tests in a specific group: ```bash ./quake test probe diff --git a/crates/quake/docs/web-architecture.md b/crates/quake/docs/web-architecture.md new file mode 100644 index 0000000..3feb78b --- /dev/null +++ b/crates/quake/docs/web-architecture.md @@ -0,0 +1,312 @@ +# Quake Web Architecture + +The `quake web` command serves a single-page application for real-time testnet monitoring and control. The backend is an Axum HTTP server; the frontend is a self-contained HTML file with inline CSS and JS using D3.js for graph visualization. + +## Architecture diagram + +``` +Browser (user) + | + | HTTP poll (every refresh_ms) + v ++---------------------------------------------------------------+ +| Axum Web Server (web.rs) | +| | +| GET / HTML SPA (web_index.html, refresh_ms) | +| GET /api/topology TopologyResponse JSON | +| POST /api/node/* Node actions (start/stop/kill/pause/...) | +| POST /api/mempool Mempool toggle (on/off) | +| | +| AppState (shared via Arc>) | +| +------------------+ +------------------+ +--------------+ | +| | ElLiveData | | ContainerStatuses| | Testnet | | +| | heights (map) | | name -> status | | manifest | | +| | peers (map) | | (from Docker) | | nodes_meta | | +| | mempool (map) | | | | infra | | +| | errors (map) | | | | | | +| +--------+---------+ +--------+---------+ +--------------+ | +| | | | ++-----------+----------------------+----------------------------+ + | | + Background tasks | + | | + +--------+--------+ +---+------------+ + | | | | + v v v v ++----------+ +----------+ +------------+ +---------------+ +| EL Node | | EL Node | | Docker | | Docker | +| Task #1 | | Task #N | | Events | | Inspect | +| (WS) | | (WS) | | Subscriber | | Poller | ++----+-----+ +----+-----+ +-----+------+ +------+--------+ + | | | | + | WebSocket | WebSocket | docker events | docker inspect + | | | (real-time) | (periodic) + v v v v ++----------+ +----------+ +---------------------------------+ +| EL Node | | EL Node | | Docker Daemon | +| (reth) | | (reth) | | (local or remote via SSH) | +| | | | | | +| - blocks | | - blocks | | Container states: | +| - peers | | - peers | | running/exited/paused/ | +| - txpool | | - txpool | | disconnected (net_count <= 1) | ++----------+ +----------+ +---------------------------------+ + + +-------------------------------------------+ + | | + | CL HTTP (per topology request) | + | (skips containers not running) | + v v + +-------------+ +-------------+ + | CL Node #1 | ... (parallel) ... | CL Node #N | + | (malachite) | | (malachite) | + | | | | + | /network-state (gossipsub mesh, peers) | | + | /status (proposer, round) | | + +-------------+ +-------------+ +``` + +### Data flow per topology poll + +``` +build_topology() + | + +-- Manifest data (always) + | build_node_list() + | build_manifest_topology_edges() + | build_node_regions() + | resolve_mempool_max() + | + +-- EL cached data (from background WS tasks) + | el_live_data.heights -> node.height + | el_live_data.peers -> EL Peers edges + node.el_peers + | el_live_data.mempool -> node.mempool + | el_live_data.errors -> error messages + | + +-- Docker cached data (from events + inspect) + | container_statuses -> node.cl_status, node.el_status + | populate_container_statuses() + | + +-- CL fresh fetches (parallel HTTP, skips non-running) + | fetch_cl_live_topology() -> CL topic edges + node.cl_peers + | fetch_current_proposer() -> proposer name + node.round + | + +-- Error detection (only when live data available) + | merge_per_node_errors() (CL + EL unified, skips disconnected) + | detect_unreachable_nodes() (unreachable vs disconnected) + | + +-> TopologyResponse JSON + { testnet_name, source, latest_height, current_proposer, + nodes, networks, errors, node_regions, mempool_max } +``` + +### Frontend rendering pipeline + +``` +fetchTopology() --poll--> /api/topology + | + +-- Detect status transitions (shake with 5s cooldown) + +-- Clear stale pending actions (button spinners, 10s timeout) + | + +-- updateStatus() header: testnet name + +-- updateTabs() tab bar: topologies label + network tabs + Graph/Map + +-- updateErrors() error banner (max 20) + +-- renderGraph() if viewMode == 'graph' + | or renderWorldView() if viewMode == 'world' + | | + | +-- resolveEdges() returns edges + directedMode + | +-- buildNodeData() D3 node datum (incl. disconnected flag) + | +-- [persistent peer overlay] purple directional arrows (Manifest tab) + | +-- [EL trusted peer overlay] orange directional arrows (Manifest tab) + | +-- renderNodeCircles() circles with status colors + | +-- renderProposerStar() gold star overlay + | +-- renderMempoolBars() queued/pending columns + | +-- renderNodeLabels() text with outline + | +-- wireNodeInteraction() click/highlight/drag + | +-- drawSubnetHulls() dashed hull shapes (graph only) + | +-- applyHighlight() dim/brighten based on selection/subnet + | + +-- updateDetailPanel() right: node inspector + +-- updateHeightsList() left: heights, rounds, mempool + +-- buildLegend() bottom-left: node types, subnets (clickable) +``` + +## Server + +### Entry point + +`run_server()` in `web.rs` binds the Axum server and spawns background tasks before serving requests. + +### Shared state (`AppState`) + +| Field | Type | Description | +|-------|------|-------------| +| `testnet` | `Arc>` | Manifest, nodes metadata, infra provider | +| `el_live_data` | `Arc>` | Block heights, peer lists, mempool counts, EL errors | +| `container_statuses` | `Arc>` | Docker container status per service name | +| `mempool_active` | `Arc` | Controls whether txpool_status is polled | +| `refresh_ms` | `u64` | Frontend poll interval (injected into HTML) | + +### Routes + +| Route | Method | Description | +|-------|--------|-------------| +| `/` | GET | Serves the HTML SPA with `refresh_ms` injected | +| `/api/topology` | GET | Returns `TopologyResponse` JSON | +| `/api/node/{name}/{action}` | POST | Node/container action. Optional `?target=cl\|el`. Returns `{"ok": true}` or `{"ok": false, "error": "..."}` | +| `/api/mempool/{on\|off}` | POST | Toggle mempool polling | + +### Node actions + +start, stop, restart, kill, pause, unpause, disconnect, reconnect. Actions execute per-container (not batched). Partial failures are reported with error details. + +## Background tasks + +### Per-node EL WebSocket task (`el_node_task`) + +One long-lived async task per EL node. Maintains a single WebSocket connection for all EL data: + +- **Block subscription** (push): `eth_subscribe newHeads` updates `el_live_data.heights` +- **Peer polling** (every `el_refresh_ms`): `admin_peers` via `raw_request` updates `el_live_data.peers` +- **Mempool polling** (every `el_refresh_ms`, only when `mempool_active`): `txpool_status` via `raw_request` updates `el_live_data.mempool` + +Both branches run concurrently via `tokio::select!`. On disconnect, the task records an error in `el_live_data.errors`, preserves the last known height, and reconnects after 2 seconds. Tolerates up to 3 consecutive `admin_peers` failures before reconnecting. + +### Docker events subscriber + +Subscribes to `docker events` for real-time container state detection (millisecond latency). Maps Docker actions to statuses: + +| Docker action | Status | +|---------------|--------| +| die, kill, stop | exited | +| start | running | +| pause | paused | +| unpause | running | + +Reconnects automatically if the event stream ends. stderr suppressed for containers not yet created. + +### Docker inspect poller + +Periodic `docker inspect` (every `container_refresh_ms`) for state that events don't cover: + +- Derives status from `State.Status`, `State.Paused`, `State.Restarting` +- Detects **network disconnection**: a running container with only 1 network (host-access) is reported as `disconnected` +- Container names come from the manifest at startup +- stderr suppressed (containers with `start_at` delays don't exist yet) + +## Topology response assembly + +`build_topology()` reads all caches and assembles the response on each `/api/topology` call. + +### Data sources + +| Source | Data | Availability | +|--------|------|-------------| +| Manifest | Node list, subnets, regions, config, peer expectations, explicit CL/EL peers | Always | +| `region_assignments.json` | Node-to-region mapping | Always (if latency emulation enabled) | +| `el_live_data` | Block heights, EL peers, mempool, EL errors | Best-effort (requires running EL) | +| Docker inspect/events | Container statuses (running, exited, paused, disconnected) | Best-effort (requires Docker) | +| CL HTTP fetches | Gossipsub mesh topology, proposer, rounds | Best-effort (fresh per request, body parsed in parallel) | + +### Assembly pipeline + +1. Build node list from manifest (`build_node_list`) with explicit CL/EL peer lists +2. Build manifest-based network (Manifest Topology from subnet defaults) +3. If live data available: + - Populate heights and container statuses (`populate_container_statuses`) + - Build EL peer edges and detail (`build_el_live_edges`, `populate_el_peer_details`) + - Fetch CL data in parallel (`fetch_cl_live_topology`, `fetch_current_proposer`), skipping containers that are not running + - Merge errors (`merge_per_node_errors`, skips disconnected containers) and detect unreachable/disconnected nodes (`detect_unreachable_nodes`) + - Populate mempool data (`populate_mempool_data`) +4. Build node regions and mempool max from manifest + +### Unreachable vs disconnected + +- **Unreachable** (red): CL or EL failed to respond, container is not in `disconnected` Docker state +- **Disconnected** (orange): failure is due to the container being network-isolated (only host-access network attached) +- Error messages use "disconnected" vs "not responding" accordingly + +## Frontend + +### Poll loop + +`fetchTopology()` runs every `refresh_ms`. On each poll: + +1. Fetch `/api/topology` +2. Detect status transitions (trigger shake with 5-second cooldown to prevent doubles) +3. Clear stale pending actions (button spinners, 10-second timeout) +4. Update all UI components + +### Rendering pipeline + +| Function | Description | +|----------|-------------| +| `updateStatus()` | Header testnet name and browser title | +| `updateTabs()` | Tab bar: topologies label + network tabs (dimmed when empty) + Graph/Map | +| `updateErrors()` | Error banner (max 20 messages) | +| `renderGraph()` / `renderWorldView()` | Main visualization (branched by `viewMode`) | +| `updateDetailPanel()` | Right-side node inspector | +| `updateHeightsList()` | Left sidebar heights, rounds, mempool columns | +| `buildLegend()` | Bottom-left legend (node types, clickable subnets) | + +### Shared rendering helpers + +Extracted to avoid duplication between Graph and Map views: + +| Helper | Description | +|--------|-------------| +| `resolveEdges(net)` | Returns edges and directedMode | +| `buildNodeData(n, activeNodeNames, proposer)` | Builds D3 node datum from GraphNode (includes disconnected flag) | +| `renderNodeCircles(node)` | Appends circle with type-based fill and status-based stroke | +| `renderProposerStar(node, proposer)` | Appends gold star to proposer node | +| `renderMempoolBars(node)` | Appends queued/pending square columns | +| `renderNodeLabels(node)` | Appends text label with outline | +| `wireNodeInteraction(node, link)` | Attaches click handlers, highlight, subnet filter | + +### View modes + +**Graph** (default): D3 force simulation with link, charge, center, collide, and subnet attraction forces. Runs to equilibrium synchronously (no animation). Nodes are draggable and positions persist across polls. Initial zoom-to-fit is instant. Fit-to-page accounts for sidebar and detail panel. + +**Map**: D3 NaturalEarth1 projection with land masses from world-atlas TopoJSON (CDN). Nodes placed at AWS region coordinates, spread in small circles when multiple nodes share a region. Edges drawn as straight lines. Region labels above clusters. + +### Toggles + +| Toggle | Visibility | Backend | Frontend effect | +|--------|-----------|---------|-----------------| +| Show proposer | Always | None | Gold star on proposer node, purple name in heights list | +| Show mempools | Always | POST `/api/mempool/on\|off` | Queued/pending bar columns on nodes, extra columns in heights list | +| Show latencies | Always | None | Inter-region latency labels on edges (from AWS matrix) | +| Show subnets | Always | None | Dashed hull shapes around subnet-grouped nodes (Graph only) | +| Show CL persistent peers | Manifest tab | None | Purple directional arrows from manifest cl_persistent_peers | +| Show EL trusted peers | Manifest tab | None | Orange directional arrows from manifest el_trusted_peers | + +Peer overlay arrows (CL persistent, EL trusted) use inline SVG path elements instead of SVG markers for instant opacity response when selecting nodes. + +### Node detail panel + +Right-side panel, resizable via drag handle: + +1. **Title row**: node name (bold/larger when selected) + action buttons for both layers +2. **Per-layer rows**: CL/EL status badge + per-container action buttons +3. **Peers section** (scrollable): merged CL + EL peer table with columns Dir, C L P E, Score, T I S +4. **Configuration**: non-default manifest fields +5. **Region**: AWS region with city/country label + +### Heights panel + +Left sidebar showing per-node block heights and consensus rounds. Heights are color-coded by lag tiers: at latest, 1 block behind, 2-5 blocks behind, 6+ blocks behind, unreachable (no data). Rounds are color-coded by severity: round 0, round 1, round > 1. + +Resizable via drag handle. Node names are clickable (selects node on graph). A status dot next to each name indicates node type (validator, non-validator) or failure mode (disconnected, unreachable). + +### Subnet filtering + +Clicking a subnet name in the legend dims all nodes not in that subnet and fades unrelated edges (including overlay arrows). Subnet names underline on hover and stay underlined when selected. Click again, click a node, or click the background to clear. + +### Button spinning + +When an action button is clicked, it spins until Docker reports a status change. Tracked via `pendingActions` map with a 10-second timeout. Spinning buttons are disabled (dimmed and non-interactive). Shake animation triggers before the API call for instant feedback. + +### Shake animation + +- Triggers on button click for: restart, stop, kill, pause, disconnect +- Triggers from poll for status transitions to exited, paused, disconnected (terminal perturbations) +- 5-second cooldown after any shake prevents double-shakes diff --git a/crates/quake/files/web_index.html b/crates/quake/files/web_index.html new file mode 100644 index 0000000..a909136 --- /dev/null +++ b/crates/quake/files/web_index.html @@ -0,0 +1,2145 @@ + + + + +Quake Live + + + + +
+ Quake Live + +
+ +
+
+ Manifest expected topology from manifest configuration
+ CL Consensus gossipsub mesh for consensus messages
+ CL Liveness gossipsub mesh for liveness/keep-alive
+ CL Proposal Parts gossipsub mesh for block proposals
+ EL Peers execution layer devp2p peer connections +
+ +
+ Dir CL connection direction: ← inbound, → outbound (who initiated the TCP connection)
+ C L P CL gossipsub mesh membership per topic: + connected, + not connected
+   C = Consensus, L = Liveness, P = Proposal Parts
+ E CL explicit/persistent peer (direct delivery, bypasses mesh)
+ Score CL gossipsub peer score
+ T I S EL peer flags from admin_peers:
+   T = Trusted, + I = Inbound, + S = Static
+ red dots = layer disconnected
+ name = both CL and EL disconnected
+
+ Data sources:
+ CL columns from each node's /network-state endpoint (gossipsub mesh, peer scores).
+ EL columns from each node's admin_peers RPC via WebSocket.
+ Peer list = manifest peers + any additional live peers discovered via gossipsub or devp2p. +
+ +
+
No topology data yet
+ + +
+
+
+
+ +
+ + + + + + diff --git a/crates/quake/scenarios/examples/14nodes.toml b/crates/quake/scenarios/examples/14nodes.toml new file mode 100644 index 0000000..2922dfe --- /dev/null +++ b/crates/quake/scenarios/examples/14nodes.toml @@ -0,0 +1,81 @@ +# 14 nodes (7 validators, 4 sentries, 1 relay, 2 full) across 6 subnets. +# Mostly hub-and-spoke: each sentry bridges one isolated subnet to untrusted, +# except group1 which uses a two-hop bridge (sentry2 + relay1). +# Validators are isolated within a single subnet; full nodes sit on untrusted only. +# +# ┌──────────────┐ ┌────────────────┐ ┌──────────────┐ ┌──────────────┐ +# │ _trusted_ │ │ _group1_ext_ │ │ _group2_ │ │ _external_ │ +# │ │ │ │ │ │ │ │ +# │ validator1 │ │ validator3 │ │ validator5 │ │ validator6 │ +# │ validator2 │ │ validator4 │ │ │ │ validator7 │ +# └──────┬───────┘ └───────┬────────┘ └──────┬───────┘ └──────┬───────┘ +# sentry1 sentry2 sentry3 sentry4 +# │ ┌─────┴──────┐ │ │ +# │ │ _group1_ │ │ │ +# │ │ relay1 │ │ │ +# │ └─────┬──────┘ │ │ +# ┌──────┴──────────────────┴──────────────────┴─────────────────┴─────┐ +# │ _untrusted_ │ +# │ full1 full2 │ +# └────────────────────────────────────────────────────────────────────┘ + +cl.config.discovery_num_inbound_peers = 2 +cl.config.discovery_num_outbound_peers = 2 + +[node_groups] +TRUSTED_VALIDATORS = ["validator1", "validator2"] +GROUP1_VALIDATORS = ["validator3", "validator4"] +EXTERNAL_VALIDATORS = ["validator6", "validator7"] + +[nodes.validator1] +subnets = ["trusted"] +cl_persistent_peers = ["TRUSTED_VALIDATORS"] +cl_gossipsub.explicit_peering = true +el.config.trusted_peers = ["TRUSTED_VALIDATORS"] + +[nodes.validator2] +subnets = ["trusted"] +cl_persistent_peers = ["TRUSTED_VALIDATORS"] + +[nodes.validator3] +subnets = ["group1_ext"] + +[nodes.validator4] +subnets = ["group1_ext"] + +[nodes.validator5] +subnets = ["group2"] + +[nodes.validator6] +subnets = ["external"] +cl_persistent_peers = ["EXTERNAL_VALIDATORS"] + +[nodes.validator7] +subnets = ["external"] +el.config.trusted_peers = ["EXTERNAL_VALIDATORS"] + +[nodes.sentry1] +subnets = ["trusted", "untrusted"] +cl_gossipsub.mesh_prioritization = true +cl_gossipsub.load = "high" +el.config.trusted_peers = ["TRUSTED_VALIDATORS"] +el.config.disable_discovery = true + +[nodes.relay1] +subnets = ["group1", "untrusted"] + +[nodes.sentry2] +subnets = ["group1", "group1_ext"] +el.config.trusted_peers = ["GROUP1_VALIDATORS"] + +[nodes.sentry3] +subnets = ["group2", "untrusted"] + +[nodes.sentry4] +subnets = ["external", "untrusted"] + +[nodes.full1] +subnets = ["untrusted"] + +[nodes.full2] +subnets = ["untrusted"] diff --git a/crates/quake/scenarios/nightly-tx-propagation.toml b/crates/quake/scenarios/nightly-tx-propagation.toml new file mode 100644 index 0000000..82db1e2 --- /dev/null +++ b/crates/quake/scenarios/nightly-tx-propagation.toml @@ -0,0 +1,97 @@ +name = "nightly-tx-propagation" +description = "Nightly sentry-topology transaction propagation check after a quiet period." + +# Four validators are isolated behind two sentries. Full nodes connect to the +# sentries, but sentries only trust validators for transaction propagation. + +[node_groups] +SENTRIES = ["sentry-1", "sentry-2"] +FULLNODES = ["full-blue", "full-green", "full-purple"] + +[nodes.validator1] +subnets = ["trusted"] +cl_persistent_peers = ["SENTRIES", "ALL_VALIDATORS"] +cl_persistent_peers_only = true +cl_gossipsub.explicit_peering = true +cl_suggested_fee_recipient = "0x1111111111111111111111111111111111111111" +[nodes.validator1.el.config] +trusted_peers = ["SENTRIES", "ALL_VALIDATORS"] +trusted_only = true +disable_discovery = true + +[nodes.validator2] +subnets = ["trusted"] +cl_persistent_peers = ["SENTRIES", "ALL_VALIDATORS"] +cl_persistent_peers_only = true +cl_gossipsub.explicit_peering = true +cl_suggested_fee_recipient = "0x2222222222222222222222222222222222222222" +[nodes.validator2.el.config] +trusted_peers = ["SENTRIES", "ALL_VALIDATORS"] +trusted_only = true +disable_discovery = true + +[nodes.validator3] +subnets = ["trusted"] +cl_persistent_peers = ["SENTRIES", "ALL_VALIDATORS"] +cl_persistent_peers_only = true +cl_gossipsub.explicit_peering = true +cl_suggested_fee_recipient = "0x3333333333333333333333333333333333333333" +[nodes.validator3.el.config] +trusted_peers = ["SENTRIES", "ALL_VALIDATORS"] +trusted_only = true +disable_discovery = true + +[nodes.validator4] +subnets = ["trusted"] +cl_persistent_peers = ["SENTRIES", "ALL_VALIDATORS"] +cl_persistent_peers_only = true +cl_gossipsub.explicit_peering = true +cl_suggested_fee_recipient = "0x4444444444444444444444444444444444444444" +[nodes.validator4.el.config] +trusted_peers = ["SENTRIES", "ALL_VALIDATORS"] +trusted_only = true +disable_discovery = true + +[nodes.sentry-1] +subnets = ["trusted", "untrusted"] +cl_persistent_peers = ["sentry-2", "ALL_VALIDATORS"] +cl_gossipsub.explicit_peering = true +cl_gossipsub.load = "high" +[nodes.sentry-1.el.config] +trusted_peers = ["ALL_VALIDATORS"] +tx_propagation_policy = "Trusted" +disable_discovery = true + +[nodes.sentry-2] +subnets = ["trusted", "untrusted"] +cl_persistent_peers = ["sentry-1", "ALL_VALIDATORS"] +cl_gossipsub.explicit_peering = true +cl_gossipsub.load = "high" +[nodes.sentry-2.el.config] +trusted_peers = ["ALL_VALIDATORS"] +tx_propagation_policy = "Trusted" +disable_discovery = true + +[nodes.full-blue] +subnets = ["untrusted"] +cl_persistent_peers = ["sentry-1", "sentry-2"] +[nodes.full-blue.el.config] +trusted_peers = ["sentry-1", "sentry-2"] +tx_propagation_policy = "Trusted" +disable_discovery = true + +[nodes.full-green] +subnets = ["untrusted"] +cl_persistent_peers = ["sentry-1", "sentry-2"] +[nodes.full-green.el.config] +trusted_peers = ["sentry-1", "sentry-2"] +tx_propagation_policy = "Trusted" +disable_discovery = true + +[nodes.full-purple] +subnets = ["untrusted"] +cl_persistent_peers = ["sentry-1", "sentry-2"] +[nodes.full-purple.el.config] +trusted_peers = ["sentry-1", "sentry-2"] +tx_propagation_policy = "Trusted" +disable_discovery = true diff --git a/crates/quake/scripts/aws-resources.sh b/crates/quake/scripts/aws-resources.sh new file mode 100755 index 0000000..ac49cb3 --- /dev/null +++ b/crates/quake/scripts/aws-resources.sh @@ -0,0 +1,1077 @@ +#!/usr/bin/env bash +# +# List and remove AWS resources created by `quake remote create` when the +# Terraform state is no longer available to drive `quake clean`. +# +# Identification: anchored VPC traversal. The VPC is found by its Name tag +# (equal to the project name); child resources are enumerated via `--filters +# Name=vpc-id,Values=`. The key pair and CloudWatch alarm live outside the +# VPC and are looked up by deterministic name. +# +# Project name: arc--testnet-, +# mirroring Terraform's `lower(replace(x, "/[/.]/", "-"))` sanitization. +# +# Requirements: +# - AWS CLI v2 (uses JMESPath filter syntax such as Tags[?Key==`Name`]), with +# AWS credentials configured. + +# `set -u` is intentionally omitted: Bash 3.2 (macOS default) reports +# `"${arr[@]}"` as an unbound variable for empty arrays, even when the array +# was declared with `arr=()`. Keeping nounset would require wrapping every +# array expansion in `${arr[@]+"${arr[@]}"}`, which is worse than losing it. +set -eo pipefail + +# --- Constants --------------------------------------------------------------- + +# Marker tag key applied to every Quake EC2 instance via var.tags. +MARKER_TAG_KEY="arc-quake-testnet" + +# Project name prefix and infix used for all Quake projects. +PROJECT_PREFIX="arc-" +PROJECT_INFIX="-testnet-" + +# Instance-state filter values for live (non-terminated) instances. Used in +# describe-instances filters as Name=instance-state-name,Values=$INSTANCE_LIVE_STATES. +INSTANCE_LIVE_STATES="pending,running,stopping,stopped,shutting-down" + +# --- Globals (populated by parse_args) --------------------------------------- + +SUBCOMMAND="" +REGION="" +USER_OVERRIDE="" +USER_OVERRIDE_PASSED=false +SKIP_PROMPT=false +VERBOSE=false +TESTNET_ARG="" + +# State shared between plan and apply. +declare -a FAILURES=() + +# --- Output helpers ---------------------------------------------------------- + +# Data on stdout; progress, warnings, and errors on stderr. + +out() { printf '%s\n' "$*"; } +log() { printf '%s\n' "$*" >&2; } +warn() { printf 'warning: %s\n' "$*" >&2; } +err() { printf 'error: %s\n' "$*" >&2; } + +die() { + err "$1" + exit "${2:-1}" +} + +die_usage() { + err "$1" + usage >&2 + exit 2 +} + +# TTY-aware color codes. Only active when stdout is a terminal. +if [[ -t 1 ]]; then + C_BOLD=$'\033[1m' + C_DIM=$'\033[2m' + C_RESET=$'\033[0m' +else + C_BOLD="" + C_DIM="" + C_RESET="" +fi + +# --- Usage ------------------------------------------------------------------- + +usage() { + local prog + prog="$(basename -- "$0")" + # Unquoted heredoc so ${prog} expands; env-var references are escaped as \$. + cat </dev/null 2>&1; then + die "required command not found: $1" + fi +} + +# True when an AWS CLI text-output value indicates "no such resource". The CLI +# emits an empty string when a query returns nothing, and the literal "None" +# when a queried field is null on an existing resource. +is_absent_or_none() { + [[ -z "${1:-}" || "$1" == "None" ]] +} + +# Mirror Terraform's lower(replace(x, "/[/.]/", "-")). +sanitize() { + local input="${1:-}" + [[ -z "$input" ]] && return + printf '%s' "$input" | tr '[:upper:]' '[:lower:]' | tr '/.' '--' +} + +# Resolve the repository root. Prefer git; fall back to path math from BASH_SOURCE. +resolve_repo_root() { + local script_dir root + script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + if root="$(git -C "$script_dir" rev-parse --show-toplevel 2>/dev/null)"; then + printf '%s' "$root" + return + fi + # Fallback: this script lives at crates/quake/scripts/, so repo root is 3 up. + (cd "$script_dir/../../.." && pwd) +} + +# Source /.env only if GITHUB_USER is unset. +load_dotenv_if_needed() { + if [[ -n "${GITHUB_USER:-}" ]]; then + return + fi + local dotenv="$1/.env" + if [[ -f "$dotenv" ]]; then + set -a + # shellcheck disable=SC1090 + source "$dotenv" + set +a + fi +} + +# Resolved effective user for project-name construction (already sanitized). +# --user takes precedence over $GITHUB_USER; missing both is a usage error. +resolve_effective_user() { + if [[ -n "$USER_OVERRIDE" ]]; then + sanitize "$USER_OVERRIDE" + return + fi + if [[ -z "${GITHUB_USER:-}" ]]; then + die_usage "--user not provided and GITHUB_USER is not set" + fi + sanitize "$GITHUB_USER" +} + +# AWS CLI wrapper that pins the region. +aws_cli() { + aws --region "$REGION" "$@" +} + +# Run an AWS describe/query and emit its `--output text` result split by whitespace +# (tabs and newlines) into one line per value. Silent on missing resources. +# Intended only for single-column queries. +collect() { + local raw + raw="$(aws_cli "$@" --output text 2>/dev/null || true)" + if is_absent_or_none "$raw"; then + return + fi + # AWS text output separates list elements with tabs; normalize. + tr '[:space:]' '\n' <<<"$raw" | sed '/^$/d' +} + +# Run an AWS describe/query expected to return multi-column text output where +# the first column is the resource ID and the second is a human-readable name +# (or "None" when absent). Emits one tab-separated record per line; silent on +# missing resources. +collect_pairs() { + local raw + raw="$(aws_cli "$@" --output text 2>/dev/null || true)" + if [[ -z "$raw" ]]; then + return + fi + local line first + while IFS= read -r line; do + [[ -z "$line" ]] && continue + first="${line%%$'\t'*}" + is_absent_or_none "$first" && continue + printf '%s\n' "$line" + done <<< "$raw" +} + +# Bash 3 replacement for `mapfile -t NAME < <(cmd)`. Reads newline-delimited +# input on stdin and assigns each line to the named array, escaping values +# through `%q` so whitespace or shell metacharacters survive the eval. +read_lines() { + local __name="$1" + eval "$__name=()" + local __line __escaped + while IFS= read -r __line; do + printf -v __escaped '%q' "$__line" + eval "$__name+=($__escaped)" + done +} + +# --- Project name handling --------------------------------------------------- + +# True if the argument looks like a fully-qualified project name. +# Requires both the arc- prefix and the -testnet- infix to reduce false positives +# on testnet basenames that happen to contain "-testnet-" as a substring. +# Caller MUST pass a sanitized (lowercase) string — the constants are lowercase +# and bash glob matches are case-sensitive. +is_full_project_name() { + local arg="$1" + [[ "$arg" == ${PROJECT_PREFIX}* && "$arg" == *${PROJECT_INFIX}* ]] +} + +# Given a testnet argument and the effective (sanitized) user, return the project name. +# The argument is sanitized before classification so an uppercased full project +# name (e.g. ARC-LOCALDEV-TESTNET-ALICE) is not mistaken for a testnet basename +# and double-wrapped with the arc- prefix and -testnet- infix. +project_name_from_arg() { + local arg="$1" effective_user="$2" + local normalized + normalized="$(sanitize "$arg")" + if is_full_project_name "$normalized"; then + printf '%s' "$normalized" + else + local testnet + testnet="$(sanitize "$(basename -- "$arg")")" + printf '%s%s%s%s' "$PROJECT_PREFIX" "$testnet" "$PROJECT_INFIX" "$effective_user" + fi +} + +# Extract the user segment (text after the last "-testnet-") from a project name. +user_from_project() { + local project="$1" + printf '%s' "${project##*${PROJECT_INFIX}}" +} + +# Extract the testnet segment (text between the "arc-" prefix and the last +# "-testnet-" infix) from a project name. +testnet_from_project() { + local project="$1" + local without_prefix="${project#${PROJECT_PREFIX}}" + # %${PROJECT_INFIX}* is shortest-match from end, i.e. strips from the LAST + # occurrence of "-testnet-" onwards. This keeps testnet names that happen to + # contain "-testnet-" as a substring intact. + printf '%s' "${without_prefix%${PROJECT_INFIX}*}" +} + +# Cross-user guardrail: refuse to operate on someone else's project unless --user +# was explicitly passed. Only relevant when the argument was a full project name; +# for testnet basenames, the constructed project name always uses the effective user. +check_cross_user_guardrail() { + local project="$1" + if [[ "$USER_OVERRIDE_PASSED" == "true" ]]; then + return + fi + local project_user gh_sanitized + project_user="$(user_from_project "$project")" + gh_sanitized="$(sanitize "${GITHUB_USER:-}")" + if [[ -n "$gh_sanitized" && "$project_user" != "$gh_sanitized" ]]; then + die_usage "project '$project' belongs to user '$project_user' but --user was not passed (current GITHUB_USER resolves to '$gh_sanitized'). Pass --user $project_user to confirm." + fi +} + +# --- Discovery: per-project ------------------------------------------------- +# +# Side effects: this function OVERWRITES module-level globals listed below. +# It is the only writer of these arrays; callers must not assume the values +# survive across calls. cmd_list_summary deliberately re-invokes per project +# in a loop, relying on this clean-slate behavior. +# +# Populates parallel *_IDS and *_NAMES arrays for each resource class, indexed +# together (NAMES[i] is the Name tag / GroupName of IDS[i], or "" if absent). +# Key pairs and CloudWatch alarms have no separate name field, so only KEY_NAMES +# and ALARM_NAMES are populated for those. +# +# VPC_IDS/VPC_NAMES VPCs with Name tag = +# INSTANCE_IDS/INSTANCE_NAMES instances tagged project= OR in the VPCs +# ENI_IDS/ENI_NAMES ENIs tagged project= OR in the VPCs +# VOLUME_IDS/VOLUME_NAMES EBS volumes tagged project= +# SUBNET_IDS/SUBNET_NAMES subnets inside the VPCs +# RTB_IDS/RTB_NAMES non-main route tables inside the VPCs +# SG_IDS/SG_NAMES non-default security groups inside the VPCs (name = GroupName) +# IGW_IDS/IGW_NAMES internet gateways attached to the VPCs +# KEY_NAMES key pairs named -key (ID == name) +# ALARM_NAMES CloudWatch alarms -cc-auto-recovery (ID == name) +# +# EBS volumes: Terraform sets `volume_tags` on aws_instance so root volumes +# inherit `project=`. With delete_on_termination=true (AMI default), +# in-use root volumes vanish on instance termination — discovery here only +# surfaces detached/orphaned volumes, plus root volumes whose instance is +# still running at discovery time. The delete phase re-checks status. +# Volumes created before this tagging change have no `project` tag and are +# invisible to this script; they will still be cleaned up by their attached +# instance's termination via delete_on_termination. + +# Scan a describe query into parallel IDS/NAMES arrays, deduping via a seen-map. +# Called once per data source; union scans (instances/ENIs) call it twice with +# the same target arrays and seen-map. +# +# _scan_into IDS_ARR NAMES_ARR SEEN_VAR aws-cli-args... +# +# Bash 3 port: namerefs and associative arrays are unavailable, so array +# identities are passed by name and resolved via `eval`, and the seen-map is +# a colon-delimited string probed with a `case` pattern match. +_scan_into() { + local ids_name="$1" names_name="$2" seen_name="$3" + shift 3 + local id name id_esc name_esc seen_val + while IFS=$'\t' read -r id name _; do + [[ -z "$id" ]] && continue + eval "seen_val=\"\${$seen_name}\"" + case ":${seen_val}:" in + *":$id:"*) continue ;; + esac + eval "$seen_name=\"\${$seen_name}:\$id\"" + [[ "$name" == "None" ]] && name="" + printf -v id_esc '%q' "$id" + printf -v name_esc '%q' "$name" + eval "$ids_name+=($id_esc)" + eval "$names_name+=($name_esc)" + done < <(collect_pairs "$@") +} + +discover_project() { + local project="$1" vpc + + VPC_IDS=(); VPC_NAMES=(); local _s_vpc="" + INSTANCE_IDS=(); INSTANCE_NAMES=(); local _s_inst="" + ENI_IDS=(); ENI_NAMES=(); local _s_eni="" + VOLUME_IDS=(); VOLUME_NAMES=(); local _s_vol="" + SUBNET_IDS=(); SUBNET_NAMES=(); local _s_subnet="" + RTB_IDS=(); RTB_NAMES=(); local _s_rtb="" + SG_IDS=(); SG_NAMES=(); local _s_sg="" + IGW_IDS=(); IGW_NAMES=(); local _s_igw="" + + local inst_state="Name=instance-state-name,Values=${INSTANCE_LIVE_STATES}" + local inst_query='Reservations[].Instances[].[InstanceId, Tags[?Key==`Name`].Value|[0]]' + local eni_query='NetworkInterfaces[].[NetworkInterfaceId, Tags[?Key==`Name`].Value|[0]]' + + # VPCs anchor the traversal. + _scan_into VPC_IDS VPC_NAMES _s_vpc \ + ec2 describe-vpcs \ + --filters "Name=tag:Name,Values=${project}" \ + --query 'Vpcs[].[VpcId, Tags[?Key==`Name`].Value|[0]]' + + # Instances and ENIs: tag-based first; VPC-scoped later in the per-VPC loop. + _scan_into INSTANCE_IDS INSTANCE_NAMES _s_inst \ + ec2 describe-instances \ + --filters "Name=tag:project,Values=${project}" "$inst_state" \ + --query "$inst_query" + _scan_into ENI_IDS ENI_NAMES _s_eni \ + ec2 describe-network-interfaces \ + --filters "Name=tag:project,Values=${project}" \ + --query "$eni_query" + + # EBS volumes: tag-based only. Volumes have no VPC association, so there is + # no per-VPC fallback. Pulled regardless of state — `available` are the true + # orphans we will delete; `in-use` ones may attach to instances we are about + # to terminate (delete_on_termination handles the cleanup) and are surfaced + # here for visibility only. The delete phase re-checks status to decide. + _scan_into VOLUME_IDS VOLUME_NAMES _s_vol \ + ec2 describe-volumes \ + --filters "Name=tag:project,Values=${project}" \ + --query 'Volumes[].[VolumeId, Tags[?Key==`Name`].Value|[0]]' + + for vpc in "${VPC_IDS[@]}"; do + _scan_into INSTANCE_IDS INSTANCE_NAMES _s_inst \ + ec2 describe-instances \ + --filters "Name=vpc-id,Values=${vpc}" "$inst_state" \ + --query "$inst_query" + _scan_into ENI_IDS ENI_NAMES _s_eni \ + ec2 describe-network-interfaces \ + --filters "Name=vpc-id,Values=${vpc}" \ + --query "$eni_query" + _scan_into SUBNET_IDS SUBNET_NAMES _s_subnet \ + ec2 describe-subnets \ + --filters "Name=vpc-id,Values=${vpc}" \ + --query 'Subnets[].[SubnetId, Tags[?Key==`Name`].Value|[0]]' + _scan_into RTB_IDS RTB_NAMES _s_rtb \ + ec2 describe-route-tables \ + --filters "Name=vpc-id,Values=${vpc}" \ + --query 'RouteTables[?Associations[?Main!=`true`] || length(Associations)==`0`].[RouteTableId, Tags[?Key==`Name`].Value|[0]]' + _scan_into SG_IDS SG_NAMES _s_sg \ + ec2 describe-security-groups \ + --filters "Name=vpc-id,Values=${vpc}" \ + --query 'SecurityGroups[?GroupName!=`default`].[GroupId, GroupName]' + _scan_into IGW_IDS IGW_NAMES _s_igw \ + ec2 describe-internet-gateways \ + --filters "Name=attachment.vpc-id,Values=${vpc}" \ + --query 'InternetGateways[].[InternetGatewayId, Tags[?Key==`Name`].Value|[0]]' + done + + # Key pairs and CloudWatch alarms: ID is the only identifier. + read_lines KEY_NAMES < <( + collect ec2 describe-key-pairs \ + --filters "Name=key-name,Values=${project}-key" \ + --query 'KeyPairs[].KeyName' + ) + read_lines ALARM_NAMES < <( + collect cloudwatch describe-alarms \ + --alarm-names "${project}-cc-auto-recovery" \ + --query 'MetricAlarms[].AlarmName' + ) +} + +# --- Discovery: cross-project (for `list` summary) --------------------------- + +# Return one project name per line for projects discovered in the region. +# Union of three sources, in order of confidence: +# 1. describe-vpcs filtered by tag-key=$MARKER_TAG_KEY; the VPC's `project` +# tag value is the project name. This is the authoritative source for +# VPCs created with the current Terraform. +# 2. describe-vpcs by Name tag matching `arc-*-testnet-*`. Back-compat for +# VPCs created before the marker tag was added; may include unrelated +# VPCs that happen to share the Name pattern. +# 3. describe-instances carrying the marker tag key; their `project` tag +# values. Catches projects whose VPC was already deleted but whose +# instances are still terminating. +discover_all_projects() { + local -a vpc_marker_projects=() vpc_names=() instance_projects=() + + read_lines vpc_marker_projects < <( + collect ec2 describe-vpcs \ + --filters "Name=tag-key,Values=${MARKER_TAG_KEY}" \ + --query 'Vpcs[].Tags[?Key==`project`].Value[]' + ) + read_lines vpc_names < <( + collect ec2 describe-vpcs \ + --filters "Name=tag-key,Values=Name" \ + --query 'Vpcs[].Tags[?Key==`Name`].Value[]' + ) + read_lines instance_projects < <( + collect ec2 describe-instances \ + --filters "Name=tag-key,Values=${MARKER_TAG_KEY}" \ + "Name=instance-state-name,Values=${INSTANCE_LIVE_STATES}" \ + --query 'Reservations[].Instances[].Tags[?Key==`project`].Value[]' + ) + + # Filter to values shaped like a project name. Returns 0 even when no input + # matches, so the function is safe to call inside `set -e` contexts. + _emit_projects() { + local item + for item in "$@"; do + [[ -z "$item" ]] && continue + if is_full_project_name "$item"; then + printf '%s\n' "$item" + fi + done + return 0 + } + { + _emit_projects "${vpc_marker_projects[@]}" + _emit_projects "${vpc_names[@]}" + _emit_projects "${instance_projects[@]}" + } | sort -u +} + +# --- Rendering --------------------------------------------------------------- + +# Print only the per-class resource rows (no header, no total). Used both by the +# full plan and by the no-arg `list` summary when expanding leftover resources. +# EC2 instances are always rendered (even with count 0) so the user sees at a +# glance whether anything is still running. Other classes are hidden when empty +# to keep the output tight. Each class header is followed by one resource per +# line, indented two spaces further than the header. +show_plan_classes() { + local indent="${1:-}" + local item_indent="${indent} " + + # Paired row (ID + name). When `always` is "true", render the row even with + # count zero, showing an indented "(none)". Array identities are passed by + # name and resolved via `eval` for Bash 3 compatibility. + _row_pair() { + local label="$1" ids_name="$2" names_name="$3" always="${4:-false}" + local count + eval "count=\${#$ids_name[@]}" + if ((count == 0)); then + if [[ "$always" == "true" ]]; then + printf '%s%s (%d):\n' "$indent" "$label" "$count" + printf '%s(none)\n' "$item_indent" + fi + return + fi + printf '%s%s (%d):\n' "$indent" "$label" "$count" + local i id name + for ((i=0; i